Fixes #419 - Deprecate w-preserve/w-preserve-if/w-preserve-body/w-preserve-body-if in favor of no-update-*. (#433)

This commit is contained in:
Austin Kelleher 2016-11-15 12:30:54 -05:00 committed by Michael Rawlings
parent c35bbf0b9d
commit 3eb3c286e8
16 changed files with 219 additions and 63 deletions

View File

@ -373,12 +373,12 @@ Sometimes it is important to _not_ re-render a DOM subtree. This may due to eith
- DOM nodes contains externally provided content
- DOM nodes have internal state that needs to be maintained
Marko Widgets allows DOM nodes to be preserved by putting a special `w-preserve`, `w-preserve-if(<condition>)`, `w-preserve-body` or `w-preserve-body-if(<condition>)` attribute on the HTML tags that should be preserved. Preserved DOM nodes will be reused and re-inserted into a widget's newly rendered DOM automatically.
Marko Widgets allows DOM nodes to be preserved by putting a special `no-update`, `no-update-if(<condition>)`, `no-update-body` or `no-update-body-if(<condition>)` attribute on the HTML tags that should be preserved. Preserved DOM nodes will be reused and re-inserted into a widget's newly rendered DOM automatically.
```xml
<div w-bind>
<span w-preserve>
<span no-update>
<p>
The root span and all its children will never
be re-rendered.
@ -387,7 +387,7 @@ Marko Widgets allows DOM nodes to be preserved by putting a special `w-preserve`
Rendered at ${Date.now()}.
</p>
</span>
<div w-preserve-body>
<div no-update-body>
Only the children of the div will preserved and
the outer HTML div tag will be re-rendered.
</div>
@ -395,7 +395,7 @@ Marko Widgets allows DOM nodes to be preserved by putting a special `w-preserve`
Don't rerender the search results if no search results
are provided.
<app-search-results items="data.searchResults"
w-preserve-if(data.searchResults == null)/>
no-update-if(data.searchResults == null)/>
</div>
```

View File

