|
88 | 88 | this._parseElementAnnotations(node, list);
|
89 | 89 | },
|
90 | 90 |
|
91 |
| - _testEscape: function(value) { |
92 |
| - var escape = value.slice(0, 2); |
93 |
| - if (escape === '{{' || escape === '[[') { |
94 |
| - return escape; |
| 91 | + _bindingRegex: /([^{[]*)({{|\[\[)([^}\]]*)(?:]]|}})/g, |
| 92 | + |
| 93 | + // TODO(kschaaf): We could modify this to allow an escape mechanism by |
| 94 | + // looking for the escape sequence in each of the matches and converting |
| 95 | + // the part back to a literal type, and then bailing if only literals |
| 96 | + // were found |
| 97 | + _parseBindings: function(text) { |
| 98 | + var re = this._bindingRegex; |
| 99 | + var parts = []; |
| 100 | + var m, lastIndex; |
| 101 | + // Example: "literal1{{binding1}}literal2[[binding2]]final" |
| 102 | + // Regex matches: |
| 103 | + // Iteration 1: Iteration 2: |
| 104 | + // m[1]: 'literal1' 'literal2' |
| 105 | + // m[2]: '{{' '[[' |
| 106 | + // m[3]: 'binding1' 'binding2' |
| 107 | + // 'final' is manually substring'ed from end |
| 108 | + while ((m = re.exec(text)) !== null) { |
| 109 | + // Add literal part |
| 110 | + if (m[1]) { |
| 111 | + parts.push({literal: m[1]}); |
| 112 | + } |
| 113 | + // Add binding part |
| 114 | + // Mode (one-way or two) |
| 115 | + var mode = m[2][0]; |
| 116 | + var value = m[3].trim(); |
| 117 | + // Negate |
| 118 | + var negate = false; |
| 119 | + if (value[0] == '!') { |
| 120 | + negate = true; |
| 121 | + value = value.substring(1).trim(); |
| 122 | + } |
| 123 | + var customEvent, notifyEvent, colon; |
| 124 | + if (mode == '{' && (colon = value.indexOf('::')) > 0) { |
| 125 | + notifyEvent = value.substring(colon + 2); |
| 126 | + value = value.substring(0, colon); |
| 127 | + customEvent = true; |
| 128 | + } |
| 129 | + parts.push({ |
| 130 | + compoundIndex: parts.length, |
| 131 | + value: value, |
| 132 | + mode: mode, |
| 133 | + negate: negate, |
| 134 | + event: notifyEvent, |
| 135 | + customEvent: customEvent |
| 136 | + }); |
| 137 | + lastIndex = re.lastIndex; |
| 138 | + } |
| 139 | + // Add a final literal part |
| 140 | + if (lastIndex && lastIndex < text.length) { |
| 141 | + var literal = text.substring(lastIndex); |
| 142 | + if (literal) { |
| 143 | + parts.push({ |
| 144 | + literal: literal |
| 145 | + }); |
| 146 | + } |
| 147 | + } |
| 148 | + if (parts.length) { |
| 149 | + return parts; |
95 | 150 | }
|
96 | 151 | },
|
97 | 152 |
|
| 153 | + _literalFromParts: function(parts) { |
| 154 | + var s = ''; |
| 155 | + for (var i=0; i<parts.length; i++) { |
| 156 | + var literal = parts[i].literal; |
| 157 | + s += literal || ''; |
| 158 | + } |
| 159 | + return s; |
| 160 | + }, |
| 161 | + |
98 | 162 | // add annotations gleaned from TextNode `node` to `list`
|
99 | 163 | _parseTextNodeAnnotation: function(node, list) {
|
100 |
| - var v = node.textContent; |
101 |
| - var escape = this._testEscape(v); |
102 |
| - if (escape) { |
103 |
| - // NOTE: use a space here so the textNode remains; some browsers |
104 |
| - // (IE) evacipate an empty textNode. |
105 |
| - node.textContent = ' '; |
| 164 | + var parts = this._parseBindings(node.textContent); |
| 165 | + if (parts) { |
| 166 | + // Initialize the textContent with any literal parts |
| 167 | + // NOTE: default to a space here so the textNode remains; some browsers |
| 168 | + // (IE) evacipate an empty textNode following cloneNode/importNode. |
| 169 | + node.textContent = this._literalFromParts(parts) || ' '; |
106 | 170 | var annote = {
|
107 | 171 | bindings: [{
|
108 | 172 | kind: 'text',
|
109 |
| - mode: escape[0], |
110 |
| - value: v.slice(2, -2).trim() |
| 173 | + name: 'textContent', |
| 174 | + parts: parts, |
| 175 | + isCompound: parts.length !== 1 |
111 | 176 | }]
|
112 | 177 | };
|
113 | 178 | list.push(annote);
|
|
152 | 217 | !node.hasAttribute('preserve-content')) {
|
153 | 218 | this._parseTemplate(node, i, list, annote);
|
154 | 219 | }
|
155 |
| - // collapse adjacent textNodes: fixes an IE issue that can cause |
| 220 | + // collapse adjacent textNodes: fixes an IE issue that can cause |
156 | 221 | // text nodes to be inexplicably split =(
|
157 | 222 | // note that root.normalize() should work but does not so we do this
|
158 | 223 | // manually.
|
|
203 | 268 | // `annotation data` is not as clear as it could be
|
204 | 269 | _parseNodeAttributeAnnotations: function(node, annotation) {
|
205 | 270 | for (var i=node.attributes.length-1, a; (a=node.attributes[i]); i--) {
|
206 |
| - var n = a.name, v = a.value; |
207 |
| - // id (unless actually an escaped binding annotation) |
208 |
| - if (n === 'id' && !this._testEscape(v)) { |
209 |
| - annotation.id = v; |
210 |
| - } |
| 271 | + var n = a.name; |
| 272 | + var v = a.value; |
| 273 | + var b; |
211 | 274 | // events (on-*)
|
212 |
| - else if (n.slice(0, 3) === 'on-') { |
| 275 | + if (n.slice(0, 3) === 'on-') { |
213 | 276 | node.removeAttribute(n);
|
214 | 277 | annotation.events.push({
|
215 | 278 | name: n.slice(3),
|
216 | 279 | value: v
|
217 | 280 | });
|
218 | 281 | }
|
219 | 282 | // bindings (other attributes)
|
220 |
| - else { |
221 |
| - var b = this._parseNodeAttributeAnnotation(node, n, v); |
222 |
| - if (b) { |
223 |
| - annotation.bindings.push(b); |
224 |
| - } |
| 283 | + else if (b = this._parseNodeAttributeAnnotation(node, n, v)) { |
| 284 | + annotation.bindings.push(b); |
| 285 | + } |
| 286 | + // static id |
| 287 | + else if (n === 'id') { |
| 288 | + annotation.id = v; |
225 | 289 | }
|
226 | 290 | }
|
227 | 291 | },
|
228 | 292 |
|
229 | 293 | // construct annotation data from a generic attribute, or undefined
|
230 |
| - _parseNodeAttributeAnnotation: function(node, n, v) { |
231 |
| - var escape = this._testEscape(v); |
232 |
| - if (escape) { |
233 |
| - var customEvent; |
234 |
| - // Cache name (`n` will be mangled) |
235 |
| - var name = n; |
236 |
| - // Mode (one-way or two) |
237 |
| - var mode = escape[0]; |
238 |
| - v = v.slice(2, -2).trim(); |
239 |
| - // Negate |
240 |
| - var not = false; |
241 |
| - if (v[0] == '!') { |
242 |
| - v = v.substring(1); |
243 |
| - not = true; |
244 |
| - } |
| 294 | + _parseNodeAttributeAnnotation: function(node, name, value) { |
| 295 | + var parts = this._parseBindings(value); |
| 296 | + if (parts) { |
245 | 297 | // Attribute or property
|
| 298 | + var origName = name; |
246 | 299 | var kind = 'property';
|
247 |
| - if (n[n.length-1] == '$') { |
248 |
| - name = n.slice(0, -1); |
| 300 | + if (name[name.length-1] == '$') { |
| 301 | + name = name.slice(0, -1); |
249 | 302 | kind = 'attribute';
|
250 | 303 | }
|
251 |
| - // Custom notification event |
252 |
| - var notifyEvent, colon; |
253 |
| - if (mode == '{' && (colon = v.indexOf('::')) > 0) { |
254 |
| - notifyEvent = v.substring(colon + 2); |
255 |
| - v = v.substring(0, colon); |
256 |
| - customEvent = true; |
| 304 | + // Initialize attribute bindings with any literal parts |
| 305 | + var literal = this._literalFromParts(parts); |
| 306 | + if (kind == 'attribute') { |
| 307 | + node.setAttribute(name, literal); |
257 | 308 | }
|
258 | 309 | // Clear attribute before removing, since IE won't allow removing
|
259 | 310 | // `value` attribute if it previously had a value (can't
|
260 | 311 | // unconditionally set '' before removing since attributes with `$`
|
261 | 312 | // can't be set using setAttribute)
|
262 |
| - if (node.localName == 'input' && n == 'value') { |
263 |
| - node.setAttribute(n, ''); |
| 313 | + if (node.localName == 'input' && name == 'value') { |
| 314 | + node.setAttribute(origName, ''); |
264 | 315 | }
|
265 | 316 | // Remove annotation
|
266 |
| - node.removeAttribute(n); |
| 317 | + node.removeAttribute(origName); |
267 | 318 | // Case hackery: attributes are lower-case, but bind targets
|
268 | 319 | // (properties) are case sensitive. Gambit is to map dash-case to
|
269 | 320 | // camel-case: `foo-bar` becomes `fooBar`.
|
|
273 | 324 | }
|
274 | 325 | return {
|
275 | 326 | kind: kind,
|
276 |
| - mode: mode, |
277 | 327 | name: name,
|
278 |
| - value: v, |
279 |
| - negate: not, |
280 |
| - event: notifyEvent, |
281 |
| - customEvent: customEvent |
| 328 | + parts: parts, |
| 329 | + literal: literal, |
| 330 | + isCompound: parts.length !== 1 |
282 | 331 | };
|
283 | 332 | }
|
284 | 333 | },
|
|
0 commit comments