Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.0] Field user as proper web component #43149

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions build/media_source/system/joomla.asset.json
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,11 @@
"joomla.dialog"
]
},
{
"name": "webcomponent.field-user",
"type": "style",
"uri": "system/fields/joomla-field-user.min.css"
},
{
"name": "webcomponent.core-loader-legacy",
"type": "script",
Expand Down
172 changes: 81 additions & 91 deletions build/media_source/system/js/fields/joomla-field-user.w-c.es6.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,110 +5,94 @@
// eslint-disable-next-line import/no-unresolved
import JoomlaDialog from 'joomla.dialog';

function getText(translateableText, fallbackText) {
const translatedText = typeof Joomla?.Text?._ === 'function' ? Joomla.Text._(translateableText) : '';

return translatedText !== translateableText ? translatedText : fallbackText;
}

const texts = {
selectUser: ['JLIB_FORM_CHANGE_USER', 'Select User'],
}

const template = Object.assign(document.createElement('template'), {
innerHTML: `
<style>svg { width: 1em; height: 1rem; vertical-align: text-bottom; }</style>
<input part="name" readonly>
<button type="button" part="opener" aria-label="${getText(texts.selectUser[0], texts.selectUser[1])}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path fill="currentColor" d="M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304H178.3z"/>
</svg>
</button>`,
});

class JoomlaFieldUser extends HTMLElement {
static formAssociated = true;

constructor() {
super();

this.onUserSelect = '';
this.onchangeStr = '';
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(template.content.cloneNode(true));

// Bind context
this.modalClose = this.modalClose.bind(this);
this.setValue = this.setValue.bind(this);
this.modalOpen = this.modalOpen.bind(this);
}

static get observedAttributes() {
return ['url', 'modal', 'modal-width', 'modal-height', 'modal-title', 'input', 'input-name', 'button-select'];
}

get url() {
return this.getAttribute('url');
}

set url(value) {
this.setAttribute('url', value);
}

get modalWidth() {
return this.getAttribute('modal-width');
}

set modalWidth(value) {
this.setAttribute('modal-width', value);
}

get modalTitle() {
return this.getAttribute('modal-title');
}

set modalTitle(value) {
this.setAttribute('modal-title', value);
this.input = this.shadowRoot.querySelector('[part=name]');
this.button = this.shadowRoot.querySelector('[part=opener]');
}

get modalHeight() {
return this.getAttribute('modal-height');
static get observedAttributes() {
return ['url', 'name'];
}

set modalHeight(value) {
this.setAttribute('modal-height', value);
}
get value() { return this.getAttribute('value'); }

get inputId() {
return this.getAttribute('input');
}
set value(value) { this.setAttribute('value', value); }

set inputId(value) {
this.setAttribute('input', value);
}
get username() { return this.getAttribute('username'); }

get inputNameClass() {
return this.getAttribute('input-name');
}
set username(value) { this.setAttribute('username', value); }

set inputNameClass(value) {
this.setAttribute('input-name', value);
}
get url() { return this.getAttribute('url'); }

get buttonSelectClass() {
return this.getAttribute('button-select');
}

set buttonSelectClass(value) {
this.setAttribute('button-select', value);
}
set url(value) { this.setAttribute('url', value); }

connectedCallback() {
// Set up elements
this.input = this.querySelector(this.inputId);
this.inputName = this.querySelector(this.inputNameClass);
this.buttonSelect = this.querySelector(this.buttonSelectClass);

if (this.buttonSelect) {
this.buttonSelect.addEventListener('click', this.modalOpen.bind(this));
// this.modal.addEventListener('hide', this.removeIframe.bind(this));

// Check for onchange callback,
this.onchangeStr = this.input.getAttribute('data-onchange');
if (this.onchangeStr) {
// eslint-disable-next-line no-new-func
this.onUserSelect = new Function(this.onchangeStr);
this.input.addEventListener('change', this.onUserSelect);
}
try {
this.internals = this.attachInternals();
this.form = this.internals.form;
} catch (error) {
throw new Error('Unsupported browser');
}
}

disconnectedCallback() {
if (this.onUserSelect && this.input) {
this.input.removeEventListener('change', this.onUserSelect);
if (this.internals && this.internals.labels.length && !(this.hasAttribute('readonly') || this.hasAttribute('disabled'))) {
this.internals.labels.forEach((label) => label.addEventListener('click', this.modalOpen));
}

if (this.internals) {
this.querySelector('input[type=hidden]')?.remove();
}

if (this.buttonSelect) {
this.buttonSelect.removeEventListener('click', this.modalOpen);
if (!(this.hasAttribute('readonly') || this.hasAttribute('disabled'))) {
this.button.addEventListener('click', this.modalOpen);
this.input.addEventListener('click', this.modalOpen);
}
if (this.hasAttribute('readonly')) {
this.button.remove();
}

this.form = this.internals.form;
this.internals.setFormValue(this.value);
this.input.value = this.value ? this.username : '';
this.input.placeholder = this.value ? '' : getText(texts.selectUser[0], texts.selectUser[1]);
}

if (this.modal) {
this.modal.removeEventListener('hide', this);
disconnectedCallback() {
if (this.internals && this.internals.labels.length) {
this.internals.labels.forEach((label) => label.removeEventListener('click', this.modalOpen));
}
}

Expand All @@ -118,9 +102,7 @@ class JoomlaFieldUser extends HTMLElement {
const dialog = new JoomlaDialog({
popupType: 'iframe',
src: this.url,
textHeader: this.modalTitle,
width: this.modalWidth,
height: this.modalHeight,
textHeader: getText(texts.selectUser[0], texts.selectUser[1]),
});
dialog.classList.add('joomla-dialog-user-field');
dialog.show();
Expand All @@ -143,9 +125,6 @@ class JoomlaFieldUser extends HTMLElement {
window.removeEventListener('message', msgListener);
dialog.destroy();
this.dialog = null;
// Focus on the input field to re-trigger the validation
this.inputName.focus();
this.buttonSelect.focus();
});

this.dialog = dialog;
Expand All @@ -160,14 +139,25 @@ class JoomlaFieldUser extends HTMLElement {

// Sets the value
setValue(value, name) {
this.input.setAttribute('value', value);
this.inputName.setAttribute('value', name || value);
// trigger change event both on the input and on the custom element
this.input.dispatchEvent(new CustomEvent('change'));
this.dispatchEvent(new CustomEvent('change', {
detail: { value, name },
bubbles: true,
}));
this.internals.setFormValue(parseInt(value, 10));
this.value = value;
this.username = name;
this.input.value = this.value ? this.username : '';
this.input.placeholder = this.value ? '' : getText(texts.selectUser[0], texts.selectUser[1]);

if (this.hasAttribute('required')){
// if (this.value === '') {
// this.internals.setValidity({ valueMissing: true }, 'User needs to be selected');
// } else {
// this.internals.setValidity({});
// }
}

const event = new Event('change', { bubbles: true });
event.name = name;
event.value = value;

this.dispatchEvent(event);
}
}

Expand Down
57 changes: 57 additions & 0 deletions build/media_source/system/scss/fields/joomla-field-user.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
joomla-field-user {
--border-color: var(--bs-input-border-color, var(--input-border-color, #ced4da));
--opener-padding-x: 1rem;
--opener-padding-y: .5rem;

display: flex;

&.valid {
--border-color: var(--bs-success, var(--success, green));
}

&.invalid {
--border-color: var(--bs-danger, var(--danger, red));
}

&::part(name) {
display: block;
width: 100%;
height: 1.5rem;
padding: .5rem 1rem;
font-family: inherit;
line-height: 1.5;
color: var(--body-color);
background-color: var(--body-bg);
background-clip: padding-box;
border: var(--border-width) solid var(--border-color);
border-radius: var(--border-radius);
border-top-right-radius: 0;
border-bottom-right-radius: 0;
appearance: none;
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}

&::part(opener) {
display: inline-block;
padding: var(--opener-padding-y) var(--opener-padding-x);
margin-left: calc(var(--border-width) * -1);
color: var(--btn-primary-color);
text-align: center;
vertical-align: middle;
cursor: pointer;
user-select: none;
background-color: var(--btn-primary-bg);
border: var(--btn-primary-border);
border-radius: var(--border-radius);
border-top-left-radius: 0;
border-bottom-left-radius: 0;
transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
appearance: none;
}

&::part(opener):focus,
&::part(opener):hover {
background-color: var(--btn-primary-bg-hvr);
border: var(--btn-primary-border-hvr);
}
}
71 changes: 21 additions & 50 deletions layouts/joomla/form/field/user.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,10 @@
* @var array $dataAttributes Miscellaneous data attribute for eg, data-*.
*/

$uri = new Uri('index.php?option=com_users&view=users&layout=modal&tmpl=component&required=0');
$uri->setVar('field', $this->escape($id));
$uri = new Uri('index.php?option=com_users&view=users&layout=modal&tmpl=component');

if ($required) {
$uri->setVar('required', 1);
}
$uri->setVar('field', $this->escape($id));
$uri->setVar('required', $required ? 1 : 0);

if (!empty($groups)) {
$uri->setVar('groups', base64_encode(json_encode($groups)));
Expand All @@ -65,56 +63,29 @@
$uri->setVar('excluded', base64_encode(json_encode($excluded)));
}

// Invalidate the input value if no user selected
if ($this->escape($userName) === Text::_('JLIB_FORM_SELECT_USER')) {
$userName = '';
}

$inputAttributes = [
'type' => 'text',
'id' => $id,
'class' => 'form-control field-user-input-name',
'value' => $this->escape($userName),
$attr = [
'class' => (string) $class,
'id' => (string) $id,
'name' => (string) $name,
'value' => $this->escape($value),
'username' => $this->escape($userName),
'url' => (string) $uri
];

if ($class) {
$inputAttributes['class'] .= ' ' . $class;
}
if ($size) {
$inputAttributes['size'] = (int) $size;
}
if ($required) {
$inputAttributes['required'] = 'required';
}
if (!$readonly) {
$inputAttributes['placeholder'] = Text::_('JLIB_FORM_SELECT_USER');
$attr['required'] = '';
}

if (!$readonly) {
Factory::getApplication()->getDocument()->getWebAssetManager()
->useScript('webcomponent.field-user');
if ($readonly) {
$attr['readonly'] = '';
}

Text::script('JLIB_FORM_CHANGE_USER');

Factory::getApplication()->getDocument()->getWebAssetManager()
->useStyle('webcomponent.field-user')
->useScript('webcomponent.field-user');
?>
<?php // Create a dummy text field with the user name. ?>
<joomla-field-user class="field-user-wrapper"
url="<?php echo (string) $uri; ?>"
modal-title="<?php echo $this->escape(Text::_('JLIB_FORM_CHANGE_USER')); ?>"
input=".field-user-input"
input-name=".field-user-input-name"
button-select=".button-select">
<div class="input-group">
<input <?php echo ArrayHelper::toString($inputAttributes), $dataAttribute; ?> readonly>
<?php if (!$readonly) : ?>
<button type="button" class="btn btn-primary button-select" title="<?php echo Text::_('JLIB_FORM_CHANGE_USER'); ?>">
<span class="icon-user icon-white" aria-hidden="true"></span>
<span class="visually-hidden"><?php echo Text::_('JLIB_FORM_CHANGE_USER'); ?></span>
</button>
<?php endif; ?>
</div>
<?php // Create the real field, hidden, that stored the user id. ?>
<?php if (!$readonly) : ?>
<input type="hidden" id="<?php echo $id; ?>_id" name="<?php echo $name; ?>" value="<?php echo $this->escape($value); ?>"
class="field-user-input <?php echo $class ? (string) $class : ''?>"
data-onchange="<?php echo $this->escape($onchange); ?>">
<?php endif; ?>
<joomla-field-user <?php echo ArrayHelper::toString($attr); ?>>
<input type="hidden" name="<?php echo $name; ?>" value="<?php echo $value; ?>">
</joomla-field-user>