diff --git a/doc/anode-pack.md b/doc/anode-pack.md index b907ff4e2..d44b7884c 100644 --- a/doc/anode-pack.md +++ b/doc/anode-pack.md @@ -1170,4 +1170,50 @@ aPack = [1,"u",1,45,6,1,3,"cmpt"] "tagName": "u" } */ -``` \ No newline at end of file +``` + + +### var + +- head: 46 +- 编码序: `{string}name, {Node}expr` + +```js +aPack = [1,"my-ui",1,46,"title",9,,2,3,"Hello ",7,,6,1,3,"name",] +/* +{ + "directives": {}, + "props": [], + "events": [], + "children": [], + "tagName": "my-ui", + "attrs": [ + { + "name": "title", + "expr": { + "type": 7, + "segs": [ + { + "type": 1, + "value": "Hello " + }, + { + "type": 5, + "expr": { + "type": 4, + "paths": [ + { + "type": 1, + "value": "name" + } + ] + }, + "filters": [] + } + ] + } + } + ] +} +*/ +``` diff --git a/package-lock.json b/package-lock.json index dd721df6c..ff72a456d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "san", - "version": "3.13.1", + "version": "3.13.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "san", - "version": "3.13.1", + "version": "3.13.3", "license": "MIT", "devDependencies": { "@wdio/cli": "^6.6.6", @@ -29,8 +29,8 @@ "karma-sourcemap-loader": "^0.3.7", "mustache": "^3.0.1", "opener": "^1.5.1", - "san-anode-cases": "^3.10.1", - "san-html-cases": "^3.13.4", + "san-anode-cases": "^3.14.0", + "san-html-cases": "^3.14.0", "source-map": "^0.7.3", "swig-templates": "^2.0.3", "typescript": "^4.3.5", @@ -9774,15 +9774,15 @@ "dev": true }, "node_modules/san-anode-cases": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/san-anode-cases/-/san-anode-cases-3.10.1.tgz", - "integrity": "sha512-N3quamwUS2iZqxFOQWukElYBbrcB6y9dHn2TnBmrJ8aj1oDyltxqflXZtpT3B5Mdtqj9v4bwHMxnXvHj0MaTjA==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/san-anode-cases/-/san-anode-cases-3.14.0.tgz", + "integrity": "sha512-pwkbuTqtM7YzGF+67rMmTVrw1S2tYTr0D1fV9BkWABkTk0XkFx6kp8tBzxls3npyxOH0XWUeJ+9pmnWOeuLRNg==", "dev": true }, "node_modules/san-html-cases": { - "version": "3.13.4", - "resolved": "https://registry.npmjs.org/san-html-cases/-/san-html-cases-3.13.4.tgz", - "integrity": "sha512-ZYoRuaylnHu9zlajqp1cHvttfy/lHfr/cdfiG9IwcB3EOqTArCsrlB+8mYEVTuu17J+SpPFftHVO+WlGo/hiew==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/san-html-cases/-/san-html-cases-3.14.0.tgz", + "integrity": "sha512-+fV/ATLw5+891kh7vSmgfwUfaoc4Gpg6KqTImDMB8bS3D9a4Zt0yRGeLN6AmL5jbqaGVBa+8bkeWut1oRmvahw==", "dev": true }, "node_modules/saucelabs": { @@ -19617,15 +19617,15 @@ "dev": true }, "san-anode-cases": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/san-anode-cases/-/san-anode-cases-3.10.1.tgz", - "integrity": "sha512-N3quamwUS2iZqxFOQWukElYBbrcB6y9dHn2TnBmrJ8aj1oDyltxqflXZtpT3B5Mdtqj9v4bwHMxnXvHj0MaTjA==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/san-anode-cases/-/san-anode-cases-3.14.0.tgz", + "integrity": "sha512-pwkbuTqtM7YzGF+67rMmTVrw1S2tYTr0D1fV9BkWABkTk0XkFx6kp8tBzxls3npyxOH0XWUeJ+9pmnWOeuLRNg==", "dev": true }, "san-html-cases": { - "version": "3.13.4", - "resolved": "https://registry.npmjs.org/san-html-cases/-/san-html-cases-3.13.4.tgz", - "integrity": "sha512-ZYoRuaylnHu9zlajqp1cHvttfy/lHfr/cdfiG9IwcB3EOqTArCsrlB+8mYEVTuu17J+SpPFftHVO+WlGo/hiew==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/san-html-cases/-/san-html-cases-3.14.0.tgz", + "integrity": "sha512-+fV/ATLw5+891kh7vSmgfwUfaoc4Gpg6KqTImDMB8bS3D9a4Zt0yRGeLN6AmL5jbqaGVBa+8bkeWut1oRmvahw==", "dev": true }, "saucelabs": { diff --git a/package.json b/package.json index 9c5b1f9ee..65f3e4172 100644 --- a/package.json +++ b/package.json @@ -54,8 +54,8 @@ "karma-sourcemap-loader": "^0.3.7", "mustache": "^3.0.1", "opener": "^1.5.1", - "san-anode-cases": "^3.10.1", - "san-html-cases": "^3.13.4", + "san-anode-cases": "^3.14.0", + "san-html-cases": "^3.14.0", "source-map": "^0.7.3", "swig-templates": "^2.0.3", "typescript": "^4.3.5", diff --git a/src/browser/input-event-compatible.js b/src/browser/input-event-compatible.js deleted file mode 100644 index 067d1ddd9..000000000 --- a/src/browser/input-event-compatible.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) Baidu Inc. All rights reserved. - * - * This source code is licensed under the MIT license. - * See LICENSE file in the project root for license information. - * - * @file 解决 IE9 在表单元素中删除字符时不触发事件的问题 - */ - -var ie = require('./ie'); -var on = require('./on'); -var trigger = require('./trigger'); - -// #[begin] allua -/* istanbul ignore if */ -if (ie === 9) { - on(document, 'selectionchange', function () { - var el = document.activeElement; - if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') { - trigger(el, 'input'); - } - }); -} -// #[end] diff --git a/src/browser/trigger.js b/src/browser/trigger.js index 87f240261..c755cf6ea 100644 --- a/src/browser/trigger.js +++ b/src/browser/trigger.js @@ -15,7 +15,7 @@ * @param {string} eventName 事件名 */ function trigger(el, eventName) { - var event = document.createEvent('HTMLEvents'); + var event = el.ownerDocument.createEvent('HTMLEvents'); event.initEvent(eventName, true, true); el.dispatchEvent(event); } diff --git a/src/main.js b/src/main.js index 8dc18f366..587318110 100644 --- a/src/main.js +++ b/src/main.js @@ -22,7 +22,6 @@ // require('./util/next-tick'); // require('./browser/ie'); // require('./browser/ie-old-than-9'); - // require('./browser/input-event-compatible'); // require('./browser/auto-close-tags'); // require('./util/data-types.js'); // require('./util/create-data-types-checker.js'); diff --git a/src/parser/integrate-attr.js b/src/parser/integrate-attr.js index 9b147674b..afd43fb88 100644 --- a/src/parser/integrate-attr.js +++ b/src/parser/integrate-attr.js @@ -80,13 +80,28 @@ function integrateAttr(aNode, name, value, options) { aNode.vars = []; } - realName = kebab2camel(realName); aNode.vars.push({ - name: realName, + name: kebab2camel(realName), expr: parseExpr(value.replace(/(^\{\{|\}\}$)/g, '')) }); break; + case 'attr': + if (!aNode.attrs) { + aNode.attrs = []; + } + + aNode.attrs.push({ + name: realName, + expr: value + ? parseText(value, options.delimiters) + : boolAttrs[realName] + ? {type: ExprType.BOOL, value: true} + : {type: ExprType.STRING, value: ''} + }); + + break; + default: if (prefix === 'prop') { name = realName; @@ -167,7 +182,6 @@ function integrateAttr(aNode, name, value, options) { } } } - } aNode.props.push( diff --git a/src/parser/unpack-anode.js b/src/parser/unpack-anode.js index 463477656..fb1bad447 100644 --- a/src/parser/unpack-anode.js +++ b/src/parser/unpack-anode.js @@ -211,6 +211,7 @@ function unpackANode(packed) { break; case 36: + case 46: node = { name: packed[++i] }; @@ -293,6 +294,11 @@ function unpackANode(packed) { current.vars.push(node); break; + case 46: + current.attrs = current.attrs || []; + current.attrs.push(node); + break; + case 37: current.directives['for'] = node; break; @@ -390,12 +396,14 @@ function unpackANode(packed) { // Expr: UNARY // Prop // var + // attr // Object Spread Item, Array Item case 11: case 2: case 33: case 34: case 36: + case 46: case 15: case 17: case 18: diff --git a/src/view/async-component.js b/src/view/async-component.js index 55bdf6590..46868b243 100644 --- a/src/view/async-component.js +++ b/src/view/async-component.js @@ -10,7 +10,6 @@ var guid = require('../util/guid'); var each = require('../util/each'); var insertBefore = require('../browser/insert-before'); -var nodeOwnCreateStump = require('./node-own-create-stump'); var nodeOwnSimpleDispose = require('./node-own-simple-dispose'); @@ -35,7 +34,7 @@ function AsyncComponent(options, loader) { this.children[0] = new PlaceholderComponent(options); } - this._create(); + this.el = hydrateWalker.doc.createComment(this.id); insertBefore(this.el, hydrateWalker.target, hydrateWalker.current); var me = this; @@ -47,7 +46,6 @@ function AsyncComponent(options, loader) { // #[end] } -AsyncComponent.prototype._create = nodeOwnCreateStump; AsyncComponent.prototype.dispose = nodeOwnSimpleDispose; /** @@ -64,7 +62,7 @@ AsyncComponent.prototype.attach = function (parentEl, beforeEl) { component.attach(parentEl, beforeEl); } - this._create(); + this.el = parentEl.ownerDocument.createComment(this.id); insertBefore(this.el, parentEl, beforeEl); var me = this; diff --git a/src/view/component.js b/src/view/component.js index 8f802bd40..791869934 100644 --- a/src/view/component.js +++ b/src/view/component.js @@ -84,6 +84,7 @@ function Component(options) { // eslint-disable-line var clazz = this.constructor; + this.inheritAttrs = !(this.inheritAttrs === false || clazz.inheritAttrs === false); this.filters = this.filters || clazz.filters || {}; this.computed = this.computed || clazz.computed || {}; this.messages = this.messages || clazz.messages || {}; @@ -227,6 +228,7 @@ function Component(options) { // eslint-disable-line this.tagName = this.tagName || this.source.tagName; this.binds = this.source._b; + this.attrs = this.source.attrs; // init s-bind data this._srcSbindData = nodeSBindInit(this.source.directives.bind, this.scope, this.owner); @@ -259,6 +261,19 @@ function Component(options) { // eslint-disable-line initData[bindInfo.name] = value; } } + + if (this.attrs) { + initData.$attrs = {}; + for (var i = 0, l = this.attrs.length; i < l; i++) { + var attr = this.attrs[i]; + + var value = evalExpr(attr.expr, this.scope, this.owner); + if (typeof value !== 'undefined') { + // See: https://github.com/ecomfe/san/issues/191 + initData.$attrs[attr.name] = value; + } + } + } } this.data = new Data(initData); @@ -307,6 +322,9 @@ function Component(options) { // eslint-disable-line } else { if (aNode.Clazz || this.components[aNode.tagName]) { + if (!aNode.Clazz && this.attrs && this.inheritAttrs) { + aNode = aNodeMergeSourceAttrs(aNode, this.source); + } this._rootNode = createHydrateNode(aNode, this, this.data, this, hydrateWalker); this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode()); } @@ -327,6 +345,9 @@ function Component(options) { // eslint-disable-line } else if (this.el) { if (aNode.Clazz || this.components[aNode.tagName]) { + if (!aNode.Clazz && this.attrs && this.inheritAttrs) { + aNode = aNodeMergeSourceAttrs(aNode, this.source); + } hydrateWalker = new DOMChildrenWalker(this.el.parentNode, this.el); this._rootNode = createHydrateNode(aNode, this, this.data, this, hydrateWalker); this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode()); @@ -749,6 +770,15 @@ Component.prototype._update = function (changes) { } } }); +debugger + each(me.attrs, function (bindItem) { + if (changeExprCompare(changeExpr, bindItem.expr, me.scope)) { + me.data.set( + bindItem._data, + evalExpr(bindItem.expr, me.scope, me.owner) + ); + } + }); each(me.sourceSlotNameProps, function (bindItem) { needReloadForSlot = needReloadForSlot || changeExprCompare(changeExpr, bindItem.expr, me.scope); @@ -833,6 +863,25 @@ Component.prototype._update = function (changes) { } } + if (this.attrs && this.inheritAttrs) { + var attrsData = this.data.get('$attrs'); + + for (var i = 0; i < this.attrs.length; i++) { + var attr = this.attrs[i]; + + if (this.aNode._pi[attr.name] == null) { + for (var j = 0; j < dataChanges.length; j++) { + var changePaths = dataChanges[j].expr.paths; + + if (changePaths[0].value === '$attrs' && changePaths[1].value === attr.name) { + getPropHandler(this.tagName, attr.name)(this.el, attrsData[attr.name], attr.name, this); + break; + } + } + } + } + } + for (var i = 0; i < this.children.length; i++) { this.children[i]._update(dataChanges); } @@ -910,7 +959,12 @@ Component.prototype._repaintChildren = function () { this._rootNode.dispose(0, 1); this.slotChildren = []; - this._rootNode = createNode(this.aNode, this, this.data, this); + var aNode = this.aNode; + if (!aNode.Clazz && this.attrs && this.inheritAttrs) { + aNode = aNodeMergeSourceAttrs(aNode, this.source); + } + + this._rootNode = createNode(aNode, this, this.data, this); this._rootNode.attach(parentEl, beforeEl); this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode()); } @@ -997,6 +1051,52 @@ Component.prototype._getElAsRootNode = function () { return this.el; }; +function aNodeMergeSourceAttrs(aNode, source) { + var startIndex = 0; + + var mergedANode = { + directives: aNode.directives, + props: aNode.props, + events: aNode.events, + children: aNode.children, + tagName: aNode.tagName, + attrs: [], + vars: aNode.vars, + _ht: aNode._ht, + _i: aNode._i, + _dp: aNode._dp, + _xp: aNode._xp, + _pi: aNode._pi, + _b: aNode._b, + _ce: aNode._ce + }; + + var aNodeAttrIndex = {}; + if (aNode.attrs) { + startIndex = aNode.attrs.length; + for (var i = 0; i < startIndex; i++) { + var attr = aNode.attrs[i]; + aNodeAttrIndex[attr.name] = i; + mergedANode.attrs[i] = attr; + } + } + + for (var i = 0; i < source.attrs.length; i++) { + var attr = source.attrs[i]; + + if (aNodeAttrIndex[attr.name] == null) { + mergedANode.attrs[startIndex] = { + name: attr.name, + expr: attr._data, + _data: attr._data + }; + startIndex++; + } + } + + return mergedANode; +} + /** * 将组件attach到页面 * @@ -1015,6 +1115,13 @@ Component.prototype.attach = function (parentEl, beforeEl) { // #[begin] devtool this._toPhase('beforeCreate'); // #[end] + + // aNode.Clazz 在 preheat 阶段为 if/else/for/fragment 等特殊标签或指令预热生成 + // 这里不能用 this.components[aNode.tagName] 判断,因为可能特殊指令和组件在同一个节点上并存 + if (!aNode.Clazz && this.attrs && this.inheritAttrs) { + aNode = aNodeMergeSourceAttrs(aNode, this.source); + } + this._rootNode = this._rootNode || createNode(aNode, this, this.data, this); this._rootNode.attach(parentEl, beforeEl); this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode()); @@ -1027,16 +1134,16 @@ Component.prototype.attach = function (parentEl, beforeEl) { // #[end] var props; - + var doc = parentEl.ownerDocument; if (aNode._ce && aNode._i > 2) { props = aNode._dp; - this.el = (aNode._el || preheatEl(aNode)).cloneNode(false); + this.el = (aNode._el || preheatEl(aNode, doc)).cloneNode(false); } else { props = aNode.props; - this.el = svgTags[this.tagName] && document.createElementNS - ? document.createElementNS('http://www.w3.org/2000/svg', this.tagName) - : document.createElement(this.tagName); + this.el = svgTags[this.tagName] && doc.createElementNS + ? doc.createElementNS('http://www.w3.org/2000/svg', this.tagName) + : doc.createElement(this.tagName); } if (this._sbindData) { @@ -1061,6 +1168,16 @@ Component.prototype.attach = function (parentEl, beforeEl) { } } + if (this.attrs && this.inheritAttrs) { + var attrsData = this.data.get('$attrs'); + for (var i = 0; i < this.attrs.length; i++) { + var attr = this.attrs[i]; + if (this.aNode._pi[attr.name] == null) { + getPropHandler(this.tagName, attr.name)(this.el, attrsData[attr.name], attr.name, this); + } + } + } + this._toPhase('created'); } diff --git a/src/view/dom-children-walker.js b/src/view/dom-children-walker.js index f827c07ac..813fa9568 100644 --- a/src/view/dom-children-walker.js +++ b/src/view/dom-children-walker.js @@ -21,6 +21,7 @@ var removeEl = require('../browser/remove-el'); function DOMChildrenWalker(el, onlyCurrent) { this.index = 0; this.target = el; + this.doc = el.ownerDocument; if (onlyCurrent) { this.children = [onlyCurrent, onlyCurrent.nextSibling]; diff --git a/src/view/element-own-attached.js b/src/view/element-own-attached.js index ee59ae63a..b0b9ed99f 100644 --- a/src/view/element-own-attached.js +++ b/src/view/element-own-attached.js @@ -11,6 +11,8 @@ var empty = require('../util/empty'); var isBrowser = require('../browser/is-browser'); var trigger = require('../browser/trigger'); +var ie = require('../browser/ie'); +var on = require('../browser/on'); var NodeType = require('./node-type'); var elementGetTransition = require('./element-get-transition'); var getEventListener = require('./get-event-listener'); @@ -133,6 +135,17 @@ function xPropOutput(element, bindInfo, data) { on(element.el, name, listener, capture); } +// #[begin] allua +function ie9InputEventCompatible(doc) { + on(doc, 'selectionchange', function () { + var el = doc.activeElement; + if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') { + trigger(el, 'input'); + } + }); +} +// #[end] + /** * 完成元素 attached 后的行为 * @@ -158,6 +171,16 @@ function elementOwnAttached() { switch (this.tagName) { case 'input': case 'textarea': + // #[begin] allua + if (ie === 9) { + var doc = this.el.ownerDocument; + if (!doc.__sanInputEventCompatible) { + doc.__sanInputEventCompatible = true; + ie9InputEventCompatible(doc); + } + } + // #[end] + if (isBrowser) { elementOnEl(this, 'change', inputOnCompositionEnd); elementOnEl(this, 'compositionstart', inputOnCompositionStart); diff --git a/src/view/element.js b/src/view/element.js index 7b4103500..32d43cb91 100644 --- a/src/view/element.js +++ b/src/view/element.js @@ -116,19 +116,20 @@ Element.prototype.nodeType = NodeType.ELEM; Element.prototype.attach = function (parentEl, beforeEl) { if (!this.lifeCycle.attached) { var aNode = this.aNode; + var doc = parentEl.ownerDocument; if (!this.el) { var props; if (aNode._ce && aNode._i > 2) { props = aNode._dp; - this.el = (aNode._el || preheatEl(aNode)).cloneNode(false); + this.el = (aNode._el || preheatEl(aNode, doc)).cloneNode(false); } else { props = aNode.props; - this.el = svgTags[this.tagName] && document.createElementNS - ? document.createElementNS('http://www.w3.org/2000/svg', this.tagName) - : document.createElement(this.tagName); + this.el = svgTags[this.tagName] && doc.createElementNS + ? doc.createElementNS('http://www.w3.org/2000/svg', this.tagName) + : doc.createElement(this.tagName); } if (this._sbindData) { diff --git a/src/view/for-node.js b/src/view/for-node.js index c8fff83cf..20dbb24bd 100644 --- a/src/view/for-node.js +++ b/src/view/for-node.js @@ -22,7 +22,6 @@ var NodeType = require('./node-type'); var createNode = require('./create-node'); var createHydrateNode = require('./create-hydrate-node'); var nodeOwnSimpleDispose = require('./node-own-simple-dispose'); -var nodeOwnCreateStump = require('./node-own-create-stump'); /** @@ -200,7 +199,7 @@ function ForNode(aNode, parent, scope, owner, hydrateWalker) { } } - this._create(); + this.el = hydrateWalker.doc.createComment(this.id); insertBefore(this.el, hydrateWalker.target, hydrateWalker.current); } // #[end] @@ -208,7 +207,6 @@ function ForNode(aNode, parent, scope, owner, hydrateWalker) { ForNode.prototype.nodeType = NodeType.FOR; -ForNode.prototype._create = nodeOwnCreateStump; ForNode.prototype.dispose = nodeOwnSimpleDispose; /** @@ -218,7 +216,7 @@ ForNode.prototype.dispose = nodeOwnSimpleDispose; * @param {HTMLElement=} beforeEl 要添加到哪个元素之前 */ ForNode.prototype.attach = function (parentEl, beforeEl) { - this._create(); + this.el = parentEl.ownerDocument.createComment(this.id); insertBefore(this.el, parentEl, beforeEl); this.listData = evalExpr(this.param.value, this.scope, this.owner); @@ -359,7 +357,7 @@ ForNode.prototype._disposeChildren = function (children, callback) { } // #[end] - this.el = document.createComment(this.id); + this.el = parentEl.ownerDocument.createComment(this.id); parentEl.appendChild(this.el); callback && callback(); } diff --git a/src/view/fragment-node.js b/src/view/fragment-node.js index b7d2ecb4c..b7fa7581c 100644 --- a/src/view/fragment-node.js +++ b/src/view/fragment-node.js @@ -50,7 +50,7 @@ function FragmentNode(aNode, parent, scope, owner, hydrateWalker) { hydrateWalker.goNext(); } else { - this.sel = document.createComment(this.id); + this.sel = hydrateWalker.doc.createComment(this.id); insertBefore(this.sel, hydrateWalker.target, hydrateWalker.current); } @@ -68,7 +68,7 @@ function FragmentNode(aNode, parent, scope, owner, hydrateWalker) { hydrateWalker.goNext(); } else { - this.el = document.createComment(this.id); + this.el = hydrateWalker.doc.createComment(this.id); insertBefore(this.el, hydrateWalker.target, hydrateWalker.current); } diff --git a/src/view/if-node.js b/src/view/if-node.js index 49cf2ca7b..a9c352df1 100644 --- a/src/view/if-node.js +++ b/src/view/if-node.js @@ -13,7 +13,6 @@ var evalExpr = require('../runtime/eval-expr'); var NodeType = require('./node-type'); var createNode = require('./create-node'); var createHydrateNode = require('./create-hydrate-node'); -var nodeOwnCreateStump = require('./node-own-create-stump'); var nodeOwnSimpleDispose = require('./node-own-simple-dispose'); /** @@ -72,7 +71,7 @@ function IfNode(aNode, parent, scope, owner, hydrateWalker) { } } - this._create(); + this.el = hydrateWalker.doc.createComment(this.id); insertBefore(this.el, hydrateWalker.target, hydrateWalker.current); } // #[end] @@ -80,7 +79,6 @@ function IfNode(aNode, parent, scope, owner, hydrateWalker) { IfNode.prototype.nodeType = NodeType.IF; -IfNode.prototype._create = nodeOwnCreateStump; IfNode.prototype.dispose = nodeOwnSimpleDispose; /** @@ -118,7 +116,7 @@ IfNode.prototype.attach = function (parentEl, beforeEl) { } - this._create(); + this.el = parentEl.ownerDocument.createComment(this.id); insertBefore(this.el, parentEl, beforeEl); }; diff --git a/src/view/node-own-create-stump.js b/src/view/node-own-create-stump.js deleted file mode 100644 index 5fca1cc01..000000000 --- a/src/view/node-own-create-stump.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) Baidu Inc. All rights reserved. - * - * This source code is licensed under the MIT license. - * See LICENSE file in the project root for license information. - * - * @file 创建节点对应的 stump comment 元素 - */ - - - -/** - * 创建节点对应的 stump comment 主元素 - */ -function nodeOwnCreateStump() { - this.el = this.el || document.createComment(this.id); -} - -exports = module.exports = nodeOwnCreateStump; diff --git a/src/view/node-own-only-children-attach.js b/src/view/node-own-only-children-attach.js index 71f3ca8df..f4401c6cd 100644 --- a/src/view/node-own-only-children-attach.js +++ b/src/view/node-own-only-children-attach.js @@ -20,7 +20,8 @@ var createNode = require('./create-node'); * @param {HTMLElement=} beforeEl 要添加到哪个元素之前 */ function nodeOwnOnlyChildrenAttach(parentEl, beforeEl) { - this.sel = document.createComment(this.id); + var doc = parentEl.ownerDocument; + this.sel = doc.createComment(this.id); insertBefore(this.sel, parentEl, beforeEl); for (var i = 0; i < this.aNode.children.length; i++) { @@ -34,7 +35,7 @@ function nodeOwnOnlyChildrenAttach(parentEl, beforeEl) { child.attach(parentEl, beforeEl); } - this.el = document.createComment(this.id); + this.el = doc.createComment(this.id); insertBefore(this.el, parentEl, beforeEl); this.lifeCycle = LifeCycle.attached; diff --git a/src/view/parse-component-template.js b/src/view/parse-component-template.js index b78c3dc0d..b1c2825d8 100644 --- a/src/view/parse-component-template.js +++ b/src/view/parse-component-template.js @@ -50,7 +50,8 @@ function parseComponentTemplate(ComponentClass) { delete aNode.tagName; } - if (proto.autoFillStyleAndId !== false && ComponentClass.autoFillStyleAndId !== false) { + if (proto.autoFillStyleAndId !== false && ComponentClass.autoFillStyleAndId !== false + && proto.inheritAttrs !== false && ComponentClass.inheritAttrs !== false) { fillStyleAndId(aNode.props); if (aNode.elses) { diff --git a/src/view/preheat-a-node.js b/src/view/preheat-a-node.js index c40f6df9b..7c8033db6 100644 --- a/src/view/preheat-a-node.js +++ b/src/view/preheat-a-node.js @@ -67,6 +67,8 @@ function preheatANode(aNode, componentInstance) { aNode._xp = []; // hotspot: x props aNode._pi = {}; // hotspot: props index aNode._b = []; // hotspot: binds + aNode._ab = []; // hotspot: attr binds + aNode._ap = []; // hotspot: attr props aNode._ce = !aNode.directives.is // cache element && aNode.tagName && aNode.tagName.indexOf('-') < 0 && !/^(template|select|input|option|button|video|audio|canvas|img|embed|object|iframe)$/i.test(aNode.tagName); @@ -77,6 +79,17 @@ function preheatANode(aNode, componentInstance) { recordHotspotData(varItem.expr); }); + each(aNode.attrs, function (attr, i) { + attr._data = { + type: ExprType.ACCESSOR, + paths: [ + {type: ExprType.STRING, value: '$attrs'}, + {type: ExprType.STRING, value: attr.name} + ] + }; + recordHotspotData(attr.expr); + }); + var props = aNode.props; var propsLen = props.length; diff --git a/src/view/preheat-el.js b/src/view/preheat-el.js index 0ffff2d99..350d60037 100644 --- a/src/view/preheat-el.js +++ b/src/view/preheat-el.js @@ -8,6 +8,7 @@ */ var svgTags = require('../browser/svg-tags'); +const nextTick = require('../util/next-tick'); /** * ANode预热HTML元素,用于循环创建时clone @@ -15,10 +16,10 @@ var svgTags = require('../browser/svg-tags'); * @param {Object} aNode 要预热的ANode * @return {HTMLElement} */ -function preheatEl(aNode) { - var el = svgTags[aNode.tagName] && document.createElementNS - ? document.createElementNS('http://www.w3.org/2000/svg', aNode.tagName) - : document.createElement(aNode.tagName); +function preheatEl(aNode, doc) { + var el = svgTags[aNode.tagName] && doc.createElementNS + ? doc.createElementNS('http://www.w3.org/2000/svg', aNode.tagName) + : doc.createElement(aNode.tagName); aNode._el = el; for (var i = 0, l = aNode.props.length; i < l; i++) { @@ -28,6 +29,11 @@ function preheatEl(aNode) { } } + nextTick(function () { + aNode._el = null; + aNode._i = 0; + }); + return el; } diff --git a/src/view/slot-node.js b/src/view/slot-node.js index 7eb299f45..a1ef3bc9c 100644 --- a/src/view/slot-node.js +++ b/src/view/slot-node.js @@ -116,7 +116,7 @@ function SlotNode(aNode, parent, scope, owner, hydrateWalker) { hydrateWalker.goNext(); } else { - this.sel = document.createComment(this.id); + this.sel = hydrateWalker.doc.createComment(this.id); hydrateWalker.current ? hydrateWalker.target.insertBefore(this.sel, hydrateWalker.current) : hydrateWalker.target.appendChild(this.sel); @@ -139,7 +139,7 @@ function SlotNode(aNode, parent, scope, owner, hydrateWalker) { hydrateWalker.goNext(); } else { - this.el = document.createComment(this.id); + this.el = hydrateWalker.doc.createComment(this.id); hydrateWalker.current ? hydrateWalker.target.insertBefore(this.el, hydrateWalker.current) : hydrateWalker.target.appendChild(this.el); diff --git a/src/view/template-component.js b/src/view/template-component.js index 5541416aa..15800c368 100644 --- a/src/view/template-component.js +++ b/src/view/template-component.js @@ -246,16 +246,17 @@ TemplateComponent.prototype.attach = function (parentEl, beforeEl) { // #[end] var props; + var doc = parentEl.ownerDocument; if (aNode._ce && aNode._i > 2) { props = aNode._dp; - this.el = (aNode._el || preheatEl(aNode)).cloneNode(false); + this.el = (aNode._el || preheatEl(aNode, doc)).cloneNode(false); } else { props = aNode.props; - this.el = svgTags[this.tagName] && document.createElementNS - ? document.createElementNS('http://www.w3.org/2000/svg', this.tagName) - : document.createElement(this.tagName); + this.el = svgTags[this.tagName] && doc.createElementNS + ? doc.createElementNS('http://www.w3.org/2000/svg', this.tagName) + : doc.createElement(this.tagName); } if (this._sbindData) { diff --git a/src/view/text-node.js b/src/view/text-node.js index 2f385723b..8e7453f3f 100644 --- a/src/view/text-node.js +++ b/src/view/text-node.js @@ -8,7 +8,7 @@ */ var guid = require('../util/guid'); -var isBrowser = require('../browser/is-browser'); +var ie = require('../browser/ie'); var removeEl = require('../browser/remove-el'); var insertBefore = require('../browser/insert-before'); var changeExprCompare = require('../runtime/change-expr-compare'); @@ -76,7 +76,7 @@ function TextNode(aNode, parent, scope, owner, hydrateWalker) { } } else { - this.el = document.createTextNode(''); + this.el = hydrateWalker.doc.createTextNode(''); insertBefore(this.el, hydrateWalker.target, hydrateWalker.current); } } @@ -97,21 +97,22 @@ TextNode.prototype.attach = function (parentEl, beforeEl) { this.content = ''; } + var doc = parentEl.ownerDocument; if (this.aNode.textExpr.original) { this.id = this.id || guid++; - this.sel = document.createComment(this.id); + this.sel = doc.createComment(this.id); insertBefore(this.sel, parentEl, beforeEl); - this.el = document.createComment(this.id); + this.el = doc.createComment(this.id); insertBefore(this.el, parentEl, beforeEl); - var tempFlag = document.createElement('script'); + var tempFlag = doc.createElement('script'); parentEl.insertBefore(tempFlag, this.el); tempFlag.insertAdjacentHTML('beforebegin', this.content); parentEl.removeChild(tempFlag); } else { - this.el = document.createTextNode(this.content); + this.el = doc.createTextNode(this.content); insertBefore(this.el, parentEl, beforeEl); } }; @@ -131,10 +132,12 @@ TextNode.prototype.dispose = function (noDetach) { this.sel = null; }; -var textUpdateProp = isBrowser - && (typeof document.createTextNode('').textContent === 'string' - ? 'textContent' - : 'data'); + +// #[begin] allua +var textUpdateProp = ie && ie < 9 ? 'data' : 'textContent'; +// #[end] + + /** * 更新 text 节点的视图 @@ -171,13 +174,18 @@ TextNode.prototype._update = function (changes) { warnSetHTML(parentEl); // #[end] - var tempFlag = document.createElement('script'); + var tempFlag = parentEl.ownerDocument.createElement('script'); parentEl.insertBefore(tempFlag, this.el); tempFlag.insertAdjacentHTML('beforebegin', text); parentEl.removeChild(tempFlag); } else { + // #[begin] allua this.el[textUpdateProp] = text; + // #[end] + // #[begin] modern + this.el.textContent = text; + // #[end] } } diff --git a/src/view/warn-set-html.js b/src/view/warn-set-html.js index 8c1d8d231..09e3c99ec 100644 --- a/src/view/warn-set-html.js +++ b/src/view/warn-set-html.js @@ -18,13 +18,12 @@ var warn = require('../util/warn'); function warnSetHTML(el) { // dont warn if not in browser runtime /* istanbul ignore if */ - if (!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document)) { + if (!(typeof window !== 'undefined' && typeof navigator !== 'undefined')) { return; } // some html elements cannot set innerHTML in old ie // see: https://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx - if (/^(col|colgroup|frameset|style|table|tbody|tfoot|thead|tr|select)$/i.test(el.tagName)) { warn('set html for element "' + el.tagName + '" may cause an error in old IE'); } diff --git a/test/component-attr-inherit.spec.js b/test/component-attr-inherit.spec.js new file mode 100644 index 000000000..f2899ed4e --- /dev/null +++ b/test/component-attr-inherit.spec.js @@ -0,0 +1,389 @@ +describe("Component Attribute Inherit", function () { + + it("1 level", function (done) { + var Inner = san.defineComponent({ + template: '' + + }); + + var MyComponent = san.defineComponent({ + template: '
{{text}}
', + + components: { + 'ui-inner': Inner + } + }); + + var myComponent = new MyComponent({ + data: { + text: 'Hahaha' + } + }); + + var wrap = document.createElement('div'); + document.body.appendChild(wrap); + myComponent.attach(wrap); + + var span = wrap.getElementsByTagName('span')[0]; + expect(span.innerHTML).toContain('Hahaha'); + expect(span.getAttribute('title')).toBe('Hahaha'); + expect(span.getAttribute('data-t')).toBe('state:Hahaha'); + + myComponent.data.set('text', 'Wuwuwu'); + + myComponent.nextTick(function () { + expect(span.innerHTML).toContain('Wuwuwu'); + expect(span.getAttribute('title')).toBe('Wuwuwu'); + expect(span.getAttribute('data-t')).toBe('state:Wuwuwu'); + + myComponent.dispose(); + document.body.removeChild(wrap); + done(); + }); + }); + + it("bool value and bool attr", function (done) { + var Inner = san.defineComponent({ + template: '' + + }); + + var MyComponent = san.defineComponent({ + template: '
{{text}}
', + + components: { + 'ui-inner': Inner + } + }); + + var myComponent = new MyComponent({ + data: { + text: 'Hahaha', + ed: true + } + }); + + var wrap = document.createElement('div'); + document.body.appendChild(wrap); + myComponent.attach(wrap); + + var btn = wrap.getElementsByTagName('button')[0]; + expect(btn.getAttribute('title')).toBe('Hahaha'); + expect(btn.getAttribute('data-d')).toBe(''); + expect(btn.disabled).toBeTruthy(); + expect(btn.getAttribute('data-disabled')).toBe('true'); + + myComponent.data.set('ed', 'false'); + + myComponent.nextTick(function () { + expect(btn.disabled).toBeTruthy(); + expect(btn.getAttribute('data-disabled')).toBe('false'); + + myComponent.dispose(); + document.body.removeChild(wrap); + done(); + }); + }); + + it("multi level", function (done) { + var DeepInner = san.defineComponent({ + template: '' + }); + + var Inner = san.defineComponent({ + template: '', + + components: { + 'ui-inner': DeepInner + } + }); + + var MyComponent = san.defineComponent({ + template: '
{{text}}
', + + components: { + 'ui-inner': Inner + } + }); + + var myComponent = new MyComponent({ + data: { + text: 'Hahaha', + clz: 'out-cls' + } + }); + + var wrap = document.createElement('div'); + document.body.appendChild(wrap); + myComponent.attach(wrap); + + var span = wrap.getElementsByTagName('span')[0]; + expect(span.innerHTML).toContain('Hahaha'); + expect(span.className).toContain('out-cls'); + expect(span.getAttribute('title')).toBe('Hahaha'); + expect(span.getAttribute('data-t')).toBe('state:Hahaha'); + expect(span.getAttribute('data-c')).toBe('cover'); + + myComponent.data.set('text', 'Wuwuwu'); + + myComponent.nextTick(function () { + expect(span.innerHTML).toContain('Wuwuwu'); + expect(span.getAttribute('title')).toBe('Wuwuwu'); + expect(span.getAttribute('data-t')).toBe('state:Wuwuwu'); + expect(span.getAttribute('data-c')).toBe('cover') + + myComponent.dispose(); + document.body.removeChild(wrap); + done(); + }); + }); + + it("inheritAttrs option to disable attr inherit, include style/class/id", function (done) { + var Inner = san.defineComponent({ + template: '', + inheritAttrs: false + }); + + var MyComponent = san.defineComponent({ + template: '
', + + components: { + 'ui-inner': Inner + } + }); + + var myComponent = new MyComponent({ + data: { + text: 'Hahaha' + } + }); + + var wrap = document.createElement('div'); + document.body.appendChild(wrap); + myComponent.attach(wrap); + + var innerComponent = myComponent.ref('inner'); + expect(innerComponent.data.get('$attrs').title).toContain('Hahaha'); + expect(innerComponent.data.get('$attrs')['data-t']).toContain('state:Hahaha'); + + var span = wrap.getElementsByTagName('span')[0]; + expect(span.innerHTML).toContain('Hahaha'); + expect(span.hasAttribute('title')).toBeFalsy(); + expect(span.hasAttribute('data-t')).toBeFalsy(); + expect(span.className).not.toContain('a'); + expect(span.id).not.toContain('happy'); + expect(span.style.display).not.toContain('none'); + + + myComponent.data.set('text', 'Wuwuwu'); + + myComponent.nextTick(function () { + expect(span.innerHTML).toContain('Wuwuwu'); + expect(span.hasAttribute('title')).toBeFalsy(); + expect(span.hasAttribute('data-t')).toBeFalsy(); + expect(span.className).not.toContain('a'); + expect(span.id).not.toContain('happy'); + expect(span.style.display).not.toContain('none'); + + myComponent.dispose(); + document.body.removeChild(wrap); + done(); + }); + }); + + it("inheritAttrs static prop to disable attr inherit, include style/class/id", function (done) { + var Inner = san.defineComponent({ + template: '' + }); + Inner.inheritAttrs = false; + + var MyComponent = san.defineComponent({ + template: '
', + + components: { + 'ui-inner': Inner + } + }); + + var myComponent = new MyComponent({ + data: { + text: 'Hahaha' + } + }); + + var wrap = document.createElement('div'); + document.body.appendChild(wrap); + myComponent.attach(wrap); + + var span = wrap.getElementsByTagName('span')[0]; + expect(span.innerHTML).toContain('Hahaha'); + expect(span.hasAttribute('title')).toBeFalsy(); + expect(span.hasAttribute('data-t')).toBeFalsy(); + expect(span.className).not.toContain('a'); + expect(span.id).not.toContain('happy'); + expect(span.style.display).not.toContain('none'); + + + myComponent.data.set('text', 'Wuwuwu'); + + myComponent.nextTick(function () { + expect(span.innerHTML).toContain('Wuwuwu'); + expect(span.hasAttribute('title')).toBeFalsy(); + expect(span.hasAttribute('data-t')).toBeFalsy(); + expect(span.className).not.toContain('a'); + expect(span.id).not.toContain('happy'); + expect(span.style.display).not.toContain('none'); + + myComponent.dispose(); + document.body.removeChild(wrap); + done(); + }); + }); + + it("component template declaration take precedence", function (done) { + var Inner = san.defineComponent({ + template: '' + }); + + var MyComponent = san.defineComponent({ + template: '
{{text}}
', + + components: { + 'ui-inner': Inner + } + }); + + var myComponent = new MyComponent({ + data: { + text: 'Hahaha' + } + }); + + var wrap = document.createElement('div'); + document.body.appendChild(wrap); + myComponent.attach(wrap); + + var span = wrap.getElementsByTagName('span')[0]; + expect(span.innerHTML).toContain('Hahaha'); + expect(span.getAttribute('title')).toBe('nothing'); + expect(span.getAttribute('data-t')).toBe('state:Hahaha'); + + myComponent.data.set('text', 'Wuwuwu'); + + myComponent.nextTick(function () { + expect(span.innerHTML).toContain('Wuwuwu'); + expect(span.getAttribute('title')).toBe('nothing'); + expect(span.getAttribute('data-t')).toBe('state:Wuwuwu'); + + myComponent.dispose(); + document.body.removeChild(wrap); + done(); + }); + }); + + it("inner component has $attrs data, not has attrXxx data", function (done) { + var Inner = san.defineComponent({ + template: '

