diff options
Diffstat (limited to 'js/components/hintable.reel')
-rw-r--r-- | js/components/hintable.reel/hintable.js | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/js/components/hintable.reel/hintable.js b/js/components/hintable.reel/hintable.js new file mode 100644 index 00000000..79813c92 --- /dev/null +++ b/js/components/hintable.reel/hintable.js | |||
@@ -0,0 +1,360 @@ | |||
1 | /* ComputedStyleSubPanel.js */ | ||
2 | var Montage = require("montage").Montage, | ||
3 | Component = require("montage/ui/component").Component, | ||
4 | Editable = require("js/components/editable.reel").Editable; | ||
5 | |||
6 | |||
7 | /* | ||
8 | |||
9 | EDITABLE - Methods | ||
10 | - startEdit | ||
11 | - stopEdit | ||
12 | - value | ||
13 | - | ||
14 | - _suggest | ||
15 | - _suggestNext | ||
16 | - _suggestPrev | ||
17 | - _clearSuggest | ||
18 | - _accept | ||
19 | - _revert | ||
20 | - _setCaret | ||
21 | |||
22 | */ | ||
23 | |||
24 | |||
25 | exports.Hintable = Montage.create(Editable, { | ||
26 | inheritsFrom : { value : Editable }, | ||
27 | _matchIndex : { value : 0 }, | ||
28 | matches : { value : [] }, | ||
29 | |||
30 | _hint : { value : null }, | ||
31 | hint : { | ||
32 | get : function() { | ||
33 | return this._hint; | ||
34 | }, | ||
35 | set : function(hint) { | ||
36 | hint = hint || ''; | ||
37 | |||
38 | ///// Set the hint element's text | ||
39 | this._getFirstTextNode(this.hintElement).textContent = hint; | ||
40 | ///// if hintElement was removed from the DOM, the object still | ||
41 | ///// exists, so it needs to be re-appended | ||
42 | if(this.hintElement.parentNode === null) { | ||
43 | this._element.appendChild(this.hintElement); | ||
44 | } | ||
45 | |||
46 | this._hint = hint; | ||
47 | } | ||
48 | }, | ||
49 | |||
50 | _hintElement : { value : null }, | ||
51 | hintElement : { | ||
52 | get : function() { | ||
53 | if(!this._hintElement) { | ||
54 | /// Remove the phantom "<BR>" element that is generated when | ||
55 | /// content editable element is empty | ||
56 | this._children(this._element, function(item) { | ||
57 | return item.nodeName === 'BR'; | ||
58 | }).forEach(function(item) { | ||
59 | this._element.removeChild(item); | ||
60 | }, this); | ||
61 | |||
62 | this._hintElement = document.createElement('span'); | ||
63 | this._hintElement.classList.add(this.hintClass); | ||
64 | |||
65 | this._element.appendChild(this._hintElement); | ||
66 | } | ||
67 | |||
68 | return this._hintElement; | ||
69 | }, | ||
70 | set : function(el) { | ||
71 | this._hintElement = el; | ||
72 | } | ||
73 | }, | ||
74 | |||
75 | _getHintDifference : { | ||
76 | value : function() { | ||
77 | if(!this.matches[this._matchIndex]) { | ||
78 | debugger; | ||
79 | } | ||
80 | return this.matches[this._matchIndex].substr(this.value.length); | ||
81 | } | ||
82 | }, | ||
83 | |||
84 | hintNext : { | ||
85 | value : function(e) { | ||
86 | if(e) { e.preventDefault(); } | ||
87 | console.log('next1'); | ||
88 | |||
89 | if(this._matchIndex < this.matches.length - 1) { | ||
90 | console.log('next'); | ||
91 | ++this._matchIndex; | ||
92 | this.hint = this._getHintDifference(); | ||
93 | } | ||
94 | } | ||
95 | }, | ||
96 | hintPrev : { | ||
97 | value : function(e) { | ||
98 | if(e) { e.preventDefault(); } | ||
99 | console.log('prev1'); | ||
100 | if(this._matchIndex !== 0) { | ||
101 | console.log('prev'); | ||
102 | --this._matchIndex; | ||
103 | this.hint = this._getHintDifference(); | ||
104 | } | ||
105 | } | ||
106 | }, | ||
107 | |||
108 | accept : { | ||
109 | value: function(e, preserveCaretPosition) { | ||
110 | if(e) { | ||
111 | e.preventDefault(); | ||
112 | } | ||
113 | var fullText = this._hint; | ||
114 | this.hint = null; | ||
115 | this.value += fullText; | ||
116 | |||
117 | if(!preserveCaretPosition) { | ||
118 | this.setCursor('end'); | ||
119 | } | ||
120 | |||
121 | this._sendEvent('accept'); | ||
122 | } | ||
123 | }, | ||
124 | revert : { | ||
125 | value : function(e, forceRevert) { | ||
126 | this.hint = null; | ||
127 | |||
128 | if(this.isEditable || forceRevert) { | ||
129 | /// revert to old value | ||
130 | this.value = (this._preEditValue); | ||
131 | this._sendEvent('revert'); | ||
132 | console.log('reverting'); | ||
133 | |||
134 | } | ||
135 | } | ||
136 | }, | ||
137 | value : { | ||
138 | get: function() { | ||
139 | return this._getFirstTextNode().textContent; | ||
140 | }, | ||
141 | set: function(str) { | ||
142 | var node = this._getFirstTextNode(); | ||
143 | node.textContent = str; | ||
144 | } | ||
145 | }, | ||
146 | |||
147 | handleKeydown : { | ||
148 | value : function handleKeydown(e) { | ||
149 | var k = e.keyCode, | ||
150 | isCaretAtEnd, selection, text; | ||
151 | |||
152 | this._super(arguments); | ||
153 | |||
154 | if(k === 39) { | ||
155 | selection = window.getSelection(); | ||
156 | text = selection.baseNode.textContent; | ||
157 | isCaretAtEnd = (selection.anchorOffset === text.length); | ||
158 | } | ||
159 | |||
160 | if(this.hint && isCaretAtEnd) { | ||
161 | ///// Advance the cursor | ||
162 | this.hint = this.hint.substr(0, 1); | ||
163 | this.accept(e); | ||
164 | this.handleInput(); | ||
165 | } | ||
166 | |||
167 | this._execKeyAction(e); | ||
168 | } | ||
169 | }, | ||
170 | ///// Text input has changed values | ||
171 | handleInput : { | ||
172 | value : function handleInput(e) { | ||
173 | this._super(arguments); | ||
174 | |||
175 | var val = this.value, | ||
176 | matches, hint; | ||
177 | console.log('val = "' + val + '"'); | ||
178 | //// Handle auto-suggest if configured | ||
179 | if(this.hints instanceof Array) { | ||
180 | |||
181 | if(val.length > 0) { // content is not empty | ||
182 | |||
183 | this._matchIndex = 0; | ||
184 | this.matches = this.hints.filter(function(h) { | ||
185 | return h.indexOf(val) === 0; | ||
186 | }).sort(); | ||
187 | |||
188 | ///// If there are no matches, or the new value doesn't match all the | ||
189 | ///// previous matches, then get new list of matches | ||
190 | if(!this.matches.length || !this._matchesAll(val)) { | ||
191 | } | ||
192 | |||
193 | if(this.matches.length) { // match(es) found | ||
194 | if(this.matches[this._matchIndex] !== val) { | ||
195 | // Suggest the matched hint, subtracting the typed-in string | ||
196 | // Only if the hint is not was the user has typed already | ||
197 | this.hint = this._getHintDifference(); | ||
198 | } else { | ||
199 | this.hint = null; | ||
200 | } | ||
201 | } else { // no matches found | ||
202 | this.hint = null; | ||
203 | } | ||
204 | } else { // no suggestion for empty string | ||
205 | this.hint = null; | ||
206 | } | ||
207 | |||
208 | } | ||
209 | } | ||
210 | }, | ||
211 | handleBackspace : { | ||
212 | value : function(e) { | ||
213 | this.matches.length = 0; | ||
214 | } | ||
215 | }, | ||
216 | _matchesAll : { | ||
217 | value : function(value) { | ||
218 | return this.matches.every(function(match) { | ||
219 | return match.indexOf(value) === 0; | ||
220 | }, this); | ||
221 | } | ||
222 | }, | ||
223 | _execKeyAction : { | ||
224 | value : function(e) { | ||
225 | var key = e.keyCode, | ||
226 | keys = this.keyActions; | ||
227 | |||
228 | if(this.hint) { | ||
229 | if( keys.hint.revert.indexOf(key) !== -1 ) { this.revert(e); } | ||
230 | if( keys.hint.accept.indexOf(key) !== -1 ) { this.accept(e); } | ||
231 | if( keys.hint.stop.indexOf(key) !== -1 ) { this.stop(e); } | ||
232 | if( keys.hint.next.indexOf(key) !== -1 ) { this.hintNext(e); } | ||
233 | if( keys.hint.prev.indexOf(key) !== -1 ) { this.hintPrev(e); } | ||
234 | if( keys.hint.backsp.indexOf(key) !== -1 ) { this.handleBackspace(e); } | ||
235 | } else { | ||
236 | if(keys.noHint.revert.indexOf(key) !== -1) { this.revert(e); } | ||