• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

baidu / san / 4565762813

pending completion
4565762813

push

github

errorrik
Merge remote-tracking branch 'origin/master'

1547 of 1686 branches covered (91.76%)

Branch coverage included in aggregate %.

2752 of 2814 relevant lines covered (97.8%)

826.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.97
/src/view/component.js
1
/**
2
 * Copyright (c) Baidu Inc. All rights reserved.
3
 *
4
 * This source code is licensed under the MIT license.
5
 * See LICENSE file in the project root for license information.
6
 *
7
 * @file 组件类
8
 */
9

10
var bind = require('../util/bind');
11
var each = require('../util/each');
12
var guid = require('../util/guid');
13
var extend = require('../util/extend');
14
var nextTick = require('../util/next-tick');
15
var emitDevtool = require('../util/emit-devtool');
16
var ExprType = require('../parser/expr-type');
17
var parseExpr = require('../parser/parse-expr');
18
var parseTemplate = require('../parser/parse-template');
19
var unpackANode = require('../parser/unpack-anode');
20
var removeEl = require('../browser/remove-el');
21
var Data = require('../runtime/data');
22
var evalExpr = require('../runtime/eval-expr');
23
var changeExprCompare = require('../runtime/change-expr-compare');
24
var DataChangeType = require('../runtime/data-change-type');
25
var insertBefore = require('../browser/insert-before');
26
var un = require('../browser/un');
27
var preprocessComponents = require('./preprocess-components');
28
var createNode = require('./create-node');
29
var preheatEl = require('./preheat-el');
30
var parseComponentTemplate = require('./parse-component-template');
31
var preheatANode = require('./preheat-a-node');
32
var LifeCycle = require('./life-cycle');
33
var getANodeProp = require('./get-a-node-prop');
34
var isDataChangeByElement = require('./is-data-change-by-element');
35
var getEventListener = require('./get-event-listener');
36
var hydrateElementChildren = require('./hydrate-element-children');
37
var NodeType = require('./node-type');
38
var styleProps = require('./style-props');
39
var nodeSBindInit = require('./node-s-bind-init');
40
var nodeSBindUpdate = require('./node-s-bind-update');
41
var elementOwnAttached = require('./element-own-attached');
42
var elementOwnDetach = require('./element-own-detach');
43
var elementOwnDispose = require('./element-own-dispose');
44
var warnEventListenMethod = require('./warn-event-listen-method');
45
var elementDisposeChildren = require('./element-dispose-children');
46
var createDataTypesChecker = require('../util/create-data-types-checker');
47
var warn = require('../util/warn');
48
var handleError = require('../util/handle-error');
49
var DOMChildrenWalker = require('./dom-children-walker');
50

51

52

53
/**
54
 * 组件类
55
 *
56
 * @class
57
 * @param {Object} options 初始化参数
58
 */
