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

baidu / san / 5730425668

pending completion
5730425668

push

github

errorrik
fix cov

1889 of 2064 branches covered (91.52%)

Branch coverage included in aggregate %.

3429 of 3520 relevant lines covered (97.41%)

674.7 hits per line

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

97.36
/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,209✔
62
        if (this[key] !== Component.prototype[key]) {
27,807✔
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,209✔
72
    this.lifeCycle = LifeCycle.start;
1,209✔
73
    this.id = guid++;
1,209✔
74

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

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

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

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

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

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

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

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

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

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

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

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

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

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

149

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

158
        if (firstCommentNode && firstCommentNode.nodeType === 8) {
140✔
159
            var stumpMatch = firstCommentNode.data.match(/^\s*s-data:([\s\S]+)?$/);
139✔
160
            if (stumpMatch) {
139!
161
                var stumpText = stumpMatch[1];
139✔
162
                
163
                // fill component data
164
                // #[begin] allua
165
                options.data = (new Function('return '
166
                    + stumpText
167
                        .replace(/^[\s\n]*/, '')
168
                        .replace(
169
                            /"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.\d+Z"/g,
170
                            function (match, y, mon, d, h, m, s) {
171
                                return 'new Date(' + (+y) + ',' + (+mon) + ',' + (+d)
172
                                    + ',' + (+h) + ',' + (+m) + ',' + (+s) + ')';
173
                            }
174
                        )
175
                ))();
176
                // #[end]
177
                // #[begin] modern
178
                options.data = JSON.parse(
139✔
179
                    stumpText.replace(/\\([^\\\/"bfnrtu])/g, "$1"), 
180
                    function (key, value) {
181
                        if (typeof value === 'string') {
583✔
182
                            var ma = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.\d+Z/g.exec(value);
279✔
183
                            if (ma) {
279✔
184
                                return new Date(ma[1], ma[2], ma[3], ma[4], ma[5], ma[6]);
2✔
185
                            }
186
                        }
187
                        return value;
581✔
188
                    }
189
                );
190
                // #[end]
191

192
                if (firstCommentNode.previousSibling) {
139✔
193
                    removeEl(firstCommentNode.previousSibling);
1✔
194
                }
195
                removeEl(firstCommentNode);
139✔
196
            }
197
        }
198
    }
199
    // #[end]
200

201

202
    if (this.source) {
1,207✔
203
        // 组件运行时传入的结构,做slot解析
204
        this._initSourceSlots(1);
447✔
205

206
        for (var i = 0, l = this.source.events.length; i < l; i++) {
447✔
207
            var eventBind = this.source.events[i];
22✔
208
            // 保存当前实例的native事件,下面创建aNode时候做合并
209
            if (eventBind.modifier.native) {
22✔
210
                // native事件数组
211
                this.nativeEvents = this.nativeEvents || [];
6✔
212
                this.nativeEvents.push(eventBind);
6✔
213
            }
214
            else {
215
                // #[begin] error
216
                warnEventListenMethod(eventBind, options.owner);
16✔
217
                // #[end]
218

219
                this.on(
16✔
220
                    eventBind.name,
221
                    getEventListener(eventBind, options.owner, this.scope, 1),
222
                    eventBind
223
                );
224
            }
225
        }
226

227
        this.tagName = this.tagName || this.source.tagName;
447✔
228
        this.binds = this.source._b;
447✔
229

230
        // init s-bind data
231
        this._srcSbindData = nodeSBindInit(this.source.directives.bind, this.scope, this.owner);
447✔
232
    }
233

234
    this._toPhase('compiled');
1,207✔
235

236

237
    // #[begin] devtool
238
    this._toPhase('beforeInit');
1,207✔
239
    // #[end]
240

241
    // init data
242
    var initData;
1,207✔
243
    try {
1,207✔
244
        initData = typeof this.initData === 'function' && this.initData();
1,207✔
245
    }
246
    catch (e) {
247
        handleError(e, this, 'initData');
1✔
248
    }
249
    initData = extend(initData || {}, options.data || this._srcSbindData);
1,207✔
250

251
    if (this.binds && this.scope) {
1,207✔
252
        for (var i = 0, l = this.binds.length; i < l; i++) {
447✔
253
            var bindInfo = this.binds[i];
472✔
254

255
            var value = evalExpr(bindInfo.expr, this.scope, this.owner);
472✔
256
            if (typeof value !== 'undefined') {
472✔
257
                // See: https://github.com/ecomfe/san/issues/191
258
                initData[bindInfo.name] = value;
419✔
259
            }
260
        }
261
    }
262

263
    this.data = new Data(initData);
1,207✔
264

265
    this.tagName = this.tagName || 'div';
1,207✔
266
    // #[begin] allua
267
    // ie8- 不支持innerHTML输出自定义标签
268
    /* istanbul ignore if */
269
    if (ieOldThan9 && this.tagName.indexOf('-') > 0) {
270
        this.tagName = 'div';
271
    }
272
    // #[end]
273

274

275
    // #[begin] error
276
    // 在初始化 + 数据绑定后,开始数据校验
277
    // NOTE: 只在开发版本中进行属性校验
278
    var dataTypes = this.dataTypes || clazz.dataTypes;
1,207✔
279
    if (dataTypes) {
1,207✔
280
        var dataTypeChecker = createDataTypesChecker(
59✔
281
            dataTypes,
282
            this.name || clazz.name
118✔
283
        );
284
        this.data.setTypeChecker(dataTypeChecker);
59✔
285
        this.data.checkDataTypes();
59✔
286
    }
287
    // #[end]
288

289
    this.computedDeps = {};
1,166✔
290
    for (var expr in this.computed) {
1,166✔
291
        if (this.computed.hasOwnProperty(expr) && !this.computedDeps[expr]) {
23✔
292
            this._calcComputed(expr);
19✔
293
        }
294
    }
295

296
    this._initDataChanger();
1,165✔
297
    this._sbindData = nodeSBindInit(this.aNode.directives.bind, this.data, this);
1,165✔
298
    this._toPhase('inited');
1,165✔
299

300
    // #[begin] hydrate
301
    var hydrateWalker = options.hydrateWalker;
1,165✔
302
    var aNode = this.aNode;
1,165✔
303
    if (hydrateWalker) {
1,165✔
304
        if (this.ssr === 'client-render') {
87✔
305
            this.attach(hydrateWalker.target, hydrateWalker.current);
5✔
306
        }
307
        else {
308
            if (aNode.Clazz || this.components[aNode.tagName]) {
82✔
309
                this._rootNode = createHydrateNode(aNode, this, this.data, this, hydrateWalker);
5✔
310
                this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
5✔
311
            }
312
            else {
313
                var currentNode = hydrateWalker.current;
77✔
314
                if (currentNode && currentNode.nodeType === 1) {
77✔
315
                    this.el = currentNode;
77✔
316
                    hydrateWalker.goNext();
77✔
317
                }
318

319
                hydrateElementChildren(this, this.data, this);
77✔
320
            }
321

322
            this._toPhase('created');
82✔
323
            this._attached();
82✔
324
            this._toPhase('attached');
82✔
325
        }
326
    }
327
    else if (this.el) {
1,078✔
328
        if (aNode.Clazz || this.components[aNode.tagName]) {
140✔
329
            hydrateWalker = new DOMChildrenWalker(this.el.parentNode, this.el);
5✔
330
            this._rootNode = createHydrateNode(aNode, this, this.data, this, hydrateWalker);
5✔
331
            this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
5✔
332
        }
333
        else {
334
            hydrateElementChildren(this, this.data, this);
135✔
335
        }
336

337
        this._toPhase('created');
140✔
338
        this._attached();
140✔
339
        this._toPhase('attached');
140✔
340
    }
341
    // #[end]
342
}
343

344

345
/**
346
 * 初始化创建组件外部传入的插槽对象
347
 *
348
 * @protected
349
 * @param {boolean} isFirstTime 是否初次对sourceSlots进行计算
350
 */
351
Component.prototype._initSourceSlots = function (isFirstTime) {
1✔
352
    this.sourceSlots.named = {};
460✔
353

354
    // 组件运行时传入的结构,做slot解析
355
    if (this.source && this.scope) {
460✔
356
        var sourceChildren = this.source.children;
460✔
357

358
        for (var i = 0, l = sourceChildren.length; i < l; i++) {
460✔
359
            var child = sourceChildren[i];
240✔
360
            var target;
240✔
361

362
            var slotBind = !child.textExpr && getANodeProp(child, 'slot');
240✔
363
            if (slotBind) {
240✔
364
                isFirstTime && this.sourceSlotNameProps.push(slotBind);
93✔
365

366
                var slotName = evalExpr(slotBind.expr, this.scope, this.owner);
93✔
367
                target = this.sourceSlots.named[slotName];
93✔
368
                if (!target) {
93✔
369
                    target = this.sourceSlots.named[slotName] = [];
59✔
370
                }
371
                target.push(child);
93✔
372
            }
373
            else if (isFirstTime) {
147!
374
                target = this.sourceSlots.noname;
147✔
375
                if (!target) {
147✔
376
                    target = this.sourceSlots.noname = [];
122✔
377
                }
378
                target.push(child);
147✔
379
            }
380
        }
381
    }
382
};
383

384
/**
385
 * 类型标识
386
 *
387
 * @type {string}
388
 */
389
Component.prototype.nodeType = NodeType.CMPT;
1✔
390

391
/**
392
 * 在下一个更新周期运行函数
393
 *
394
 * @param {Function} fn 要运行的函数
395
 */
396
Component.prototype.nextTick = nextTick;
1✔
397

398
Component.prototype._ctx = (new Date()).getTime().toString(16);
1✔
399

400
/* eslint-disable operator-linebreak */
401
/**
402
 * 使节点到达相应的生命周期
403
 *
404
 * @protected
405
 * @param {string} name 生命周期名称
406
 */
407
Component.prototype._toPhase = function (name) {
1✔
408
    if (!this.lifeCycle[name]) {
14,342✔
409
        this.lifeCycle = LifeCycle[name] || this.lifeCycle;
14,336✔
410
        if (typeof this[name] === 'function') {
14,336✔
411
            try {
142✔
412
                this[name]();
142✔
413
            }
414
            catch (e) {
415
                handleError(e, this, 'hook:' + name);
2✔
416
            }
417
        }
418

419
        this._afterLife = this.lifeCycle;
14,336✔
420

421
        // 通知devtool
422
        // #[begin] devtool
423
        emitDevtool('comp-' + name, this);
14,336✔
424
        // #[end]
425
    }
426
};
427
/* eslint-enable operator-linebreak */
428

429

430
/**
431
 * 添加事件监听器
432
 *
433
 * @param {string} name 事件名
434
 * @param {Function} listener 监听器
435
 * @param {string?} declaration 声明式
436
 */
437
Component.prototype.on = function (name, listener, declaration) {
1✔
438
    if (typeof listener === 'function') {
19!
439
        if (!this.listeners[name]) {
19✔
440
            this.listeners[name] = [];
18✔
441
        }
442
        this.listeners[name].push({fn: listener, declaration: declaration});
19✔
443
    }
444
};
445

446
/**
447
 * 移除事件监听器
448
 *
449
 * @param {string} name 事件名
450
 * @param {Function=} listener 监听器
451
 */
452
Component.prototype.un = function (name, listener) {
1✔
453
    var nameListeners = this.listeners[name];
2✔
454
    var len = nameListeners && nameListeners.length;
2✔
455

456
    while (len--) {
2✔
457
        if (!listener || listener === nameListeners[len].fn) {
2✔
458
            nameListeners.splice(len, 1);
2✔
459
        }
460
    }
461
};
462

463

464
/**
465
 * 派发事件
466
 *
467
 * @param {string} name 事件名
468
 * @param {Object} event 事件对象
469
 */
470
Component.prototype.fire = function (name, event) {
1✔
471
    var me = this;
25✔
472
    // #[begin] devtool
473
    emitDevtool('comp-event', {
25✔
474
        name: name,
475
        event: event,
476
        target: this
477
    });
478
    // #[end]
479

480
    each(this.listeners[name], function (listener) {
25✔
481
        try {
22✔
482
            listener.fn.call(me, event);
22✔
483
        }
484
        catch (e) {
485
            handleError(e, me, 'event:' + name);
1✔
486
        }
487
    });
488
};
489

490
/**
491
 * 计算 computed 属性的值
492
 *
493
 * @private
494
 * @param {string} computedExpr computed表达式串
495
 */
496
Component.prototype._calcComputed = function (computedExpr) {
1✔
497
    var computedDeps = this.computedDeps[computedExpr];
57✔
498
    if (!computedDeps) {
57✔
499
        computedDeps = this.computedDeps[computedExpr] = {};
23✔
500
    }
501

502
    var me = this;
57✔
503
    try {
57✔
504
        var result = this.computed[computedExpr].call({
57✔
505
            data: {
506
                get: function (expr) {
507
                    // #[begin] error
508
                    if (!expr) {
76✔
509
                        throw new Error('[SAN ERROR] call get method in computed need argument');
1✔
510
                    }
511
                    // #[end]
512

513
                    if (!computedDeps[expr]) {
75✔
514
                        computedDeps[expr] = 1;
31✔
515

516
                        if (me.computed[expr] && !me.computedDeps[expr]) {
31✔
517
                            me._calcComputed(expr);
4✔
518
                        }
519

520
                        me.watch(expr, function () {
31✔
521
                            me._calcComputed(computedExpr);
34✔
522
                        });
523
                    }
524

525
                    return me.data.get(expr);
75✔
526
                }
527
            }
528
        });
529
        this.data.set(computedExpr, result);
55✔
530
    }
531
    catch (e) {
532
        handleError(e, this, 'computed:' + computedExpr);
2✔
533
    }
534
};
535

536
/**
537
 * 派发消息
538
 * 组件可以派发消息,消息将沿着组件树向上传递,直到遇上第一个处理消息的组件
539
 *
540
 * @param {string} name 消息名称
541
 * @param {*?} value 消息值
542
 */
543
Component.prototype.dispatch = function (name, value) {
1✔
544
    var parentComponent = this.parentComponent;
27✔
545

546
    while (parentComponent) {
27✔
547
        var handler = parentComponent.messages[name] || parentComponent.messages['*'];
28✔
548
        if (typeof handler === 'function') {
28✔
549
            // #[begin] devtool
550
            emitDevtool('comp-message', {
27✔
551
                target: this,
552
                value: value,
553
                name: name,
554
                receiver: parentComponent
555
            });
556
            // #[end]
557

558
            try {
27✔
559
                handler.call(
27✔
560
                    parentComponent,
561
                    {target: this, value: value, name: name}
562
                );
563
            }
564
            catch (e) {
565
                handleError(e, parentComponent, 'message:' + (name || '*'));
1!
566
            }
567
            return;
27✔
568
        }
569

570
        parentComponent = parentComponent.parentComponent;
1✔
571
    }
572

573
    // #[begin] devtool
574
    emitDevtool('comp-message', {target: this, value: value, name: name});
×
575
    // #[end]
576
};
577

578
/**
579
 * 获取组件内部的 slot
580
 *
581
 * @param {string=} name slot名称,空为default slot
582
 * @return {Array}
583
 */
584
Component.prototype.slot = function (name) {
1✔
585
    var result = [];
24✔
586
    var me = this;
24✔
587

588
    function childrenTraversal(children) {
1✔
589
        each(children, function (child) {
70✔
590
            if (child.nodeType === NodeType.SLOT && child.owner === me) {
99✔
591
                if (child.isNamed && child.name === name
53✔
592
                    || !child.isNamed && !name
593
                ) {
594
                    result.push(child);
26✔
595
                }
596
            }
597
            else {
598
                childrenTraversal(child.children);
46✔
599
            }
600
        });
601
    }
602

603
    childrenTraversal(this.children);
24✔
604
    return result;
24✔
605
};
606

607
/**
608
 * 获取带有 san-ref 指令的子组件引用
609
 *
610
 * @param {string} name 子组件的引用名
611
 * @return {Component}
612
 */
613
Component.prototype.ref = function (name) {
1✔
614
    var refTarget;
69✔
615
    var owner = this;
69✔
616

617
    function childrenTraversal(children) {
1✔
618
        if (children) {
159✔
619
            for (var i = 0, l = children.length; i < l; i++) {
124✔
620
                elementTraversal(children[i]);
138✔
621
                if (refTarget) {
138✔
622
                    return;
91✔
623
                }
624
            }
625
        }
626
    }
627

628
    function elementTraversal(element) {
1✔
629
        var nodeType = element.nodeType;
140✔
630
        if (nodeType === NodeType.TEXT) {
140✔
631
            return;
22✔
632
        }
633

634
        if (element.owner === owner) {
118✔
635
            var ref;
109✔
636
            switch (element.nodeType) {
109✔
637
                case NodeType.ELEM:
91✔
638
                    ref = element.aNode.directives.ref;
23✔
639
                    if (ref && evalExpr(ref.value, element.scope, owner) === name) {
23✔
640
                        refTarget = element.el;
6✔
641
                    }
642
                    break;
23✔
643

644
                case NodeType.CMPT:
645
                    ref = element.source.directives.ref;
68✔
646
                    if (ref && evalExpr(ref.value, element.scope, owner) === name) {
68✔
647
                        refTarget = element;
60✔
648
                    }
649
            }
650

651
            if (refTarget) {
109✔
652
                return;
66✔
653
            }
654

655
            childrenTraversal(element.slotChildren);
43✔
656
        }
657

658
        if (refTarget) {
52✔
659
            return;
3✔
660
        }
661

662
        childrenTraversal(element.children);
49✔
663
    }
664

665
    this._rootNode ? elementTraversal(this._rootNode) : childrenTraversal(this.children);
69✔
666

667
    return refTarget;
69✔
668
};
669

670

671
/**
672
 * 视图更新函数
673
 *
674
 * @param {Array?} changes 数据变化信息
675
 */
676
Component.prototype._update = function (changes) {
1✔
677
    if (this.lifeCycle.disposed) {
1,110✔
678
        return;
133✔
679
    }
680

681
    var me = this;
977✔
682

683

684
    var needReloadForSlot = false;
977✔
685
    this._notifyNeedReload = function () {
977✔
686
        needReloadForSlot = true;
36✔
687
    };
688

689
    if (changes) {
977✔
690
        if (this.source) {
257✔
691
            this._srcSbindData = nodeSBindUpdate(
255✔
692
                this.source.directives.bind,
693
                this._srcSbindData,
694
                this.scope,
695
                this.owner,
696
                changes,
697
                function (name, value) {
698
                    if (name in me.source._pi) {
14✔
699
                        return;
2✔
700
                    }
701

702
                    me.data.set(name, value, {
12✔
703
                        target: {
704
                            node: me.owner
705
                        }
706
                    });
707
                }
708
            );
709
        }
710

711
        each(changes, function (change) {
257✔
712
            var changeExpr = change.expr;
390✔
713

714
            each(me.binds, function (bindItem) {
390✔
715
                var relation;
573✔
716
                var setExpr = bindItem.name;
573✔
717
                var updateExpr = bindItem.expr;
573✔
718

719
                if (!isDataChangeByElement(change, me, setExpr)
573✔
720
                    && (relation = changeExprCompare(changeExpr, updateExpr, me.scope))
721
                ) {
722
                    if (relation > 2) {
217✔
723
                        setExpr = {
41✔
724
                            type: ExprType.ACCESSOR,
725
                            paths: [
726
                                {
727
                                    type: ExprType.STRING,
728
                                    value: setExpr
729
                                }
730
                            ].concat(changeExpr.paths.slice(updateExpr.paths.length))
731
                        };
732
                        updateExpr = changeExpr;
41✔
733
                    }
734

735
                    if (relation >= 2 && change.type === DataChangeType.SPLICE) {
217✔
736
                        me.data.splice(setExpr, [change.index, change.deleteCount].concat(change.insertions), {
18✔
737
                            target: {
738
                                node: me.owner
739
                            }
740
                        });
741
                    }
742
                    else {
743
                        me.data.set(setExpr, evalExpr(updateExpr, me.scope, me.owner), {
199✔
744
                            target: {
745
                                node: me.owner
746
                            }
747
                        });
748
                    }
749
                }
750
            });
751

752
            each(me.sourceSlotNameProps, function (bindItem) {
390✔
753
                needReloadForSlot = needReloadForSlot || changeExprCompare(changeExpr, bindItem.expr, me.scope);
142✔
754
                return !needReloadForSlot;
142✔
755
            });
756
        });
757

758
        if (needReloadForSlot) {
257✔
759
            this._initSourceSlots();
4✔
760
            this._repaintChildren();
4✔
761
        }
762
        else {
763
            var slotChildrenLen = this.slotChildren.length;
253✔
764
            while (slotChildrenLen--) {
253✔
765
                var slotChild = this.slotChildren[slotChildrenLen];
207✔
766

767
                if (slotChild.lifeCycle.disposed) {
207✔
768
                    this.slotChildren.splice(slotChildrenLen, 1);
6✔
769
                }
770
                else if (slotChild.isInserted) {
201✔
771
                    slotChild._update(changes, 1);
144✔
772
                }
773
            }
774
        }
775
    }
776

777
    var dataChanges = this._dataChanges;
977✔
778
    if (dataChanges) {
977✔
779
        // #[begin] devtool
780
        this._toPhase('beforeUpdate');
826✔
781
        // #[end]
782

783
        this._dataChanges = null;
826✔
784

785
        this._sbindData = nodeSBindUpdate(
826✔
786
            this.aNode.directives.bind,
787
            this._sbindData,
788
            this.data,
789
            this,
790
            dataChanges,
791
            function (name, value) {
792
                if (me._rootNode || (name in me.aNode._pi)) {
18✔
793
                    return;
3✔
794
                }
795

796
                getPropHandler(me.tagName, name)(me.el, value, name, me);
15✔
797
            }
798
        );
799

800
        var htmlDirective = this.aNode.directives.html;
826✔
801

802
        if (this._rootNode) {
826✔
803
            this._rootNode._update(dataChanges);
50✔
804
            this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
50✔
805
        }
806
        else if (htmlDirective) {
776✔
807
            var len = dataChanges.length;
1✔
808
            while (len--) {
1✔
809
                if (changeExprCompare(dataChanges[len].expr, htmlDirective.value, this.data)) {
1!
810
                    // #[begin] error
811
                    warnSetHTML(this.el);
1✔
812
                    // #[end]
813

814
                    this.el.innerHTML = evalExpr(htmlDirective.value, this.data, this);
1✔
815
                    break;
1✔
816
                }
817
            }
818
        }
819
        else {
820
            var dynamicProps = this.aNode._dp;
775✔
821
            for (var i = 0; i < dynamicProps.length; i++) {
775✔
822
                var prop = dynamicProps[i];
2,371✔
823

824
                for (var j = 0; j < dataChanges.length; j++) {
2,371✔
825
                    var change = dataChanges[j];
3,158✔
826
                    if (changeExprCompare(change.expr, prop.expr, this.data)
3,158!
827
                        || prop.hintExpr && changeExprCompare(change.expr, prop.hintExpr, this.data)
828
                    ) {
829
                        prop.handler(this.el, evalExpr(prop.expr, this.data, this), prop.name, this);
65✔
830
                        break;
65✔
831
                    }
832
                }
833
            }
834

835
            for (var i = 0; i < this.children.length; i++) {
775✔
836
                this.children[i]._update(dataChanges);
1,120✔
837
            }
838
        }
839

840
        if (needReloadForSlot) {
826✔
841
            this._initSourceSlots();
9✔
842
            this._repaintChildren();
9✔
843
        }
844

845
        for (var i = 0; i < this.implicitChildren.length; i++) {
826✔
846
            this.implicitChildren[i]._update(dataChanges);
4✔
847
        }
848

849
        if (typeof this.updated === 'function') {
826✔
850
            this.updated();
5✔
851
        }
852

853
        if (this.owner && this._updateBindxOwner(dataChanges)) {
826✔
854
            this.owner._update();
25✔
855
        }
856
    }
857

858
    this._notifyNeedReload = null;
977✔
859
};
860

861
Component.prototype._updateBindxOwner = function (dataChanges) {
1✔
862
    var me = this;
212✔
863
    var xbindUped;
212✔
864

865
    each(dataChanges, function (change) {
212✔
866
        each(me.binds, function (bindItem) {
283✔
867
            var changeExpr = change.expr;
479✔
868
            if (bindItem.x
479✔
869
                && !isDataChangeByElement(change, me.owner)
870
                && changeExprCompare(changeExpr, parseExpr(bindItem.name), me.data)
871
            ) {
872
                var updateScopeExpr = bindItem.expr;
32✔
873
                if (changeExpr.paths.length > 1) {
32✔
874
                    updateScopeExpr = {
13✔
875
                        type: ExprType.ACCESSOR,
876
                        paths: bindItem.expr.paths.concat(changeExpr.paths.slice(1))
877
                    };
878
                }
879

880
                xbindUped = 1;
32✔
881
                me.scope.set(
32✔
882
                    updateScopeExpr,
883
                    evalExpr(changeExpr, me.data, me),
884
                    {
885
                        target: {
886
                            node: me,
887
                            prop: bindItem.name
888
                        }
889
                    }
890
                );
891
            }
892
        });
893
    });
894

895
    return xbindUped;
212✔
896
};
897

898
/**
899
 * 重新绘制组件的内容
900
 * 当 dynamic slot name 发生变更或 slot 匹配发生变化时,重新绘制
901
 * 在组件级别重绘有点粗暴,但是能保证视图结果正确性
902
 */
903
Component.prototype._repaintChildren = function () {
1✔
904
    // TODO: repaint once?
905

906
    if (this._rootNode) {
13!
907
        var parentEl = this._rootNode.el.parentNode;
×
908
        var beforeEl = this._rootNode.el.nextSibling;
×
909
        this._rootNode.dispose(0, 1);
×
910
        this.slotChildren = [];
×
911

912
        this._rootNode = createNode(this.aNode, this, this.data, this);
×
913
        this._rootNode.attach(parentEl, beforeEl);
×
914
        this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
×
915
    }
916
    else {
917
        elementDisposeChildren(this.children, 0, 1);
13✔
918
        this.children = [];
13✔
919
        this.slotChildren = [];
13✔
920

921
        for (var i = 0, l = this.aNode.children.length; i < l; i++) {
13✔
922
            var child = createNode(this.aNode.children[i], this, this.data, this);
52✔
923
            this.children.push(child);
52✔
924
            child.attach(this.el);
52✔
925
        }
926
    }
927
};
928

929

930
/**
931
 * 初始化组件内部监听数据变化
932
 *
933
 * @private
934
 * @param {Object} change 数据变化信息
935
 */
936
Component.prototype._initDataChanger = function () {
1✔
937
    var me = this;
1,165✔
938

939
    this._dataChanger = function (change) {
1,165✔
940
        if (me._afterLife.created) {
1,329✔
941
            if (!me._dataChanges) {
1,116✔
942
                nextTick(me._update, me);
828✔
943
                me._dataChanges = [];
828✔
944
            }
945

946
            me._dataChanges.push(change);
1,116✔
947
        }
948
        else if (me.lifeCycle.inited && me.owner) {
213✔
949
            me._updateBindxOwner([change]);
1✔
950
        }
951
    };
952

953
    this.data.listen(this._dataChanger);
1,165✔
954
};
955

956

957
/**
958
 * 监听组件的数据变化
959
 *
960
 * @param {string} dataName 变化的数据项
961
 * @param {Function} listener 监听函数
962
 */
963
Component.prototype.watch = function (dataName, listener) {
1✔
964
    var dataExpr = parseExpr(dataName);
40✔
965
    var value = evalExpr(dataExpr, this.data, this);
40✔
966
    var me = this;
40✔
967

968
    this.data.listen(function (change) {
40✔
969
        if (changeExprCompare(change.expr, dataExpr, me.data)) {
153✔
970
            var newValue = evalExpr(dataExpr, me.data, me);
47✔
971

972
            if (newValue !== value) {
47!
973
                var oldValue = value;
47✔
974
                value = newValue;
47✔
975

976
                try {
47✔
977
                    listener.call(
47✔
978
                        me,
979
                        newValue,
980
                        {
981
                            oldValue: oldValue,
982
                            newValue: newValue,
983
                            change: change
984
                        }
985
                    );
986
                }
987
                catch (e) {
988
                    handleError(e, me, 'watch:' + dataName);
1✔
989
                }
990
            }
991
        }
992
    });
993
};
994

995
Component.prototype._getElAsRootNode = function () {
1✔
996
    return this.el;
30✔
997
};
998

999
/**
1000
 * 将组件attach到页面
1001
 *
1002
 * @param {HTMLElement} parentEl 要添加到的父元素
1003
 * @param {HTMLElement=} beforeEl 要添加到哪个元素之前
1004
 */
1005
Component.prototype.attach = function (parentEl, beforeEl) {
1✔
1006
    if (!this.lifeCycle.attached) {
931✔
1007
        // #[begin] devtool
1008
        this._toPhase('beforeAttach');
930✔
1009
        // #[end]
1010

1011
        var aNode = this.aNode;
930✔
1012

1013
        if (aNode.Clazz || this.components[aNode.tagName]) {
930✔
1014
            // #[begin] devtool
1015
            this._toPhase('beforeCreate');
42✔
1016
            // #[end]
1017
            this._rootNode = this._rootNode || createNode(aNode, this, this.data, this);
42✔
1018
            this._rootNode.attach(parentEl, beforeEl);
42✔
1019
            this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
42✔
1020
            this._toPhase('created');
42✔
1021
        }
1022
        else {
1023
            if (!this.el) {
888✔
1024
                // #[begin] devtool
1025
                this._toPhase('beforeCreate');
883✔
1026
                // #[end]
1027

1028
                var props;
883✔
1029

1030
                if (aNode._ce && aNode._i > 2) {
883✔
1031
                    props = aNode._dp;
63✔
1032
                    this.el = (aNode._el || preheatEl(aNode)).cloneNode(false);
63✔
1033
                }
1034
                else {
1035
                    props = aNode.props;
820✔
1036
                    this.el = createEl(this.tagName);
820✔
1037
                }
1038

1039
                if (this._sbindData) {
883✔
1040
                    for (var key in this._sbindData) {
1✔
1041
                        if (this._sbindData.hasOwnProperty(key)) {
6!
1042
                            getPropHandler(this.tagName, key)(
6✔
1043
                                this.el,
1044
                                this._sbindData[key],
1045
                                key,
1046
                                this
1047
                            );
1048
                        }
1049
                    }
1050
                }
1051

1052
                for (var i = 0, l = props.length; i < l; i++) {
883✔
1053
                    var prop = props[i];
2,742✔
1054
                    var value = evalExpr(prop.expr, this.data, this);
2,742✔
1055

1056
                    if (value || !styleProps[prop.name]) {
2,742✔
1057
                        prop.handler(this.el, value, prop.name, this);
1,040✔
1058
                    }
1059
                }
1060

1061
                this._toPhase('created');
883✔
1062
            }
1063

1064
            insertBefore(this.el, parentEl, beforeEl);
888✔
1065

1066
            if (!this._contentReady) {
888✔
1067
                var htmlDirective = aNode.directives.html;
883✔
1068

1069
                if (htmlDirective) {
883✔
1070
                    // #[begin] error
1071
                    warnSetHTML(this.el);
1✔
1072
                    // #[end]
1073

1074
                    this.el.innerHTML = evalExpr(htmlDirective.value, this.data, this);
1✔
1075
                }
1076
                else {
1077
                    for (var i = 0, l = aNode.children.length; i < l; i++) {
882✔
1078
                        var childANode = aNode.children[i];
1,152✔
1079
                        var child = childANode.Clazz
1,152✔
1080
                            ? new childANode.Clazz(childANode, this, this.data, this)
1,152✔
1081
                            : createNode(childANode, this, this.data, this);
1082
                        this.children.push(child);
1,151✔
1083
                        child.attach(this.el);
1,151✔
1084
                    }
1085
                }
1086

1087
                this._contentReady = 1;
882✔
1088
            }
1089

1090
            this._attached();
887✔
1091
        }
1092

1093
        this._toPhase('attached');
929✔
1094

1095
        // element 都是内部创建的,只有动态创建的 component 才会进入这个分支
1096
        if (this.owner && !this.parent) {
929✔
1097
            this.owner.implicitChildren.push(this);
6✔
1098
        }
1099
    }
1100
};
1101

1102
Component.prototype.detach = elementOwnDetach;
1✔
1103
Component.prototype.dispose = elementOwnDispose;
1✔
1104
Component.prototype._attached = elementOwnAttached;
1✔
1105
Component.prototype._leave = function () {
1✔
1106
    if (this.leaveDispose) {
1,147✔
1107
        if (!this.lifeCycle.disposed) {
1,135!
1108
            // #[begin] devtool
1109
            this._toPhase('beforeDetach');
1,135✔
1110
            // #[end]
1111
            this.data.unlisten();
1,135✔
1112
            this.dataChanger = null;
1,135✔
1113
            this._dataChanges = null;
1,135✔
1114

1115
            var len = this.implicitChildren.length;
1,135✔
1116
            while (len--) {
1,135✔
1117
                this.implicitChildren[len].dispose(0, 1);
6✔
1118
            }
1119

1120
            this.implicitChildren = null;
1,135✔
1121

1122
            this.source = null;
1,135✔
1123
            this.sourceSlots = null;
1,135✔
1124
            this.sourceSlotNameProps = null;
1,135✔
1125

1126
            // 这里不用挨个调用 dispose 了,因为 children 释放链会调用的
1127
            this.slotChildren = null;
1,135✔
1128

1129

1130
            if (this._rootNode) {
1,135✔
1131
                // 如果没有parent,说明是一个root component,一定要从dom树中remove
1132
                this._rootNode.dispose(this.disposeNoDetach && this.parent);
46✔
1133
            }
1134
            else {
1135
                var len = this.children.length;
1,089✔
1136
                while (len--) {
1,089✔
1137
                    this.children[len].dispose(1, 1);
1,437✔
1138
                }
1139

1140
                if (this._elFns) {
1,089✔
1141
                    len = this._elFns.length;
26✔
1142
                    while (len--) {
26✔
1143
                        var fn = this._elFns[len];
33✔
1144
                        un(this.el, fn[0], fn[1], fn[2]);
33✔
1145
                    }
1146
                    this._elFns = null;
26✔
1147
                }
1148

1149
                // #[begin] allua
1150
                /* istanbul ignore if */
1151
                if (this._inputTimer) {
1152
                    clearInterval(this._inputTimer);
1153
                    this._inputTimer = null;
1154
                }
1155
                // #[end]
1156

1157
                // 如果没有parent,说明是一个root component,一定要从dom树中remove
1158
                if (!this.disposeNoDetach || !this.parent) {
1,089✔
1159
                    removeEl(this.el);
736✔
1160
                }
1161
            }
1162

1163
            this._toPhase('detached');
1,135✔
1164

1165
            // #[begin] devtool
1166
            this._toPhase('beforeDispose');
1,135✔
1167
            // #[end]
1168

1169
            this._rootNode = null;
1,135✔
1170
            this.el = null;
1,135✔
1171
            this.owner = null;
1,135✔
1172
            this.scope = null;
1,135✔
1173
            this.children = null;
1,135✔
1174

1175
            this._toPhase('disposed');
1,135✔
1176

1177
            if (this._ondisposed) {
1,135✔
1178
                this._ondisposed();
15✔
1179
            }
1180
        }
1181
    }
1182
    else if (this.lifeCycle.attached) {
12✔
1183
        // #[begin] devtool
1184
        this._toPhase('beforeDetach');
11✔
1185
        // #[end]
1186

1187
        if (this._rootNode) {
11✔
1188
            if (this._rootNode.detach) {
5✔
1189
                this._rootNode.detach();
1✔
1190
            }
1191
            else {
1192
                this._rootNode.dispose();
4✔
1193
                this._rootNode = null;
4✔
1194
            }
1195
        }
1196
        else {
1197
            removeEl(this.el);
6✔
1198
        }
1199

1200
        this._toPhase('detached');
11✔
1201
    }
1202
};
1203

1204

1205
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