' + }); + + var MyComponent = san.defineComponent({ + template: '
{{text}}
', + + components: { + 'ui-inner': Inner + } + }); + + var myComponent = new MyComponent({ + data: { + text: 'Hahaha' + } + }); + + var wrap = document.createElement('div'); + document.body.appendChild(wrap); + myComponent.attach(wrap); + + var innComponent = myComponent.ref('inn'); + var innAttrs = innComponent.data.get('$attrs'); + expect(innAttrs.title).toBe('Hahaha'); + expect(innAttrs['data-t']).toBe('state:Hahaha'); + expect(innComponent.data.get('attrTitle')).toBeUndefined(); + + + var p = wrap.getElementsByTagName('p')[0]; + expect(p.getAttribute('title')).toBe('Hahaha'); + expect(p.getAttribute('data-t')).toBe('state:Hahaha'); + + var span = wrap.getElementsByTagName('span')[0]; + expect(span.innerHTML).toContain('Hahaha'); + expect(span.hasAttribute('title')).toBeFalsy(); + expect(span.hasAttribute('data-t')).toBeFalsy(); + + myComponent.data.set('text', 'Wuwuwu'); + + myComponent.nextTick(function () { + expect(span.innerHTML).toContain('Wuwuwu'); + expect(p.getAttribute('title')).toBe('Wuwuwu'); + expect(p.getAttribute('data-t')).toBe('state:Wuwuwu'); + + + var innAttrs = innComponent.data.get('$attrs'); + expect(innAttrs.title).toBe('Wuwuwu'); + expect(innAttrs['data-t']).toBe('state:Wuwuwu'); + + expect(innComponent.data.get('attrTitle')).toBeUndefined(); + + myComponent.dispose(); + document.body.removeChild(wrap); + done(); + }); + }); + + it("spread inherit attrs to other element", function (done) { + var Inner = san.defineComponent({ + template: '