59
function Component(options) { // eslint-disable-line
1✔
60
    // #[begin] error
61
    for (var key in Component.prototype) {
1,170✔
62
        if (this[key] !== Component.prototype[key]) {
26,910✔
63
            /* eslint-disable max-len */
64
            warn('\`' + key + '\` is a reserved key of san components. Overriding this property may cause unknown exceptions.');
1✔
65
            /* eslint-enable max-len */
66
        }
67
    }
68
    // #[end]
69

70

71
    options = options || {};
1,170✔
72
    this.lifeCycle = LifeCycle.start;
1,170✔
73
    this.id = guid++;
1,170✔
74

75
    if (typeof this.construct === 'function') {
1,170✔
76
        this.construct(options);
4✔
77
    }
78

79
    this.children = [];
1,170✔
80
    this.listeners = {};
1,170✔
81
    this.slotChildren = [];
1,170✔
82
    this.implicitChildren = [];
1,170✔
83

84
    var clazz = this.constructor;
1,170✔
85

86
    this.filters = this.filters || clazz.filters || {};
1,170✔
87
    this.computed = this.computed || clazz.computed || {};
1,170✔
88
    this.messages = this.messages || clazz.messages || {};
1,170✔
89
    this.ssr = this.ssr || clazz.ssr;
1,170✔
90

91
    if (options.transition) {
1,170✔
92
        this.transition = options.transition;
1✔
93
    }
94

95
    this.owner = options.owner;
1,170✔
96
    this.scope = options.scope;
1,170✔
97
    this.el = options.el;
1,170✔
98
    var parent = options.parent;
1,170✔
99
    if (parent) {
1,170✔
100
        this.parent = parent;
424✔
101
        this.parentComponent = parent.nodeType === NodeType.CMPT
424✔
102
            ? parent
424✔
103
            : parent && parent.parentComponent;
414✔
104
    }
105
    else if (this.owner) {
746✔
106
        this.parentComponent = this.owner;
6✔
107
        this.scope = this.owner.data;
6✔
108
    }
109

110
    this.sourceSlotNameProps = [];
1,170✔
111
    this.sourceSlots = {
1,170✔
112
        named: {}
113
    };
114

115
    // #[begin] devtool
116
    this._toPhase('beforeCompile');
1,170✔
117
    // #[end]
118

119
    var proto = clazz.prototype;
1,170✔
120

121
    // pre define components class
122
    /* istanbul ignore else  */
123
    if (!proto.hasOwnProperty('_cmptReady')) {
1,170✔
124
        preprocessComponents(clazz);
1,003✔
125
    }
126

127
    // compile
128
    if (!proto.hasOwnProperty('aNode')) {
1,170✔
129
        var aPack = clazz.aPack || proto.hasOwnProperty('aPack') && proto.aPack;
999✔
130
        if (aPack) {
999✔
131
            proto.aNode = unpackANode(aPack);
2✔
132
            clazz.aPack = proto.aPack = null;
2✔
133
        }
134
        else {
135
            proto.aNode = parseComponentTemplate(clazz);
997✔
136
        }
137
    }
138

139
    preheatANode(proto.aNode, this);
1,168✔
140

141
    this.tagName = proto.aNode.tagName;
1,168✔
142
    this.source = typeof options.source === 'string'
1,168✔
143
        ? parseTemplate(options.source).children[0]
1,168✔
144
        : options.source;
145

146
    preheatANode(this.source);
1,168✔
147
    proto.aNode._i++;
1,168✔
148

149

150
    // #[begin] hydrate
151
    // 组件反解,读取注入的组件数据
152
    if (this.el) {
1,168✔
153
        var firstCommentNode = this.el.firstChild;
120✔
154
        if (firstCommentNode && firstCommentNode.nodeType === 3) {
120✔
155
            firstCommentNode = firstCommentNode.nextSibling;
1✔
156
        }
157

158
        if (firstCommentNode && firstCommentNode.nodeType === 8) {
120✔
159
            var stumpMatch = firstCommentNode.data.match(/^\s*s-data:([\s\S]+)?$/);
119✔
160
            if (stumpMatch) {
119!
161
                var stumpText = stumpMatch[1];
119✔
162

163
                // fill component data
164
                options.data = (new Function('return '
119✔
165
                    + stumpText
166
                        .replace(/^[\s\n]*/, '')
167
                        .replace(
168
                            /"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.\d+Z"/g,
169
                            function (match, y, mon, d, h, m, s) {
170
                                return 'new Date(' + (+y) + ',' + (+mon) + ',' + (+d)
2✔
171
                                    + ',' + (+h) + ',' + (+m) + ',' + (+s) + ')';
172
                            }
173
                        )
174
                ))();
175

176
                if (firstCommentNode.previousSibling) {
119✔
177
                    removeEl(firstCommentNode.previousSibling);
1✔
178
                }
179
                removeEl(firstCommentNode);
119✔
180
            }
181
        }
182
    }
183
    // #[end]
184

185

186
    if (this.source) {
1,168✔
187
        // 组件运行时传入的结构,做slot解析
188
        this._initSourceSlots(1);
428✔
189

190
        for (var i = 0, l = this.source.events.length; i < l; i++) {
428✔
191
            var eventBind = this.source.events[i];
22✔
192
            // 保存当前实例的native事件,下面创建aNode时候做合并
193
            if (eventBind.modifier.native) {
22✔
194
                // native事件数组
195
                this.nativeEvents = this.nativeEvents || [];
6✔
196
                this.nativeEvents.push(eventBind);
6✔
197
            }
198
            else {
199
                // #[begin] error
200
                warnEventListenMethod(eventBind, options.owner);
16✔
201
                // #[end]
202

203
                this.on(
16✔
204
                    eventBind.name,
205
                    getEventListener(eventBind, options.owner, this.scope, 1),
206
                    eventBind
207
                );
208
            }
209
        }
210

211
        this.tagName = this.tagName || this.source.tagName;
428✔
212
        this.binds = this.source._b;
428✔
213

214
        // init s-bind data
215
        this._srcSbindData = nodeSBindInit(this.source.directives.bind, this.scope, this.owner);
428✔
216
    }
217

218
    this._toPhase('compiled');
1,168✔
219

220

221
    // #[begin] devtool
222
    this._toPhase('beforeInit');
1,168✔
223
    // #[end]
224

225
    // init data
226
    var initData;
1,168✔
227
    try {
1,168✔
228
        initData = typeof this.initData === 'function' && this.initData();
1,168✔
229
    }
230
    catch (e) {
231
        handleError(e, this, 'initData');
1✔
232
    }
233
    initData = extend(initData || {}, options.data || this._srcSbindData);
1,168✔
234

235
    if (this.binds && this.scope) {
1,168✔
236
        for (var i = 0, l = this.binds.length; i < l; i++) {
428✔
237
            var bindInfo = this.binds[i];
447✔
238

239
            var value = evalExpr(bindInfo.expr, this.scope, this.owner);
447✔
240
            if (typeof value !== 'undefined') {
447✔
241
                // See: https://github.com/ecomfe/san/issues/191
242
                initData[bindInfo.name] = value;
395✔
243
            }
244
        }
245
    }
246

247
    this.data = new Data(initData);
1,168✔
248

249
    this.tagName = this.tagName || 'div';
1,168✔
250
    // #[begin] allua
251
    // ie8- 不支持innerHTML输出自定义标签
252
    /* istanbul ignore if */
253
    if (ieOldThan9 && this.tagName.indexOf('-') > 0) {
1,168✔
254
        this.tagName = 'div';
1✔
255
    }
256
    // #[end]
257

258

259
    // #[begin] error
260
    // 在初始化 + 数据绑定后,开始数据校验
261
    // NOTE: 只在开发版本中进行属性校验
262
    var dataTypes = this.dataTypes || clazz.dataTypes;
1,168✔
263
    if (dataTypes) {
1,168✔
264
        var dataTypeChecker = createDataTypesChecker(
59✔
265
            dataTypes,
266
            this.name || clazz.name
118✔
267
        );
268
        this.data.setTypeChecker(dataTypeChecker);
59✔
269
        this.data.checkDataTypes();
59✔
270
    }
271
    // #[end]
272

273
    this.computedDeps = {};
1,127✔
274
    for (var expr in this.computed) {
1,127✔
275
        if (this.computed.hasOwnProperty(expr) && !this.computedDeps[expr]) {
23✔
276
            this._calcComputed(expr);
19✔
277
        }
278
    }
279

280
    this._initDataChanger();
1,126✔
281
    this._sbindData = nodeSBindInit(this.aNode.directives.bind, this.data, this);
1,126✔
282
    this._toPhase('inited');
1,126✔
283

284
    // #[begin] hydrate
285
    var hydrateWalker = options.hydrateWalker;
1,126✔
286
    var aNode = this.aNode;
1,126✔
287
    if (hydrateWalker) {
1,126✔
288
        if (this.ssr === 'client-render') {
69!
289
            this.attach(hydrateWalker.target, hydrateWalker.current);
×
290
        }
291
        else {
292
            if (aNode.Clazz || this.components[aNode.tagName]) {
69✔
293
                this._rootNode = createHydrateNode(aNode, this, this.data, this, hydrateWalker);
5✔
294
                this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
5✔
295
            }
296
            else {
297
                var currentNode = hydrateWalker.current;
64✔
298
                if (currentNode && currentNode.nodeType === 1) {
64✔
299
                    this.el = currentNode;
64✔
300
                    hydrateWalker.goNext();
64✔
301
                }
302

303
                hydrateElementChildren(this, this.data, this);
64✔
304
            }
305

306
            this._toPhase('created');
69✔
307
            this._attached();
69✔
308
            this._toPhase('attached');
69✔
309
        }
310
    }
311
    else if (this.el) {
1,057✔
312
        if (aNode.Clazz || this.components[aNode.tagName]) {
120✔
313
            hydrateWalker = new DOMChildrenWalker(this.el.parentNode, this.el);
5✔
314
            this._rootNode = createHydrateNode(aNode, this, this.data, this, hydrateWalker);
5✔
315
            this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
5✔
316
        }
317
        else {
318
            hydrateElementChildren(this, this.data, this);
115✔
319
        }
320

321
        this._toPhase('created');
120✔
322
        this._attached();
120✔
323
        this._toPhase('attached');
120✔
324
    }
325
    // #[end]
326
}
327

