Skip to content

Commit

Permalink
Relate input to recipients element using slot
Browse files Browse the repository at this point in the history
  • Loading branch information
naokikimura committed Jan 13, 2020
1 parent 1165a63 commit 285e5e2
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 47 deletions.
30 changes: 30 additions & 0 deletions internationalization.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export default class Internationalization {
constructor(messages, defaultLocale, ...requestedLocales) {
// See https://tc39.es/ecma402/#sec-bestavailablelocale
const bestAvailableLocale = (availableLocales, locale) => {
let candidate = locale;
while(true) {
if (availableLocales.includes(candidate)) return candidate;
const pos = candidate.lastIndexOf('-');
if (pos === -1) return undefined;
candidate = candidate.substring(0, pos);
}
};

// https://tc39.es/ecma402/#sec-lookupmatcher
const lookupMatcher = (availableLocales, requestedLocales) => {
for (const locale of requestedLocales) {
const noExtensionsLocale = (new window.Intl.Locale(locale)).baseName;
const availableLocale = bestAvailableLocale(availableLocales, noExtensionsLocale);
if (availableLocale !== undefined) return availableLocale;
}
return defaultLocale;
};
const locale = lookupMatcher(Object.keys(messages), requestedLocales);
this._messages = messages[locale.toString()];
}

getMessage(messageName) {
return this._messages[messageName] && this._messages[messageName].message;
}
}
9 changes: 5 additions & 4 deletions popup.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fieldset#card {
margin-bottom: 1ex;
}

fieldset#card input#recipients_slot {
fieldset#card input[is="unipos-suggest-members"] {
line-height: 1.5em;
}

Expand All @@ -51,15 +51,16 @@ fieldset#card #point::before {
content: '+';
}

fieldset#card #point input[name="point"] {
fieldset#card #point input[is="unipos-point"] {
width: 5em;
}

fieldset#card #message textarea[name="message"] {
fieldset#card #message textarea[is="unipos-message"] {
width: 100%;
}

form#send_card button[type="submit"] {
form#send_card button {
margin-right: .5ex;
padding: .5ex 1ex;
background-color: #55cbe1;
border-radius: 2px;
Expand Down
13 changes: 9 additions & 4 deletions popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,21 @@
</span>
<button id="remove" part="remeve">×</button>
</template>
<template id="unipos-recipients">
<slot name="recipients" part="recipients"></slot>
<slot name="input" part="input"></slot>
</template>
</head>

