ui: {{phrase-editor}} amends (#5991)

1. Re-focus the input element on phrase removal
2. Move all actions to `actions:`
3. Move to a form looking `value` rather than `items`
4. Move placeholder functionalit yinto the component
5. Force DDAU instead of two way binding with `slice` and `onchange`
6. Begin to deprecate the `searchable` interface
This commit is contained in:
John Cowen 2019-06-21 11:38:14 +01:00 committed by John Cowen
parent 6d8a706b4d
commit 62fa803d9f
5 changed files with 110 additions and 66 deletions

View File

@ -1,44 +1,67 @@
import Component from '@ember/component';
import { get, set } from '@ember/object';
import { inject as service } from '@ember/service';
export default Component.extend({
dom: service('dom'),
classNames: ['phrase-editor'],
item: '',
remove: function(index, e) {
this.items.removeAt(index, 1);
didInsertElement: function() {
this._super(...arguments);
// TODO: use {{ref}}
this.input = get(this, 'dom').element('input', this.element);
},
onchange: function(e) {},
search: function(e) {
// TODO: Temporarily continue supporting `searchable`
let searchable = get(this, 'searchable');
if (searchable) {
if (!Array.isArray(searchable)) {
searchable = [searchable];
}
searchable.forEach(item => {
item.search(get(this, 'value'));
});
}
this.onchange(e);
},
add: function(e) {
const value = get(this, 'item').trim();
if (value !== '') {
set(this, 'item', '');
const currentItems = get(this, 'items') || [];
const items = new Set(currentItems).add(value);
if (items.size > currentItems.length) {
set(this, 'items', [...items]);
this.onchange(e);
oninput: function(e) {},
onkeydown: function(e) {},
actions: {
keydown: function(e) {
switch (e.keyCode) {
case 8: // backspace
if (e.target.value == '' && get(this, 'value').length > 0) {
this.actions.remove.bind(this)(get(this, 'value').length - 1);
}
break;
case 27: // escape
set(this, 'value', []);
this.search({ target: this });
break;
}
}
},
onkeydown: function(e) {
switch (e.keyCode) {
case 8:
if (e.target.value == '' && this.items.length > 0) {
this.remove(this.items.length - 1);
this.onkeydown({ target: this });
},
input: function(e) {
set(this, 'item', e.target.value);
this.oninput({ target: this });
},
remove: function(index, e) {
get(this, 'value').removeAt(index, 1);
this.search({ target: this });
this.input.focus();
},
add: function(e) {
const item = get(this, 'item').trim();
if (item !== '') {
set(this, 'item', '');
const currentItems = get(this, 'value') || [];
const items = new Set(currentItems).add(item);
if (items.size > currentItems.length) {
set(this, 'value', [...items]);
this.search({ target: this });
}
break;
}
},
oninput: function(e) {
set(this, 'item', e.target.value);
},
onchange: function(e) {
let searchable = get(this, 'searchable');
if (!Array.isArray(searchable)) {
searchable = [searchable];
}
searchable.forEach(item => {
item.search(get(this, 'items'));
});
}
},
},
});

View File

@ -8,4 +8,5 @@
padding: 0;
height: 10px;
margin-right: 3px;
font-size: 0;
}

View File

@ -1,11 +1,11 @@
<ul>
{{#each items as |item index|}}
<li>
<button type="button" onclick={{action remove index}}>Remove</button>{{item}}
</li>
{{/each}}
</ul>
<label class="type-search">
<ul>
{{#each value as |item index|}}
<li>
<button type="button" {{action 'remove' index}}>Remove</button>{{item}}
</li>
{{/each}}
</ul>
<label class="type-search">
<span>Search</span>
<input onchange={{action add}} onsearch={{action add}} oninput={{action oninput}} onkeydown={{action onkeydown}} placeholder="{{placeholder}}" value="{{item}}" type="search" name="s" autofocus="autofocus" />
</label>
<input onchange={{action 'add'}} onsearch={{action 'add'}} oninput={{action 'input'}} onkeydown={{action 'keydown'}} placeholder={{if (eq value.length 0) placeholder}} value={{item}} type="search" name="s" autofocus="autofocus" />
</label>

View File

@ -10,7 +10,12 @@
{{/block-slot}}
{{#block-slot 'toolbar'}}
{{#if (gt items.length 0) }}
{{#phrase-editor placeholder=(if (eq terms.length 0) 'service:name tag:name status:critical search-term' '') items=terms searchable=searchable}}{{/phrase-editor}}
{{phrase-editor
placeholder='service:name tag:name status:critical search-term'
value=(slice 0 terms.length terms)
onchange=(action (mut terms) value='target.value')
searchable=searchable
}}
{{/if}}
{{/block-slot}}
{{#block-slot 'content'}}

View File

@ -5,30 +5,45 @@ moduleForComponent('phrase-editor', 'Integration | Component | phrase editor', {
integration: true,
});
test('it renders', function(assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
this.render(hbs`{{phrase-editor}}`);
assert.equal(
test('it renders a phrase', function(assert) {
this.set('value', ['phrase']);
this.render(hbs`{{phrase-editor value=value}}`);
assert.notEqual(
this.$()
.text()
.trim(),
'Search'
);
// Template block usage:
this.render(hbs`
{{#phrase-editor}}
template block text
{{/phrase-editor}}
`);
assert.equal(
this.$()
.text()
.trim(),
'Search'
.trim()
.indexOf('phrase'),
-1
);
});
test('it calls onchange when a phrase is removed by clicking the phrase remove button and refocuses', function(assert) {
assert.expect(3);
this.set('value', ['phrase']);
this.on('change', function(e) {
assert.equal(e.target.value.length, 0);
});
this.render(hbs`{{phrase-editor value=value onchange=(action 'change')}}`);
assert.notEqual(
this.$()
.text()
.trim()
.indexOf('phrase'),
-1
);
const $input = this.$('input');
const $button = this.$('button');
$button.trigger('click');
assert.equal(document.activeElement, $input.get(0));
});
test('it calls onchange when a phrase is added', function(assert) {
assert.expect(1);
this.on('change', function(e) {
assert.equal(e.target.value.length, 2);
});
this.set('value', ['phrase']);
this.render(hbs`{{phrase-editor value=value onchange=(action 'change')}}`);
const $input = this.$('input');
$input.get(0).value = 'phrase 2';
$input.trigger('input');
$input.trigger('search');
});