328

329
/**
330
 * 初始化创建组件外部传入的插槽对象
331
 *
332
 * @protected
333
 * @param {boolean} isFirstTime 是否初次对sourceSlots进行计算
334
 */
335
Component.prototype._initSourceSlots = function (isFirstTime) {
1✔
336
    this.sourceSlots.named = {};
441✔
337

338
    // 组件运行时传入的结构,做slot解析
339
    if (this.source && this.scope) {
441✔
340
        var sourceChildren = this.source.children;
441✔
341

342
        for (var i = 0, l = sourceChildren.length; i < l; i++) {
441✔
343
            var child = sourceChildren[i];
240✔
344
            var target;
240✔
345

346
            var slotBind = !child.textExpr && getANodeProp(child, 'slot');
240✔
347
            if (slotBind) {
240✔
348
                isFirstTime && this.sourceSlotNameProps.push(slotBind);
93✔
349

350
                var slotName = evalExpr(slotBind.expr, this.scope, this.owner);
93✔
351
                target = this.sourceSlots.named[slotName];
93✔
352
                if (!target) {
93✔
353
                    target = this.sourceSlots.named[slotName] = [];
59✔
354
                }
355
                target.push(child);
93✔
356
            }
357
            else if (isFirstTime) {
147!
358
                target = this.sourceSlots.noname;
147✔
359
                if (!target) {
147✔
360
                    target = this.sourceSlots.noname = [];
122✔
361
                }
362
                target.push(child);
147✔
363
            }
364
        }
365
    }
366
};
367

368
/**
369
 * 类型标识
370
 *
371
 * @type {string}
372
 */
373
Component.prototype.nodeType = NodeType.CMPT;
1✔
374

375
/**
376
 * 在下一个更新周期运行函数
377
 *
378
 * @param {Function} fn 要运行的函数
379
 */
380
Component.prototype.nextTick = nextTick;
1✔
381

382
Component.prototype._ctx = (new Date()).getTime().toString(16);
1✔
383

384
/* eslint-disable operator-linebreak */
385
/**
386
 * 使节点到达相应的生命周期
387
 *
388
 * @protected
389
 * @param {string} name 生命周期名称
390
 */
391
Component.prototype._toPhase = function (name) {
1✔
392
    if (!this.lifeCycle[name]) {
13,913✔
393
        this.lifeCycle = LifeCycle[name] || this.lifeCycle;
13,907✔
394
        if (typeof this[name] === 'function') {
13,907✔
395
            try {
142✔
396
                this[name]();
142✔
397
            }
398
            catch (e) {
399
                handleError(e, this, 'hook:' + name);
2✔
400
            }
401
        }
402

403
        this._afterLife = this.lifeCycle;
13,907✔
404

405
        // 通知devtool
406
        // #[begin] devtool
407
        emitDevtool('comp-' + name, this);
13,907✔
408
        // #[end]
409
    }
410
};
411
/* eslint-enable operator-linebreak */
412