', + inheritAttrs: false + }); + + var MyComponent = san.defineComponent({ + template: '
{{text}}
', + + components: { + 'ui-inner': Inner + } + }); + + var myComponent = new MyComponent({ + data: { + text: 'Hahaha' + } + }); + + var wrap = document.createElement('div'); + document.body.appendChild(wrap); + myComponent.attach(wrap); + + var span = wrap.getElementsByTagName('span')[0]; + expect(span.innerHTML).toContain('Hahaha'); + expect(span.getAttribute('title')).toBe('Hahaha'); + expect(span.getAttribute('data-t')).toBe('state:Hahaha'); + + var p = wrap.getElementsByTagName('p')[0]; + expect(p.hasAttribute('title')).toBeFalsy(); + expect(p.hasAttribute('data-t')).toBeFalsy(); + + myComponent.data.set('text', 'Wuwuwu'); + + myComponent.nextTick(function () { + expect(span.innerHTML).toContain('Wuwuwu'); + expect(span.getAttribute('title')).toBe('Wuwuwu'); + expect(span.getAttribute('data-t')).toBe('state:Wuwuwu'); + + myComponent.dispose(); + document.body.removeChild(wrap); + done(); + }); + }); + +}); diff --git a/test/component.spec.js b/test/component.spec.js index 52d61da76..775a9a6e8 100644 --- a/test/component.spec.js +++ b/test/component.spec.js @@ -534,20 +534,6 @@ describe("Component", function () { }) }); - it("attach without parentEl, and dispose, got collect life cycle", function () { - var MyComponent = san.defineComponent({ - template: '
hello san
' - }); - - var myComponent = new MyComponent(); - myComponent.attach(); - - expect(!!myComponent.lifeCycle.is('attached')).toBeTruthy(); - - myComponent.dispose(); - expect(!!myComponent.lifeCycle.is('disposed')).toBeTruthy(); - - }); it("data set in inited should not update view", function (done) { var up = false; diff --git a/test/index-min.html b/test/index-min.html index ca0018187..e9b1bab3e 100755 --- a/test/index-min.html +++ b/test/index-min.html @@ -33,6 +33,7 @@ + diff --git a/test/index-modern.html b/test/index-modern.html index 7aca7ccdd..64bb6797d 100755 --- a/test/index-modern.html +++ b/test/index-modern.html @@ -33,6 +33,7 @@ + diff --git a/test/index.html b/test/index.html index 278e655fd..e178e5da3 100755 --- a/test/index.html +++ b/test/index.html @@ -96,6 +96,7 @@ +