@ -123,9 +123,9 @@ For the example above it is assumed that the nested widget will emit the custom
this.emit('handleSomeCustomEvent', { foo: bar });
```
<a name="w-preserve"></a>
<a name="no-update"></a>
## w-preserve
## no-update
Preserves the DOM subtree associated with the DOM element or widget such that it won't be modified or rerendered when rerendering the UI component.
@ -133,7 +133,7 @@ Example:
```xml
<div>
<table w-preserve> <!-- Don't ever rerender this table -->
<table no-update> <!-- Don't ever rerender this table -->
...
</table>
</div>
@ -141,39 +141,39 @@ Example:
```xml
<div>
<app-map w-preserve/> <!-- Don't ever rerender this UI component -->
<app-map no-update/> <!-- Don't ever rerender this UI component -->
</div>
```
## w-preserve-if
## no-update-if
Similar to [w-preserve](#w-preserve) except that the DOM subtree is conditionally preserved:
Similar to [no-update](#no-update) except that the DOM subtree is conditionally preserved:
```xml
<div>
<table w-preserve-if(data.tableData == null)>
<table no-update-if(data.tableData == null)>
...
</table>
</div>
```
## w-preserve-body
## no-update-body
Similar to [w-preserve](#w-preserve) except that only the child DOM nodes are preserved:
Similar to [no-update](#no-update) except that only the child DOM nodes are preserved:
```xml
<div w-preserve-body> <!-- Don't ever rerender any nested DOM elements -->
<div no-update-body> <!-- Don't ever rerender any nested DOM elements -->
...
</div>
```
## w-preserve-body-if
## no-update-body-if
Similar to [w-preserve-if](#w-preserve) except that only the child DOM nodes are preserved:
Similar to [no-update-if](#no-update) except that only the child DOM nodes are preserved:
```xml
<div>
<table w-preserve-if(data.tableData == null)>
<table no-update-if(data.tableData == null)>
...
</table>
</div>

View File

@ -23,6 +23,10 @@
"w-preserve-body",
"w-preserve-if",
"w-preserve-body-if",
"no-update",
"no-update-body",
"no-update-if",
"no-update-body-if",
"w-preserve-attrs",
"w-on*"
]

View File

@ -47,6 +47,7 @@
"w-widget",
"init-widgets",
"w-preserve",
"no-update",
"widget-types",
"body"
]

View File

@ -40,6 +40,7 @@
"macro",
"macro-body",
"marko-preserve-whitespace",
"no-update",
"pre",
"script",
"style",

View File

@ -1,6 +1,6 @@
<div w-bind>
<div.unpreserved-counter>${data.counter}</div>
<span class="preserve" data-counter=data.counter w-preserve-body>
<span class="preserve" data-counter=data.counter no-update-body>
<div.preserved-counter>${data.counter}</div>
</span>
</div>

View File

@ -1,6 +1,6 @@
<div w-bind>
<div.unpreserved-counter>${data.counter}</div>
<span ref="preserve" data-counter=data.counter w-preserve-body>
<span ref="preserve" data-counter=data.counter no-update-body>
<div.preserved-counter>${data.counter}</div>
</span>
</div>

View File

@ -1,6 +1,6 @@
<div w-bind>
<div.unpreserved-counter>${data.counter}</div>
<span class="preserve" data-counter=data.counter w-preserve>
<span class="preserve" data-counter=data.counter no-update>
<div.preserved-counter>${data.counter}</div>
</span>
</div>

View File

@ -1,6 +1,6 @@
<div w-bind>
<div.unpreserved-counter>${data.counter}</div>
<span ref="preserve" data-counter=data.counter w-preserve>
<span ref="preserve" data-counter=data.counter no-update>
<div.preserved-counter>${data.counter}</div>
</span>
</div>

View File

@ -1,18 +1,18 @@
<div w-bind>
<h1>Preserve Begin</h1>
<span ref="preserve" w-preserve-if(data.preserveCondition) data-renderId=data.renderId>${data.renderId}</span>
<span ref="preserve" no-update-if(data.preserveCondition) data-renderId=data.renderId>${data.renderId}</span>
<span ref="preserveBody" w-preserve-body-if(data.preserveCondition) data-renderId=data.renderId>${data.renderId}</span>
<span ref="preserveBody" no-update-body-if(data.preserveCondition) data-renderId=data.renderId>${data.renderId}</span>
<app-stateful-rerender ref="widget" name=(data.renderId.toString()) w-preserve-if(data.preserveCondition) />
<app-stateful-rerender ref="widget" name=(data.renderId.toString()) no-update-if(data.preserveCondition) />
<!-- Test without an ID -->
<span class="preserve" w-preserve-if(data.preserveCondition) data-renderId=data.renderId>${data.renderId}</span>
<span class="preserve" no-update-if(data.preserveCondition) data-renderId=data.renderId>${data.renderId}</span>
<span class="preserve-body" w-preserve-body-if(data.preserveCondition) data-renderId=data.renderId>${data.renderId}</span>
<span class="preserve-body" no-update-body-if(data.preserveCondition) data-renderId=data.renderId>${data.renderId}</span>
<app-stateful-rerender class="widget-no-id" name=(data.renderId.toString()) w-preserve-if(data.preserveCondition) data-renderId=data.renderId/>
<app-stateful-rerender class="widget-no-id" name=(data.renderId.toString()) no-update-if(data.preserveCondition) data-renderId=data.renderId/>
<h1>Preserve End</h1>
</div>

View File

@ -1 +1 @@
<div w-bind><div w-preserve for(color in ['red', 'green', 'blue'])>${color}</div></div>
<div w-bind><div no-update for(color in ['red', 'green', 'blue'])>${color}</div></div>

View File

@ -1,3 +1,3 @@
<div w-bind w-preserve>
<div w-bind no-update>
Hello ${data.name}! You have ${data.messageCount} new messages.
</div>

View File

@ -16,12 +16,11 @@
'use strict';
function addPreserve(transformHelper, bodyOnly, condition) {
let el = transformHelper.el;
let context = transformHelper.context;
let builder = transformHelper.builder;
var el = transformHelper.el;
var context = transformHelper.context;
var builder = transformHelper.builder;
var preserveAttrs = {};
let preserveAttrs = {};
if (bodyOnly) {
preserveAttrs['body-only'] = builder.literal(bodyOnly);
@ -31,13 +30,13 @@ function addPreserve(transformHelper, bodyOnly, condition) {
preserveAttrs['if'] = condition;
}
var widgetIdInfo = transformHelper.assignWidgetId(true /* repeated */);
var idVarNode = widgetIdInfo.idVarNode ? null : widgetIdInfo.createIdVarNode();
let widgetIdInfo = transformHelper.assignWidgetId(true /* repeated */);
let idVarNode = widgetIdInfo.idVarNode ? null : widgetIdInfo.createIdVarNode();
preserveAttrs.id = transformHelper.getIdExpression();
var preserveNode = context.createNodeForEl('w-preserve', preserveAttrs);
var idVarNodeTarget;
let preserveNode = context.createNodeForEl('w-preserve', preserveAttrs);
let idVarNodeTarget;
if (bodyOnly) {
el.moveChildrenTo(preserveNode);
@ -57,33 +56,127 @@ function addPreserve(transformHelper, bodyOnly, condition) {
return preserveNode;
}
function deprecatedWarning(preserveType, transformHelper, el) {
let attribute = preserveType.attribute;
let suffix = preserveType.suffix;
let context = transformHelper.getCompileContext();
let newAttributeName = 'no-update';
if (suffix) {
newAttributeName += suffix;
}
console.warn(`The '${attribute}' attribute is deprecated. Please use '${newAttributeName}' instead. (${el.pos ? context.getPosInfo(el.pos) : context.filename})`);
}
function preserveHandler(transformHelper, preserveType, el) {
if (preserveType.deprecated) {
deprecatedWarning(preserveType, transformHelper, el);
}
el.removeAttribute(preserveType.attribute);
addPreserve(transformHelper, false);
}
function preserveIfHandler(transformHelper, preserveType, el) {
if (preserveType.deprecated) {
deprecatedWarning(preserveType, transformHelper, el);
}
let attribute = preserveType.attribute;
let preserveIfAttr = el.getAttribute(attribute);
let preserveIfCondition = preserveIfAttr.argument;
if (!preserveIfCondition) {
transformHelper.addError(`The '${attribute}' attribute should have an argument. For example: <div ${attribute}(someCondition)>`);
return;
}
addPreserve(transformHelper, false, transformHelper.builder.expression(preserveIfCondition));
el.removeAttribute(attribute);
}
function preserveBodyHandler(transformHelper, preserveType, el) {
if (preserveType.deprecated) {
deprecatedWarning(preserveType, transformHelper, el);
}
el.removeAttribute(preserveType.attribute);
addPreserve(transformHelper, true);
}
function preserveBodyIfHandler(transformHelper, preserveType, el) {
if (preserveType.deprecated) {
deprecatedWarning(preserveType, transformHelper, el);
}
let attribute = preserveType.attribute;
let preserveBodyIfAttr = el.getAttribute(attribute);
let preserveBodyIfCondition = preserveBodyIfAttr.argument;
if (!preserveBodyIfCondition) {
transformHelper.addError(`The '${attribute}' attribute should have an argument. For example: <div ${attribute}(someCondition)>`);
return;
}
addPreserve(transformHelper, true, transformHelper.builder.expression(preserveBodyIfCondition));
el.removeAttribute('w-preserve-body-if');
}
const preserveTypes = [
// The new preserve types
{
attribute: 'no-update',
handler: preserveHandler
},
{
attribute: 'no-update-if',
handler: preserveIfHandler
},
{
attribute: 'no-update-body',
handler: preserveBodyHandler
},
{
attribute: 'no-update-body-if',
handler: preserveBodyIfHandler
},
// The deprecated preserve types
{
attribute: 'w-preserve',
handler: preserveHandler,
deprecated: true
},
{
attribute: 'w-preserve-if',
suffix: '-if',
handler: preserveIfHandler,
deprecated: true
},
{
attribute: 'w-preserve-body',
suffix: '-body',
handler: preserveBodyHandler,
deprecated: true
},
{
attribute: 'w-preserve-body-if',
suffix: '-body-if',
handler: preserveBodyIfHandler,
deprecated: true
}
];
module.exports = function handleWidgetPreserve() {
var el = this.el;
let el = this.el;
if (el.hasAttribute('w-preserve')) {
el.removeAttribute('w-preserve');
addPreserve(this, false);
} else if (el.hasAttribute('w-preserve-if')) {
let preserveIfAttr = el.getAttribute('w-preserve-if');
var preserveIfCondition = preserveIfAttr.argument;
if (!preserveIfCondition) {
this.addError('The `w-preserve-if` attribute should have an argument. For example: <div w-preserve-if(someCondition)>');
for (let i = 0; i < preserveTypes.length; i++) {
let preserveType = preserveTypes[i];
if (el.hasAttribute(preserveType.attribute)) {
preserveType.handler(this, preserveType, el);
return;
}
addPreserve(this, false, this.builder.expression(preserveIfCondition));
el.removeAttribute('w-preserve-if');
} else if (el.hasAttribute('w-preserve-body')) {
el.removeAttribute('w-preserve-body');
addPreserve(this, true);
} else if (el.hasAttribute('w-preserve-body-if')) {
let preserveBodyIfAttr = el.getAttribute('w-preserve-body-if');
var preserveBodyIfCondition = preserveBodyIfAttr.argument;
if (!preserveBodyIfCondition) {
this.addError('The `w-preserve-body-if` attribute should have an argument. For example: <div w-preserve-body-if(someCondition)>');
return;
}
addPreserve(this, true, this.builder.expression(preserveBodyIfCondition));
el.removeAttribute('w-preserve-body-if');
}
};

View File

@ -57,6 +57,10 @@ class TransformHelper {
return this.widgetIdInfo;
}
getCompileContext() {
return this.context;
}
getDefaultWidgetModule() {
var dirname = this.dirname;
if (resolveFrom(dirname, './component')) {

View File

@ -97,6 +97,7 @@
"preserve-name": true,
"autocomplete": [
{
"displayText": "w-preserve (Deprecated)",
"descriptionMoreURL": "http://markojs.com/docs/marko-widgets/#preserving-dom-nodes-during-re-render"
}
]
@ -106,6 +107,7 @@
"preserve-name": true,
"autocomplete": [
{
"displayText": "w-preserve-body (Deprecated)",
"descriptionMoreURL": "http://markojs.com/docs/marko-widgets/#preserving-dom-nodes-during-re-render"
}
]
@ -114,6 +116,7 @@
"preserve-name": true,
"autocomplete": [
{
"displayText": "w-preserve-if (Deprecated)",
"snippet": "w-preserve-if(${1:condition})",
"descriptionMoreURL": "http://markojs.com/docs/marko-widgets/#preserving-dom-nodes-during-re-render"
}
@ -123,11 +126,48 @@
"preserve-name": true,
"autocomplete": [
{
"displayText": "w-preserve-body-if (Deprecated)",
"snippet": "w-preserve-body-if(${1:condition})",
"descriptionMoreURL": "http://markojs.com/docs/marko-widgets/#preserving-dom-nodes-during-re-render"
}
]
},
"@no-update": {
"type": "flag",
"preserve-name": true,
"autocomplete": [
{
"descriptionMoreURL": "http://markojs.com/docs/marko-widgets/#preserving-dom-nodes-during-re-render"
}
]
},
"@no-update-body": {
"type": "flag",
"preserve-name": true,
"autocomplete": [
{
"descriptionMoreURL": "http://markojs.com/docs/marko-widgets/#preserving-dom-nodes-during-re-render"
}
]
},
"@no-update-if": {
"preserve-name": true,
"autocomplete": [
{
"snippet": "no-update-if(${1:condition})",
"descriptionMoreURL": "http://markojs.com/docs/marko-widgets/#preserving-dom-nodes-during-re-render"
}
]
},
"@no-update-body-if": {
"preserve-name": true,
"autocomplete": [
{
"snippet": "no-update-body-if(${1:condition})",
"descriptionMoreURL": "http://markojs.com/docs/marko-widgets/#preserving-dom-nodes-during-re-render"
}
]
},
"@w-preserve-attrs": {
"type": "string",
"preserve-name": true,
@ -168,6 +208,13 @@
"@body-only": "expression",
"autocomplete": []
},
"<no-update>": {
"renderer": "./preserve-tag.js",
"@id": "string",
"@if": "expression",
"@body-only": "expression",
"autocomplete": []
},
"<widget-types>": {
"code-generator": "./widget-types-tag.js",
"@*": "string",

View File

@ -53,7 +53,13 @@ module.exports = function transform(el, context) {
transformHelper.handleWidgetExtend();
}
if (el.hasAttribute('w-preserve') ||
if (/* New preserve attributes */
el.hasAttribute('no-update') ||
el.hasAttribute('no-update-body') ||
el.hasAttribute('no-update-if') ||
el.hasAttribute('no-update-body-if') ||
/* Old preserve attributes */
el.hasAttribute('w-preserve') ||
el.hasAttribute('w-preserve-body') ||
el.hasAttribute('w-preserve-if') ||
el.hasAttribute('w-preserve-body-if')) {