413

414
/**
415
 * 添加事件监听器
416
 *
417
 * @param {string} name 事件名
418
 * @param {Function} listener 监听器
419
 * @param {string?} declaration 声明式
420
 */
421
Component.prototype.on = function (name, listener, declaration) {
1✔
422
    if (typeof listener === 'function') {
19!
423
        if (!this.listeners[name]) {
19✔
424
            this.listeners[name] = [];
18✔
425
        }
426
        this.listeners[name].push({fn: listener, declaration: declaration});
19✔
427
    }
428
};
429

430
/**
431
 * 移除事件监听器
432
 *
433
 * @param {string} name 事件名
434
 * @param {Function=} listener 监听器
435
 */
436
Component.prototype.un = function (name, listener) {
1✔
437
    var nameListeners = this.listeners[name];
2✔
438
    var len = nameListeners && nameListeners.length;
2✔
439

440
    while (len--) {
2✔
441
        if (!listener || listener === nameListeners[len].fn) {
2✔
442
            nameListeners.splice(len, 1);
2✔
443
        }
444
    }
445
};
446

447

448
/**
449
 * 派发事件
450
 *
451
 * @param {string} name 事件名
452
 * @param {Object} event 事件对象
453
 */
454
Component.prototype.fire = function (name, event) {
1✔
455
    var me = this;
25✔
456
    // #[begin] devtool
457
    emitDevtool('comp-event', {
25✔
458
        name: name,
459
        event: event,
460
        target: this
461
    });
462
    // #[end]
463

464
    each(this.listeners[name], function (listener) {
25✔
465
        try {
22✔
466
            listener.fn.call(me, event);
22✔
467
        }
468
        catch (e) {
469
            handleError(e, me, 'event:' + name);
1✔
470
        }
471
    });
472
};
473

474
/**
475
 * 计算 computed 属性的值
476
 *
477
 * @private
478
 * @param {string} computedExpr computed表达式串
479
 */
480
Component.prototype._calcComputed = function (computedExpr) {
1✔
481
    var computedDeps = this.computedDeps[computedExpr];
57✔
482
    if (!computedDeps) {
57✔
483
        computedDeps = this.computedDeps[computedExpr] = {};
23✔
484
    }
485

486
    var me = this;
57✔
487
    try {
57✔
488
        var result = this.computed[computedExpr].call({
57✔
489
            data: {
490
                get: function (expr) {
491
                    // #[begin] error
492
                    if (!expr) {
76✔
493
                        throw new Error('[SAN ERROR] call get method in computed need argument');
1✔
494
                    }
495
                    // #[end]
496

497
                    if (!computedDeps[expr]) {
75✔
498
                        computedDeps[expr] = 1;
31✔
499

500
                        if (me.computed[expr] && !me.computedDeps[expr]) {
31✔
501
                            me._calcComputed(expr);
4✔
502
                        }
503

504
                        me.watch(expr, function () {
31✔
505
                            me._calcComputed(computedExpr);
34✔
506
                        });
507
                    }
508

509
                    return me.data.get(expr);
75✔
510
                }
511
            }
512
        });
513
        this.data.set(computedExpr, result);
55✔
514
    }
515
    catch (e) {
516
        handleError(e, this, 'computed:' + computedExpr);
2✔
517
    }
518
};
519

520
/**
521
 * 派发消息
522
 * 组件可以派发消息,消息将沿着组件树向上传递,直到遇上第一个处理消息的组件
523
 *
524
 * @param {string} name 消息名称
525
 * @param {*?} value 消息值
526
 */
527
Component.prototype.dispatch = function (name, value) {
1✔
528
    var parentComponent = this.parentComponent;
27✔
529

530
    while (parentComponent) {
27✔
531
        var handler = parentComponent.messages[name] || parentComponent.messages['*'];
28✔
532
        if (typeof handler === 'function') {
28✔
533
            // #[begin] devtool
534
            emitDevtool('comp-message', {
27✔
535
                target: this,
536
                value: value,
537
                name: name,
538
                receiver: parentComponent
539
            });
540
            // #[end]
541

542
            try {
27✔
543
                handler.call(
27✔
544
                    parentComponent,
545
                    {target: this, value: value, name: name}
546
                );
547
            }
548
            catch (e) {
549
                handleError(e, parentComponent, 'message:' + (name || '*'));
1!
550
            }
551
            return;
27✔
552
        }
553

554
        parentComponent = parentComponent.parentComponent;
1✔
555
    }
556

557
    // #[begin] devtool
558
    emitDevtool('comp-message', {target: this, value: value, name: name});
×
559
    // #[end]
560
};
561

562
/**
563
 * 获取组件内部的 slot
564
 *
565
 * @param {string=} name slot名称,空为default slot
566
 * @return {Array}
567
 */
568
Component.prototype.slot = function (name) {
1✔
569
    var result = [];
24✔
570
    var me = this;
24✔
571

572
    function childrenTraversal(children) {
1✔
573
        each(children, function (child) {
70✔
574
            if (child.nodeType === NodeType.SLOT && child.owner === me) {
99✔
575
                if (child.isNamed && child.name === name
53✔
576
                    || !child.isNamed && !name
577
                ) {
578
                    result.push(child);
26✔
579
                }
580
            }
581
            else {
582
                childrenTraversal(child.children);
46✔
583
            }
584
        });
585
    }
586

587
    childrenTraversal(this.children);
24✔
588
    return result;
24✔
589
};
590

