Skip to content

Commit 516775e

Browse files
committed
rewrite the handling of promises for autocomplete and tokens
1 parent 2fbd612 commit 516775e

6 files changed

Lines changed: 188 additions & 51 deletions

File tree

addon/components/input-tokenfield.js

Lines changed: 64 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,78 +7,94 @@ export default Ember.TextField.extend({
77

88
classNames: ['form-control'],
99

10-
_setElement$: function() {
11-
var element$ = Ember.$('#' + this.get('elementId'));
12-
this.set('element$', element$);
10+
/*
11+
Appends only truthy values that are not promises.
12+
*/
13+
_appendOption: function(options, attributeName) {
14+
let attrValue = this.get(attributeName);
15+
if (attrValue && typeof attrValue.then !== 'function') {
16+
options[attributeName] = attrValue;
17+
}
18+
return options;
1319
},
1420

15-
tokens: null,
16-
17-
autocomplete: null,
21+
_buildTokenfieldOptions: function() {
22+
var options = {};
23+
options = this._appendOption(options, 'limit');
24+
options = this._appendOption(options, 'minLength');
25+
options = this._appendOption(options, 'minWidth');
26+
options = this._appendOption(options, 'showAutocompleteOnFocus');
27+
options = this._appendOption(options, 'delimiter');
28+
options = this._appendOption(options, 'beautify');
29+
options = this._appendOption(options, 'inputType');
30+
options = this._appendOption(options, 'createTokenOnBlur');
31+
options = this._appendOption(options, 'typeahead');
32+
options = this._appendOption(options, 'tokens');
33+
options = this._appendOption(options, 'autocomplete');
34+
return options;
35+
},
1836

1937
didInsertElement: function() {
20-
this._setElement$();
21-
22-
var tokens = this._processTokensObject();
23-
var autocomplete = this._processAutocompleteObject();
38+
let element$ = Ember.$('#' + this.get('elementId'));
39+
this.set('element$', element$);
2440

25-
var options = {};
26-
if ( tokens ) { options.tokens = tokens; }
27-
if ( this.limit ) { options.limit = this.limit; }
28-
if ( this.minLength ) { options.minLength = this.minLength; }
29-
if ( this.minWidth ) { options.minWidth = this.minWidth; }
30-
if ( autocomplete ) { options.autocomplete = autocomplete; }
31-
if ( this.showAutocompleteOnFocus ) {
32-
options.showAutocompleteOnFocus = true;
33-
}
41+
var options = this._buildTokenfieldOptions();
3442

35-
var typeahead = this.get('typeahead');
36-
if ( typeahead ) { options.typeahead = typeahead; }
37-
if ( this.createTokensOnBlur ) {
38-
options.createTokensOnBlur = this.createTokensOnBlur;
39-
}
40-
if ( this.delimiter ) { options.delimiter = this.delimiter; }
41-
if ( this.beautify ) { options.beautify = this.beautify; }
42-
if ( this.inputType ) { options.inputType = this.inputType; }
43+
element$.tokenfield(options);
4344

44-
this.get('element$').tokenfield(options);
45+
this._consumeAutocompletePromise();
46+
this._consumeTokensPromise();
4547
},
4648

47-
/*
48-
Knows how to handle the autocomplete object if its a promise.
49-
*/
50-
_processAutocompleteObject: Ember.observer('autocomplete', function() {
49+
_consumeAutocompletePromise: function() {
5150
var autocomplete = this.get('autocomplete');
5251

53-
if ( ! autocomplete || typeof autocomplete.then !== 'function') {
54-
return autocomplete;
52+
if (!autocomplete || typeof autocomplete.then !== 'function') {
53+
return; // nothing to do, value already passed in with the options object
5554
}
5655

57-
var _this = this;
58-
autocomplete.then(function(resolvedAutocomplete) {
59-
_this.get('element$')
60-
.data('bs.tokenfield')
61-
.$input.autocomplete(resolvedAutocomplete);
56+
autocomplete.then(autocompleteValues => {
57+
let element$ = this.get('element$');
58+
if (element$) {
59+
element$
60+
.data('bs.tokenfield')
61+
.$input.autocomplete(autocompleteValues);
62+
}
63+
64+
this.set('autocomplete', autocompleteValues);
6265
});
66+
},
6367

64-
return null;
68+
_observeAutocomplete: Ember.observer('autocomplete', function() {
69+
this._consumeAutocompletePromise();
6570
}),
6671

67-
_processTokensObject: Ember.observer('tokens', 'tokens.[]', function() {
72+
_consumeTokensPromise: function() {
6873
var tokens = this.get('tokens');
6974

70-
if ( ! tokens ) {
71-
return;
75+
if (!tokens || typeof tokens.then !== 'function') {
76+
return; // nothing to do, value already passed in with the options object
7277
}
7378

7479
// test for Ember.ArrayProxy
75-
if ( typeof tokens.get('hasArrayObservers') === 'boolean') {
76-
tokens = tokens.toArray();
80+
/*
81+
if ( tokens.get && typeof tokens.get('hasArrayObservers') === 'boolean') {
82+
let tokensList = tokens.toArray();
83+
element$.tokenfield('setTokens', tokensList);
7784
}
85+
*/
86+
87+
tokens.then(tokensList => {
88+
let element$ = this.get('element$');
89+
if (element$) {
90+
element$.tokenfield('setTokens', tokensList);
91+
}
92+
this.set('tokens', tokensList);
93+
});
94+
},
7895

79-
this.get('element$').tokenfield('setTokens', tokens);
80-
81-
return tokens;
96+
_observeTokens: Ember.observer('tokens', function() {
97+
this._consumeTokensPromise();
8298
})
8399

84100
});

blueprints/ember-cli-bootstrap-tokenfield/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ module.exports = {
1818
var _this = this;
1919

2020
console.log('Note: bootstrap-tokenfield depends on jquery 2.1.0');
21-
console.log(' Use that.');
21+
console.log(' Use that version or higher.');
2222

2323
return _this.addBowerPackageToProject('jquery', '2.1.0').then(function() {
2424
return _this.addBowerPackageToProject('jquery-ui', '1.11.4').then(function() {

tests/dummy/app/controllers/application.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ export default Ember.Controller.extend({
2323
});
2424
}),
2525

26+
tokensPromise: Ember.computed(function() {
27+
var tokens = [
28+
{value: 1, label: 'one'},
29+
{value: 2, label: 'two'},
30+
{value: 3, label: 'three'}
31+
];
32+
33+
return new Ember.RSVP.Promise(function(resolve) {
34+
Ember.run.later(function() {
35+
resolve( tokens );
36+
}, 3000); // simulate a 3 seconds delay
37+
});
38+
}),
39+
2640
typeahead: (function() {
2741
var engine = new Bloodhound({
2842
local: [{value: 'red'}, {value: 'blue'}, {value: 'green'} ,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<div class="panel panel-info">
2+
<div class="panel-heading">
3+
Set tokens with a promise
4+
</div>
5+
6+
<div class="panel-body">
7+
<p>Promises can be used to set the current tokens.</p>
8+
{{input-tokenfield tokens=tokensPromise
9+
autocomplete=autocomplete
10+
placeholder="Type something and hit enter"}}
11+
<br>
12+
13+
Controller
14+
<pre>// contribed example, this data is likely to come from ember-data
15+
tokensPromise: Ember.computed(function() {
16+
var tokens = [
17+
{value: 1, label: 'one'},
18+
{value: 2, label: 'two'},
19+
{value: 3, label: 'three'}
20+
];
21+
22+
return new Ember.RSVP.Promise(function(resolve) {
23+
Ember.run.later(function() {
24+
resolve( tokens );
25+
}, 3000); // simulate a 3 seconds delay
26+
});
27+
})</pre>
28+
29+
Template
30+
<pre>\{{input-tokenfield tokens=tokensPromise
31+
autocomplete=autocomplete
32+
placeholder="Type something and hit enter"}}</pre>
33+
</div>
34+
</div>

tests/dummy/app/templates/application.hbs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ ember install:addon ember-cli-bootstrap-tokenfield
2424

2525
{{partial 'autocomplete-promise'}}
2626

27+
{{partial 'tokens-promise'}}
28+
2729
{{partial 'typeahead'}}
2830

2931
</div><!-- col -->

tests/unit/components/input-tokenfield-test.js

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import Ember from 'ember';
12
import {
23
moduleForComponent,
34
test
45
} from 'ember-qunit';
56

6-
moduleForComponent('input-tokenfield', {
7+
moduleForComponent('input-tokenfield', 'Unit | Component | input-tokenfield', {
78
// specify the other units that are required for this test
89
// needs: ['component:foo', 'helper:bar']
910
unit: true
@@ -21,8 +22,9 @@ test('it renders', function(assert) {
2122
assert.equal(component._state, 'inDOM');
2223
});
2324

24-
test('getTokens', function(assert) {
25+
test('tokenfield.getTokens', function(assert) {
2526
assert.expect(2);
27+
2628
var component = this.subject({
2729
value: 'red'
2830
});
@@ -39,3 +41,72 @@ test('getTokens', function(assert) {
3941
expected = [];
4042
assert.deepEqual(tokens, expected);
4143
});
44+
45+
test('_appendOption', function(assert) {
46+
assert.expect(3);
47+
48+
let component = this.subject({
49+
autocomplete: {source: ['1', '2']}
50+
});
51+
52+
let options = {};
53+
options = component._appendOption(options, 'not-a-real-option');
54+
assert.deepEqual(options, {}, 'only truthy values are added');
55+
56+
options = {};
57+
options = component._appendOption(options, 'autocomplete');
58+
assert.deepEqual(options, {autocomplete: {source: ['1', '2']}});
59+
60+
Ember.run(() => {
61+
this.render();
62+
component.set('autocomplete', Ember.RSVP.resolve({source: ['a', 'b']}));
63+
options = {};
64+
options = component._appendOption(options, 'autocomplete');
65+
assert.deepEqual(options, {}, 'promises are ignored');
66+
});
67+
});
68+
69+
test('autocomplete is a promise', function(assert) {
70+
assert.expect(1);
71+
72+
let autocompletePromise = new Ember.RSVP.Promise(function(resolve) {
73+
Ember.run.later(function() {
74+
let autocomplete = {source: ['a', 'b']};
75+
resolve(autocomplete);
76+
}, 7); // 7 ms must be enough to not let the test pass synchronously
77+
});
78+
let component = this.subject({
79+
autocomplete: autocompletePromise
80+
});
81+
this.render();
82+
83+
Ember.run(() => {
84+
component._consumeAutocompletePromise();
85+
component.get('autocomplete').then(() => {
86+
assert.deepEqual(component.get('autocomplete'), {source: ['a', 'b']});
87+
});
88+
});
89+
});
90+
91+
test('tokens is a promise', function(assert) {
92+
assert.expect(1);
93+
94+
let tokensPromise = new Ember.RSVP.Promise(function(resolve) {
95+
Ember.run.later(function() {
96+
let tokens = [{value: '1', label: 'uno'}, {value: '2', label: 'dos'}];
97+
resolve(tokens);
98+
}, 7); // 7 ms must be enough to not let the test pass synchronously
99+
});
100+
let component = this.subject({
101+
tokens: tokensPromise
102+
});
103+
this.render();
104+
105+
Ember.run(() => {
106+
component._consumeTokensPromise();
107+
component.get('tokens').then(() => {
108+
let expected = [{value: '1', label: 'uno'}, {value: '2', label: 'dos'}];
109+
assert.deepEqual(component.get('tokens'), expected);
110+
});
111+
});
112+
});

0 commit comments

Comments
 (0)