@ckeditor/ckeditor5-ui 35.2.1 → 35.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +31 -23
- package/src/bindings/addkeyboardhandlingforgrid.js +45 -57
- package/src/bindings/clickoutsidehandler.js +15 -21
- package/src/bindings/injectcsstransitiondisabler.js +16 -20
- package/src/bindings/preventdefault.js +6 -8
- package/src/bindings/submithandler.js +5 -7
- package/src/button/button.js +5 -0
- package/src/button/buttonview.js +220 -259
- package/src/button/switchbuttonview.js +56 -71
- package/src/colorgrid/colorgridview.js +135 -197
- package/src/colorgrid/colortileview.js +37 -47
- package/src/colorgrid/utils.js +57 -66
- package/src/componentfactory.js +79 -93
- package/src/dropdown/button/dropdownbutton.js +5 -0
- package/src/dropdown/button/dropdownbuttonview.js +44 -57
- package/src/dropdown/button/splitbuttonview.js +159 -207
- package/src/dropdown/dropdownpanelfocusable.js +5 -0
- package/src/dropdown/dropdownpanelview.js +101 -112
- package/src/dropdown/dropdownview.js +396 -438
- package/src/dropdown/utils.js +164 -213
- package/src/editableui/editableuiview.js +125 -141
- package/src/editableui/inline/inlineeditableuiview.js +44 -54
- package/src/editorui/bodycollection.js +61 -75
- package/src/editorui/boxed/boxededitoruiview.js +91 -104
- package/src/editorui/editoruiview.js +30 -39
- package/src/focuscycler.js +214 -245
- package/src/formheader/formheaderview.js +58 -70
- package/src/icon/iconview.js +145 -111
- package/src/iframe/iframeview.js +37 -49
- package/src/index.js +0 -17
- package/src/input/inputview.js +170 -198
- package/src/inputnumber/inputnumberview.js +48 -56
- package/src/inputtext/inputtextview.js +14 -18
- package/src/label/labelview.js +44 -53
- package/src/labeledfield/labeledfieldview.js +212 -235
- package/src/labeledfield/utils.js +39 -57
- package/src/labeledinput/labeledinputview.js +190 -221
- package/src/list/listitemview.js +40 -50
- package/src/list/listseparatorview.js +15 -19
- package/src/list/listview.js +94 -115
- package/src/model.js +19 -25
- package/src/notification/notification.js +151 -202
- package/src/panel/balloon/balloonpanelview.js +535 -628
- package/src/panel/balloon/contextualballoon.js +611 -732
- package/src/panel/sticky/stickypanelview.js +238 -270
- package/src/template.js +1049 -1479
- package/src/toolbar/balloon/balloontoolbar.js +337 -424
- package/src/toolbar/block/blockbuttonview.js +32 -42
- package/src/toolbar/block/blocktoolbar.js +375 -477
- package/src/toolbar/normalizetoolbarconfig.js +17 -21
- package/src/toolbar/toolbarlinebreakview.js +15 -19
- package/src/toolbar/toolbarseparatorview.js +15 -19
- package/src/toolbar/toolbarview.js +866 -1053
- package/src/tooltipmanager.js +324 -353
- package/src/view.js +389 -430
- package/src/viewcollection.js +147 -178
- package/src/button/button.jsdoc +0 -165
- package/src/dropdown/button/dropdownbutton.jsdoc +0 -22
- package/src/dropdown/dropdownpanelfocusable.jsdoc +0 -27
package/src/template.js
CHANGED
|
@@ -2,24 +2,18 @@
|
|
|
2
2
|
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
5
|
/**
|
|
7
6
|
* @module ui/template
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
8
|
/* global document */
|
|
11
|
-
|
|
12
9
|
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
|
|
13
|
-
import
|
|
14
|
-
import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
|
|
10
|
+
import { Emitter } from '@ckeditor/ckeditor5-utils/src/emittermixin';
|
|
15
11
|
import View from './view';
|
|
16
12
|
import ViewCollection from './viewcollection';
|
|
17
13
|
import isNode from '@ckeditor/ckeditor5-utils/src/dom/isnode';
|
|
18
14
|
import { isObject, cloneDeepWith } from 'lodash-es';
|
|
19
15
|
import toArray from '@ckeditor/ckeditor5-utils/src/toarray';
|
|
20
|
-
|
|
21
16
|
const xhtmlNs = 'http://www.w3.org/1999/xhtml';
|
|
22
|
-
|
|
23
17
|
/**
|
|
24
18
|
* A basic Template class. It renders a DOM HTML element or text from a
|
|
25
19
|
* {@link module:ui/template~TemplateDefinition definition} and supports element attributes, children,
|
|
@@ -56,880 +50,802 @@ const xhtmlNs = 'http://www.w3.org/1999/xhtml';
|
|
|
56
50
|
*
|
|
57
51
|
* @mixes module:utils/emittermixin~EmitterMixin
|
|
58
52
|
*/
|
|
59
|
-
export default class Template {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
*/
|
|
779
|
-
_bindToObservable( { schema, updater, data } ) {
|
|
780
|
-
const revertData = data.revertData;
|
|
781
|
-
|
|
782
|
-
// Set initial values.
|
|
783
|
-
syncValueSchemaValue( schema, updater, data );
|
|
784
|
-
|
|
785
|
-
const revertBindings = schema
|
|
786
|
-
// Filter "falsy" (false, undefined, null, '') value schema components out.
|
|
787
|
-
.filter( item => !isFalsy( item ) )
|
|
788
|
-
// Filter inactive bindings from schema, like static strings ('foo'), numbers (42), etc.
|
|
789
|
-
.filter( item => item.observable )
|
|
790
|
-
// Once only the actual binding are left, let the emitter listen to observable change:attribute event.
|
|
791
|
-
// TODO: Reduce the number of listeners attached as many bindings may listen
|
|
792
|
-
// to the same observable attribute.
|
|
793
|
-
.map( templateBinding => templateBinding.activateAttributeListener( schema, updater, data ) );
|
|
794
|
-
|
|
795
|
-
if ( revertData ) {
|
|
796
|
-
revertData.bindings.push( revertBindings );
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
/**
|
|
801
|
-
* Reverts {@link module:ui/template~RenderData#revertData template data} from a node to
|
|
802
|
-
* return it to the original state.
|
|
803
|
-
*
|
|
804
|
-
* @protected
|
|
805
|
-
* @param {HTMLElement|Text} node A node to be reverted.
|
|
806
|
-
* @param {Object} revertData An object that stores information about what changes have been made by
|
|
807
|
-
* {@link #apply} to the node. See {@link module:ui/template~RenderData#revertData} for more information.
|
|
808
|
-
*/
|
|
809
|
-
_revertTemplateFromNode( node, revertData ) {
|
|
810
|
-
for ( const binding of revertData.bindings ) {
|
|
811
|
-
// Each binding may consist of several observable+observable#attribute.
|
|
812
|
-
// like the following has 2:
|
|
813
|
-
//
|
|
814
|
-
// class: [
|
|
815
|
-
// 'x',
|
|
816
|
-
// bind.to( 'foo' ),
|
|
817
|
-
// 'y',
|
|
818
|
-
// bind.to( 'bar' )
|
|
819
|
-
// ]
|
|
820
|
-
//
|
|
821
|
-
for ( const revertBinding of binding ) {
|
|
822
|
-
revertBinding();
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
if ( revertData.text ) {
|
|
827
|
-
node.textContent = revertData.text;
|
|
828
|
-
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
for ( const attrName in revertData.attributes ) {
|
|
833
|
-
const attrValue = revertData.attributes[ attrName ];
|
|
834
|
-
|
|
835
|
-
// When the attribute has **not** been set before #apply().
|
|
836
|
-
if ( attrValue === null ) {
|
|
837
|
-
node.removeAttribute( attrName );
|
|
838
|
-
} else {
|
|
839
|
-
node.setAttribute( attrName, attrValue );
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
for ( let i = 0; i < revertData.children.length; ++i ) {
|
|
844
|
-
this._revertTemplateFromNode( node.childNodes[ i ], revertData.children[ i ] );
|
|
845
|
-
}
|
|
846
|
-
}
|
|
53
|
+
export default class Template extends Emitter {
|
|
54
|
+
/**
|
|
55
|
+
* Creates an instance of the {@link ~Template} class.
|
|
56
|
+
*
|
|
57
|
+
* @param {module:ui/template~TemplateDefinition} def The definition of the template.
|
|
58
|
+
*/
|
|
59
|
+
constructor(def) {
|
|
60
|
+
super();
|
|
61
|
+
Object.assign(this, normalize(clone(def)));
|
|
62
|
+
/**
|
|
63
|
+
* Indicates whether this particular Template instance has been
|
|
64
|
+
* {@link #render rendered}.
|
|
65
|
+
*
|
|
66
|
+
* @readonly
|
|
67
|
+
* @protected
|
|
68
|
+
* @member {Boolean}
|
|
69
|
+
*/
|
|
70
|
+
this._isRendered = false;
|
|
71
|
+
/**
|
|
72
|
+
* The tag (`tagName`) of this template, e.g. `div`. It also indicates that the template
|
|
73
|
+
* renders to an HTML element.
|
|
74
|
+
*
|
|
75
|
+
* @member {String} #tag
|
|
76
|
+
*/
|
|
77
|
+
/**
|
|
78
|
+
* The text of the template. It also indicates that the template renders to a DOM text node.
|
|
79
|
+
*
|
|
80
|
+
* @member {Array.<String|module:ui/template~TemplateValueSchema>} #text
|
|
81
|
+
*/
|
|
82
|
+
/**
|
|
83
|
+
* The attributes of the template, e.g. `{ id: [ 'ck-id' ] }`, corresponding with
|
|
84
|
+
* the attributes of an HTML element.
|
|
85
|
+
*
|
|
86
|
+
* **Note**: This property only makes sense when {@link #tag} is defined.
|
|
87
|
+
*
|
|
88
|
+
* @member {Object} #attributes
|
|
89
|
+
*/
|
|
90
|
+
/**
|
|
91
|
+
* The children of the template. They can be either:
|
|
92
|
+
* * independent instances of {@link ~Template} (sub–templates),
|
|
93
|
+
* * native DOM Nodes.
|
|
94
|
+
*
|
|
95
|
+
* **Note**: This property only makes sense when {@link #tag} is defined.
|
|
96
|
+
*
|
|
97
|
+
* @member {Array.<module:ui/template~Template|Node>} #children
|
|
98
|
+
*/
|
|
99
|
+
/**
|
|
100
|
+
* The DOM event listeners of the template.
|
|
101
|
+
*
|
|
102
|
+
* @member {Object} #eventListeners
|
|
103
|
+
*/
|
|
104
|
+
/**
|
|
105
|
+
* The data used by the {@link #revert} method to restore a node to its original state.
|
|
106
|
+
*
|
|
107
|
+
* See: {@link #apply}.
|
|
108
|
+
*
|
|
109
|
+
* @readonly
|
|
110
|
+
* @protected
|
|
111
|
+
* @member {module:ui/template~RenderData}
|
|
112
|
+
*/
|
|
113
|
+
this._revertData = null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Renders a DOM Node (an HTML element or text) out of the template.
|
|
117
|
+
*
|
|
118
|
+
* const domNode = new Template( { ... } ).render();
|
|
119
|
+
*
|
|
120
|
+
* See: {@link #apply}.
|
|
121
|
+
*
|
|
122
|
+
* @returns {HTMLElement|Text}
|
|
123
|
+
*/
|
|
124
|
+
render() {
|
|
125
|
+
const node = this._renderNode({
|
|
126
|
+
intoFragment: true
|
|
127
|
+
});
|
|
128
|
+
this._isRendered = true;
|
|
129
|
+
return node;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Applies the template to an existing DOM Node, either HTML element or text.
|
|
133
|
+
*
|
|
134
|
+
* **Note:** No new DOM nodes will be created. Applying extends:
|
|
135
|
+
*
|
|
136
|
+
* {@link module:ui/template~TemplateDefinition attributes},
|
|
137
|
+
* {@link module:ui/template~TemplateDefinition event listeners}, and
|
|
138
|
+
* `textContent` of {@link module:ui/template~TemplateDefinition children} only.
|
|
139
|
+
*
|
|
140
|
+
* **Note:** Existing `class` and `style` attributes are extended when a template
|
|
141
|
+
* is applied to an HTML element, while other attributes and `textContent` are overridden.
|
|
142
|
+
*
|
|
143
|
+
* **Note:** The process of applying a template can be easily reverted using the
|
|
144
|
+
* {@link module:ui/template~Template#revert} method.
|
|
145
|
+
*
|
|
146
|
+
* const element = document.createElement( 'div' );
|
|
147
|
+
* const observable = new Model( { divClass: 'my-div' } );
|
|
148
|
+
* const emitter = Object.create( EmitterMixin );
|
|
149
|
+
* const bind = Template.bind( observable, emitter );
|
|
150
|
+
*
|
|
151
|
+
* new Template( {
|
|
152
|
+
* attributes: {
|
|
153
|
+
* id: 'first-div',
|
|
154
|
+
* class: bind.to( 'divClass' )
|
|
155
|
+
* },
|
|
156
|
+
* on: {
|
|
157
|
+
* click: bind( 'elementClicked' ) // Will be fired by the observable.
|
|
158
|
+
* },
|
|
159
|
+
* children: [
|
|
160
|
+
* 'Div text.'
|
|
161
|
+
* ]
|
|
162
|
+
* } ).apply( element );
|
|
163
|
+
*
|
|
164
|
+
* console.log( element.outerHTML ); // -> '<div id="first-div" class="my-div"></div>'
|
|
165
|
+
*
|
|
166
|
+
* @see module:ui/template~Template#render
|
|
167
|
+
* @see module:ui/template~Template#revert
|
|
168
|
+
* @param {Node} node Root node for the template to apply.
|
|
169
|
+
*/
|
|
170
|
+
apply(node) {
|
|
171
|
+
this._revertData = getEmptyRevertData();
|
|
172
|
+
this._renderNode({
|
|
173
|
+
node,
|
|
174
|
+
intoFragment: false,
|
|
175
|
+
isApplying: true,
|
|
176
|
+
revertData: this._revertData
|
|
177
|
+
});
|
|
178
|
+
return node;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Reverts a template {@link module:ui/template~Template#apply applied} to a DOM node.
|
|
182
|
+
*
|
|
183
|
+
* @param {Node} node The root node for the template to revert. In most of the cases, it is the
|
|
184
|
+
* same node used by {@link module:ui/template~Template#apply}.
|
|
185
|
+
*/
|
|
186
|
+
revert(node) {
|
|
187
|
+
if (!this._revertData) {
|
|
188
|
+
/**
|
|
189
|
+
* Attempting to revert a template which has not been applied yet.
|
|
190
|
+
*
|
|
191
|
+
* @error ui-template-revert-not-applied
|
|
192
|
+
*/
|
|
193
|
+
throw new CKEditorError('ui-template-revert-not-applied', [this, node]);
|
|
194
|
+
}
|
|
195
|
+
this._revertTemplateFromNode(node, this._revertData);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Returns an iterator which traverses the template in search of {@link module:ui/view~View}
|
|
199
|
+
* instances and returns them one by one.
|
|
200
|
+
*
|
|
201
|
+
* const viewFoo = new View();
|
|
202
|
+
* const viewBar = new View();
|
|
203
|
+
* const viewBaz = new View();
|
|
204
|
+
* const template = new Template( {
|
|
205
|
+
* tag: 'div',
|
|
206
|
+
* children: [
|
|
207
|
+
* viewFoo,
|
|
208
|
+
* {
|
|
209
|
+
* tag: 'div',
|
|
210
|
+
* children: [
|
|
211
|
+
* viewBar
|
|
212
|
+
* ]
|
|
213
|
+
* },
|
|
214
|
+
* viewBaz
|
|
215
|
+
* ]
|
|
216
|
+
* } );
|
|
217
|
+
*
|
|
218
|
+
* // Logs: viewFoo, viewBar, viewBaz
|
|
219
|
+
* for ( const view of template.getViews() ) {
|
|
220
|
+
* console.log( view );
|
|
221
|
+
* }
|
|
222
|
+
*
|
|
223
|
+
* @returns {Iterable.<module:ui/view~View>}
|
|
224
|
+
*/
|
|
225
|
+
*getViews() {
|
|
226
|
+
function* search(def) {
|
|
227
|
+
if (def.children) {
|
|
228
|
+
for (const child of def.children) {
|
|
229
|
+
if (isView(child)) {
|
|
230
|
+
yield child;
|
|
231
|
+
}
|
|
232
|
+
else if (isTemplate(child)) {
|
|
233
|
+
yield* search(child);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
yield* search(this);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* An entry point to the interface which binds DOM nodes to
|
|
242
|
+
* {@link module:utils/observablemixin~Observable observables}.
|
|
243
|
+
* There are two types of bindings:
|
|
244
|
+
*
|
|
245
|
+
* * HTML element attributes or text `textContent` synchronized with attributes of an
|
|
246
|
+
* {@link module:utils/observablemixin~Observable}. Learn more about {@link module:ui/template~BindChain#to}
|
|
247
|
+
* and {@link module:ui/template~BindChain#if}.
|
|
248
|
+
*
|
|
249
|
+
* const bind = Template.bind( observable, emitter );
|
|
250
|
+
*
|
|
251
|
+
* new Template( {
|
|
252
|
+
* attributes: {
|
|
253
|
+
* // Binds the element "class" attribute to observable#classAttribute.
|
|
254
|
+
* class: bind.to( 'classAttribute' )
|
|
255
|
+
* }
|
|
256
|
+
* } ).render();
|
|
257
|
+
*
|
|
258
|
+
* * DOM events fired on HTML element propagated through
|
|
259
|
+
* {@link module:utils/observablemixin~Observable}. Learn more about {@link module:ui/template~BindChain#to}.
|
|
260
|
+
*
|
|
261
|
+
* const bind = Template.bind( observable, emitter );
|
|
262
|
+
*
|
|
263
|
+
* new Template( {
|
|
264
|
+
* on: {
|
|
265
|
+
* // Will be fired by the observable.
|
|
266
|
+
* click: bind( 'elementClicked' )
|
|
267
|
+
* }
|
|
268
|
+
* } ).render();
|
|
269
|
+
*
|
|
270
|
+
* Also see {@link module:ui/view~View#bindTemplate}.
|
|
271
|
+
*
|
|
272
|
+
* @param {module:utils/observablemixin~Observable} observable An observable which provides boundable attributes.
|
|
273
|
+
* @param {module:utils/emittermixin~Emitter} emitter An emitter that listens to observable attribute
|
|
274
|
+
* changes or DOM Events (depending on the kind of the binding). Usually, a {@link module:ui/view~View} instance.
|
|
275
|
+
* @returns {module:ui/template~BindChain}
|
|
276
|
+
*/
|
|
277
|
+
static bind(observable, emitter) {
|
|
278
|
+
return {
|
|
279
|
+
to(eventNameOrFunctionOrAttribute, callback) {
|
|
280
|
+
return new TemplateToBinding({
|
|
281
|
+
eventNameOrFunction: eventNameOrFunctionOrAttribute,
|
|
282
|
+
attribute: eventNameOrFunctionOrAttribute,
|
|
283
|
+
observable, emitter, callback
|
|
284
|
+
});
|
|
285
|
+
},
|
|
286
|
+
if(attribute, valueIfTrue, callback) {
|
|
287
|
+
return new TemplateIfBinding({
|
|
288
|
+
observable, emitter, attribute, valueIfTrue, callback
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Extends an existing {@link module:ui/template~Template} instance with some additional content
|
|
295
|
+
* from another {@link module:ui/template~TemplateDefinition}.
|
|
296
|
+
*
|
|
297
|
+
* const bind = Template.bind( observable, emitter );
|
|
298
|
+
*
|
|
299
|
+
* const template = new Template( {
|
|
300
|
+
* tag: 'p',
|
|
301
|
+
* attributes: {
|
|
302
|
+
* class: 'a',
|
|
303
|
+
* data-x: bind.to( 'foo' )
|
|
304
|
+
* },
|
|
305
|
+
* children: [
|
|
306
|
+
* {
|
|
307
|
+
* tag: 'span',
|
|
308
|
+
* attributes: {
|
|
309
|
+
* class: 'b'
|
|
310
|
+
* },
|
|
311
|
+
* children: [
|
|
312
|
+
* 'Span'
|
|
313
|
+
* ]
|
|
314
|
+
* }
|
|
315
|
+
* ]
|
|
316
|
+
* } );
|
|
317
|
+
*
|
|
318
|
+
* // Instance-level extension.
|
|
319
|
+
* Template.extend( template, {
|
|
320
|
+
* attributes: {
|
|
321
|
+
* class: 'b',
|
|
322
|
+
* data-x: bind.to( 'bar' )
|
|
323
|
+
* },
|
|
324
|
+
* children: [
|
|
325
|
+
* {
|
|
326
|
+
* attributes: {
|
|
327
|
+
* class: 'c'
|
|
328
|
+
* }
|
|
329
|
+
* }
|
|
330
|
+
* ]
|
|
331
|
+
* } );
|
|
332
|
+
*
|
|
333
|
+
* // Child extension.
|
|
334
|
+
* Template.extend( template.children[ 0 ], {
|
|
335
|
+
* attributes: {
|
|
336
|
+
* class: 'd'
|
|
337
|
+
* }
|
|
338
|
+
* } );
|
|
339
|
+
*
|
|
340
|
+
* the `outerHTML` of `template.render()` is:
|
|
341
|
+
*
|
|
342
|
+
* <p class="a b" data-x="{ observable.foo } { observable.bar }">
|
|
343
|
+
* <span class="b c d">Span</span>
|
|
344
|
+
* </p>
|
|
345
|
+
*
|
|
346
|
+
* @param {module:ui/template~Template} template An existing template instance to be extended.
|
|
347
|
+
* @param {module:ui/template~TemplateDefinition} def Additional definition to be applied to a template.
|
|
348
|
+
*/
|
|
349
|
+
static extend(template, def) {
|
|
350
|
+
if (template._isRendered) {
|
|
351
|
+
/**
|
|
352
|
+
* Extending a template after rendering may not work as expected. To make sure
|
|
353
|
+
* the {@link module:ui/template~Template.extend extending} works for an element,
|
|
354
|
+
* make sure it happens before {@link #render} is called.
|
|
355
|
+
*
|
|
356
|
+
* @error template-extend-render
|
|
357
|
+
*/
|
|
358
|
+
throw new CKEditorError('template-extend-render', [this, template]);
|
|
359
|
+
}
|
|
360
|
+
extendTemplate(template, normalize(clone(def)));
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Renders a DOM Node (either an HTML element or text) out of the template.
|
|
364
|
+
*
|
|
365
|
+
* @protected
|
|
366
|
+
* @param {module:ui/template~RenderData} data Rendering data.
|
|
367
|
+
*/
|
|
368
|
+
_renderNode(data) {
|
|
369
|
+
let isInvalid;
|
|
370
|
+
if (data.node) {
|
|
371
|
+
// When applying, a definition cannot have "tag" and "text" at the same time.
|
|
372
|
+
isInvalid = this.tag && this.text;
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
// When rendering, a definition must have either "tag" or "text": XOR( this.tag, this.text ).
|
|
376
|
+
isInvalid = this.tag ? this.text : !this.text;
|
|
377
|
+
}
|
|
378
|
+
if (isInvalid) {
|
|
379
|
+
/**
|
|
380
|
+
* Node definition cannot have the "tag" and "text" properties at the same time.
|
|
381
|
+
* Node definition must have either "tag" or "text" when rendering a new Node.
|
|
382
|
+
*
|
|
383
|
+
* @error ui-template-wrong-syntax
|
|
384
|
+
*/
|
|
385
|
+
throw new CKEditorError('ui-template-wrong-syntax', this);
|
|
386
|
+
}
|
|
387
|
+
if (this.text) {
|
|
388
|
+
return this._renderText(data);
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
return this._renderElement(data);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Renders an HTML element out of the template.
|
|
396
|
+
*
|
|
397
|
+
* @protected
|
|
398
|
+
* @param {module:ui/template~RenderData} data Rendering data.
|
|
399
|
+
*/
|
|
400
|
+
_renderElement(data) {
|
|
401
|
+
let node = data.node;
|
|
402
|
+
if (!node) {
|
|
403
|
+
node = data.node = document.createElementNS(this.ns || xhtmlNs, this.tag);
|
|
404
|
+
}
|
|
405
|
+
this._renderAttributes(data);
|
|
406
|
+
this._renderElementChildren(data);
|
|
407
|
+
this._setUpListeners(data);
|
|
408
|
+
return node;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Renders a text node out of {@link module:ui/template~Template#text}.
|
|
412
|
+
*
|
|
413
|
+
* @protected
|
|
414
|
+
* @param {module:ui/template~RenderData} data Rendering data.
|
|
415
|
+
*/
|
|
416
|
+
_renderText(data) {
|
|
417
|
+
let node = data.node;
|
|
418
|
+
// Save the original textContent to revert it in #revert().
|
|
419
|
+
if (node) {
|
|
420
|
+
data.revertData.text = node.textContent;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
node = data.node = document.createTextNode('');
|
|
424
|
+
}
|
|
425
|
+
// Check if this Text Node is bound to Observable. Cases:
|
|
426
|
+
//
|
|
427
|
+
// text: [ Template.bind( ... ).to( ... ) ]
|
|
428
|
+
//
|
|
429
|
+
// text: [
|
|
430
|
+
// 'foo',
|
|
431
|
+
// Template.bind( ... ).to( ... ),
|
|
432
|
+
// ...
|
|
433
|
+
// ]
|
|
434
|
+
//
|
|
435
|
+
if (hasTemplateBinding(this.text)) {
|
|
436
|
+
this._bindToObservable({
|
|
437
|
+
schema: this.text,
|
|
438
|
+
updater: getTextUpdater(node),
|
|
439
|
+
data
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
// Simply set text. Cases:
|
|
443
|
+
//
|
|
444
|
+
// text: [ 'all', 'are', 'static' ]
|
|
445
|
+
//
|
|
446
|
+
// text: [ 'foo' ]
|
|
447
|
+
//
|
|
448
|
+
else {
|
|
449
|
+
node.textContent = this.text.join('');
|
|
450
|
+
}
|
|
451
|
+
return node;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Renders HTML element attributes out of {@link module:ui/template~Template#attributes}.
|
|
455
|
+
*
|
|
456
|
+
* @protected
|
|
457
|
+
* @param {module:ui/template~RenderData} data Rendering data.
|
|
458
|
+
*/
|
|
459
|
+
_renderAttributes(data) {
|
|
460
|
+
if (!this.attributes) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const node = data.node;
|
|
464
|
+
const revertData = data.revertData;
|
|
465
|
+
for (const attrName in this.attributes) {
|
|
466
|
+
// Current attribute value in DOM.
|
|
467
|
+
const domAttrValue = node.getAttribute(attrName);
|
|
468
|
+
// The value to be set.
|
|
469
|
+
const attrValue = this.attributes[attrName];
|
|
470
|
+
// Save revert data.
|
|
471
|
+
if (revertData) {
|
|
472
|
+
revertData.attributes[attrName] = domAttrValue;
|
|
473
|
+
}
|
|
474
|
+
// Detect custom namespace:
|
|
475
|
+
//
|
|
476
|
+
// class: {
|
|
477
|
+
// ns: 'abc',
|
|
478
|
+
// value: Template.bind( ... ).to( ... )
|
|
479
|
+
// }
|
|
480
|
+
//
|
|
481
|
+
const attrNs = isNamespaced(attrValue) ? attrValue[0].ns : null;
|
|
482
|
+
// Activate binding if one is found. Cases:
|
|
483
|
+
//
|
|
484
|
+
// class: [
|
|
485
|
+
// Template.bind( ... ).to( ... )
|
|
486
|
+
// ]
|
|
487
|
+
//
|
|
488
|
+
// class: [
|
|
489
|
+
// 'bar',
|
|
490
|
+
// Template.bind( ... ).to( ... ),
|
|
491
|
+
// 'baz'
|
|
492
|
+
// ]
|
|
493
|
+
//
|
|
494
|
+
// class: {
|
|
495
|
+
// ns: 'abc',
|
|
496
|
+
// value: Template.bind( ... ).to( ... )
|
|
497
|
+
// }
|
|
498
|
+
//
|
|
499
|
+
if (hasTemplateBinding(attrValue)) {
|
|
500
|
+
// Normalize attributes with additional data like namespace:
|
|
501
|
+
//
|
|
502
|
+
// class: {
|
|
503
|
+
// ns: 'abc',
|
|
504
|
+
// value: [ ... ]
|
|
505
|
+
// }
|
|
506
|
+
//
|
|
507
|
+
const valueToBind = isNamespaced(attrValue) ? attrValue[0].value : attrValue;
|
|
508
|
+
// Extend the original value of attributes like "style" and "class",
|
|
509
|
+
// don't override them.
|
|
510
|
+
if (revertData && shouldExtend(attrName)) {
|
|
511
|
+
valueToBind.unshift(domAttrValue);
|
|
512
|
+
}
|
|
513
|
+
this._bindToObservable({
|
|
514
|
+
schema: valueToBind,
|
|
515
|
+
updater: getAttributeUpdater(node, attrName, attrNs),
|
|
516
|
+
data
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
// Style attribute could be an Object so it needs to be parsed in a specific way.
|
|
520
|
+
//
|
|
521
|
+
// style: {
|
|
522
|
+
// width: '100px',
|
|
523
|
+
// height: Template.bind( ... ).to( ... )
|
|
524
|
+
// }
|
|
525
|
+
//
|
|
526
|
+
else if (attrName == 'style' && typeof attrValue[0] !== 'string') {
|
|
527
|
+
this._renderStyleAttribute(attrValue[0], data);
|
|
528
|
+
}
|
|
529
|
+
// Otherwise simply set the static attribute:
|
|
530
|
+
//
|
|
531
|
+
// class: [ 'foo' ]
|
|
532
|
+
//
|
|
533
|
+
// class: [ 'all', 'are', 'static' ]
|
|
534
|
+
//
|
|
535
|
+
// class: [
|
|
536
|
+
// {
|
|
537
|
+
// ns: 'abc',
|
|
538
|
+
// value: [ 'foo' ]
|
|
539
|
+
// }
|
|
540
|
+
// ]
|
|
541
|
+
//
|
|
542
|
+
else {
|
|
543
|
+
// Extend the original value of attributes like "style" and "class",
|
|
544
|
+
// don't override them.
|
|
545
|
+
if (revertData && domAttrValue && shouldExtend(attrName)) {
|
|
546
|
+
attrValue.unshift(domAttrValue);
|
|
547
|
+
}
|
|
548
|
+
const value = attrValue
|
|
549
|
+
// Retrieve "values" from:
|
|
550
|
+
//
|
|
551
|
+
// class: [
|
|
552
|
+
// {
|
|
553
|
+
// ns: 'abc',
|
|
554
|
+
// value: [ ... ]
|
|
555
|
+
// }
|
|
556
|
+
// ]
|
|
557
|
+
//
|
|
558
|
+
.map((val) => val ? (val.value || val) : val)
|
|
559
|
+
// Flatten the array.
|
|
560
|
+
.reduce((prev, next) => prev.concat(next), [])
|
|
561
|
+
// Convert into string.
|
|
562
|
+
.reduce(arrayValueReducer, '');
|
|
563
|
+
if (!isFalsy(value)) {
|
|
564
|
+
node.setAttributeNS(attrNs, attrName, value);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Renders the `style` attribute of an HTML element based on
|
|
571
|
+
* {@link module:ui/template~Template#attributes}.
|
|
572
|
+
*
|
|
573
|
+
* A style attribute is an {Object} with static values:
|
|
574
|
+
*
|
|
575
|
+
* attributes: {
|
|
576
|
+
* style: {
|
|
577
|
+
* color: 'red'
|
|
578
|
+
* }
|
|
579
|
+
* }
|
|
580
|
+
*
|
|
581
|
+
* or values bound to {@link module:ui/model~Model} properties:
|
|
582
|
+
*
|
|
583
|
+
* attributes: {
|
|
584
|
+
* style: {
|
|
585
|
+
* color: bind.to( ... )
|
|
586
|
+
* }
|
|
587
|
+
* }
|
|
588
|
+
*
|
|
589
|
+
* Note: The `style` attribute is rendered without setting the namespace. It does not seem to be
|
|
590
|
+
* needed.
|
|
591
|
+
*
|
|
592
|
+
* @private
|
|
593
|
+
* @param {Object} styles Styles located in `attributes.style` of {@link module:ui/template~TemplateDefinition}.
|
|
594
|
+
* @param {module:ui/template~RenderData} data Rendering data.
|
|
595
|
+
*/
|
|
596
|
+
_renderStyleAttribute(styles, data) {
|
|
597
|
+
const node = data.node;
|
|
598
|
+
for (const styleName in styles) {
|
|
599
|
+
const styleValue = styles[styleName];
|
|
600
|
+
// Cases:
|
|
601
|
+
//
|
|
602
|
+
// style: {
|
|
603
|
+
// color: bind.to( 'attribute' )
|
|
604
|
+
// }
|
|
605
|
+
//
|
|
606
|
+
if (hasTemplateBinding(styleValue)) {
|
|
607
|
+
this._bindToObservable({
|
|
608
|
+
schema: [styleValue],
|
|
609
|
+
updater: getStyleUpdater(node, styleName),
|
|
610
|
+
data
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
// Cases:
|
|
614
|
+
//
|
|
615
|
+
// style: {
|
|
616
|
+
// color: 'red'
|
|
617
|
+
// }
|
|
618
|
+
//
|
|
619
|
+
else {
|
|
620
|
+
node.style[styleName] = styleValue;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Recursively renders HTML element's children from {@link module:ui/template~Template#children}.
|
|
626
|
+
*
|
|
627
|
+
* @protected
|
|
628
|
+
* @param {module:ui/template~RenderData} data Rendering data.
|
|
629
|
+
*/
|
|
630
|
+
_renderElementChildren(data) {
|
|
631
|
+
const node = data.node;
|
|
632
|
+
const container = data.intoFragment ? document.createDocumentFragment() : node;
|
|
633
|
+
const isApplying = data.isApplying;
|
|
634
|
+
let childIndex = 0;
|
|
635
|
+
for (const child of this.children) {
|
|
636
|
+
if (isViewCollection(child)) {
|
|
637
|
+
if (!isApplying) {
|
|
638
|
+
child.setParent(node);
|
|
639
|
+
// Note: ViewCollection renders its children.
|
|
640
|
+
for (const view of child) {
|
|
641
|
+
container.appendChild(view.element);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
else if (isView(child)) {
|
|
646
|
+
if (!isApplying) {
|
|
647
|
+
if (!child.isRendered) {
|
|
648
|
+
child.render();
|
|
649
|
+
}
|
|
650
|
+
container.appendChild(child.element);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
else if (isNode(child)) {
|
|
654
|
+
container.appendChild(child);
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
if (isApplying) {
|
|
658
|
+
const revertData = data.revertData;
|
|
659
|
+
const childRevertData = getEmptyRevertData();
|
|
660
|
+
revertData.children.push(childRevertData);
|
|
661
|
+
child._renderNode({
|
|
662
|
+
intoFragment: false,
|
|
663
|
+
node: container.childNodes[childIndex++],
|
|
664
|
+
isApplying: true,
|
|
665
|
+
revertData: childRevertData
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
container.appendChild(child.render());
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (data.intoFragment) {
|
|
674
|
+
node.appendChild(container);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Activates `on` event listeners from the {@link module:ui/template~TemplateDefinition}
|
|
679
|
+
* on an HTML element.
|
|
680
|
+
*
|
|
681
|
+
* @protected
|
|
682
|
+
* @param {module:ui/template~RenderData} data Rendering data.
|
|
683
|
+
*/
|
|
684
|
+
_setUpListeners(data) {
|
|
685
|
+
if (!this.eventListeners) {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
for (const key in this.eventListeners) {
|
|
689
|
+
const revertBindings = this.eventListeners[key].map(schemaItem => {
|
|
690
|
+
const [domEvtName, domSelector] = key.split('@');
|
|
691
|
+
return schemaItem.activateDomEventListener(domEvtName, domSelector, data);
|
|
692
|
+
});
|
|
693
|
+
if (data.revertData) {
|
|
694
|
+
data.revertData.bindings.push(revertBindings);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* For a given {@link module:ui/template~TemplateValueSchema} containing {@link module:ui/template~TemplateBinding}
|
|
700
|
+
* activates the binding and sets its initial value.
|
|
701
|
+
*
|
|
702
|
+
* Note: {@link module:ui/template~TemplateValueSchema} can be for HTML element attributes or
|
|
703
|
+
* text node `textContent`.
|
|
704
|
+
*
|
|
705
|
+
* @protected
|
|
706
|
+
* @param {Object} options Binding options.
|
|
707
|
+
* @param {module:ui/template~TemplateValueSchema} options.schema
|
|
708
|
+
* @param {Function} options.updater A function which updates the DOM (like attribute or text).
|
|
709
|
+
* @param {module:ui/template~RenderData} options.data Rendering data.
|
|
710
|
+
*/
|
|
711
|
+
_bindToObservable({ schema, updater, data }) {
|
|
712
|
+
const revertData = data.revertData;
|
|
713
|
+
// Set initial values.
|
|
714
|
+
syncValueSchemaValue(schema, updater, data);
|
|
715
|
+
const revertBindings = schema
|
|
716
|
+
// Filter "falsy" (false, undefined, null, '') value schema components out.
|
|
717
|
+
.filter(item => !isFalsy(item))
|
|
718
|
+
// Filter inactive bindings from schema, like static strings ('foo'), numbers (42), etc.
|
|
719
|
+
.filter((item) => item.observable)
|
|
720
|
+
// Once only the actual binding are left, let the emitter listen to observable change:attribute event.
|
|
721
|
+
// TODO: Reduce the number of listeners attached as many bindings may listen
|
|
722
|
+
// to the same observable attribute.
|
|
723
|
+
.map(templateBinding => templateBinding.activateAttributeListener(schema, updater, data));
|
|
724
|
+
if (revertData) {
|
|
725
|
+
revertData.bindings.push(revertBindings);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Reverts {@link module:ui/template~RenderData#revertData template data} from a node to
|
|
730
|
+
* return it to the original state.
|
|
731
|
+
*
|
|
732
|
+
* @protected
|
|
733
|
+
* @param {HTMLElement|Text} node A node to be reverted.
|
|
734
|
+
* @param {Object} revertData An object that stores information about what changes have been made by
|
|
735
|
+
* {@link #apply} to the node. See {@link module:ui/template~RenderData#revertData} for more information.
|
|
736
|
+
*/
|
|
737
|
+
_revertTemplateFromNode(node, revertData) {
|
|
738
|
+
for (const binding of revertData.bindings) {
|
|
739
|
+
// Each binding may consist of several observable+observable#attribute.
|
|
740
|
+
// like the following has 2:
|
|
741
|
+
//
|
|
742
|
+
// class: [
|
|
743
|
+
// 'x',
|
|
744
|
+
// bind.to( 'foo' ),
|
|
745
|
+
// 'y',
|
|
746
|
+
// bind.to( 'bar' )
|
|
747
|
+
// ]
|
|
748
|
+
//
|
|
749
|
+
for (const revertBinding of binding) {
|
|
750
|
+
revertBinding();
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (revertData.text) {
|
|
754
|
+
node.textContent = revertData.text;
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const element = node;
|
|
758
|
+
for (const attrName in revertData.attributes) {
|
|
759
|
+
const attrValue = revertData.attributes[attrName];
|
|
760
|
+
// When the attribute has **not** been set before #apply().
|
|
761
|
+
if (attrValue === null) {
|
|
762
|
+
element.removeAttribute(attrName);
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
element.setAttribute(attrName, attrValue);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
for (let i = 0; i < revertData.children.length; ++i) {
|
|
769
|
+
this._revertTemplateFromNode(element.childNodes[i], revertData.children[i]);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
847
772
|
}
|
|
848
|
-
|
|
849
|
-
mix( Template, EmitterMixin );
|
|
850
|
-
|
|
851
773
|
/**
|
|
852
774
|
* Describes a binding created by the {@link module:ui/template~Template.bind} interface.
|
|
853
775
|
*
|
|
854
776
|
* @protected
|
|
777
|
+
* @internal
|
|
855
778
|
*/
|
|
856
779
|
export class TemplateBinding {
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
// Allows revert of the listener.
|
|
927
|
-
return () => {
|
|
928
|
-
this.emitter.stopListening( this.observable, 'change:' + this.attribute, callback );
|
|
929
|
-
};
|
|
930
|
-
}
|
|
780
|
+
/**
|
|
781
|
+
* Creates an instance of the {@link module:ui/template~TemplateBinding} class.
|
|
782
|
+
*
|
|
783
|
+
* @param {module:ui/template~TemplateDefinition} def The definition of the binding.
|
|
784
|
+
*/
|
|
785
|
+
constructor(def) {
|
|
786
|
+
this.attribute = def.attribute;
|
|
787
|
+
this.observable = def.observable;
|
|
788
|
+
this.emitter = def.emitter;
|
|
789
|
+
this.callback = def.callback;
|
|
790
|
+
/**
|
|
791
|
+
* An observable instance of the binding. It either:
|
|
792
|
+
*
|
|
793
|
+
* * provides the attribute with the value,
|
|
794
|
+
* * or passes the event when a corresponding DOM event is fired.
|
|
795
|
+
*
|
|
796
|
+
* @member {module:utils/observablemixin~ObservableMixin} module:ui/template~TemplateBinding#observable
|
|
797
|
+
*/
|
|
798
|
+
/**
|
|
799
|
+
* An {@link module:utils/emittermixin~Emitter} used by the binding to:
|
|
800
|
+
*
|
|
801
|
+
* * listen to the attribute change in the {@link module:ui/template~TemplateBinding#observable},
|
|
802
|
+
* * or listen to the event in the DOM.
|
|
803
|
+
*
|
|
804
|
+
* @member {module:utils/emittermixin~EmitterMixin} module:ui/template~TemplateBinding#emitter
|
|
805
|
+
*/
|
|
806
|
+
/**
|
|
807
|
+
* The name of the {@link module:ui/template~TemplateBinding#observable observed attribute}.
|
|
808
|
+
*
|
|
809
|
+
* @member {String} module:ui/template~TemplateBinding#attribute
|
|
810
|
+
*/
|
|
811
|
+
/**
|
|
812
|
+
* A custom function to process the value of the {@link module:ui/template~TemplateBinding#attribute}.
|
|
813
|
+
*
|
|
814
|
+
* @member {Function} [module:ui/template~TemplateBinding#callback]
|
|
815
|
+
*/
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Returns the value of the binding. It is the value of the {@link module:ui/template~TemplateBinding#attribute} in
|
|
819
|
+
* {@link module:ui/template~TemplateBinding#observable}. The value may be processed by the
|
|
820
|
+
* {@link module:ui/template~TemplateBinding#callback}, if such has been passed to the binding.
|
|
821
|
+
*
|
|
822
|
+
* @param {Node} [node] A native DOM node, passed to the custom {@link module:ui/template~TemplateBinding#callback}.
|
|
823
|
+
* @returns {*} The value of {@link module:ui/template~TemplateBinding#attribute} in
|
|
824
|
+
* {@link module:ui/template~TemplateBinding#observable}.
|
|
825
|
+
*/
|
|
826
|
+
getValue(node) {
|
|
827
|
+
const value = this.observable[this.attribute];
|
|
828
|
+
return this.callback ? this.callback(value, node) : value;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Activates the listener which waits for changes of the {@link module:ui/template~TemplateBinding#attribute} in
|
|
832
|
+
* {@link module:ui/template~TemplateBinding#observable}, then updates the DOM with the aggregated
|
|
833
|
+
* value of {@link module:ui/template~TemplateValueSchema}.
|
|
834
|
+
*
|
|
835
|
+
* @param {module:ui/template~TemplateValueSchema} schema A full schema to generate an attribute or text in the DOM.
|
|
836
|
+
* @param {Function} updater A DOM updater function used to update the native DOM attribute or text.
|
|
837
|
+
* @param {module:ui/template~RenderData} data Rendering data.
|
|
838
|
+
* @returns {Function} A function to sever the listener binding.
|
|
839
|
+
*/
|
|
840
|
+
activateAttributeListener(schema, updater, data) {
|
|
841
|
+
const callback = () => syncValueSchemaValue(schema, updater, data);
|
|
842
|
+
this.emitter.listenTo(this.observable, `change:${this.attribute}`, callback);
|
|
843
|
+
// Allows revert of the listener.
|
|
844
|
+
return () => {
|
|
845
|
+
this.emitter.stopListening(this.observable, `change:${this.attribute}`, callback);
|
|
846
|
+
};
|
|
847
|
+
}
|
|
931
848
|
}
|
|
932
|
-
|
|
933
849
|
/**
|
|
934
850
|
* Describes either:
|
|
935
851
|
*
|
|
@@ -939,91 +855,87 @@ export class TemplateBinding {
|
|
|
939
855
|
* It is created by the {@link module:ui/template~BindChain#to} method.
|
|
940
856
|
*
|
|
941
857
|
* @protected
|
|
858
|
+
* @internal
|
|
942
859
|
*/
|
|
943
860
|
export class TemplateToBinding extends TemplateBinding {
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
861
|
+
constructor(def) {
|
|
862
|
+
super(def);
|
|
863
|
+
this.eventNameOrFunction = def.eventNameOrFunction;
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Activates the listener for the native DOM event, which when fired, is propagated by
|
|
867
|
+
* the {@link module:ui/template~TemplateBinding#emitter}.
|
|
868
|
+
*
|
|
869
|
+
* @param {String} domEvtName The name of the native DOM event.
|
|
870
|
+
* @param {String} domSelector The selector in the DOM to filter delegated events.
|
|
871
|
+
* @param {module:ui/template~RenderData} data Rendering data.
|
|
872
|
+
* @returns {Function} A function to sever the listener binding.
|
|
873
|
+
*/
|
|
874
|
+
activateDomEventListener(domEvtName, domSelector, data) {
|
|
875
|
+
const callback = (evt, domEvt) => {
|
|
876
|
+
if (!domSelector || domEvt.target.matches(domSelector)) {
|
|
877
|
+
if (typeof this.eventNameOrFunction == 'function') {
|
|
878
|
+
this.eventNameOrFunction(domEvt);
|
|
879
|
+
}
|
|
880
|
+
else {
|
|
881
|
+
this.observable.fire(this.eventNameOrFunction, domEvt);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
this.emitter.listenTo(data.node, domEvtName, callback);
|
|
886
|
+
// Allows revert of the listener.
|
|
887
|
+
return () => {
|
|
888
|
+
this.emitter.stopListening(data.node, domEvtName, callback);
|
|
889
|
+
};
|
|
890
|
+
}
|
|
971
891
|
}
|
|
972
|
-
|
|
973
892
|
/**
|
|
974
893
|
* Describes a binding to {@link module:utils/observablemixin~ObservableMixin} created by the {@link module:ui/template~BindChain#if}
|
|
975
894
|
* method.
|
|
976
895
|
*
|
|
977
896
|
* @protected
|
|
897
|
+
* @internal
|
|
978
898
|
*/
|
|
979
899
|
export class TemplateIfBinding extends TemplateBinding {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
* {@link module:ui/template~TemplateBinding#observable} is `true`.
|
|
992
|
-
*
|
|
993
|
-
* @member {String} [module:ui/template~TemplateIfBinding#valueIfTrue]
|
|
994
|
-
*/
|
|
900
|
+
constructor(def) {
|
|
901
|
+
super(def);
|
|
902
|
+
this.valueIfTrue = def.valueIfTrue;
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* @inheritDoc
|
|
906
|
+
*/
|
|
907
|
+
getValue(node) {
|
|
908
|
+
const value = super.getValue(node);
|
|
909
|
+
return isFalsy(value) ? false : (this.valueIfTrue || true);
|
|
910
|
+
}
|
|
995
911
|
}
|
|
996
|
-
|
|
997
912
|
// Checks whether given {@link module:ui/template~TemplateValueSchema} contains a
|
|
998
913
|
// {@link module:ui/template~TemplateBinding}.
|
|
999
914
|
//
|
|
1000
915
|
// @param {module:ui/template~TemplateValueSchema} schema
|
|
1001
916
|
// @returns {Boolean}
|
|
1002
|
-
function hasTemplateBinding(
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
return false;
|
|
917
|
+
function hasTemplateBinding(schema) {
|
|
918
|
+
if (!schema) {
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
// Normalize attributes with additional data like namespace:
|
|
922
|
+
//
|
|
923
|
+
// class: {
|
|
924
|
+
// ns: 'abc',
|
|
925
|
+
// value: [ ... ]
|
|
926
|
+
// }
|
|
927
|
+
//
|
|
928
|
+
if (schema.value) {
|
|
929
|
+
schema = schema.value;
|
|
930
|
+
}
|
|
931
|
+
if (Array.isArray(schema)) {
|
|
932
|
+
return schema.some(hasTemplateBinding);
|
|
933
|
+
}
|
|
934
|
+
else if (schema instanceof TemplateBinding) {
|
|
935
|
+
return true;
|
|
936
|
+
}
|
|
937
|
+
return false;
|
|
1025
938
|
}
|
|
1026
|
-
|
|
1027
939
|
// Assembles the value using {@link module:ui/template~TemplateValueSchema} and stores it in a form of
|
|
1028
940
|
// an Array. Each entry of the Array corresponds to one of {@link module:ui/template~TemplateValueSchema}
|
|
1029
941
|
// items.
|
|
@@ -1031,62 +943,58 @@ function hasTemplateBinding( schema ) {
|
|
|
1031
943
|
// @param {module:ui/template~TemplateValueSchema} schema
|
|
1032
944
|
// @param {Node} node DOM Node updated when {@link module:utils/observablemixin~ObservableMixin} changes.
|
|
1033
945
|
// @returns {Array}
|
|
1034
|
-
function getValueSchemaValue(
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
} );
|
|
946
|
+
function getValueSchemaValue(schema, node) {
|
|
947
|
+
return schema.map(schemaItem => {
|
|
948
|
+
// Process {@link module:ui/template~TemplateBinding} bindings.
|
|
949
|
+
if (schemaItem instanceof TemplateBinding) {
|
|
950
|
+
return schemaItem.getValue(node);
|
|
951
|
+
}
|
|
952
|
+
// All static values like strings, numbers, and "falsy" values (false, null, undefined, '', etc.) just pass.
|
|
953
|
+
return schemaItem;
|
|
954
|
+
});
|
|
1044
955
|
}
|
|
1045
|
-
|
|
1046
956
|
// A function executed each time the bound Observable attribute changes, which updates the DOM with a value
|
|
1047
957
|
// constructed from {@link module:ui/template~TemplateValueSchema}.
|
|
1048
958
|
//
|
|
1049
959
|
// @param {module:ui/template~TemplateValueSchema} schema
|
|
1050
960
|
// @param {Function} updater A function which updates the DOM (like attribute or text).
|
|
1051
961
|
// @param {Node} node DOM Node updated when {@link module:utils/observablemixin~ObservableMixin} changes.
|
|
1052
|
-
function syncValueSchemaValue(
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
962
|
+
function syncValueSchemaValue(schema, updater, { node }) {
|
|
963
|
+
const values = getValueSchemaValue(schema, node);
|
|
964
|
+
let value;
|
|
965
|
+
// Check if schema is a single Template.bind.if, like:
|
|
966
|
+
//
|
|
967
|
+
// class: Template.bind.if( 'foo' )
|
|
968
|
+
//
|
|
969
|
+
if (schema.length == 1 && schema[0] instanceof TemplateIfBinding) {
|
|
970
|
+
value = values[0];
|
|
971
|
+
}
|
|
972
|
+
else {
|
|
973
|
+
value = values.reduce(arrayValueReducer, '');
|
|
974
|
+
}
|
|
975
|
+
if (isFalsy(value)) {
|
|
976
|
+
updater.remove();
|
|
977
|
+
}
|
|
978
|
+
else {
|
|
979
|
+
updater.set(value);
|
|
980
|
+
}
|
|
1070
981
|
}
|
|
1071
|
-
|
|
1072
982
|
// Returns an object consisting of `set` and `remove` functions, which
|
|
1073
983
|
// can be used in the context of DOM Node to set or reset `textContent`.
|
|
1074
984
|
// @see module:ui/view~View#_bindToObservable
|
|
1075
985
|
//
|
|
1076
986
|
// @param {Node} node DOM Node to be modified.
|
|
1077
987
|
// @returns {Object}
|
|
1078
|
-
function getTextUpdater(
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
};
|
|
988
|
+
function getTextUpdater(node) {
|
|
989
|
+
return {
|
|
990
|
+
set(value) {
|
|
991
|
+
node.textContent = value;
|
|
992
|
+
},
|
|
993
|
+
remove() {
|
|
994
|
+
node.textContent = '';
|
|
995
|
+
}
|
|
996
|
+
};
|
|
1088
997
|
}
|
|
1089
|
-
|
|
1090
998
|
// Returns an object consisting of `set` and `remove` functions, which
|
|
1091
999
|
// can be used in the context of DOM Node to set or reset an attribute.
|
|
1092
1000
|
// @see module:ui/view~View#_bindToObservable
|
|
@@ -1095,18 +1003,16 @@ function getTextUpdater( node ) {
|
|
|
1095
1003
|
// @param {String} attrName Name of the attribute to be modified.
|
|
1096
1004
|
// @param {String} [ns=null] Namespace to use.
|
|
1097
1005
|
// @returns {Object}
|
|
1098
|
-
function getAttributeUpdater(
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
};
|
|
1006
|
+
function getAttributeUpdater(el, attrName, ns) {
|
|
1007
|
+
return {
|
|
1008
|
+
set(value) {
|
|
1009
|
+
el.setAttributeNS(ns, attrName, value);
|
|
1010
|
+
},
|
|
1011
|
+
remove() {
|
|
1012
|
+
el.removeAttributeNS(ns, attrName);
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1108
1015
|
}
|
|
1109
|
-
|
|
1110
1016
|
// Returns an object consisting of `set` and `remove` functions, which
|
|
1111
1017
|
// can be used in the context of CSSStyleDeclaration to set or remove a style.
|
|
1112
1018
|
// @see module:ui/view~View#_bindToObservable
|
|
@@ -1114,43 +1020,39 @@ function getAttributeUpdater( el, attrName, ns ) {
|
|
|
1114
1020
|
// @param {Node} node DOM Node to be modified.
|
|
1115
1021
|
// @param {String} styleName Name of the style to be modified.
|
|
1116
1022
|
// @returns {Object}
|
|
1117
|
-
function getStyleUpdater(
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
};
|
|
1023
|
+
function getStyleUpdater(el, styleName) {
|
|
1024
|
+
return {
|
|
1025
|
+
set(value) {
|
|
1026
|
+
el.style[styleName] = value;
|
|
1027
|
+
},
|
|
1028
|
+
remove() {
|
|
1029
|
+
el.style[styleName] = null;
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1127
1032
|
}
|
|
1128
|
-
|
|
1129
1033
|
// Clones definition of the template.
|
|
1130
1034
|
//
|
|
1131
1035
|
// @param {module:ui/template~TemplateDefinition} def
|
|
1132
1036
|
// @returns {module:ui/template~TemplateDefinition}
|
|
1133
|
-
function clone(
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
return clone;
|
|
1037
|
+
function clone(def) {
|
|
1038
|
+
const clone = cloneDeepWith(def, value => {
|
|
1039
|
+
// Don't clone the `Template.bind`* bindings because of the references to Observable
|
|
1040
|
+
// and DomEmitterMixin instances inside, which would also be traversed and cloned by greedy
|
|
1041
|
+
// cloneDeepWith algorithm. There's no point in cloning Observable/DomEmitterMixins
|
|
1042
|
+
// along with the definition.
|
|
1043
|
+
//
|
|
1044
|
+
// Don't clone Template instances if provided as a child. They're simply #render()ed
|
|
1045
|
+
// and nothing should interfere.
|
|
1046
|
+
//
|
|
1047
|
+
// Also don't clone View instances if provided as a child of the Template. The template
|
|
1048
|
+
// instance will be extracted from the View during the normalization and there's no need
|
|
1049
|
+
// to clone it.
|
|
1050
|
+
if (value && (value instanceof TemplateBinding || isTemplate(value) || isView(value) || isViewCollection(value))) {
|
|
1051
|
+
return value;
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
return clone;
|
|
1152
1055
|
}
|
|
1153
|
-
|
|
1154
1056
|
// Normalizes given {@link module:ui/template~TemplateDefinition}.
|
|
1155
1057
|
//
|
|
1156
1058
|
// See:
|
|
@@ -1161,47 +1063,42 @@ function clone( def ) {
|
|
|
1161
1063
|
//
|
|
1162
1064
|
// @param {module:ui/template~TemplateDefinition} def
|
|
1163
1065
|
// @returns {module:ui/template~TemplateDefinition} Normalized definition.
|
|
1164
|
-
function normalize(
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
def.children = children;
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
return def;
|
|
1066
|
+
function normalize(def) {
|
|
1067
|
+
if (typeof def == 'string') {
|
|
1068
|
+
def = normalizePlainTextDefinition(def);
|
|
1069
|
+
}
|
|
1070
|
+
else if (def.text) {
|
|
1071
|
+
normalizeTextDefinition(def);
|
|
1072
|
+
}
|
|
1073
|
+
if (def.on) {
|
|
1074
|
+
def.eventListeners = normalizeListeners(def.on);
|
|
1075
|
+
// Template mixes EmitterMixin, so delete #on to avoid collision.
|
|
1076
|
+
delete def.on;
|
|
1077
|
+
}
|
|
1078
|
+
if (!def.text) {
|
|
1079
|
+
if (def.attributes) {
|
|
1080
|
+
normalizeAttributes(def.attributes);
|
|
1081
|
+
}
|
|
1082
|
+
const children = [];
|
|
1083
|
+
if (def.children) {
|
|
1084
|
+
if (isViewCollection(def.children)) {
|
|
1085
|
+
children.push(def.children);
|
|
1086
|
+
}
|
|
1087
|
+
else {
|
|
1088
|
+
for (const child of def.children) {
|
|
1089
|
+
if (isTemplate(child) || isView(child) || isNode(child)) {
|
|
1090
|
+
children.push(child);
|
|
1091
|
+
}
|
|
1092
|
+
else {
|
|
1093
|
+
children.push(new Template(child));
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
def.children = children;
|
|
1099
|
+
}
|
|
1100
|
+
return def;
|
|
1203
1101
|
}
|
|
1204
|
-
|
|
1205
1102
|
// Normalizes "attributes" section of {@link module:ui/template~TemplateDefinition}.
|
|
1206
1103
|
//
|
|
1207
1104
|
// attributes: {
|
|
@@ -1223,16 +1120,14 @@ function normalize( def ) {
|
|
|
1223
1120
|
// }
|
|
1224
1121
|
//
|
|
1225
1122
|
// @param {Object} attributes
|
|
1226
|
-
function normalizeAttributes(
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
}
|
|
1123
|
+
function normalizeAttributes(attributes) {
|
|
1124
|
+
for (const a in attributes) {
|
|
1125
|
+
if (attributes[a].value) {
|
|
1126
|
+
attributes[a].value = toArray(attributes[a].value);
|
|
1127
|
+
}
|
|
1128
|
+
arrayify(attributes, a);
|
|
1129
|
+
}
|
|
1234
1130
|
}
|
|
1235
|
-
|
|
1236
1131
|
// Normalizes "on" section of {@link module:ui/template~TemplateDefinition}.
|
|
1237
1132
|
//
|
|
1238
1133
|
// on: {
|
|
@@ -1251,14 +1146,12 @@ function normalizeAttributes( attributes ) {
|
|
|
1251
1146
|
//
|
|
1252
1147
|
// @param {Object} listeners
|
|
1253
1148
|
// @returns {Object} Object containing normalized listeners.
|
|
1254
|
-
function normalizeListeners(
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
return listeners;
|
|
1149
|
+
function normalizeListeners(listeners) {
|
|
1150
|
+
for (const l in listeners) {
|
|
1151
|
+
arrayify(listeners, l);
|
|
1152
|
+
}
|
|
1153
|
+
return listeners;
|
|
1260
1154
|
}
|
|
1261
|
-
|
|
1262
1155
|
// Normalizes "string" {@link module:ui/template~TemplateDefinition}.
|
|
1263
1156
|
//
|
|
1264
1157
|
// "foo"
|
|
@@ -1269,12 +1162,11 @@ function normalizeListeners( listeners ) {
|
|
|
1269
1162
|
//
|
|
1270
1163
|
// @param {String} def
|
|
1271
1164
|
// @returns {module:ui/template~TemplateDefinition} Normalized template definition.
|
|
1272
|
-
function normalizePlainTextDefinition(
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1165
|
+
function normalizePlainTextDefinition(def) {
|
|
1166
|
+
return {
|
|
1167
|
+
text: [def]
|
|
1168
|
+
};
|
|
1276
1169
|
}
|
|
1277
|
-
|
|
1278
1170
|
// Normalizes text {@link module:ui/template~TemplateDefinition}.
|
|
1279
1171
|
//
|
|
1280
1172
|
// children: [
|
|
@@ -1290,10 +1182,9 @@ function normalizePlainTextDefinition( def ) {
|
|
|
1290
1182
|
// ]
|
|
1291
1183
|
//
|
|
1292
1184
|
// @param {module:ui/template~TemplateDefinition} def
|
|
1293
|
-
function normalizeTextDefinition(
|
|
1294
|
-
|
|
1185
|
+
function normalizeTextDefinition(def) {
|
|
1186
|
+
def.text = toArray(def.text);
|
|
1295
1187
|
}
|
|
1296
|
-
|
|
1297
1188
|
// Wraps an entry in Object in an Array, if not already one.
|
|
1298
1189
|
//
|
|
1299
1190
|
// {
|
|
@@ -1310,26 +1201,26 @@ function normalizeTextDefinition( def ) {
|
|
|
1310
1201
|
//
|
|
1311
1202
|
// @param {Object} obj
|
|
1312
1203
|
// @param {String} key
|
|
1313
|
-
function arrayify(
|
|
1314
|
-
|
|
1204
|
+
function arrayify(obj, key) {
|
|
1205
|
+
obj[key] = toArray(obj[key]);
|
|
1315
1206
|
}
|
|
1316
|
-
|
|
1317
1207
|
// A helper which concatenates the value avoiding unwanted
|
|
1318
1208
|
// leading white spaces.
|
|
1319
1209
|
//
|
|
1320
1210
|
// @param {String} prev
|
|
1321
1211
|
// @param {String} cur
|
|
1322
1212
|
// @returns {String}
|
|
1323
|
-
function arrayValueReducer(
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1213
|
+
function arrayValueReducer(prev, cur) {
|
|
1214
|
+
if (isFalsy(cur)) {
|
|
1215
|
+
return prev;
|
|
1216
|
+
}
|
|
1217
|
+
else if (isFalsy(prev)) {
|
|
1218
|
+
return cur;
|
|
1219
|
+
}
|
|
1220
|
+
else {
|
|
1221
|
+
return `${prev} ${cur}`;
|
|
1222
|
+
}
|
|
1331
1223
|
}
|
|
1332
|
-
|
|
1333
1224
|
// Extends one object defined in the following format:
|
|
1334
1225
|
//
|
|
1335
1226
|
// {
|
|
@@ -1344,423 +1235,105 @@ function arrayValueReducer( prev, cur ) {
|
|
|
1344
1235
|
// @param {Object} obj Base object.
|
|
1345
1236
|
// @param {Object} ext Object extending base.
|
|
1346
1237
|
// @returns {String}
|
|
1347
|
-
function extendObjectValueArray(
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1238
|
+
function extendObjectValueArray(obj, ext) {
|
|
1239
|
+
for (const a in ext) {
|
|
1240
|
+
if (obj[a]) {
|
|
1241
|
+
obj[a].push(...ext[a]);
|
|
1242
|
+
}
|
|
1243
|
+
else {
|
|
1244
|
+
obj[a] = ext[a];
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1355
1247
|
}
|
|
1356
|
-
|
|
1357
1248
|
// A helper for {@link module:ui/template~Template#extend}. Recursively extends {@link module:ui/template~Template} instance
|
|
1358
1249
|
// with content from {@link module:ui/template~TemplateDefinition}. See {@link module:ui/template~Template#extend} to learn more.
|
|
1359
1250
|
//
|
|
1360
1251
|
// @param {module:ui/template~Template} def A template instance to be extended.
|
|
1361
1252
|
// @param {module:ui/template~TemplateDefinition} def A definition which is to extend the template instance.
|
|
1362
1253
|
// @param {Object} Error context.
|
|
1363
|
-
function extendTemplate(
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
template
|
|
1394
|
-
);
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
let childIndex = 0;
|
|
1398
|
-
|
|
1399
|
-
for ( const childDef of def.children ) {
|
|
1400
|
-
extendTemplate( template.children[ childIndex++ ], childDef );
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1254
|
+
function extendTemplate(template, def) {
|
|
1255
|
+
if (def.attributes) {
|
|
1256
|
+
if (!template.attributes) {
|
|
1257
|
+
template.attributes = {};
|
|
1258
|
+
}
|
|
1259
|
+
extendObjectValueArray(template.attributes, def.attributes);
|
|
1260
|
+
}
|
|
1261
|
+
if (def.eventListeners) {
|
|
1262
|
+
if (!template.eventListeners) {
|
|
1263
|
+
template.eventListeners = {};
|
|
1264
|
+
}
|
|
1265
|
+
extendObjectValueArray(template.eventListeners, def.eventListeners);
|
|
1266
|
+
}
|
|
1267
|
+
if (def.text) {
|
|
1268
|
+
template.text.push(...def.text);
|
|
1269
|
+
}
|
|
1270
|
+
if (def.children && def.children.length) {
|
|
1271
|
+
if (template.children.length != def.children.length) {
|
|
1272
|
+
/**
|
|
1273
|
+
* The number of children in extended definition does not match.
|
|
1274
|
+
*
|
|
1275
|
+
* @error ui-template-extend-children-mismatch
|
|
1276
|
+
*/
|
|
1277
|
+
throw new CKEditorError('ui-template-extend-children-mismatch', template);
|
|
1278
|
+
}
|
|
1279
|
+
let childIndex = 0;
|
|
1280
|
+
for (const childDef of def.children) {
|
|
1281
|
+
extendTemplate(template.children[childIndex++], childDef);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1403
1284
|
}
|
|
1404
|
-
|
|
1405
1285
|
// Checks if value is "falsy".
|
|
1406
1286
|
// Note: 0 (Number) is not "falsy" in this context.
|
|
1407
1287
|
//
|
|
1408
1288
|
// @private
|
|
1409
1289
|
// @param {*} value Value to be checked.
|
|
1410
|
-
function isFalsy(
|
|
1411
|
-
|
|
1290
|
+
function isFalsy(value) {
|
|
1291
|
+
return !value && value !== 0;
|
|
1412
1292
|
}
|
|
1413
|
-
|
|
1414
1293
|
// Checks if the item is an instance of {@link module:ui/view~View}
|
|
1415
1294
|
//
|
|
1416
1295
|
// @private
|
|
1417
1296
|
// @param {*} value Value to be checked.
|
|
1418
|
-
function isView(
|
|
1419
|
-
|
|
1297
|
+
function isView(item) {
|
|
1298
|
+
return item instanceof View;
|
|
1420
1299
|
}
|
|
1421
|
-
|
|
1422
1300
|
// Checks if the item is an instance of {@link module:ui/template~Template}
|
|
1423
1301
|
//
|
|
1424
1302
|
// @private
|
|
1425
1303
|
// @param {*} value Value to be checked.
|
|
1426
|
-
function isTemplate(
|
|
1427
|
-
|
|
1304
|
+
function isTemplate(item) {
|
|
1305
|
+
return item instanceof Template;
|
|
1428
1306
|
}
|
|
1429
|
-
|
|
1430
1307
|
// Checks if the item is an instance of {@link module:ui/viewcollection~ViewCollection}
|
|
1431
1308
|
//
|
|
1432
1309
|
// @private
|
|
1433
1310
|
// @param {*} value Value to be checked.
|
|
1434
|
-
function isViewCollection(
|
|
1435
|
-
|
|
1311
|
+
function isViewCollection(item) {
|
|
1312
|
+
return item instanceof ViewCollection;
|
|
1313
|
+
}
|
|
1314
|
+
// Checks if value array contains the one with namespace.
|
|
1315
|
+
function isNamespaced(attrValue) {
|
|
1316
|
+
return isObject(attrValue[0]) && attrValue[0].ns;
|
|
1436
1317
|
}
|
|
1437
|
-
|
|
1438
1318
|
// Creates an empty skeleton for {@link module:ui/template~Template#revert}
|
|
1439
1319
|
// data.
|
|
1440
1320
|
//
|
|
1441
1321
|
// @private
|
|
1442
1322
|
function getEmptyRevertData() {
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1323
|
+
return {
|
|
1324
|
+
children: [],
|
|
1325
|
+
bindings: [],
|
|
1326
|
+
attributes: {}
|
|
1327
|
+
};
|
|
1448
1328
|
}
|
|
1449
|
-
|
|
1450
1329
|
// Checks whether an attribute should be extended when
|
|
1451
1330
|
// {@link module:ui/template~Template#apply} is called.
|
|
1452
1331
|
//
|
|
1453
1332
|
// @private
|
|
1454
1333
|
// @param {String} attrName Attribute name to check.
|
|
1455
|
-
function shouldExtend(
|
|
1456
|
-
|
|
1334
|
+
function shouldExtend(attrName) {
|
|
1335
|
+
return attrName == 'class' || attrName == 'style';
|
|
1457
1336
|
}
|
|
1458
|
-
|
|
1459
|
-
/**
|
|
1460
|
-
* A definition of the {@link module:ui/template~Template}. It describes what kind of
|
|
1461
|
-
* node a template will render (HTML element or text), attributes of an element, DOM event
|
|
1462
|
-
* listeners and children.
|
|
1463
|
-
*
|
|
1464
|
-
* Also see:
|
|
1465
|
-
* * {@link module:ui/template~TemplateValueSchema} to learn about HTML element attributes,
|
|
1466
|
-
* * {@link module:ui/template~TemplateListenerSchema} to learn about DOM event listeners.
|
|
1467
|
-
*
|
|
1468
|
-
* A sample definition on an HTML element can look like this:
|
|
1469
|
-
*
|
|
1470
|
-
* new Template( {
|
|
1471
|
-
* tag: 'p',
|
|
1472
|
-
* children: [
|
|
1473
|
-
* {
|
|
1474
|
-
* tag: 'span',
|
|
1475
|
-
* attributes: { ... },
|
|
1476
|
-
* children: [ ... ],
|
|
1477
|
-
* },
|
|
1478
|
-
* {
|
|
1479
|
-
* text: 'static–text'
|
|
1480
|
-
* },
|
|
1481
|
-
* 'also-static–text',
|
|
1482
|
-
* ],
|
|
1483
|
-
* attributes: {
|
|
1484
|
-
* class: {@link module:ui/template~TemplateValueSchema},
|
|
1485
|
-
* id: {@link module:ui/template~TemplateValueSchema},
|
|
1486
|
-
* style: {@link module:ui/template~TemplateValueSchema}
|
|
1487
|
-
*
|
|
1488
|
-
* // ...
|
|
1489
|
-
* },
|
|
1490
|
-
* on: {
|
|
1491
|
-
* 'click': {@link module:ui/template~TemplateListenerSchema}
|
|
1492
|
-
*
|
|
1493
|
-
* // Document.querySelector format is also accepted.
|
|
1494
|
-
* 'keyup@a.some-class': {@link module:ui/template~TemplateListenerSchema}
|
|
1495
|
-
*
|
|
1496
|
-
* // ...
|
|
1497
|
-
* }
|
|
1498
|
-
* } );
|
|
1499
|
-
*
|
|
1500
|
-
* A {@link module:ui/view~View}, another {@link module:ui/template~Template} or a native DOM node
|
|
1501
|
-
* can also become a child of a template. When a view is passed, its {@link module:ui/view~View#element} is used:
|
|
1502
|
-
*
|
|
1503
|
-
* const view = new SomeView();
|
|
1504
|
-
* const childTemplate = new Template( { ... } );
|
|
1505
|
-
* const childNode = document.createElement( 'b' );
|
|
1506
|
-
*
|
|
1507
|
-
* new Template( {
|
|
1508
|
-
* tag: 'p',
|
|
1509
|
-
*
|
|
1510
|
-
* children: [
|
|
1511
|
-
* // view#element will be added as a child of this <p>.
|
|
1512
|
-
* view,
|
|
1513
|
-
*
|
|
1514
|
-
* // The output of childTemplate.render() will be added here.
|
|
1515
|
-
* childTemplate,
|
|
1516
|
-
*
|
|
1517
|
-
* // Native DOM nodes are included directly in the rendered output.
|
|
1518
|
-
* childNode
|
|
1519
|
-
* ]
|
|
1520
|
-
* } );
|
|
1521
|
-
*
|
|
1522
|
-
* An entire {@link module:ui/viewcollection~ViewCollection} can be used as a child in the definition:
|
|
1523
|
-
*
|
|
1524
|
-
* const collection = new ViewCollection();
|
|
1525
|
-
* collection.add( someView );
|
|
1526
|
-
*
|
|
1527
|
-
* new Template( {
|
|
1528
|
-
* tag: 'p',
|
|
1529
|
-
*
|
|
1530
|
-
* children: collection
|
|
1531
|
-
* } );
|
|
1532
|
-
*
|
|
1533
|
-
* @typedef module:ui/template~TemplateDefinition
|
|
1534
|
-
* @type Object
|
|
1535
|
-
*
|
|
1536
|
-
* @property {String} tag See the template {@link module:ui/template~Template#tag} property.
|
|
1537
|
-
*
|
|
1538
|
-
* @property {Array.<module:ui/template~TemplateDefinition>} [children]
|
|
1539
|
-
* See the template {@link module:ui/template~Template#children} property.
|
|
1540
|
-
*
|
|
1541
|
-
* @property {Object.<String, module:ui/template~TemplateValueSchema>} [attributes]
|
|
1542
|
-
* See the template {@link module:ui/template~Template#attributes} property.
|
|
1543
|
-
*
|
|
1544
|
-
* @property {String|module:ui/template~TemplateValueSchema|Array.<String|module:ui/template~TemplateValueSchema>} [text]
|
|
1545
|
-
* See the template {@link module:ui/template~Template#text} property.
|
|
1546
|
-
*
|
|
1547
|
-
* @property {Object.<String, module:ui/template~TemplateListenerSchema>} [on]
|
|
1548
|
-
* See the template {@link module:ui/template~Template#eventListeners} property.
|
|
1549
|
-
*/
|
|
1550
|
-
|
|
1551
|
-
/**
|
|
1552
|
-
* Describes a value of an HTML element attribute or `textContent`. It allows combining multiple
|
|
1553
|
-
* data sources like static values and {@link module:utils/observablemixin~Observable} attributes.
|
|
1554
|
-
*
|
|
1555
|
-
* Also see:
|
|
1556
|
-
* * {@link module:ui/template~TemplateDefinition} to learn where to use it,
|
|
1557
|
-
* * {@link module:ui/template~Template.bind} to learn how to configure
|
|
1558
|
-
* {@link module:utils/observablemixin~Observable} attribute bindings,
|
|
1559
|
-
* * {@link module:ui/template~Template#render} to learn how to render a template,
|
|
1560
|
-
* * {@link module:ui/template~BindChain#to `to()`} and {@link module:ui/template~BindChain#if `if()`}
|
|
1561
|
-
* methods to learn more about bindings.
|
|
1562
|
-
*
|
|
1563
|
-
* Attribute values can be described in many different ways:
|
|
1564
|
-
*
|
|
1565
|
-
* // Bind helper will create bindings to attributes of the observable.
|
|
1566
|
-
* const bind = Template.bind( observable, emitter );
|
|
1567
|
-
*
|
|
1568
|
-
* new Template( {
|
|
1569
|
-
* tag: 'p',
|
|
1570
|
-
* attributes: {
|
|
1571
|
-
* // A plain string schema.
|
|
1572
|
-
* 'class': 'static-text',
|
|
1573
|
-
*
|
|
1574
|
-
* // An object schema, binds to the "foo" attribute of the
|
|
1575
|
-
* // observable and follows its value.
|
|
1576
|
-
* 'class': bind.to( 'foo' ),
|
|
1577
|
-
*
|
|
1578
|
-
* // An array schema, combines the above.
|
|
1579
|
-
* 'class': [
|
|
1580
|
-
* 'static-text',
|
|
1581
|
-
* bind.to( 'bar', () => { ... } ),
|
|
1582
|
-
*
|
|
1583
|
-
* // Bindings can also be conditional.
|
|
1584
|
-
* bind.if( 'baz', 'class-when-baz-is-true' )
|
|
1585
|
-
* ],
|
|
1586
|
-
*
|
|
1587
|
-
* // An array schema, with a custom namespace, e.g. useful for creating SVGs.
|
|
1588
|
-
* 'class': {
|
|
1589
|
-
* ns: 'http://ns.url',
|
|
1590
|
-
* value: [
|
|
1591
|
-
* bind.if( 'baz', 'value-when-true' ),
|
|
1592
|
-
* 'static-text'
|
|
1593
|
-
* ]
|
|
1594
|
-
* },
|
|
1595
|
-
*
|
|
1596
|
-
* // An object schema, specific for styles.
|
|
1597
|
-
* style: {
|
|
1598
|
-
* color: 'red',
|
|
1599
|
-
* backgroundColor: bind.to( 'qux', () => { ... } )
|
|
1600
|
-
* }
|
|
1601
|
-
* }
|
|
1602
|
-
* } );
|
|
1603
|
-
*
|
|
1604
|
-
* Text nodes can also have complex values:
|
|
1605
|
-
*
|
|
1606
|
-
* const bind = Template.bind( observable, emitter );
|
|
1607
|
-
*
|
|
1608
|
-
* // Will render a "foo" text node.
|
|
1609
|
-
* new Template( {
|
|
1610
|
-
* text: 'foo'
|
|
1611
|
-
* } );
|
|
1612
|
-
*
|
|
1613
|
-
* // Will render a "static text: {observable.foo}" text node.
|
|
1614
|
-
* // The text of the node will be updated as the "foo" attribute changes.
|
|
1615
|
-
* new Template( {
|
|
1616
|
-
* text: [
|
|
1617
|
-
* 'static text: ',
|
|
1618
|
-
* bind.to( 'foo', () => { ... } )
|
|
1619
|
-
* ]
|
|
1620
|
-
* } );
|
|
1621
|
-
*
|
|
1622
|
-
* @typedef module:ui/template~TemplateValueSchema
|
|
1623
|
-
* @type {Object|String|Array}
|
|
1624
|
-
*/
|
|
1625
|
-
|
|
1626
|
-
/**
|
|
1627
|
-
* Describes an event listener attached to an HTML element. Such listener can propagate DOM events
|
|
1628
|
-
* through an {@link module:utils/observablemixin~Observable} instance, execute custom callbacks
|
|
1629
|
-
* or both, if necessary.
|
|
1630
|
-
*
|
|
1631
|
-
* Also see:
|
|
1632
|
-
* * {@link module:ui/template~TemplateDefinition} to learn more about template definitions,
|
|
1633
|
-
* * {@link module:ui/template~BindChain#to `to()`} method to learn more about bindings.
|
|
1634
|
-
*
|
|
1635
|
-
* Check out different ways of attaching event listeners below:
|
|
1636
|
-
*
|
|
1637
|
-
* // Bind helper will propagate events through the observable.
|
|
1638
|
-
* const bind = Template.bind( observable, emitter );
|
|
1639
|
-
*
|
|
1640
|
-
* new Template( {
|
|
1641
|
-
* tag: 'p',
|
|
1642
|
-
* on: {
|
|
1643
|
-
* // An object schema. The observable will fire the "clicked" event upon DOM "click".
|
|
1644
|
-
* click: bind.to( 'clicked' )
|
|
1645
|
-
*
|
|
1646
|
-
* // An object schema. It will work for "click" event on "a.foo" children only.
|
|
1647
|
-
* 'click@a.foo': bind.to( 'clicked' )
|
|
1648
|
-
*
|
|
1649
|
-
* // An array schema, makes the observable propagate multiple events.
|
|
1650
|
-
* click: [
|
|
1651
|
-
* bind.to( 'clicked' ),
|
|
1652
|
-
* bind.to( 'executed' )
|
|
1653
|
-
* ],
|
|
1654
|
-
*
|
|
1655
|
-
* // An array schema with a custom callback.
|
|
1656
|
-
* 'click@a.foo': {
|
|
1657
|
-
* bind.to( 'clicked' ),
|
|
1658
|
-
* bind.to( evt => {
|
|
1659
|
-
* console.log( `${ evt.target } has been clicked!` );
|
|
1660
|
-
* } }
|
|
1661
|
-
* }
|
|
1662
|
-
* }
|
|
1663
|
-
* } );
|
|
1664
|
-
*
|
|
1665
|
-
* @typedef module:ui/template~TemplateListenerSchema
|
|
1666
|
-
* @type {Object|String|Array}
|
|
1667
|
-
*/
|
|
1668
|
-
|
|
1669
|
-
/**
|
|
1670
|
-
* The return value of {@link ~Template.bind `Template.bind()`}. It provides `to()` and `if()`
|
|
1671
|
-
* methods to create the {@link module:utils/observablemixin~Observable observable} attribute and event bindings.
|
|
1672
|
-
*
|
|
1673
|
-
* @interface module:ui/template~BindChain
|
|
1674
|
-
*/
|
|
1675
|
-
|
|
1676
|
-
/**
|
|
1677
|
-
* Binds an {@link module:utils/observablemixin~Observable observable} to either:
|
|
1678
|
-
*
|
|
1679
|
-
* * an HTML element attribute or a text node `textContent`, so it remains in sync with the observable
|
|
1680
|
-
* attribute as it changes,
|
|
1681
|
-
* * or an HTML element DOM event, so the DOM events are propagated through an observable.
|
|
1682
|
-
*
|
|
1683
|
-
* Some common use cases of `to()` bindings are presented below:
|
|
1684
|
-
*
|
|
1685
|
-
* const bind = Template.bind( observable, emitter );
|
|
1686
|
-
*
|
|
1687
|
-
* new Template( {
|
|
1688
|
-
* tag: 'p',
|
|
1689
|
-
* attributes: {
|
|
1690
|
-
* // class="..." attribute gets bound to `observable#a`
|
|
1691
|
-
* class: bind.to( 'a' )
|
|
1692
|
-
* },
|
|
1693
|
-
* children: [
|
|
1694
|
-
* // <p>...</p> gets bound to observable#b; always `toUpperCase()`.
|
|
1695
|
-
* {
|
|
1696
|
-
* text: bind.to( 'b', ( value, node ) => value.toUpperCase() )
|
|
1697
|
-
* }
|
|
1698
|
-
* ],
|
|
1699
|
-
* on: {
|
|
1700
|
-
* click: [
|
|
1701
|
-
* // An observable will fire "clicked" upon "click" in the DOM.
|
|
1702
|
-
* bind.to( 'clicked' ),
|
|
1703
|
-
*
|
|
1704
|
-
* // A custom callback will be executed upon "click" in the DOM.
|
|
1705
|
-
* bind.to( () => {
|
|
1706
|
-
* ...
|
|
1707
|
-
* } )
|
|
1708
|
-
* ]
|
|
1709
|
-
* }
|
|
1710
|
-
* } ).render();
|
|
1711
|
-
*
|
|
1712
|
-
* Learn more about using `to()` in the {@link module:ui/template~TemplateValueSchema} and
|
|
1713
|
-
* {@link module:ui/template~TemplateListenerSchema}.
|
|
1714
|
-
*
|
|
1715
|
-
* @method #to
|
|
1716
|
-
* @param {String|Function} eventNameOrFunctionOrAttribute An attribute name of
|
|
1717
|
-
* {@link module:utils/observablemixin~Observable} or a DOM event name or an event callback.
|
|
1718
|
-
* @param {Function} [callback] Allows for processing of the value. Accepts `Node` and `value` as arguments.
|
|
1719
|
-
* @returns {module:ui/template~TemplateBinding}
|
|
1720
|
-
*/
|
|
1721
|
-
|
|
1722
|
-
/**
|
|
1723
|
-
* Binds an {@link module:utils/observablemixin~Observable observable} to an HTML element attribute or a text
|
|
1724
|
-
* node `textContent` so it remains in sync with the observable attribute as it changes.
|
|
1725
|
-
*
|
|
1726
|
-
* Unlike {@link module:ui/template~BindChain#to}, it controls the presence of the attribute or `textContent`
|
|
1727
|
-
* depending on the "falseness" of an {@link module:utils/observablemixin~Observable} attribute.
|
|
1728
|
-
*
|
|
1729
|
-
* const bind = Template.bind( observable, emitter );
|
|
1730
|
-
*
|
|
1731
|
-
* new Template( {
|
|
1732
|
-
* tag: 'input',
|
|
1733
|
-
* attributes: {
|
|
1734
|
-
* // <input checked> when `observable#a` is not undefined/null/false/''
|
|
1735
|
-
* // <input> when `observable#a` is undefined/null/false
|
|
1736
|
-
* checked: bind.if( 'a' )
|
|
1737
|
-
* },
|
|
1738
|
-
* children: [
|
|
1739
|
-
* {
|
|
1740
|
-
* // <input>"b-is-not-set"</input> when `observable#b` is undefined/null/false/''
|
|
1741
|
-
* // <input></input> when `observable#b` is not "falsy"
|
|
1742
|
-
* text: bind.if( 'b', 'b-is-not-set', ( value, node ) => !value )
|
|
1743
|
-
* }
|
|
1744
|
-
* ]
|
|
1745
|
-
* } ).render();
|
|
1746
|
-
*
|
|
1747
|
-
* Learn more about using `if()` in the {@link module:ui/template~TemplateValueSchema}.
|
|
1748
|
-
*
|
|
1749
|
-
* @method #if
|
|
1750
|
-
* @param {String} attribute An attribute name of {@link module:utils/observablemixin~Observable} used in the binding.
|
|
1751
|
-
* @param {String} [valueIfTrue] Value set when the {@link module:utils/observablemixin~Observable} attribute is not
|
|
1752
|
-
* undefined/null/false/'' (empty string).
|
|
1753
|
-
* @param {Function} [callback] Allows for processing of the value. Accepts `Node` and `value` as arguments.
|
|
1754
|
-
* @returns {module:ui/template~TemplateBinding}
|
|
1755
|
-
*/
|
|
1756
|
-
|
|
1757
|
-
/**
|
|
1758
|
-
* The {@link module:ui/template~Template#_renderNode} configuration.
|
|
1759
|
-
*
|
|
1760
|
-
* @private
|
|
1761
|
-
* @interface module:ui/template~RenderData
|
|
1762
|
-
*/
|
|
1763
|
-
|
|
1764
1337
|
/**
|
|
1765
1338
|
* Tells {@link module:ui/template~Template#_renderNode} to render
|
|
1766
1339
|
* children into `DocumentFragment` first and then append the fragment
|
|
@@ -1768,20 +1341,17 @@ function shouldExtend( attrName ) {
|
|
|
1768
1341
|
*
|
|
1769
1342
|
* @member {Boolean} #intoFragment
|
|
1770
1343
|
*/
|
|
1771
|
-
|
|
1772
1344
|
/**
|
|
1773
1345
|
* A node which is being rendered.
|
|
1774
1346
|
*
|
|
1775
1347
|
* @member {HTMLElement|Text} #node
|
|
1776
1348
|
*/
|
|
1777
|
-
|
|
1778
1349
|
/**
|
|
1779
1350
|
* Indicates whether the {@module:ui/template~RenderNodeOptions#node} has
|
|
1780
1351
|
* been provided by {@module:ui/template~Template#apply}.
|
|
1781
1352
|
*
|
|
1782
1353
|
* @member {Boolean} #isApplying
|
|
1783
1354
|
*/
|
|
1784
|
-
|
|
1785
1355
|
/**
|
|
1786
1356
|
* An object storing the data that helps {@module:ui/template~Template#revert}
|
|
1787
1357
|
* bringing back an element to its initial state, i.e. before
|