591
/**
592
 * 获取带有 san-ref 指令的子组件引用
593
 *
594
 * @param {string} name 子组件的引用名
595
 * @return {Component}
596
 */
597
Component.prototype.ref = function (name) {
1✔
598
    var refTarget;
69✔
599
    var owner = this;
69✔
600

601
    function childrenTraversal(children) {
1✔
602
        if (children) {
159✔
603
            for (var i = 0, l = children.length; i < l; i++) {
124✔
604
                elementTraversal(children[i]);
138✔
605
                if (refTarget) {
138✔
606
                    return;
91✔
607
                }
608
            }
609
        }
610
    }
611

612
    function elementTraversal(element) {
1✔
613
        var nodeType = element.nodeType;
140✔
614
        if (nodeType === NodeType.TEXT) {
140✔
615
            return;
22✔
616
        }
617

618
        if (element.owner === owner) {
118✔
619
            var ref;
109✔
620
            switch (element.nodeType) {
109✔
621
                case NodeType.ELEM:
91✔
622
                    ref = element.aNode.directives.ref;
23✔
623
                    if (ref && evalExpr(ref.value, element.scope, owner) === name) {
23✔
624
                        refTarget = element.el;
6✔
625
                    }
626
                    break;
23✔
627

628
                case NodeType.CMPT:
629
                    ref = element.source.directives.ref;
68✔
630
                    if (ref && evalExpr(ref.value, element.scope, owner) === name) {
68✔
631
                        refTarget = element;
60✔
632
                    }
633
            }
634

635
            if (refTarget) {
109✔
636
                return;
66✔
637
            }
638

639
            childrenTraversal(element.slotChildren);
43✔
640
        }
641

642
        if (refTarget) {
52✔
643
            return;
3✔
644
        }
645

646
        childrenTraversal(element.children);
49✔
647
    }
648

649
    this._rootNode ? elementTraversal(this._rootNode) : childrenTraversal(this.children);
69✔
650

651
    return refTarget;
69✔
652
};
653

654

655
/**
656
 * 视图更新函数
657
 *
658
 * @param {Array?} changes 数据变化信息
659
 */