<body>
<form action="" method="POST" enctype="multipart/form-data" id="send_card">
<fieldset id="card">
<fieldset>
<unipos-recipients id="recipients" name="to"></unipos-recipients>
<input is="unipos-suggest-members" type="search" name="recipients_slot" id="recipients_slot"
list="suggest_members" recipients="recipients" required autofocus placeholder="宛先" />
<datalist id="suggest_members"></datalist>
<unipos-recipients id="recipients" name="to" required>
<input is="unipos-suggest-members" slot="input" type="search" list="suggest_members" autofocus
placeholder="宛先" />
<datalist id="suggest_members"></datalist>
</unipos-recipients>
</fieldset>
<span id="point">
<input is="unipos-point" type="number" name="point" min="0" max="120" recipients="recipients" required
Expand Down
28 changes: 13 additions & 15 deletions popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ window.addEventListener('DOMContentLoaded', (event) => {
document.getElementById('send_card').addEventListener('reset', (event) => {
document.getElementById('progress').value = 0;
document.getElementById('status_text').textContent = '';
recipients.textContent = '';

for (const node of event.target.querySelectorAll('fieldset#card, fieldset#buttons')) {
node.disabled = false;
Expand Down Expand Up @@ -59,22 +58,21 @@ window.addEventListener('DOMContentLoaded', (event) => {
});

const recipients = document.getElementById('recipients');
recipients.addEventListener('change', (event) => {
const length = recipients.members.length;
document.getElementById('recipients_slot').required = length === 0;
});

document.querySelector('#point input[is="unipos-point"]').fetchAvailablePoint = async () => {
try {
const profile = await api.getProfile();
return profile && profile.member.pocket.available_point;
} catch (error) {
console.error(error);
}
};
for (const point of document.querySelectorAll('input[is="unipos-point"]')) {
point.fetchAvailablePoint = async () => {
try {
const profile = await api.getProfile();
return profile && profile.member.pocket.available_point;
} catch (error) {
console.error(error);
}
};
}

document.getElementById('recipients_slot').findSuggestMembers
= (value) => api.findSuggestMembers(value, 10).catch(console.error);
for (const suggestMembers of document.querySelectorAll('input[is="unipos-suggest-members"]')) {
suggestMembers.findSuggestMembers = (value) => api.findSuggestMembers(value, 10).catch(console.error);
}

chrome.storage.sync.get(['options'], result => {
const { recipientMembers = [], point = null, message = '' } = result.options || {};
Expand Down
12 changes: 10 additions & 2 deletions unipos/recipient/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default class UniposRecipientElement extends HTMLElement {
const template = document.getElementById('unipos-recipient');
shadow.appendChild(document.importNode(template.content, true));
shadow.getElementById('remove').addEventListener('click', (event) => {
this.parentNode.removeChild(this);
this.remove();
});
}

Expand Down Expand Up @@ -56,7 +56,7 @@ export default class UniposRecipientElement extends HTMLElement {
attributeChangedCallback(name, oldValue, newValue) {
switch (name) {
case 'disabled':
this.disabled = newValue != null;
this.disabled = newValue !== null;
break;

case 'data-id':
Expand All @@ -83,6 +83,14 @@ export default class UniposRecipientElement extends HTMLElement {
this.setAttribute('name');
}

get disabled() {
return !!this._disabled;
}

set disabled(value) {
this._disabled = !!value;
}

get member() {
return {
id: this.dataset.id,
Expand Down
116 changes: 94 additions & 22 deletions unipos/recipients/element.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,97 @@
import UniposRecipientElement from '../recipient/element.js';
import Internationalization from '../../internationalization.js';

const i18n = new Internationalization({
en: {
valueMissing: {
message: 'Please specify at least one recipient.'
}
},
ja: {
valueMissing: {
message: '宛先を 1 つ以上指定してください。'
}
}
}, 'en', ...window.navigator.languages);

const validate = function () {
this._internals.setValidity({
valueMissing: this.required && this.recipientElements.length === 0
}, i18n.getMessage('valueMissing'), this._input);
};

export default class UniposRecipientsElement extends HTMLElement {
static get observedAttributes() { return ['disabled']; }
static get observedAttributes() { return ['disabled', 'required']; }
static get formAssociated() { return true; }

constructor() {
super();
this.disabled = false;
this.internals = this.attachInternals();
this.internals.setFormValue(null);
this.pastForm = null;
(new MutationObserver((mutations) => {
mutations
.filter(mutation => mutation.type === 'childList')
.forEach(mutation => {
this.dispatchEvent(new CustomEvent('change', { detail: mutation }));
});
})).observe(this, { childList: true, subtree: true });
this._internals = this.attachInternals();
this._internals.setFormValue(null);
const shadow = this.attachShadow({ mode: "open" });
const template = document.getElementById('unipos-recipients');
shadow.appendChild(document.importNode(template.content, true));
this._pastForm = null;
this._input = undefined;

const inputSlot = shadow.querySelector('slot[name="input"]');
inputSlot.addEventListener('slotchange', (event) => {
this._input = inputSlot.assignedElements({ flatten: true })
.filter(element => element instanceof HTMLInputElement)[0];
validate.call(this);
});

const recipientsSlot = shadow.querySelector('slot[name="recipients"]');
recipientsSlot.addEventListener('slotchange', (event) => {
validate.call(this);
this.dispatchEvent(new CustomEvent('change'));
});
}

attributeChangedCallback(name, oldValue, newValue) {
if (name === 'disabled') {
this.disabled = newValue !== null;
switch (name) {
case 'disabled':
this.disabled = newValue !== null;
break;

case 'required':
if (this.required) validate.call(this);
break;

default:
break;
}
}

formResetCallback() {
for (const element of this.recipientElements)
element.remove();
}

formAssociatedCallback(form) {
this.pastForm && this.pastForm.removeEventListener('formdata', this.formdataEventListener);
this._pastForm && this._pastForm.removeEventListener('formdata', this.formdataEventListener);
form && form.addEventListener('formdata', this.formdataEventListener);
this.pastForm = form;
this._pastForm = form;
}

formDisabledCallback(disabled) {
this.disabled = disabled;
this.querySelector('unipos-recipient')
.forEach(recipient => recipient.disabled);
this.recipientElements
.forEach(recipient => recipient.disabled = disabled);
}

formdataEventListener = (event) => {
if (this.disabled) return;
const data = event.formData;
for (const member of this.querySelectorAll('unipos-recipient')) {
if (member.disabled) continue;
data.append(this.name, member.member.id);
for (const recipient of this.recipientElements) {
if (recipient.disabled) continue;
data.append(this.name, recipient.member.id);
}
}

get form() {
return this.internals.form;
return this._internals.form;
}

get name() {
Expand All @@ -56,18 +102,44 @@ export default class UniposRecipientsElement extends HTMLElement {
this.setAttribute('name', value);
}

get disabled() {
return !!this._disabled;
}

set disabled(value) {
this._disabled = !!value;
}

get required() {
return this.hasAttribute('required');
}

set required(value) {
if (value)
this.setAttribute('required', '');
else
this.removeAttribute('required');
}

get members() {
return [...this.querySelectorAll('unipos-recipient')]
return [...this.recipientElements]
.map(recipient => recipient.member);
}

get recipientElements() {
const recipientsSlot = this.shadowRoot.querySelector('slot[name="recipients"]');
return recipientsSlot.assignedElements({ flatten: true })
.filter(element => element instanceof UniposRecipientElement);
}

findMembers(...ids) {
return this.members.filter(member => ids.includes(member.id));
}

createRecipientNode(member) {
const element = this.ownerDocument.createElement('unipos-recipient');
element.member = member;
element.setAttribute('slot', 'recipients');
return element;
}

Expand Down

0 comments on commit 285e5e2

Please sign in to comment.