Backed out body-slot support and significant code size reductions

This commit is contained in:
Patrick Steele-Idem 2016-12-30 19:02:31 -07:00
parent 306e19b88e
commit 74d802b074
75 changed files with 188 additions and 453 deletions

View File

@ -14,13 +14,13 @@
};
</script>
div
h1 - Hello ${state.name}!
h1 -- Hello ${state.name}!
div.colors
ul if(state.colors.length)
for(color in state.colors)
li.color style="background-color: ${color}"
- ${color}
-- ${color}
div else
- No colors!
-- No colors!
button type="button" onClick('handleButtonClick')
- You clicked the button ${state.clickCount} time(s)
-- You clicked the button ${state.clickCount} time(s)

View File

@ -34,31 +34,23 @@ var proto = RenderResult.prototype = {
return rerenderWidget;
}
return getWidgetDefs(this)[0].widget;
return getWidgetDefs(this)[0].$__widget;
},
getWidgets: function(selector) {
checkAddedToDOM(this, 'getWidgets');
var widgetDefs = getWidgetDefs(this);
var widgets;
var widgets = [];
var i;
if (selector) {
// use the selector to find the widgets that the caller wants
widgets = [];
for (i = 0; i < widgetDefs.length; i++) {
var widget = widgetDefs[i].widget;
if (selector(widget)) {
widgets.push(widget);
}
}
} else {
// return all widgets
widgets = new Array(widgetDefs.length);
for (i = 0; i < widgetDefs.length; i++) {
widgets[i] = widgetDefs[i].widget;
for (i = 0; i < widgetDefs.length; i++) {
var widget = widgetDefs[i].$__widget;
if (!selector || selector(widget)) {
widgets.push(widget);
}
}
return widgets;
},

View File

@ -56,7 +56,7 @@ var proto = AsyncVDOMBuilder.prototype = {
n: function(node) {
// NOTE: We do a shallow clone since we assume the node is being reused
// and a node can only have one parent node.
return this.node(node.cloneNode());
return this.node(node.$__cloneNode());
},
node: function(node) {
@ -185,8 +185,7 @@ var proto = AsyncVDOMBuilder.prototype = {
},
getResult: function() {
this.$__result = this.$__result || new RenderResult(this);
return this.$__result;
return this.$__result || (this.$__result = new RenderResult(this));
},
on: function(event, callback) {

View File

@ -13,7 +13,7 @@ Comment.prototype = {
return document.createComment(this.nodeValue);
},
cloneNode: function() {
$__cloneNode: function() {
return new Comment(this.nodeValue);
}
};

View File

@ -18,7 +18,7 @@ DocumentFragment.prototype = {
$__nsAware: true,
cloneNode: function() {
$__cloneNode: function() {
return new DocumentFragmentClone(this);
},

View File

@ -187,7 +187,7 @@ HTMLElement.prototype = {
targetNode._vattrs = attrs;
},
cloneNode: function() {
$__cloneNode: function() {
return new HTMLElementClone(this);
},
@ -247,7 +247,7 @@ HTMLElement.prototype = {
* @param {String} value The value for the new Comment node
*/
n: function(node) {
this.$__appendChild(node.cloneNode());
this.$__appendChild(node.$__cloneNode());
return this.$__finishChild();
},

View File

@ -13,7 +13,7 @@ Text.prototype = {
return document.createTextNode(this.nodeValue);
},
cloneNode: function() {
$__cloneNode: function() {
return new Text(this.nodeValue);
}
};

View File

@ -16,9 +16,6 @@ function virtualize(node) {
var attributes = node.attributes;
var attrCount = attributes.length;
var childNodes = node.childNodes;
var childCount = childNodes.length;
var attrs;
if (attrCount) {
@ -38,7 +35,7 @@ function virtualize(node) {
}
}
var vdomEL = new HTMLElement(node.nodeName, attrs, childCount);
var vdomEL = new HTMLElement(node.nodeName, attrs);
if (vdomEL.$__isTextArea) {
vdomEL.value = node.value;

View File

@ -1,19 +1,3 @@
/*
* Copyright 2011 eBay Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
module.exports = {
render: function (input, out) {
@ -50,10 +34,10 @@ module.exports = {
return asyncOut.error(err);
}
if (result.cloneNode) {
if (result.$__cloneNode) {
var curChild = result.firstChild;
while(curChild) {
asyncOut.node(curChild.cloneNode());
asyncOut.node(curChild.$__cloneNode());
curChild = curChild.nextSibling;
}
asyncOut.end();

View File

@ -1,11 +0,0 @@
module.exports = function codeGenerator(elNode, generator) {
var context = generator.context;
var builder = generator.builder;
var includeNode = context.createNodeForEl('include');
includeNode.addProp(
'_target',
builder.memberExpression(
builder.identifier('data'),
builder.identifier('renderBody')));
return includeNode;
};

View File

@ -170,24 +170,6 @@ var coreAttrHandlers = [
context.addError(el, 'The include attribute must have an argument. For example: include("./target.marko") or include(data.renderBody)');
}
}
],
[
'body-slot', function(attr, node, el) {
var context = this.context;
if (attr.argument) {
context.addError(el, 'The body-slot attribute should not have an argument.');
return;
}
if (attr.value) {
context.addError(el, 'The body-slot attribute should not have a value.');
return;
}
var bodySlot = context.createNodeForEl('body-slot');
node.appendChild(bodySlot);
}
]
];

View File

@ -9,17 +9,6 @@
}
]
},
"<body-slot>": {
"code-generator": "./body-slot-tag",
"attributes": {},
"autocomplete": [
{
"displayText": "body-slot",
"snippet": "body-slot",
"openTagOnly": true
}
]
},
"<else>": {
"node-factory": "./else-tag",
"attributes": {},

View File

@ -1 +0,0 @@
<div><h1>Test</h1><div class="body"><strong>This is the body</strong></div></div>

View File

@ -1,3 +0,0 @@
<div body-slot(invalid)>
</div>

View File

@ -1,8 +0,0 @@
var expect = require('chai').expect;
exports.templateData = {};
exports.checkError = function(e) {
var message = e.toString();
expect(message).to.contain('The body-slot attribute should not have an argument');
};

View File

@ -1 +0,0 @@
<div><h1>Test</h1><div class="body"><strong>This is the body</strong></div></div>

View File

@ -1,3 +0,0 @@
<div body-slot='invalid'>
</div>

View File

@ -1,8 +0,0 @@
var expect = require('chai').expect;
exports.templateData = {};
exports.checkError = function(e) {
var message = e.toString();
expect(message).to.contain('The body-slot attribute should not have a value');
};

View File

@ -1 +0,0 @@
<div><h1>Test</h1><div class="body"><strong>This is the body</strong></div></div>

View File

@ -1,4 +0,0 @@
<div>
<h1>Test</h1>
<div.body body-slot/>
</div>

View File

@ -1,3 +0,0 @@
<include('./include-target.marko')>
<strong>This is the body</strong>
</include>

View File

@ -1 +0,0 @@
exports.templateData = {};

View File

@ -1 +0,0 @@
<div><h1>Test</h1><div class="body"><strong>This is the body</strong></div></div>

View File

@ -1,6 +0,0 @@
<div>
<h1>Test</h1>
<div.body>
<body-slot/>
</div>
</div>

View File

@ -1,3 +0,0 @@
<include('./include-target.marko')>
<strong>This is the body</strong>
</include>

View File

@ -1 +0,0 @@
exports.templateData = {};

View File

@ -18,7 +18,6 @@
"ref",
"w-for",
"w-id",
"body-slot",
"w-body",
"w-preserve",
"w-preserve-body",

View File

@ -11,7 +11,6 @@
"lasso-resource",
"browser-refresh",
"assign",
"body-slot",
"else",
"else-if",
"for",

View File

@ -16,7 +16,6 @@
"await-timeout",
"bar",
"body",
"body-slot",
"browser-refresh",
"cached-fragment",
"else",

View File

@ -2,7 +2,7 @@ var expect = require('chai').expect;
module.exports = function(helpers) {
var comment = helpers.vdom.createComment('This is a comment');
var commentClone = comment.cloneNode();
var commentClone = comment.$__cloneNode();
expect(commentClone).to.not.equal(comment);
expect(comment.nodeValue).to.equal('This is a comment');
expect(commentClone.nodeValue).to.equal('This is a comment');

View File

@ -7,7 +7,7 @@ module.exports = function(helpers) {
svg.$__appendChild(docFragment);
expect(docFragment.namespaceURI).to.equal('http://www.w3.org/2000/svg');
var docFragmentClone = docFragment.cloneNode();
var docFragmentClone = docFragment.$__cloneNode();
expect(docFragmentClone.namespaceURI).to.equal('http://www.w3.org/2000/svg');
expect(docFragmentClone.nextSibling).to.equal(undefined);
expect(docFragmentClone.parentNode).to.equal(undefined);

View File

@ -2,7 +2,7 @@ var expect = require('chai').expect;
module.exports = function(helpers) {
var text = helpers.vdom.createText('Hello World');
var textClone = text.cloneNode();
var textClone = text.$__cloneNode();
expect(textClone).to.not.equal(text);
expect(text.nodeValue).to.equal('Hello World');
expect(textClone.nodeValue).to.equal('Hello World');

View File

@ -19,7 +19,7 @@ module.exports = function(helpers) {
'xlink:href': 'http://ebay.com/'
}, 0);
var clone = el.cloneNode();
var clone = el.$__cloneNode();
expect(clone).to.not.equal(el);
expect(clone.nodeName).to.equal('div');
expect(clone.hasAttributeNS(null, 'class')).to.equal(true);

View File

@ -4,7 +4,7 @@ module.exports = function(helpers) {
var div = helpers.vdom.createElement('div', null, 0 /* childCount */, 'abc123' /* key */);
var span = helpers.vdom.createElement('span', null, 0 /* childCount */);
var divClone = div.cloneNode();
var divClone = div.$__cloneNode();
expect(div.isSameNode(divClone)).to.equal(true);
expect(divClone.isSameNode(div)).to.equal(true);

View File

@ -1,6 +1,6 @@
<div class=data.className role="alert" w-bind>
<div>
<h1>ALERT! ${data.type} ${Date.now()}</h1>
<h1>ALERT! ${data.type}</h1>
<div w-body/>
</div>
</div>

View File

@ -3,14 +3,11 @@ module.exports = {
return {
size: input.size || 'normal',
variant: input.variant || 'primary',
className: input['class']
className: input['class'],
body: input.label || input.renderBody
};
},
getInitialBody: function(input) {
return input.label || input.renderBody;
},
handleClick: function(event) {
// Every Widget instance is also an EventEmitter instance.
// We will emit a custom "click" event when a DOM click event

View File

@ -4,5 +4,7 @@
'app-button-' + state.variant,
'app-button-' + state.size
] onClick("handleClick")>
<span body-slot/>
<span>
<include(state.body)/>
</span>
</button>

View File

@ -3,12 +3,10 @@ module.exports = {
this.state = {
checked: input.checked === true,
className: input['class'],
data: input.data
data: input.data,
body: input.label || input.renderBody
};
},
getInitialBody: function(input) {
return input.label || input.renderBody;
},
isChecked: function() {
return this.state.checked === true;

View File

@ -1,6 +1,6 @@
<app-button ref="button" class=state.className onClick('handleClick')>
<span class="app-checkbox-icon"/>
<span ref="checkboxLabel">
<body-slot/>
<include(state.body)/>
</span>
</app-button>

View File

@ -2,7 +2,8 @@
module.exports = {
onInput: function(input) {
this.state = {
name: input.name
name: input.name,
body: input.renderBody
};
}
};
@ -10,5 +11,7 @@
<div.foo>
<span>Hello ${state.name}!</span>
<div.body><body-slot/></div>
<div.body>
<include(state.body)/>
</div>
</div>

View File

@ -3,9 +3,9 @@ module.exports = {
onInput: function(input) {
this.state = {
size: input.size || 'normal',
variant: input.variant || 'primary'
variant: input.variant || 'primary',
body: input.renderBody
};
this.body = input.renderBody;
},
// Add any other methods here
@ -27,5 +27,7 @@ var variantClassName=(state.variant !== 'primary' && 'app-button-' + state.varia
var sizeClassName=(state.size !== 'normal' && 'app-button-' + state.size)
<button class=['app-button', variantClassName, sizeClassName]>
<span body-slot/>
<span>
<include(state.body)/>
</span>
</button>

View File

@ -3,7 +3,8 @@ module.exports = {
onInput: function(input) {
this.state = {
size: input.size || 'normal',
variant: input.variant || 'primary'
variant: input.variant || 'primary',
body: input.renderBody
};
},
@ -27,6 +28,6 @@ var sizeClassName=(state.size !== 'normal' && 'app-button-' + state.size)
<button class=['app-button', variantClassName, sizeClassName]>
<span>
<body-slot/>
<include(state.body)/>
</span>
</button>

View File

@ -3,9 +3,9 @@ module.exports = {
onInput: function(input) {
this.state = {
size: input.size || 'normal',
variant: input.variant || 'primary'
variant: input.variant || 'primary',
body: input.renderBody
};
this.body = input.renderBody;
},
// Add any other methods here
@ -28,6 +28,6 @@ var sizeClassName=(state.size !== 'normal' && 'app-button-' + state.size)
<button class=['app-button', variantClassName, sizeClassName]>
<span>
<body-slot/>
<include(state.body)/>
</span>
</button>

View File

@ -9,15 +9,13 @@ module.exports = function(helpers) {
expect(buttonWidget.el.innerHTML).to.contain('Frank');
expect(buttonWidget.el.className).to.equal('app-button app-button-small');
// Button widget will not rerender since it's state did not change and that means that the
// button content will remain as 'John' instead of 'Frank'
widget.setProps({ name: 'John '});
widget.update();
expect(buttonWidget.el.innerHTML).to.contain('Frank');
expect(buttonWidget.el.innerHTML).to.contain('John');
buttonWidget.setSize('large');
buttonWidget.update();
expect(buttonWidget.el.innerHTML).to.contain('Frank');
expect(buttonWidget.el.innerHTML).to.contain('John');
expect(buttonWidget.el.className).to.equal('app-button app-button-large');
};

View File

@ -4,9 +4,9 @@ module.exports = {
size: input.size || 'normal',
variant: input.variant || 'primary',
className: input['class'],
attrs: input['*']
attrs: input['*'],
body: input.label || input.renderBody
};
this.body = input.label || input.renderBody;
},
getTemplateData: function(state, input) {
var rootAttrs = {};

View File

@ -1,4 +1,4 @@
<button ${data.rootAttrs}
onClick("handleClick")>
<body-slot/>
<include(state.body)/>
</button>

View File

@ -4,13 +4,10 @@ module.exports = {
size: input.size || 'normal',
variant: input.variant || 'primary',
className: input['class'],
attrs: input['*']
attrs: input['*'],
body: input.label || input.renderBody
};
},
getInitialBody: function(input) {
return input.label || input.renderBody;
},
getTemplateData: function(state, input) {
var rootAttrs = {};

View File

@ -1,3 +1,3 @@
<button ${data.rootAttrs} onClick("handleClick")>
<body-slot/>
<include(state.body)/>
</button>

View File

@ -4,13 +4,10 @@ module.exports = {
size: input.size || 'normal',
variant: input.variant || 'primary',
className: input['class'],
attrs: input['*']
attrs: input['*'],
body: input.label || input.renderBody
};
},
getInitialBody: function(input) {
return input.label || input.renderBody;
},
getTemplateData: function(state, input) {
var rootAttrs = {};

View File

@ -1,4 +1,4 @@
<button ${data.rootAttrs}
onClick("handleClick")>
<body-slot/>
<include(state.body)/>
</button>

View File

@ -1,17 +1,12 @@
module.exports = {
onInput: function(input) {
var type = input.type || 'success';
this.state = {
type: type
type: input.type || 'success',
body: input.message || input.renderBody
};
},
getInitialBody: function(input) {
return input.message || input.renderBody;
},
setType: function(newType) {
this.setState('type', newType);
}

View File

@ -3,9 +3,9 @@ var className=('alert alert-'+type)
<div class=className role="alert">
<div>
<h1>ALERT! ${type} ${Date.now()}</h1>
<h1>ALERT! ${type}</h1>
<div>
<body-slot/>
<include(state.body)/>
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
<button ${data.rootAttrs}
onClick("handleClick")>
<body-slot/>
<include(data.body)/>
</button>

View File

@ -1,7 +1,4 @@
module.exports = {
getInitialBody: function(input) {
return input.label || input.renderBody;
},
getTemplateData: function(state, input) {
var rootAttrs = {};
@ -37,7 +34,8 @@ module.exports = {
return {
type: type,
rootAttrs: rootAttrs
rootAttrs: rootAttrs,
body: input.label || input.renderBody
};
}
};

View File

@ -11,29 +11,31 @@ var SubscriptionTracker = require('listener-tracker');
var inherit = require('raptor-util/inherit');
var updateManager = require('./update-manager');
var morphdom = require('morphdom');
var widgetLookup = require('./lookup').widgets;
var widgetLookup = require('./lookup').$__widgets;
var slice = Array.prototype.slice;
var MORPHDOM_SKIP = false;
var WIDGET_SUBSCRIBE_TO_OPTIONS = null;
var WIDGET_SUBSCRIBE_TO_OPTIONS;
var NON_WIDGET_SUBSCRIBE_TO_OPTIONS = {
addDestroyListener: false
};
var emit = EventEmitter.prototype.emit;
var lifecycleEventMethods = {
'beforeDestroy': 'onBeforeDestroy',
'destroy': 'onDestroy',
'beforeUpdate': 'onBeforeUpdate',
'update': 'onUpdate',
'mount': 'onMount',
'render': 'onRender',
'beforeInit': 'onBeforeInit',
'afterInit': 'onAfterInit'
};
var lifecycleEventMethods = {};
['beforeDestroy',
'destroy',
'beforeUpdate',
'update',
'mount',
'render',
'beforeInit',
'afterInit'].forEach(function(eventName) {
lifecycleEventMethods[eventName] = 'on' + eventName.charAt(0).toUpperCase() + eventName.substring(1);
});
function removeListener(eventListenerHandle) {
eventListenerHandle();
@ -72,10 +74,10 @@ function removeDOMEventListeners(widget) {
}
function destroyWidgetForEl(el) {
var widgetToDestroy = el.__widget;
var widgetToDestroy = el._w;
if (widgetToDestroy) {
destroyWidgetHelper(widgetToDestroy);
el.__widget = null;
el._w = null;
while ((widgetToDestroy = widgetToDestroy.$__rootFor)) {
widgetToDestroy.$__rootFor = null;
@ -95,12 +97,12 @@ function destroyElRecursive(el) {
}
function destroyWidgetHelper(widget) {
if (widget.isDestroyed()) {
if (widget.$__destroyed) {
return;
}
emitLifecycleEvent(widget, 'beforeDestroy');
widget.$__lifecycleState = 'destroyed';
widget.$__destroyed = true;
widget.els = null;
widget.el = null;
@ -236,7 +238,7 @@ function Widget(id, document) {
this.$__roots = null;
this.$__subscriptions = null;
this.$__domEventListenerHandles = null;
this.$__lifecycleState = null;
this.$__destroyed = false;
this.$__customEvents = null;
this.$__scope = null;
this.$__updateQueued = false;
@ -303,14 +305,13 @@ Widget.prototype = widgetProto = {
return els;
},
getWidget: function(id, index) {
var targetWidgetId = getElIdHelper(this, id, index);
return widgetLookup[targetWidgetId];
return widgetLookup[getElIdHelper(this, id, index)];
},
getWidgets: function(id) {
var widgets = [];
var i=0;
while(true) {
var widget = this.getWidget(id, i);
var widget = widgetLookup[getElIdHelper(this, id, i)];
if (!widget) {
break;
}
@ -320,7 +321,7 @@ Widget.prototype = widgetProto = {
return widgets;
},
destroy: function () {
if (this.isDestroyed()) {
if (this.$__destroyed) {
return;
}
@ -348,43 +349,47 @@ Widget.prototype = widgetProto = {
destroyWidgetHelper(this);
},
isDestroyed: function () {
return this.$__lifecycleState === 'destroyed';
return this.$__destroyed;
},
get state() {
return this.$__state;
},
set state(value) {
if(!this.$__state && value) {
this.$__state = new this.State(this, value);
this.$__state = new this.$__State(this, value);
} else {
this.$__state.$__replace(value);
}
},
setState: function(name, value) {
var state = this.$__state;
if (typeof name === 'object') {
// Merge in the new state with the old state
var newState = name;
for (var k in newState) {
if (newState.hasOwnProperty(k)) {
this.state.$__set(k, newState[k], true /* ensure:true */);
state.$__set(k, newState[k], true /* ensure:true */);
}
}
return;
}
this.state.$__set(name, value, true /* ensure:true */);
state.$__set(name, value, true /* ensure:true */);
},
setStateDirty: function(name, value) {
var state = this.$__state;
if (arguments.length === 1) {
value = this.state[name];
value = state[name];
}
this.state.$__set(name, value, true /* ensure:true */, true /* forceDirty:true */);
state.$__set(name, value, true /* ensure:true */, true /* forceDirty:true */);
},
replaceState: function(newState) {
this.state.$__replace(newState);
this.$__state.$__replace(newState);
},
/**
@ -418,13 +423,15 @@ Widget.prototype = widgetProto = {
},
update: function() {
if (this.isDestroyed()) {
return;
if (this.$__destroyed) {
return;
}
var newProps = this.$__newProps;
if (this.shouldUpdate(newProps, this.state) === false) {
var state = this.$__state;
if (this.shouldUpdate(newProps, state) === false) {
resetWidget(this);
return;
}
@ -435,7 +442,7 @@ Widget.prototype = widgetProto = {
return;
}
var state = this.$__state;
if (!state.$__dirty) {
// Don't even bother trying to update this widget since it is
@ -471,7 +478,7 @@ Widget.prototype = widgetProto = {
return this.$__state.$__dirty;
},
_reset: function(shouldRemoveDOMEventListeners) {
$__reset: function(shouldRemoveDOMEventListeners) {
resetWidget(this);
if (shouldRemoveDOMEventListeners) {
@ -499,7 +506,6 @@ Widget.prototype = widgetProto = {
}
var renderer = self.renderer;
self.$__lifecycleState = 'rerender';
var state = self.$__state;
@ -547,15 +553,6 @@ Widget.prototype = widgetProto = {
var preserved = widgetsContext.$__preserved[id];
if (preserved && !preserved.$__bodyOnly) {
if (preserved.$__bodyEl) {
morphdom(preserved.$__bodyEl, toEl, {
childrenOnly: true,
onNodeDiscarded: onNodeDiscarded,
onBeforeElUpdated: onBeforeElUpdated,
onBeforeElChildrenUpdated: onBeforeElChildrenUpdated
});
}
// Don't morph elements that are associated with widgets that are being
// reused or elements that are being preserved. For widgets being reused,
// the morphing will take place when the reused widget updates.
@ -607,8 +604,6 @@ Widget.prototype = widgetProto = {
result.afterInsert(doc);
self.$__lifecycleState = null;
if (!props) {
// We have re-rendered with the new state so our state
// is no longer dirty. Before updating a widget

View File

@ -1,5 +1,5 @@
'use strict';
var repeatedId = require('./repeated-id');
var nextRepeatedId = require('./nextRepeatedId');
var repeatedRegExp = /\[\]$/;
var uniqueId = require('./uniqueId');
@ -19,13 +19,12 @@ function WidgetDef(id, out, widgetStack, widgetStackLen) {
this.$__state = // Widget state object (may be null)
this.$__scope = // The ID of the widget that this widget is scoped within
this.$__customEvents = // An array containing information about custom events
this.$__bodyElId = // The ID for the default body element (if any any)
this.$__roots = // IDs of root elements if there are multiple root elements
this.b =
this.$__existingWidget =
this.$__children = // An array of nested WidgetDef instances
this.$__domEvents = // An array of DOM events that need to be added (in sets of three)
this.widget = // This is used by RenderResult to reference the associated widget instance after creation
this.$__widget = // This is used by RenderResult to reference the associated widget instance after creation
null;
this.$__nextIdIndex = 0; // The unique integer to use for the next scoped ID
@ -70,7 +69,7 @@ WidgetDef.prototype = {
return this.id;
} else {
if (typeof nestedId === 'string' && repeatedRegExp.test(nestedId)) {
return repeatedId.$__nextId(this.$__out, this.id, nestedId);
return nextRepeatedId(this.$__out, this.id, nestedId);
} else {
return this.id + '-' + nestedId;
}
@ -112,7 +111,6 @@ WidgetDef.prototype = {
p: customEvents && this.$__scope, // Only serialize scope if we need to attach custom events
e: this.$__domEvents,
ce: this.$__customEvents,
b: this.$__bodyElId,
c: this.$__config
};
@ -139,7 +137,6 @@ WidgetDef.$__deserialize = function(o, types) {
$__scope: extra.p,
$__domEvents: extra.e,
$__customEvents: extra.ce,
$__bodyElId: extra.b,
$__config: extra.c
};
};

View File

@ -52,12 +52,12 @@ WidgetsContext.prototype = {
var parent = widgetStack[widgetStack.length - 1];
return parent.$__nextId();
},
$__preserveDOMNode: function(existingEl, bodyOnly, bodyEl) {
$__preserveDOMNode: function(elId, bodyOnly) {
var preserved = this.$__preserved ;
if (preserved === EMPTY_OBJECT) {
preserved = this.$__preserved = {};
}
preserved[existingEl.id] = { $__bodyOnly: bodyOnly, $__bodyEl: bodyEl };
preserved[elId] = { $__bodyOnly: bodyOnly };
}
};

View File

@ -43,7 +43,7 @@ module.exports = function defineWidget(def, renderer) {
// we he have set up the prototype chain using the inherit function
proto = Widget.prototype = WidgetClass.prototype;
proto.initWidget = WidgetClass;
proto.$__initWidget = WidgetClass;
proto.constructor = def.constructor = Widget;
@ -51,9 +51,10 @@ module.exports = function defineWidget(def, renderer) {
// a widget so that we can short-circuit this work later
Widget.$__isWidget = true;
// Set widget state constructor
proto.State = function State() { BaseState.apply(this, arguments); };
inherit(proto.State, BaseState);
function State() { BaseState.apply(this, arguments); }
inherit(State, BaseState);
proto.$__State = State;
if (!renderer) {
renderer = WidgetClass.renderer || WidgetClass.prototype.renderer;

View File

@ -1,5 +1,5 @@
var updateManager = require('./update-manager');
var widgetLookup = require('./lookup').widgets;
var widgetLookup = require('./lookup').$__widgets;
var warp10Parse = require('warp10/parse');
function getObjectAttribute(el, attrName) {

View File

@ -8,7 +8,7 @@ function getWidgetForEl(el, doc) {
if (el) {
var node = typeof el === 'string' ? (doc || window.document).getElementById(el) : el;
if (node) {
var widget = node.__widget;
var widget = node._w;
while(widget) {
var rootFor = widget.$__rootFor;

View File

@ -4,8 +4,9 @@ var eventDelegation = require('./event-delegation');
var win = window;
var defaultDocument = document;
var events = require('../runtime/events');
var widgetLookup = require('./lookup').widgets;
var widgetLookup = require('./lookup').$__widgets;
var WidgetDef = require('./WidgetDef');
var extend = require('raptor-util/extend');
var registry; // We initialize this later to avoid issues with circular dependencies
@ -37,24 +38,6 @@ function addDOMEventListeners(widget, el, eventType, targetMethodName, extraArgs
handles.push(handle);
}
function getNestedEl(widget, nestedId, document) {
if (nestedId == null) {
return null;
}
if (nestedId === '') {
return widget.getEl();
}
if (typeof nestedId === 'string') {
if (nestedId.charAt(0) === '#') {
return document.getElementById(nestedId.substring(1));
}
}
return widget.getEl(nestedId);
}
function initWidget(widgetDef, doc) {
var type = widgetDef.$__type;
var id = widgetDef.id;
@ -63,7 +46,6 @@ function initWidget(widgetDef, doc) {
var scope = widgetDef.$__scope;
var domEvents = widgetDef.$__domEvents;
var customEvents = widgetDef.$__customEvents;
var bodyElId = widgetDef.$__bodyElId;
var existingWidget = widgetDef.$__existingWidget;
var el;
@ -83,10 +65,10 @@ function initWidget(widgetDef, doc) {
}
if (existingWidget) {
existingWidget._reset(true /* shouldRemoveDOMEventListeners */);
existingWidget.$__reset(true /* shouldRemoveDOMEventListeners */);
widget = existingWidget;
} else {
widget = registry.createWidget(type, id, doc);
widget = registry.$__createWidget(type, id, doc);
}
var els;
@ -110,7 +92,7 @@ function initWidget(widgetDef, doc) {
} else {
var rootEl = doc.getElementById(nestedId);
if (rootEl) {
rootEl.__widget = widget;
rootEl._w = widget;
els.push(rootEl);
}
}
@ -119,7 +101,7 @@ function initWidget(widgetDef, doc) {
el = els[0];
} else {
el = doc.getElementById(id);
el.__widget = widget;
el._w = widget;
els = [el];
}
@ -135,7 +117,6 @@ function initWidget(widgetDef, doc) {
widget.el = el;
widget.els = els;
widget.$__rootWidgets = rootWidgets;
widget.$__bodyEl = getNestedEl(widget, bodyElId, doc);
if (domEvents) {
var eventListenerHandles = [];
@ -187,8 +168,8 @@ function initWidget(widgetDef, doc) {
events.emit('initWidget', initEventArgs);
widget.$__emitLifecycleEvent('beforeInit', initEventArgs);
copyConfigToWidget(widget, config);
widget.initWidget(config);
extend(widget, config);
widget.$__initWidget(config);
widget.$__emitLifecycleEvent('afterInit', initEventArgs);
widget.$__emitLifecycleEvent('render', { firstRender: true });
@ -199,12 +180,6 @@ function initWidget(widgetDef, doc) {
return widget;
}
function copyConfigToWidget(widget, config) {
for(var key in config) {
widget[key] = config[key];
}
}
/**
* This method is used to initialized widgets associated with UI components
* rendered in the browser. While rendering UI components a "widgets context"
@ -234,7 +209,7 @@ function initClientRendered(widgetDefs, doc) {
widgetDef,
doc);
widgetDef.widget = widget;
widgetDef.$__widget = widget;
}
}

View File

@ -1,4 +1,4 @@
'use strict';
module.exports = function load(typeName) {
throw new Error('Unable to load: ' + typeName);
throw new Error('Not found: ' + typeName);
};

View File

@ -1,3 +1,3 @@
var widgets = {};
exports.widgets = widgets;
exports.$__widgets = widgets;

15
widgets/nextRepeatedId.js Normal file
View File

@ -0,0 +1,15 @@
var REPEATED_ID_KEY = '$rep';
module.exports = function nextRepeatedId(out, parentId, id) {
var nextIdLookup = out.global[REPEATED_ID_KEY] || (out.global[REPEATED_ID_KEY] = {});
var indexLookupKey = parentId + '-' + id;
var currentIndex = nextIdLookup[indexLookupKey];
if (currentIndex == null) {
currentIndex = nextIdLookup[indexLookupKey] = 0;
} else {
currentIndex = ++nextIdLookup[indexLookupKey];
}
return indexLookupKey.slice(0, -2) + '[' + currentIndex + ']';
};

View File

@ -86,8 +86,7 @@ function createWidget(typeName, id, document) {
}
exports.$__register = register;
exports.createWidget = createWidget;
exports.load = load;
exports.$__createWidget = createWidget;
defineWidget = require('./defineWidget');
defineRenderer = require('./defineRenderer');

View File

@ -1,6 +1,5 @@
var widgetLookup = require('./lookup').widgets;
var includeTag = require('./taglib/include-tag');
var repeatedId = require('./repeated-id');
var widgetLookup = require('./lookup').$__widgets;
var nextRepeatedId = require('./nextRepeatedId');
var getRootEls = require('./getRootEls');
var repeatedRegExp = /\[\]$/;
var WidgetsContext = require('./WidgetsContext');
@ -17,7 +16,7 @@ function resolveWidgetRef(out, ref, scope) {
var resolvedId;
if (repeatedRegExp.test(ref)) {
resolvedId = repeatedId.$__nextId(out, scope, ref);
resolvedId = nextRepeatedId(out, scope, ref);
} else {
resolvedId = scope + '-' + ref;
}
@ -26,46 +25,23 @@ function resolveWidgetRef(out, ref, scope) {
}
}
function preserveWidgetEls(existingWidget, out, widgetsContext, widgetBody) {
function preserveWidgetEls(existingWidget, out, widgetsContext) {
var rootEls = getRootEls(existingWidget, {});
var rootElIds = Object.keys(rootEls);
var bodyEl;
if (widgetBody && (bodyEl = existingWidget.$__bodyEl)) {
if (rootElIds.length > 1) {
// If there are multiple roots then we don't know which root el
// contains the body element so we will just need to rerender the
// UI component
return false;
}
}
for (var elId in rootEls) {
var el = rootEls[elId];
var tagName = el.tagName;
// We put a placeholder element in the output stream to ensure that the existing
// DOM node is matched up correctly when using morphdom.
out.element(el.tagName, { id: elId });
out.beginElement(tagName, { id: elId });
if (bodyEl) {
includeTag({
_target: widgetBody
}, out);
}
out.endElement();
widgetsContext.$__preserveDOMNode(el, false, bodyEl); // Mark the element as being preserved (for morphdom)
widgetsContext.$__preserveDOMNode(elId); // Mark the element as being preserved (for morphdom)
}
existingWidget._reset(); // The widget is no longer dirty so reset internal flags
existingWidget.$__reset(); // The widget is no longer dirty so reset internal flags
return true;
}
function handleBeginAsync(event) {
var parentOut = event.parentOut;
var asyncOut = event.out;
@ -109,7 +85,6 @@ module.exports = function createRendererFunc(templateRenderFunc, widgetProps, re
}
var typeName = widgetProps.type;
var bodyElId = widgetProps.body;
var roots = widgetProps.roots;
var assignedId = widgetProps.id;
@ -159,10 +134,8 @@ module.exports = function createRendererFunc(templateRenderFunc, widgetProps, re
var lightweightWidget = Object.create(renderingLogic);
lightweightWidget.onInput(input);
widgetState = lightweightWidget.state;
widgetBody = lightweightWidget.body;
widgetConfig = lightweightWidget;
delete widgetConfig.state;
delete widgetConfig.body;
} else {
if (getWidgetConfig) {
// If getWidgetConfig() was implemented then use that to
@ -256,22 +229,14 @@ module.exports = function createRendererFunc(templateRenderFunc, widgetProps, re
// rerender the widget if that is not necessary. If the widget is a stateful
// widget then we update the existing widget with the new state.
if (widgetState) {
if (existingWidget.$__replaceState(widgetState)) {
// If _processUpdateHandlers() returns true then that means
// that the widget is now up-to-date and we can skip rerendering it, but only if we
// are able to preserve the existing widget els
if (preserveWidgetEls(existingWidget, out, widgetsContext, widgetBody)) {
return;
}
}
existingWidget.$__replaceState(widgetState);
}
// If the widget is not dirty (no state changes) and shouldUpdate() returns false
// then skip rerendering the widget.
if (!existingWidget.$__dirty && !existingWidget.shouldUpdate(input, widgetState)) {
if (preserveWidgetEls(existingWidget, out, widgetsContext, widgetBody)) {
return;
}
if (!widgetBody && (!existingWidget.$__isDirty || !existingWidget.shouldUpdate(input, widgetState))) {
preserveWidgetEls(existingWidget, out, widgetsContext);
return;
}
}
@ -293,7 +258,6 @@ module.exports = function createRendererFunc(templateRenderFunc, widgetProps, re
widgetDef.$__customEvents = customEvents;
widgetDef.$__scope = scope;
widgetDef.$__existingWidget = existingWidget;
widgetDef.$__bodyElId = bodyElId;
widgetDef.$__roots = roots;
widgetDef.b = widgetBody;

View File

@ -1,28 +0,0 @@
var REPEATED_ID_KEY = '$rep';
function RepeatedId() {
this.$__nextIdLookup = {};
}
RepeatedId.prototype = {
$__nextId: function(parentId, id) {
var indexLookupKey = parentId + '-' + id;
var currentIndex = this.$__nextIdLookup[indexLookupKey];
if (currentIndex == null) {
currentIndex = this.$__nextIdLookup[indexLookupKey] = 0;
} else {
currentIndex = ++this.$__nextIdLookup[indexLookupKey];
}
return indexLookupKey.slice(0, -2) + '[' + currentIndex + ']';
}
};
exports.$__nextId = function(out, parentId, id) {
var repeatedId = out.global[REPEATED_ID_KEY];
if (repeatedId == null) {
repeatedId = out.global[REPEATED_ID_KEY] = new RepeatedId();
}
return repeatedId.$__nextId(parentId, id);
};

View File

@ -1,41 +0,0 @@
'use strict';
var includeTagForWidgets = require.resolve('../include-tag');
module.exports = function(bodySlotNode) {
if (!this.hasBoundWidgetForTemplate()) {
return;
}
var context = this.context;
var builder = this.builder;
var parentNode = bodySlotNode.parentNode;
parentNode._normalizeChildTextNodes(context);
if (parentNode.childCount !== 1) {
throw new Error('TBD');
}
let parentTransformHelper = this.getTransformHelper(parentNode);
let includeNode = context.createNodeForEl('include');
includeNode.data.bodySlot = true;
includeNode.addProp('_target', builder.memberExpression(builder.identifier('widget'), builder.identifier('b')));
includeNode.addProp('_elId', parentTransformHelper.getIdExpression());
includeNode.addProp('_arg', builder.identifier('widget'));
parentTransformHelper.assignWidgetId(false /* not repeated */);
var widgetProps = this.getWidgetProps();
widgetProps.body = parentTransformHelper.getNestedIdExpression();
includeNode.setRendererPath(includeTagForWidgets);
// includeNode.onBeforeGenerateCode(function() {
// includeNode.addProp('_elId', parentTransformHelper.getIdExpression());
// includeNode.addProp('_arg', builder.identifier('widget'));
// });
bodySlotNode.replaceWith(includeNode);
};

View File

@ -142,7 +142,6 @@ class TransformHelper {
}
TransformHelper.prototype.assignWidgetId = require('./assignWidgetId');
TransformHelper.prototype.handleBodySlotNode = require('./handleBodySlotNode');
TransformHelper.prototype.handleRootNodes = require('./handleRootNodes');
TransformHelper.prototype.handleIncludeNode = require('./handleIncludeNode');
TransformHelper.prototype.handleWidgetEvents = require('./handleWidgetEvents');

View File

@ -17,7 +17,7 @@ module.exports = function include(input, out) {
var existingEl = document.getElementById(elId);
if (existingEl) {
var widgetsContext = WidgetsContext.$__getWidgetsContext(out);
widgetsContext.$__preserveDOMNode(existingEl, true /* body only */);
widgetsContext.$__preserveDOMNode(elId, true /* body only */);
}
}
};

View File

@ -96,9 +96,6 @@
}
]
},
"@body-slot": {
"preserve-name": true
},
"@w-body": {
"preserve-name": true,
"autocomplete": []

View File

@ -27,11 +27,10 @@ module.exports = function render(input, out) {
// then that means that we have need to render a placeholder to
// mark the target location. We can then replace the placeholder
// node with the existing DOM node
out.beginElement(tagName, { id: id });
out.endElement();
out.element(tagName, { id: id });
}
widgetsContext.$__preserveDOMNode(existingEl, bodyOnly);
widgetsContext.$__preserveDOMNode(id, bodyOnly);
return;
}
}

View File

@ -10,23 +10,22 @@ module.exports = function transform(el, context) {
}
if (el.hasAttribute('w-body')) {
let builder = context.builder;
let bodyValue = el.getAttributeValue('w-body');
el.removeAttribute('w-body');
if (bodyValue) {
let includeNode = context.createNodeForEl('include');
includeNode.addProp('_target', bodyValue);
el.appendChild(includeNode);
} else {
el.setAttributeValue('body-slot', null);
let includeNode = context.createNodeForEl('include');
if (!bodyValue) {
bodyValue = builder.memberExpression(
builder.identifier('widget'),
builder.identifier('b'));
includeNode.data.bodySlot = true;
}
}
if (el.hasAttribute('body-slot')) {
el.removeAttribute('body-slot');
let bodySlotNode = context.createNodeForEl('body-slot');
el.appendChild(bodySlotNode);
includeNode.addProp('_target', bodyValue);
el.appendChild(includeNode);
}
if (el.tagName === 'widget-types') {
@ -35,9 +34,6 @@ module.exports = function transform(el, context) {
transformHelper.handleIncludeNode(el);
transformHelper.getWidgetArgs().compile(transformHelper);
return;
} else if (el.tagName === 'body-slot') {
transformHelper.handleBodySlotNode(el);
return;
}
if (el.hasAttribute('w-el-id')) {

View File

@ -1,20 +1,15 @@
function IdProvider(out) {
var global = this.global = out.global;
this.$__prefix = global.widgetIdPrefix || 'w';
var KEY = Symbol();
if (global._nextWidgetId == null) {
global._nextWidgetId = 0;
}
function UniqueId(out) {
this.prefix = out.global.widgetIdPrefix || 'w';
this.nextId = 0;
}
IdProvider.prototype.$__nextId = function() {
return this.$__prefix + (this.global._nextWidgetId++);
};
module.exports = function (out) {
var global = out.global;
var idProvider = global._widgetIdProvider ||
(global._widgetIdProvider = new IdProvider(out));
return idProvider.$__nextId();
var idProvider = global[KEY] ||
(global[KEY] = new UniqueId(out));
return idProvider.prefix + (idProvider.nextId++);
};

View File

@ -32,7 +32,6 @@ if (!setImmediate) {
}
}
function notifyAfterUpdateListeners() {
var len = queuedListeners.length;
if (len) {
@ -102,7 +101,7 @@ function batchUpdate(func) {
var isOuter = batchStack.length === 0;
var batch = {
queue: null
$__queue: null
};
batchStack.push(batch);
@ -113,8 +112,8 @@ function batchUpdate(func) {
try {
// Update all of the widgets that where queued up
// in this batch (if any)
if (batch.queue) {
updateWidgets(batch.queue);
if (batch.$__queue) {
updateWidgets(batch.$__queue);
}
} finally {
// Now that we have completed the update of all the widgets
@ -154,10 +153,10 @@ function queueWidgetUpdate(widget) {
// We default the batch queue to null to avoid creating an Array instance
// unnecessarily. If it is null then we create a new Array, otherwise
// we push it onto the existing Array queue
if (batch.queue) {
batch.queue.push(widget);
if (batch.$__queue) {
batch.$__queue.push(widget);
} else {
batch.queue = [widget];
batch.$__queue = [widget];
}
} else {
// We are not within a batched update. We need to schedule a batch update