660
Component.prototype._update = function (changes) {
1✔
661
    if (this.lifeCycle.disposed) {
1,062✔
662
        return;
116✔
663
    }
664

665
    var me = this;
946✔
666

667

668
    var needReloadForSlot = false;
946✔
669
    this._notifyNeedReload = function () {
946✔
670
        needReloadForSlot = true;
36✔
671
    };
672

673
    if (changes) {
946✔
674
        if (this.source) {
240✔
675
            this._srcSbindData = nodeSBindUpdate(
238✔
676
                this.source.directives.bind,
677
                this._srcSbindData,
678
                this.scope,
679
                this.owner,
680
                changes,
681
                function (name, value) {
682
                    if (name in me.source._pi) {
14✔
683
                        return;
2✔
684
                    }
685

686
                    me.data.set(name, value, {
12✔
687
                        target: {
688
                            node: me.owner
689
                        }
690
                    });
691
                }
692
            );
693
        }
694

695
        each(changes, function (change) {
240✔
696
            var changeExpr = change.expr;
366✔
697

698
            each(me.binds, function (bindItem) {
366✔
699
                var relation;
540✔
700
                var setExpr = bindItem.name;
540✔
701
                var updateExpr = bindItem.expr;
540✔
702

703
                if (!isDataChangeByElement(change, me, setExpr)
540✔
704
                    && (relation = changeExprCompare(changeExpr, updateExpr, me.scope))
705
                ) {
706
                    if (relation > 2) {
197✔
707
                        setExpr = {
41✔
708
                            type: ExprType.ACCESSOR,
709
                            paths: [
710
                                {
711
                                    type: ExprType.STRING,
712
                                    value: setExpr
713
                                }
714
                            ].concat(changeExpr.paths.slice(updateExpr.paths.length))
715
                        };
716
                        updateExpr = changeExpr;
41✔
717
                    }
718

719
                    if (relation >= 2 && change.type === DataChangeType.SPLICE) {
197✔
720
                        me.data.splice(setExpr, [change.index, change.deleteCount].concat(change.insertions), {
18✔
721
                            target: {
722
                                node: me.owner
723
                            }
724
                        });
725
                    }
726
                    else {
727
                        me.data.set(setExpr, evalExpr(updateExpr, me.scope, me.owner), {
179✔
728
                            target: {
729
                                node: me.owner
730
                            }
731
                        });
732
                    }
733
                }
734
            });
735

736
            each(me.sourceSlotNameProps, function (bindItem) {
366✔
737
                needReloadForSlot = needReloadForSlot || changeExprCompare(changeExpr, bindItem.expr, me.scope);
142✔
738
                return !needReloadForSlot;
142✔
739
            });
740
        });
741

742
        if (needReloadForSlot) {
240✔
743
            this._initSourceSlots();
4✔
744
            this._repaintChildren();
4✔
745
        }
746
        else {
747
            var slotChildrenLen = this.slotChildren.length;
236✔
748
            while (slotChildrenLen--) {
236✔
749
                var slotChild = this.slotChildren[slotChildrenLen];
207✔
750

751
                if (slotChild.lifeCycle.disposed) {
207✔
752
                    this.slotChildren.splice(slotChildrenLen, 1);
6✔
753
                }
754
                else if (slotChild.isInserted) {
201✔
755
                    slotChild._update(changes, 1);
144✔
756
                }
757
            }
758
        }
759
    }
760

761
    var dataChanges = this._dataChanges;
946✔
762
    if (dataChanges) {
946✔
763
        // #[begin] devtool
764
        this._toPhase('beforeUpdate');
795✔
765
        // #[end]
766

767
        this._dataChanges = null;
795✔
768

769
        this._sbindData = nodeSBindUpdate(
795✔
770
            this.aNode.directives.bind,
771
            this._sbindData,
772
            this.data,
773
            this,
774
            dataChanges,
775
            function (name, value) {
776
                if (me._rootNode || (name in me.aNode._pi)) {
18✔
777
                    return;
3✔
778
                }
779

780
                getPropHandler(me.tagName, name)(me.el, value, name, me);
15✔
781
            }
782
        );
783

784
        var htmlDirective = this.aNode.directives.html;
795✔
785

786
        if (this._rootNode) {
795✔
787
            this._rootNode._update(dataChanges);
46✔
788
            this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
46✔
789
        }
790
        else if (htmlDirective) {
749✔
791
            var len = dataChanges.length;
1✔
792
            while (len--) {
1✔
793
                if (changeExprCompare(dataChanges[len].expr, htmlDirective.value, this.data)) {
1!
794
                    // #[begin] error
795
                    warnSetHTML(this.el);
1✔
796
                    // #[end]
797

798
                    this.el.innerHTML = evalExpr(htmlDirective.value, this.data, this);
1✔
799
                    break;
1✔
800
                }
801
            }
802
        }
803
        else {
804
            var dynamicProps = this.aNode._dp;
748✔
805
            for (var i = 0; i < dynamicProps.length; i++) {
748✔
806
                var prop = dynamicProps[i];
2,280✔
807

808
                for (var j = 0; j < dataChanges.length; j++) {
2,280✔
809
                    var change = dataChanges[j];
3,055✔
810
                    if (changeExprCompare(change.expr, prop.expr, this.data)
3,055!
811
                        || prop.hintExpr && changeExprCompare(change.expr, prop.hintExpr, this.data)
812
                    ) {
813
                        prop.handler(this.el, evalExpr(prop.expr, this.data, this), prop.name, this);
55✔
814
                        break;
55✔
815
                    }
816
                }
817
            }
818

819
            for (var i = 0; i < this.children.length; i++) {
748✔
820
                this.children[i]._update(dataChanges);
1,083✔
821
            }
822
        }
823

824
        if (needReloadForSlot) {
795✔
825
            this._initSourceSlots();
9✔
826
            this._repaintChildren();
9✔
827
        }
828

829
        for (var i = 0; i < this.implicitChildren.length; i++) {
795✔
830
            this.implicitChildren[i]._update(dataChanges);
4✔
831
        }
832

833
        if (typeof this.updated === 'function') {
795✔
834
            this.updated();
5✔
835
        }
836

837
        if (this.owner && this._updateBindxOwner(dataChanges)) {
795✔
838
            this.owner._update();
25✔
839
        }
840
    }
841

842
    this._notifyNeedReload = null;
946✔
843
};
844

845
Component.prototype._updateBindxOwner = function (dataChanges) {
1✔
846
    var me = this;
195✔
847
    var xbindUped;
195✔
848

849
    each(dataChanges, function (change) {
195✔
850
        each(me.binds, function (bindItem) {
263✔
851
            var changeExpr = change.expr;
450✔
852
            if (bindItem.x
450✔
853
                && !isDataChangeByElement(change, me.owner)
854
                && changeExprCompare(changeExpr, parseExpr(bindItem.name), me.data)
855
            ) {
856
                var updateScopeExpr = bindItem.expr;
32✔
857
                if (changeExpr.paths.length > 1) {
32✔
858
                    updateScopeExpr = {
13✔
859
                        type: ExprType.ACCESSOR,
860
                        paths: bindItem.expr.paths.concat(changeExpr.paths.slice(1))
861
                    };
862
                }
863

864
                xbindUped = 1;
32✔
865
                me.scope.set(
32✔
866
                    updateScopeExpr,
867
                    evalExpr(changeExpr, me.data, me),
868
                    {
869
                        target: {
870
                            node: me,
871
                            prop: bindItem.name
872
                        }
873
                    }
874
                );
875
            }
876
        });
877
    });
878

879
    return xbindUped;
195✔
880
};
881

882
/**
883
 * 重新绘制组件的内容
884
 * 当 dynamic slot name 发生变更或 slot 匹配发生变化时,重新绘制
885
 * 在组件级别重绘有点粗暴,但是能保证视图结果正确性
886
 */
887
Component.prototype._repaintChildren = function () {
1✔
888
    // TODO: repaint once?
889

890
    if (this._rootNode) {
13!
891
        var parentEl = this._rootNode.el.parentNode;
×
892
        var beforeEl = this._rootNode.el.nextSibling;
×
893
        this._rootNode.dispose(0, 1);
×
894
        this.slotChildren = [];
×
895

896
        this._rootNode = createNode(this.aNode, this, this.data, this);
×
897
        this._rootNode.attach(parentEl, beforeEl);
×
898
        this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
×
899
    }
900
    else {
901
        elementDisposeChildren(this.children, 0, 1);
13✔
902
        this.children = [];
13✔
903
        this.slotChildren = [];
13✔
904

905
        for (var i = 0, l = this.aNode.children.length; i < l; i++) {
13✔
906
            var child = createNode(this.aNode.children[i], this, this.data, this);
52✔
907
            this.children.push(child);
52✔
908
            child.attach(this.el);
52✔
909
        }
910
    }
911
};
912

913

914
/**
915
 * 初始化组件内部监听数据变化
916
 *
917
 * @private
918
 * @param {Object} change 数据变化信息
919
 */
920
Component.prototype._initDataChanger = function () {
1✔
921
    var me = this;
1,126✔
922

923
    this._dataChanger = function (change) {
1,126✔
924
        if (me._afterLife.created) {
1,292✔
925
            if (!me._dataChanges) {
1,079✔
926
                nextTick(me._update, me);
797✔
927
                me._dataChanges = [];
797✔
928
            }
929

930
            me._dataChanges.push(change);
1,079✔
931
        }
932
        else if (me.lifeCycle.inited && me.owner) {
213✔
933
            me._updateBindxOwner([change]);
1✔
934
        }
935
    };
936

937
    this.data.listen(this._dataChanger);
1,126✔
938
};
939

940

941
/**
942
 * 监听组件的数据变化
943
 *
944
 * @param {string} dataName 变化的数据项
945
 * @param {Function} listener 监听函数
946
 */
947
Component.prototype.watch = function (dataName, listener) {
1✔
948
    var dataExpr = parseExpr(dataName);
40✔
949
    var value = evalExpr(dataExpr, this.data, this);
40✔
950
    var me = this;
40✔
951

952
    this.data.listen(function (change) {
40✔
953
        if (changeExprCompare(change.expr, dataExpr, me.data)) {
153✔
954
            var newValue = evalExpr(dataExpr, me.data, me);
47✔
955

956
            if (newValue !== value) {
47!
957
                var oldValue = value;
47✔
958
                value = newValue;
47✔
959

960
                try {
47✔
961
                    listener.call(
47✔
962
                        me,
963
                        newValue,
964
                        {
965
                            oldValue: oldValue,
966
                            newValue: newValue,
967
                            change: change
968
                        }
969
                    );
970
                }
971
                catch (e) {
972
                    handleError(e, me, 'watch:' + dataName);
1✔
973
                }
974
            }
975
        }
976
    });
977
};
978

979
Component.prototype._getElAsRootNode = function () {
1✔
980
    return this.el;
28✔
981
};
982

983
/**
984
 * 将组件attach到页面
985
 *
986
 * @param {HTMLElement} parentEl 要添加到的父元素
987
 * @param {HTMLElement=} beforeEl 要添加到哪个元素之前
988
 */
989
Component.prototype.attach = function (parentEl, beforeEl) {
1✔
990
    if (!this.lifeCycle.attached) {
925✔
991
        // #[begin] devtool
992
        this._toPhase('beforeAttach');
924✔
993
        // #[end]
994

995
        var aNode = this.aNode;
924✔
996

997
        if (aNode.Clazz || this.components[aNode.tagName]) {
924✔
998
            // #[begin] devtool
999
            this._toPhase('beforeCreate');
38✔
1000
            // #[end]
1001
            this._rootNode = this._rootNode || createNode(aNode, this, this.data, this);
38✔
1002
            this._rootNode.attach(parentEl, beforeEl);
38✔
1003
            this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
38✔
1004
            this._toPhase('created');
38✔
1005
        }
1006
        else {
1007
            if (!this.el) {
886✔
1008
                // #[begin] devtool
1009
                this._toPhase('beforeCreate');
881✔
1010
                // #[end]
1011

1012
                var props;
881✔
1013

1014
                if (aNode._ce && aNode._i > 2) {
881✔
1015
                    props = aNode._dp;
63✔
1016
                    this.el = (aNode._el || preheatEl(aNode)).cloneNode(false);
63✔
1017
                }
1018
                else {
1019
                    props = aNode.props;
818✔
1020
                    this.el = createEl(this.tagName);
818✔
1021
                }
1022

1023
                if (this._sbindData) {
881✔
1024
                    for (var key in this._sbindData) {
1✔
1025
                        if (this._sbindData.hasOwnProperty(key)) {
6!
1026
                            getPropHandler(this.tagName, key)(
6✔
1027
                                this.el,
1028
                                this._sbindData[key],
1029
                                key,
1030
                                this
1031
                            );
1032
                        }
1033
                    }
1034
                }
1035

1036
                for (var i = 0, l = props.length; i < l; i++) {
881✔
1037
                    var prop = props[i];
2,734✔
1038
                    var value = evalExpr(prop.expr, this.data, this);
2,734✔
1039

1040
                    if (value || !styleProps[prop.name]) {
2,734✔
1041
                        prop.handler(this.el, value, prop.name, this);
1,036✔
1042
                    }
1043
                }
1044

1045
                this._toPhase('created');
881✔
1046
            }
1047

1048
            insertBefore(this.el, parentEl, beforeEl);
886✔
1049

1050
            if (!this._contentReady) {
886✔
1051
                var htmlDirective = aNode.directives.html;
881✔
1052

1053
                if (htmlDirective) {
881✔
1054
                    // #[begin] error
1055
                    warnSetHTML(this.el);
1✔
1056
                    // #[end]
1057

1058
                    this.el.innerHTML = evalExpr(htmlDirective.value, this.data, this);
1✔
1059
                }
1060
                else {
1061
                    for (var i = 0, l = aNode.children.length; i < l; i++) {
880✔
1062
                        var childANode = aNode.children[i];
1,150✔
1063
                        var child = childANode.Clazz
1,150✔
1064
                            ? new childANode.Clazz(childANode, this, this.data, this)
1,150✔
1065
                            : createNode(childANode, this, this.data, this);
1066
                        this.children.push(child);
1,149✔
1067
                        child.attach(this.el);
1,149✔
1068
                    }
1069
                }
1070

1071
                this._contentReady = 1;
880✔
1072
            }
1073

1074
            this._attached();
885✔
1075
        }
1076

1077
        this._toPhase('attached');
923✔
1078

1079
        // element 都是内部创建的,只有动态创建的 component 才会进入这个分支
1080
        if (this.owner && !this.parent) {
923✔
1081
            this.owner.implicitChildren.push(this);
6✔
1082
        }
1083
    }
1084
};
1085

1086
Component.prototype.detach = elementOwnDetach;
1✔
1087
Component.prototype.dispose = elementOwnDispose;
1✔
1088
Component.prototype._attached = elementOwnAttached;
1✔
1089
Component.prototype._leave = function () {
1✔
1090
    if (this.leaveDispose) {
1,109✔
1091
        if (!this.lifeCycle.disposed) {
1,097!
1092
            // #[begin] devtool
1093
            this._toPhase('beforeDetach');
1,097✔
1094
            // #[end]
1095
            this.data.unlisten();
1,097✔
1096
            this.dataChanger = null;
1,097✔
1097
            this._dataChanges = null;
1,097✔
1098

1099
            var len = this.implicitChildren.length;
1,097✔
1100
            while (len--) {
1,097✔
1101
                this.implicitChildren[len].dispose(0, 1);
6✔
1102
            }
1103

1104
            this.implicitChildren = null;
1,097✔
1105

1106
            this.source = null;
1,097✔
1107
            this.sourceSlots = null;
1,097✔
1108
            this.sourceSlotNameProps = null;
1,097✔
1109

1110
            // 这里不用挨个调用 dispose 了,因为 children 释放链会调用的
1111
            this.slotChildren = null;
1,097✔
1112

1113

1114
            if (this._rootNode) {
1,097✔
1115
                // 如果没有parent,说明是一个root component,一定要从dom树中remove
1116
                this._rootNode.dispose(this.disposeNoDetach && this.parent);
42✔
1117
            }
1118
            else {
1119
                var len = this.children.length;
1,055✔
1120
                while (len--) {
1,055✔
1121
                    this.children[len].dispose(1, 1);
1,393✔
1122
                }
1123

1124
                if (this._elFns) {
1,055✔
1125
                    len = this._elFns.length;
26✔
1126
                    while (len--) {
26✔
1127
                        var fn = this._elFns[len];
33✔
1128
                        un(this.el, fn[0], fn[1], fn[2]);
33✔
1129
                    }
1130
                    this._elFns = null;
26✔
1131
                }
1132

1133
                // #[begin] allua
1134
                /* istanbul ignore if */
1135
                if (this._inputTimer) {
1,055!
1136
                    clearInterval(this._inputTimer);
1✔
1137
                    this._inputTimer = null;
1✔
1138
                }
1139
                // #[end]
1140

1141
                // 如果没有parent,说明是一个root component,一定要从dom树中remove
1142
                if (!this.disposeNoDetach || !this.parent) {
1,055✔
1143
                    removeEl(this.el);
717✔
1144
                }
1145
            }
1146

1147
            this._toPhase('detached');
1,097✔
1148

1149
            // #[begin] devtool
1150
            this._toPhase('beforeDispose');
1,097✔
1151
            // #[end]
1152

1153
            this._rootNode = null;
1,097✔
1154
            this.el = null;
1,097✔
1155
            this.owner = null;
1,097✔
1156
            this.scope = null;
1,097✔
1157
            this.children = null;
1,097✔
1158

1159
            this._toPhase('disposed');
1,097✔
1160

1161
            if (this._ondisposed) {
1,097✔
1162
                this._ondisposed();
15✔
1163
            }
1164
        }
1165
    }
1166
    else if (this.lifeCycle.attached) {
12✔
1167
        // #[begin] devtool
1168
        this._toPhase('beforeDetach');
11✔
1169
        // #[end]
1170

1171
        if (this._rootNode) {
11✔
1172
            if (this._rootNode.detach) {
5✔
1173
                this._rootNode.detach();
1✔
1174
            }
1175
            else {
1176
                this._rootNode.dispose();
4✔
1177
                this._rootNode = null;
4✔
1178
            }
1179
        }
1180
        else {
1181
            removeEl(this.el);
6✔
1182
        }
1183

1184
        this._toPhase('detached');
11✔
1185
    }
1186
};
1187

1188

1189
exports = module.exports = Component;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc