-
-
-
+
{{iter.description}}
- x
-
+ {{iter.quantity}} x {{Vn.Value.format(iter.price, '%.2d€')}}
-
+ {{Vn.Value.format(iter.quantity * iter.price, '%.2d€')}}
@@ -131,22 +112,15 @@
-
-
-
-
- #
-
-
-
-
+
{{iter.name}}
+
#{{iter.id}}
+
{{iter.quantity}}
diff --git a/forms/news/news/ui.xml b/forms/news/news/ui.xml
index 4eb06118..4994fedd 100644
--- a/forms/news/news/ui.xml
+++ b/forms/news/news/ui.xml
@@ -24,7 +24,7 @@
title="_EditNew">
+ on-click="this.onDeleteClick($iter)"/>
diff --git a/js/db/model.js b/js/db/model.js
index 942b9fec..449fc7a1 100644
--- a/js/db/model.js
+++ b/js/db/model.js
@@ -844,6 +844,7 @@ Model.implement({
if (!(op.type & Operation.DELETE
&& op.type & Operation.INSERT))
+ // eslint-disable-next-line no-unused-vars
isOperation = true;
if (op.type & Operation.DELETE) {
diff --git a/js/hedera/form.js b/js/hedera/form.js
index 63f273e4..a7092fb6 100644
--- a/js/hedera/form.js
+++ b/js/hedera/form.js
@@ -20,11 +20,11 @@ module.exports = new Class({
const hash = this.hash;
const builder = new Vn.Builder();
- builder.compileFile('forms/'+ this.formInfo.path +'/ui.xml');
+ builder.compileFile(`forms/${this.formInfo.path}/ui.xml`);
const scope = this.builder = builder.load(null, this);
this.$ = scope.$;
- scope.link(null, {conn, hash});
+ scope.link({conn, hash});
this.node = scope.$.form;
const paramsLot = this.$.params;
diff --git a/js/hedera/report.js b/js/hedera/report.js
index 06c53530..c0221059 100644
--- a/js/hedera/report.js
+++ b/js/hedera/report.js
@@ -69,7 +69,7 @@ module.exports = new Class({
builder.compileFile('reports/'+ this.info.path +'/ui.xml');
var scope = this.scope = builder.load(this.doc, this);
- scope.link(null, {
+ scope.link({
lot: this.lot,
conn: this.conn
});
diff --git a/js/htk/columns/button/style.scss b/js/htk/columns/button/style.scss
index d56f10d6..e9e60890 100644
--- a/js/htk/columns/button/style.scss
+++ b/js/htk/columns/button/style.scss
@@ -12,6 +12,8 @@ td.cell-button {
width: 44px;
margin: 0 auto;
border: none;
+ border-radius: 50%;
+ padding: 10px;
background-color: transparent;
box-sizing: border-box;
diff --git a/js/htk/repeater/index.js b/js/htk/repeater/index.js
index 53b3f3b6..b7ce3f23 100644
--- a/js/htk/repeater/index.js
+++ b/js/htk/repeater/index.js
@@ -5,13 +5,11 @@ module.exports = new Class({
Extends: Component
,Tag: 'htk-repeater'
,Child: 'model'
- ,Properties:
- {
+ ,Properties: {
/**
* The source data model.
*/
- model:
- {
+ model: {
type: Db.Model
,set: function(x) {
this.link({_model: x}, {
@@ -30,8 +28,7 @@ module.exports = new Class({
/**
* The identifier for internal iterator.
*/
- ,formId:
- {
+ ,formId: {
type: String
,set: function(x) {
this._formId = x;
@@ -44,8 +41,7 @@ module.exports = new Class({
* {Function (Vn.BuilderResult, Db.Form)} Function to call after every
* box rendering.
*/
- ,renderer:
- {
+ ,renderer: {
type: Function
,set: function(x) {
this._renderer = x;
@@ -57,8 +53,7 @@ module.exports = new Class({
/**
* Wether to show the model status.
*/
- ,showStatus:
- {
+ ,showStatus: {
type: Boolean
,set: function(x) {
this._showStatus = x;
@@ -71,8 +66,7 @@ module.exports = new Class({
/**
* Message that should be displayed when source model is not ready.
*/
- ,emptyMessage:
- {
+ ,emptyMessage: {
type: String
,value: null
}
@@ -105,7 +99,7 @@ module.exports = new Class({
}
,getBuilder: function(index) {
- return this._childsData[index].builder;
+ return this._childsData[index].scope;
}
,getForm: function(index) {
@@ -119,14 +113,12 @@ module.exports = new Class({
});
var scope = this._builder.load(this.doc, null, this._parentScope);
- scope.link([set.getObject()], {
- [this._formId]: set
+ scope.link({
+ $iter: set,
+ [this._formId]: set.getObject()
});
- this._childsData.push({
- builder: scope,
- set: set
- });
+ this._childsData.push({scope, set});
if (this._renderer)
this._renderer(scope, set);
@@ -228,7 +220,7 @@ module.exports = new Class({
,_unrefChildData: function(index) {
var childData = this._childsData[index];
childData.set.unref();
- childData.builder.unref();
+ childData.scope.unref();
}
,destroy: function() {
diff --git a/js/vn/builder.js b/js/vn/builder.js
index 000d2082..ee6182b6 100644
--- a/js/vn/builder.js
+++ b/js/vn/builder.js
@@ -1,16 +1,22 @@
const VnObject = require('./object');
-const Component = require('./component');
-const VnNode = require('./node');
const Scope = require('./scope');
-const Type = require('./type');
const kebabToCamel = require('./string-util').kebabToCamel;
+const CompilerObject = require('./compiler-object');
+const CompilerElement = require('./compiler-element');
+const CompilerText = require('./compiler-text');
+
+const regCompilers = [
+ CompilerText,
+ CompilerObject,
+ CompilerElement
+];
+
/**
* Creates a object from a XML specification.
*/
module.exports = new Class({
Extends: VnObject
- ,_contexts: null
/**
* Compiles an XML file.
@@ -30,8 +36,8 @@ module.exports = new Class({
* @return {Boolean} %true on success, %false othersise
*/
,compileString: function(xmlString) {
- var parser = new DOMParser();
- var doc = parser.parseFromString(xmlString, 'text/xml');
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(xmlString, 'text/xml');
return this.compileDocument(doc);
}
@@ -41,12 +47,12 @@ module.exports = new Class({
* @param {Document} doc The DOM document
* @return {Boolean} %true on success, %false othersise
*/
- ,compileDocument: function(doc, exprArgs) {
+ ,compileDocument: function(doc) {
if (!doc)
return false;
- this._preCompile(exprArgs);
- var docElement = doc.documentElement;
+ this._preCompile();
+ const docElement = doc.documentElement;
if (docElement.tagName !== 'vn') {
this.showError('The toplevel tag should be named \'vn\'');
@@ -54,9 +60,9 @@ module.exports = new Class({
return false;
}
- var childs = docElement.childNodes;
+ const childs = docElement.childNodes;
if (childs)
- for (var i = 0; i < childs.length; i++)
+ for (let i = 0; i < childs.length; i++)
this._compile(childs[i]);
this._postCompile();
@@ -69,8 +75,8 @@ module.exports = new Class({
* @path Node The DOM node
* @return %true on success, %false othersise
*/
- ,compileNode: function(node, exprArgs) {
- this._preCompile(exprArgs);
+ ,compileNode: function(node) {
+ this._preCompile();
this._mainContext = this._compile(node).id;
this._postCompile();
return true;
@@ -79,28 +85,27 @@ module.exports = new Class({
/**
* Called before starting to compile nodes.
*/
- ,_preCompile: function(exprArgs) {
+ ,_preCompile: function() {
this._path = null;
this._tags = {};
this._contexts = [];
+ this._exprContexts = [];
this._contextMap = {};
this._links = [];
this._mainContext = null;
- this._baseExprArgs = ['_', '$'];
- if (exprArgs)
- this._baseExprArgs = this._baseExprArgs.concat(exprArgs);
-
- this._baseEventArgs = this._baseExprArgs.concat(['$event']);
-
- this._exprArgs = this._baseExprArgs.join(',');
- this._eventArgs = this._baseEventArgs.join(',');
+ this._compilers = [];
+ for (regCompiler of regCompilers)
+ this._compilers.push(new regCompiler(this));
}
/**
* Called after all nodes have been compiled.
*/
- ,_postCompile: function() {}
+ ,_postCompile: function() {
+ for (const compiler of this._compilers)
+ compiler.postCompile(this._contextMap);
+ }
/**
* Compiles a node.
@@ -115,24 +120,24 @@ module.exports = new Class({
else if (node.nodeType !== Node.TEXT_NODE
|| /^[\n\r\t]*$/.test(node.textContent))
return null;
-
- context =
- this.textCompile(node, tagName)
- || this.objectCompile(node, tagName)
- || this.elementCompile(node, tagName);
+
+ let i;
+ const compilers = this._compilers;
+ for (i = 0; i < compilers.length && context === null; i++)
+ context = compilers[i].compile(this, node, tagName);
context.id = this._contexts.length;
+ context.compiler = compilers[i - 1];
if (isElement) {
- var nodeId = node.getAttribute('id');
+ const nodeId = node.getAttribute('id');
if (nodeId) {
this._contextMap[kebabToCamel(nodeId)] = context.id;
context.nodeId = nodeId;
}
- var tags = this._tags[tagName];
-
+ let tags = this._tags[tagName];
if (!tags)
this._tags[tagName] = tags = [];
@@ -143,537 +148,21 @@ module.exports = new Class({
return context;
}
- ,getMain: function(scope) {
- return scope.objects[this._mainContext];
- }
-
- ,getByTagName: function(scope, tagName) {
- var tags = this._tags[tagName];
-
- if (tags) {
- var arr = new Array(tags.length);
-
- for (var i = 0; i < tags.length; i++)
- arr[i] = scope.objects[tags[i]];
-
- return arr;
- }
-
- return [];
- }
-
,load: function(dstDocument, thisArg, parentScope) {
- if (this._contexts === null)
- return null;
-
+ if (!this._contexts) return null;
const doc = dstDocument ? dstDocument : document;
- const contexts = this._contexts;
- const len = contexts.length;
- const objects = new Array(len);
- const scope = new Scope(this, objects, thisArg, parentScope);
-
- for (var i = 0; i < len; i++) {
- var context = contexts[i];
-
- if (context.tagName)
- objects[i] = this.elementInstantiate(doc, context, scope);
- else if (context.klass)
- objects[i] = this.objectInstantiate(doc, context, scope);
- else
- objects[i] = this.textInstantiate(doc, context, scope);
- }
-
- return scope;
- }
-
- ,link: function(scope) {
- const objects = scope.objects;
- const links = this._links;
-
- // Pre-link
-
- for (var i = links.length - 1; i >= 0; i--) {
- const link = links[i];
- const object = objects[link.context.id];
- const objectRef = scope.$[link.objectId];
-
- if (objectRef === undefined) {
- this.showError('Referenced unexistent object with id \'%s\'',
- link.objectId);
- continue;
- }
-
- if (link.prop)
- object[link.prop] = objectRef;
- else
- object.appendChild(objectRef);
- }
-
- // Post-link
-
- const contexts = this._contexts;
- for (var i = 0; i < contexts.length; i++) {
- const context = contexts[i];
- const object = objects[i];
-
- if (context.tagName)
- this.elementLink(context, object, objects, scope);
- else if (context.klass)
- this.objectLink(context, object, objects, scope);
- }
- }
-
- ,digest(scope) {
- const contexts = this._contexts;
- const objects = scope.objects;
- const exprScope = scope.exprScope;
-
- for (let i = 0; i < contexts.length; i++) {
- const context = contexts[i];
- const object = objects[i];
-
- if (context.exprs) {
- const values = [];
- let isEmpty = false;
-
- for (expr of context.exprs) {
- let value = undefined;
- try {
- value = expr.apply(scope.thisArg, exprScope);
- if (value == null) {
- isEmpty = true;
- break;
- }
- } catch (e) {
- console.warn('Expression error:', e.message);
- continue;
- }
- values.push(value);
- }
-
- let text;
-
- if (!isEmpty) {
- let k = 0;
- text = context.text.replace(/{{\d+}}/g, function() {
- return values[k++];
- });
- } else
- text = '';
-
- object.textContent = text;
- } else {
- const dynProps = context.dynProps;
-
- for (const prop in dynProps) {
- let value = undefined;
- try {
- value = dynProps[prop].apply(scope.thisArg, exprScope);
- } catch (e) {
- console.warn('Expression error:', e.message);
- continue;
- }
-
- if (context.tagName)
- object.setAttribute(prop, value);
- else
- object[prop] = value;
- }
- }
- }
+ const objects = new Array(this._contexts.length);
+ const exprValues = new Array(this._exprContexts.length);
+ return new Scope(this, doc, objects, exprValues, thisArg, parentScope);
}
,showError: function(error) {
- var path = this._path ? this._path : 'Node';
- var logArgs = ['Vn.Builder: %s: '+ error, path];
+ const path = this._path ? this._path : 'Node';
+ const logArgs = ['Vn.Builder: %s: '+ error, path];
- for (var i = 1; i < arguments.length; i++)
+ for (let i = 1; i < arguments.length; i++)
logArgs.push(arguments[i]);
console.warn.apply(null, logArgs);
}
-
- ,_addLink: function(context, prop, objectId) {
- this._links.push({
- context
- ,prop
- ,objectId: kebabToCamel(objectId)
- });
- }
-
- ,fnExpr(expr) {
- return new Function(this._exprArgs,
- '"use strict"; return ' + expr + ';'
- );
- }
-
- ,matchExpr(value) {
- const match = /^{{(.*)}}$/.exec(value);
- if (!match) return null;
- return this.fnExpr(match[1]);
- }
-
- ,_translateValue: function(value) {
- var chr = value.charAt(0);
-
- if (chr === '_')
- return _(value.substr(1));
- else if (chr === '\\' && value.charAt(1) === '_')
- return value.substr(1);
-
- return value;
- }
-
- ,_getMethod: function(value) {
- let method;
-
- if (this.isIdentifier(value)) {
- // XXX: Compatibility with old events
- method = value;
- } else {
- try {
- method = new Function(this._eventArgs,
- '"use strict"; return ' + value + ';'
- );
- } catch (err) {
- this.showError(`Method: ${err.message}: ${value}`);
- }
- }
-
- return method;
- }
-
- ,_isEvent: function(attribute) {
- return /^on-\w+/.test(attribute);
- }
-
- ,isIdentifier: function(value) {
- return /^[a-zA-Z_$][\w$]*$/.test(value);
- }
-
- ,_replaceFunc: function(token) {
- return token.charAt(1).toUpperCase();
- }
-
- //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TextNode
-
- /**
- * Creates a text node context.
- */
- ,textCompile: function(node, tagName) {
- if (!tagName) {
- let text = node.textContent;
-
- if (/{{.*}}/.test(text)) {
- let i = 0;
- const self = this;
- const exprs = [];
- text = text.replace(/{{((?:(?!}}).)*)}}/g, function(match, capture) {
- exprs.push(self.fnExpr(capture));
- return `{{${i++}}}`;
- });
-
- return {text, exprs};
- } else
- return {text};
- } else if (tagName === 't')
- return {text: _(node.firstChild.textContent)};
- else
- return null;
- }
-
- ,textInstantiate: function(doc, context) {
- return doc.createTextNode(context.exprs ? '' : context.text);
- }
-
- //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Vn.Object
-
- /**
- * Creates a object context.
- */
- ,objectCompile: function(node, tagName) {
- var klass = vnCustomTags[tagName];
-
- if (!klass)
- return null;
-
- var props = {};
- var objectProps = {};
- var childs = [];
- var events = {};
-
- var context = {
- klass: klass,
- props: props,
- dynProps: {},
- funcProps: {},
- objectProps: objectProps,
- childs: childs,
- events: events,
- custom: null
- };
-
- var a = node.attributes;
-
- for (var i = 0; i < a.length; i++) {
- var attribute = a[i].nodeName;
- var value = a[i].nodeValue;
-
- if (this._isEvent(attribute)) {
- var handler = this._getMethod(value)
-
- if (handler)
- events[attribute.substr(3)] = handler;
- } else if (!/^(id|property)$/.test(attribute)) {
- this.propCompile(context, klass, props,
- node, attribute, value);
- }
- }
-
- var childNodes = node.childNodes;
-
- if (childNodes)
- for (var i = 0; i < childNodes.length; i++) {
- var child = childNodes[i];
- var isElement = child.nodeType === Node.ELEMENT_NODE;
- var childTagName = isElement ? child.tagName.toLowerCase() : null;
- var childContext;
-
- if (childTagName === 'pointer') {
- this._addLink(context, null, child.getAttribute('object'));
- } else if (childTagName === 'custom') {
- context.custom = child;
- } else if (childContext = this._compile(child)) {
- var prop = isElement ? child.getAttribute('property') : null;
-
- if (prop) {
- prop = prop.replace(/-./g, this._replaceFunc);
- objectProps[prop] = childContext.id;
- } else
- childs.push(childContext.id);
- }
- }
-
- return context;
- }
-
- ,propCompile: function(context, klass, props, node, attribute, value) {
- let isLink = false;
- let propError = false;
- let newValue = null;
- const propName = attribute.replace(/-./g, this._replaceFunc);
- const propInfo = klass.Properties[propName];
-
- if (!propInfo) {
- this.showError('Attribute \'%s\' not valid for tag \'%s\'',
- attribute, node.tagName);
- return;
- }
- if (!value) {
- this.showError('Attribute \'%s\' empty on tag \'%s\'',
- attribute, node.tagName);
- return;
- }
-
- const expr = this.matchExpr(value);
-
- if (expr) {
- context.dynProps[propName] = expr;
- } else {
- switch (propInfo.type) {
- case Boolean:
- newValue = (/^(true|1)$/i).test(value);
- break;
- case Number:
- newValue = 0 + new Number(value);
- break;
- case String:
- newValue = this._translateValue(value);
- break;
- case Function:
- context.funcProps[propName] = this._getMethod(value);
- break;
- case Type:
- newValue = window[value];
- break;
- default:
- if (propInfo.enumType)
- newValue = propInfo.enumType[value];
- else if (propInfo.type instanceof Function)
- isLink = true;
- else
- propError = true;
- }
-
- if (isLink)
- this._addLink(context, propName, value);
- else if (newValue !== null && newValue !== undefined)
- props[propName] = newValue;
- else if (propError)
- this.showError('Attribute \'%s\' invalid for tag \'%s\'',
- attribute, node.tagName);
- }
- }
-
- ,objectInstantiate: function(doc, context, scope) {
- const object = new context.klass();
- object.setProperties(context.props);
-
- if (context.nodeId && object instanceof Component) {
- var id = context.nodeId;
- object.htmlId = scope.getHtmlId(id);
- object.className = '_'+ id +' '+ (object.className || '');
- }
-
- return object;
- }
-
- ,objectLink: function(context, object, objects, scope) {
- const objectProps = context.objectProps;
- for (const prop in objectProps)
- object[prop] = objects[objectProps[prop]];
-
- const childs = context.childs;
- for (let i = 0; i < childs.length; i++)
- object.appendChild(objects[childs[i]]);
-
- const funcProps = context.funcProps;
- for (const prop in funcProps) {
- let method;
- const handler = funcProps[prop];
-
- if (typeof handler === 'string') {
- // XXX: Compatibility with old expressions
- method = scope.thisArg[handler];
- if (!method)
- this.showError(`Function '${handler}' not found`);
- method = method.bind(scope.thisArg);
- } else {
- method = function() {
- handler.apply(scope.thisArg, scope.exprScope);
- };
- }
-
- if (method)
- object[prop] = method;
- }
-
- const events = context.events;
- for (const event in events) {
- let listener;
- const handler = events[event];
-
- if (typeof handler === 'string') {
- // XXX: Compatibility with old expressions
- listener = scope.thisArg[handler];
- if (!listener)
- this.showError(`Function '${handler}' not found`);
- } else {
- listener = function() {
- handler.apply(scope.thisArg, scope.exprScope.concat(arguments));
- };
- }
-
- if (listener)
- object.on(event, listener, scope.thisArg);
- }
-
- if (context.custom)
- object.loadXml(scope, context.custom);
- }
-
- //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Element
-
- /**
- * Creates a HTML node context.
- */
- ,elementCompile: function(node, tagName) {
- var attributes = {};
- var dynProps = {};
- var childs = [];
- var events = {};
- var handler;
-
- var a = node.attributes;
-
- for (var i = 0; i < a.length; i++) {
- var attribute = a[i].nodeName;
- var value = a[i].nodeValue;
-
- if (this._isEvent(attribute)) {
- var handler = this._getMethod(value);
- if (handler) events[attribute.substr(3)] = handler;
- } else if (attribute !== 'id') {
- const expr = this.matchExpr(value);
- if (expr)
- dynProps[attribute] = expr;
- else
- attributes[attribute] = this._translateValue(value);
- }
- }
-
- var childContext;
- var childNodes = node.childNodes;
-
- if (childNodes)
- for (var i = 0; i < childNodes.length; i++)
- if (childContext = this._compile(childNodes[i]))
- childs.push(childContext.id);
-
- return {
- tagName,
- attributes,
- dynProps,
- childs,
- events
- };
- }
-
- ,elementInstantiate: function(doc, context, scope) {
- var object = doc.createElement(context.tagName);
-
- const attributes = context.attributes;
- for (const attribute in attributes)
- object.setAttribute(attribute, attributes[attribute]);
-
- if (context.nodeId) {
- const id = context.nodeId;
- object.setAttribute('id', scope.getHtmlId(id));
- VnNode.addClass(object, '_'+ id);
- }
-
- return object;
- }
-
- ,elementLink: function(context, object, objects, scope) {
- const childs = context.childs;
- for (var i = 0; i < childs.length; i++) {
- let child = objects[childs[i]];
-
- if (child instanceof Component)
- child = child.node;
- if (child instanceof Node)
- object.appendChild(child);
- }
-
- const events = context.events;
- for (const event in events) {
- let listener;
- const handler = events[event];
- if (typeof handler === 'string') {
- // XXX: Compatibility with old expressions
- listener = scope.thisArg[handler];
- if (!listener)
- this.showError(`Function '${handler}' not found`);
- listener = listener.bind(scope.thisArg);
- } else {
- listener = function(e) {
- handler.apply(scope.thisArg, scope.exprScope.concat(e));
- };
- }
-
- if (listener)
- object.addEventListener(event, listener);
- }
- }
});
diff --git a/js/vn/compiler-element.js b/js/vn/compiler-element.js
new file mode 100644
index 00000000..61441f9c
--- /dev/null
+++ b/js/vn/compiler-element.js
@@ -0,0 +1,85 @@
+const Compiler = require('./compiler');
+const Component = require('./component');
+const VnNode = require('./node');
+
+/**
+ * Compiles a @HTMLElement from element tag.
+ */
+ module.exports = new Class({
+ Extends: Compiler
+
+ ,compile: function(builder, node, tagName) {
+ const context = {
+ tagName,
+ attributes: {},
+ childs: [],
+ events: {}
+ };
+ const {attributes} = context;
+
+ const a = node.attributes;
+ for (let i = 0; i < a.length; i++) {
+ const attribute = a[i].nodeName;
+ const value = a[i].nodeValue;
+
+ if (this.isEvent(attribute)) {
+ const handler = this._getMethod(value);
+ if (handler) context.events[attribute.substr(3)] = handler;
+ } else if (!/^(id|property)$/.test(attribute)) {
+ if (this.isExpr(value))
+ this.compileExpr(context, attribute, value);
+ else
+ attributes[attribute] = this._translateValue(value);
+ }
+ }
+
+ let childContext;
+ const childNodes = node.childNodes;
+
+ if (childNodes)
+ for (let i = 0; i < childNodes.length; i++)
+ if (childContext = builder._compile(childNodes[i]))
+ context.childs.push(childContext.id);
+
+ return context;
+ }
+
+ ,instantiate: function(doc, context, scope) {
+ const object = doc.createElement(context.tagName);
+
+ const attributes = context.attributes;
+ for (const attribute in attributes)
+ object.setAttribute(attribute, attributes[attribute]);
+
+ if (context.nodeId) {
+ const id = context.nodeId;
+ object.setAttribute('id', scope.getHtmlId(id));
+ VnNode.addClass(object, '_'+ id);
+ }
+
+ return object;
+ }
+
+ ,setProperty(object, property, value) {
+ object.setAttribute(property, value);
+ }
+
+ ,link: function(context, object, objects, scope) {
+ const childs = context.childs;
+ for (let i = 0; i < childs.length; i++) {
+ let child = objects[childs[i]];
+
+ if (child instanceof Component)
+ child = child.node;
+ if (child instanceof Node)
+ object.appendChild(child);
+ }
+
+ const events = context.events;
+ for (const event in events) {
+ const listener = this.bindMethod(events[event], scope, true);
+ if (listener)
+ object.addEventListener(event, listener);
+ }
+ }
+});
diff --git a/js/vn/compiler-object.js b/js/vn/compiler-object.js
new file mode 100644
index 00000000..e623f838
--- /dev/null
+++ b/js/vn/compiler-object.js
@@ -0,0 +1,216 @@
+const Compiler = require('./compiler');
+const Component = require('./component');
+const Type = require('./type');
+const kebabToCamel = require('./string-util').kebabToCamel;
+
+/**
+ * Compiles a @Vn.Object from element tag.
+ */
+module.exports = new Class({
+ Extends: Compiler
+
+ ,_links: []
+
+ /**
+ * Creates a object context.
+ */
+ ,compile: function(builder, node, tagName) {
+ const klass = vnCustomTags[tagName];
+ if (!klass) return null;
+
+ const context = {
+ klass,
+ props: {},
+ funcProps: {},
+ objectProps: {},
+ childs: [],
+ events: {},
+ custom: null
+ };
+
+ const a = node.attributes;
+ for (let i = 0; i < a.length; i++) {
+ const attribute = a[i].nodeName;
+ const value = a[i].nodeValue;
+
+ if (this.isEvent(attribute)) {
+ const handler = this._getMethod(value)
+ if (handler) context.events[attribute.substr(3)] = handler;
+ } else if (!/^(id|property)$/.test(attribute)) {
+ this.propCompile(context, node, attribute, value);
+ }
+ }
+
+ const childNodes = node.childNodes;
+
+ if (childNodes)
+ for (let i = 0; i < childNodes.length; i++) {
+ const child = childNodes[i];
+ const isElement = child.nodeType === Node.ELEMENT_NODE;
+ const childTagName = isElement ? child.tagName.toLowerCase() : null;
+ let childContext;
+
+ if (childTagName === 'pointer') {
+ this._addLink(context, null, child.getAttribute('object'));
+ } else if (childTagName === 'custom') {
+ context.custom = child;
+ } else if (childContext = builder._compile(child)) {
+ let prop = isElement ? child.getAttribute('property') : null;
+
+ if (prop) {
+ prop = kebabToCamel(prop);
+ context.objectProps[prop] = childContext.id;
+ } else
+ context.childs.push(childContext.id);
+ }
+ }
+
+ return context;
+ }
+
+ ,propCompile: function(context, node, attribute, value) {
+ const tagName = node.tagName;
+ const propName = kebabToCamel(attribute);
+ const propInfo = context.klass.Properties[propName];
+
+ if (!value) {
+ this.showError('Attribute \'%s\' empty on tag \'%s\'',
+ attribute, tagName);
+ return;
+ }
+
+ if (propName == 'vModel') {
+ context.vModel = this.modelExpr(value);
+ return;
+ }
+
+ if (!propInfo) {
+ this.showError('Attribute \'%s\' not valid for tag \'%s\'',
+ attribute, tagName);
+ return;
+ }
+
+ if (this.isExpr(value)) {
+ this.compileExpr(context, propName, value);
+ } else {
+ let isLink = false;
+ let propError = false;
+ let newValue = null;
+
+ switch (propInfo.type) {
+ case Boolean:
+ newValue = (/^(true|1)$/i).test(value);
+ break;
+ case Number:
+ newValue = 0 + new Number(value);
+ break;
+ case String:
+ newValue = this._translateValue(value);
+ break;
+ case Function:
+ context.funcProps[propName] = this._getMethod(value);
+ break;
+ case Type:
+ newValue = window[value];
+ break;
+ default:
+ if (propInfo.enumType)
+ newValue = propInfo.enumType[value];
+ else if (propInfo.type instanceof Function)
+ isLink = true;
+ else
+ propError = true;
+ }
+
+ if (isLink)
+ this._addLink(context, propName, value);
+ else if (newValue !== null && newValue !== undefined)
+ context.props[propName] = newValue;
+ else if (propError)
+ this.showError('Attribute \'%s\' invalid for tag \'%s\'',
+ attribute, tagName);
+ }
+ }
+
+ ,instantiate: function(doc, context, scope) {
+ const object = new context.klass();
+ object.setProperties(context.props);
+
+ if (context.nodeId && object instanceof Component) {
+ const id = context.nodeId;
+ object.htmlId = scope.getHtmlId(id);
+ object.className = '_'+ id +' '+ (object.className || '');
+ }
+
+ return object;
+ }
+
+ ,setProperty(object, property, value) {
+ object[property] = value;
+ }
+
+ ,preLink(scope) {
+ const objects = scope.objects;
+ const links = this._links;
+
+ for (let i = links.length - 1; i >= 0; i--) {
+ const link = links[i];
+ const object = objects[link.context.id];
+ const objectRef = scope.$[link.objectId];
+
+ if (objectRef === undefined) {
+ this.showError('Referenced unexistent object with id \'%s\'',
+ link.objectId);
+ continue;
+ }
+
+ if (link.prop)
+ object[link.prop] = objectRef;
+ else
+ object.appendChild(objectRef);
+ }
+ }
+
+ ,link: function(context, object, objects, scope) {
+ const objectProps = context.objectProps;
+ for (const prop in objectProps)
+ object[prop] = objects[objectProps[prop]];
+
+ const childs = context.childs;
+ for (let i = 0; i < childs.length; i++)
+ object.appendChild(objects[childs[i]]);
+
+ const funcProps = context.funcProps;
+ for (const prop in funcProps)
+ object[prop] = this.bindMethod(funcProps[prop], scope);
+
+ const events = context.events;
+ for (const event in events) {
+ const listener = this.bindMethod(events[event], scope);
+ if (listener)
+ object.on(event, listener, scope.thisArg);
+ }
+
+ if (context.vModel) {
+ object.on('change', function(lot) {
+ context.vModel.call(scope.thisArg, scope.$, lot.$);
+ scope.digest();
+ }, scope);
+ }
+
+ if (context.custom)
+ object.loadXml(scope, context.custom);
+ }
+
+ ,_addLink: function(context, prop, objectId) {
+ this._links.push({
+ context
+ ,prop
+ ,objectId: kebabToCamel(objectId)
+ });
+ }
+
+ ,_replaceFunc: function(token) {
+ return token.charAt(1).toUpperCase();
+ }
+});
\ No newline at end of file
diff --git a/js/vn/compiler-text.js b/js/vn/compiler-text.js
new file mode 100644
index 00000000..6b21a697
--- /dev/null
+++ b/js/vn/compiler-text.js
@@ -0,0 +1,33 @@
+const Compiler = require('./compiler');
+
+/**
+ * Compiles a @Text from text node.
+ */
+module.exports = new Class({
+ Extends: Compiler
+
+ ,compile: function(builder, node, tagName) {
+ if (tagName && tagName != 't')
+ return null;
+
+ const text = node.textContent;
+ const context = {text};
+
+ if (tagName === 't') {
+ context.text = _(node.firstChild.textContent);
+ } else if (this.isExpr(text, true)) {
+ context.text = '';
+ this.compileExpr(context, null, text, true);
+ }
+
+ return context;
+ }
+
+ ,instantiate: function(doc, context) {
+ return doc.createTextNode(context.text);
+ }
+
+ ,setProperty(object, property, value) {
+ object.textContent = value;
+ }
+});
diff --git a/js/vn/compiler.js b/js/vn/compiler.js
new file mode 100644
index 00000000..b505d3b0
--- /dev/null
+++ b/js/vn/compiler.js
@@ -0,0 +1,150 @@
+
+var VnObject = require('./object');
+
+/**
+ * Base class for compilers.
+ */
+module.exports = new Class({
+ Extends: VnObject
+
+ ,compile: function() {}
+ ,postCompile: function() {}
+ ,instantiate: function() {}
+ ,preLink: function() {}
+ ,link: function() {}
+ ,connect: function() {}
+ ,postLink: function() {}
+ ,setProperty: function() {}
+ ,free: function() {}
+
+ ,initialize: function(builder) {
+ this._builder = builder;
+ this._interpoler = builder._interpoler;
+ this.parent();
+ }
+
+ /**
+ * Checks if the passed attribute name it's an event.
+ *
+ * @param {String} attribute The attribute name
+ * @return {Boolean} %true if it's an event, otherwise %false
+ */
+ ,isEvent: function(attribute) {
+ return /^on-\w+/.test(attribute);
+ }
+
+ ,isIdentifier: function(value) {
+ return /^[a-zA-Z_$][\w$]*$/.test(value);
+ }
+
+ /**
+ * Logs an error parsing the node.
+ *
+ * @param {String} error The error message template
+ * @param {...} varArgs The message template arguments
+ */
+ ,showError: function() {
+ this._builder.showError.apply(this._builder, arguments);
+ }
+
+ ,_getMethod: function(value) {
+ // XXX: Compatibility with old methods
+ return this.isIdentifier(value)
+ ? value
+ : this.fnExpr(value);
+ }
+
+ ,bindMethod(handler, scope, isEvent) {
+ // XXX: Compatibility with old methods
+ if (typeof handler === 'string') {
+ const method = scope.thisArg[handler];
+ if (!method) {
+ this.showError(`Function '${handler}' not found`);
+ return undefined;
+ }
+
+ return method.bind(scope.thisArg);
+ }
+
+ return function($event) {
+ let handlerScope;
+ if (isEvent) {
+ handlerScope = Object.create(scope.$);
+ Object.assign(handlerScope, {$event});
+ } else
+ handlerScope = scope.$;
+
+ handler.call(this, handlerScope);
+ }.bind(scope.thisArg);
+ }
+
+ ,matchExpr(value) {
+ const match = /^{{(.*)}}$/.exec(value);
+ if (!match) return null;
+ return this.fnExpr(match[1]);
+ }
+
+ ,modelExpr(expr) {
+ try {
+ return new Function('$scope', '$value',
+ `"use strict"; $scope.${expr} = $value;`
+ );
+ } catch (err) {
+ this.showError(`${err.message}:`, expr);
+ }
+ }
+
+ ,exprRegex: /^{{((?:(?!}}).)*)}}$/
+ ,exprRegexMulti: /{{((?:(?!}}).)*)}}/g
+
+ ,isExpr(expr, isMulti) {
+ return isMulti
+ ? this.exprRegexMulti.test(expr)
+ : this.exprRegex.test(expr);
+ }
+
+ ,compileExpr(context, property, value, isMulti) {
+ const exprContext = {
+ context,
+ property,
+ value
+ };
+
+ if (isMulti) {
+ let i = 0;
+ const self = this;
+ exprContext.exprs = [];
+ exprContext.template = value.replace(this.exprRegexMulti,
+ function(match, capture) {
+ exprContext.exprs.push(self.fnExpr(capture));
+ return `{{${i++}}}`;
+ });
+ } else {
+ const match = this.exprRegex.exec(value);
+ exprContext.expr = this.fnExpr(match[1]);
+ }
+
+ this._builder._exprContexts.push(exprContext);
+ }
+
+ ,fnExpr(expr) {
+ try {
+ return new Function('$scope',
+ `with($scope) { return ${expr}; }`
+ );
+ } catch (err) {
+ this.showError(`${err.message}:`, expr);
+ }
+ }
+
+ ,_translateValue: function(value) {
+ var chr = value.charAt(0);
+
+ if (chr === '_')
+ return _(value.substr(1));
+ else if (chr === '\\' && value.charAt(1) === '_')
+ return value.substr(1);
+
+ return value;
+ }
+});
diff --git a/js/vn/lot.js b/js/vn/lot.js
index bd6bbe09..490b22fd 100644
--- a/js/vn/lot.js
+++ b/js/vn/lot.js
@@ -29,7 +29,11 @@ module.exports = new Class({
}
,initialize: function(props) {
- this._params = {};
+ this._params = new Proxy({}, {
+ set(obj, prop, value) {
+ return Reflect.set(obj, prop, value);
+ }
+ });
VnObject.prototype.initialize.call(this, props);
}
diff --git a/js/vn/scope.js b/js/vn/scope.js
index 65a2656c..f4043180 100644
--- a/js/vn/scope.js
+++ b/js/vn/scope.js
@@ -6,21 +6,30 @@ let scopeUid = 0;
module.exports = new Class({
Extends: VnObject
- ,initialize: function(builder, objects, thisArg, parent) {
+ ,initialize: function(builder, doc, objects, exprValues, thisArg, parent) {
this.builder = builder;
this.objects = objects;
+ this.exprValues = exprValues;
this.thisArg = thisArg;
this.parent = parent;
this.uid = ++scopeUid;
this.$ = parent ? Object.create(parent.$) : {};
if (parent) {
- parent.on('lot-change', this.onLotChange, this);
+ parent.ref();
+ // XXX: Keep commented until optimized
+ //parent.on('change', this.onChange, this);
if (!thisArg) this.thisArg = parent.thisArg;
}
+
+ const contexts = builder._contexts;
+ for (let i = 0; i < contexts.length; i++) {
+ const context = contexts[i];
+ objects[i] = context.compiler.instantiate(doc, context, this);
+ }
}
- ,link: function(exprScope, extraObjects) {
+ ,link: function(extraObjects) {
var contextMap = this.builder._contextMap;
for (var id in extraObjects)
@@ -28,26 +37,88 @@ module.exports = new Class({
for (var id in contextMap)
this.$[id] = this.objects[contextMap[id]];
- this.exprScope = [
- _,
- this.$
- ].concat(exprScope);
+ const builder = this.builder;
+ const contexts = builder._contexts;
+ const objects = this.objects;
- this.builder.link(this);
- this.builder.digest(this);
+ for (const compiler of builder._compilers)
+ compiler.preLink(this);
+
+ for (let i = 0; i < contexts.length; i++) {
+ const context = contexts[i];
+ context.compiler.link(context, objects[i], objects, this);
+ }
+
+ for (let i = 0; i < contexts.length; i++) {
+ const context = contexts[i];
+ context.compiler.connect(context, objects[i], objects, this);
+ }
+
+ for (const compiler of builder._compilers)
+ compiler.postLink(this);
+
+ this.digest();
for (const object of this.objects)
if (object.assignLot)
- object.on('change', this.onLotChange, this);
+ object.on('change', this.onChange, this);
}
- ,onLotChange() {
- this.emit('lot-change');
- this.builder.digest(this);
+ ,digest() {
+ const exprContexts = this.builder._exprContexts;
+ const exprValues = this.exprValues;
+ const objects = this.objects;
+
+ for (let i = 0; i < exprContexts.length; i++) {
+ const exprContext = exprContexts[i];
+ let newValue;
+
+ if (exprContext.template) {
+ const values = [];
+ let isEmpty = false;
+
+ for (expr of exprContext.exprs) {
+ const value = this.execExpr(expr);
+ if (value == null) {
+ isEmpty = true;
+ break;
+ }
+ values.push(value);
+ }
+
+ if (!isEmpty) {
+ let k = 0;
+ newValue = exprContext.template.replace(/{{\d+}}/g, function() {
+ return values[k++];
+ });
+ } else
+ newValue = '';
+ } else
+ newValue = this.execExpr(exprContext.expr);
+
+ if (newValue !== exprValues[i]) {
+ const context = exprContext.context;
+ context.compiler.setProperty(objects[context.id],
+ exprContext.property, newValue);
+ exprValues[i] = newValue;
+ }
+ }
+ }
+
+ ,execExpr(expr) {
+ try {
+ return expr.call(this.thisArg, this.$);
+ // eslint-disable-next-line no-empty
+ } catch (e) {}
+ }
+
+ ,onChange() {
+ this.emit('change');
+ this.digest();
}
,getMain: function() {
- return this.builder.getMain(this);
+ return this.objects[this.builder._mainContext];
}
,getById: function(objectId) {
@@ -56,7 +127,18 @@ module.exports = new Class({
}
,getByTagName: function(tagName) {
- return this.builder.getByTagName(this, tagName);
+ const tags = this.builder._tags[tagName];
+
+ if (tags) {
+ const arr = new Array(tags.length);
+
+ for (let i = 0; i < tags.length; i++)
+ arr[i] = this.objects[tags[i]];
+
+ return arr;
+ }
+
+ return [];
}
,getHtmlId: function(nodeId) {
diff --git a/package.json b/package.json
index 20973eb9..e24f4e03 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "hedera-web",
- "version": "1.407.79",
+ "version": "1.407.80",
"description": "Verdnatura web page",
"license": "GPL-3.0",
"repository": {
diff --git a/web/html-service.php b/web/html-service.php
index 5eb3cc6e..cef4c6ec 100644
--- a/web/html-service.php
+++ b/web/html-service.php
@@ -66,7 +66,7 @@ class HtmlService extends Service {
// Setting the version
- setcookie('vnVersion', $this->getVersion());
+ setcookie('vnVersion', $this->getVersion(), ['samesite' => 'Lax']);
// Loading the requested page
diff --git a/web/service.php b/web/service.php
index 5ed93443..b0a45be5 100644
--- a/web/service.php
+++ b/web/service.php
@@ -124,7 +124,10 @@ abstract class Service {
);
if (isset($row['access'])) {
- setcookie('vnVisit', $row['visit'], time() + 31536000); // 1 Year
+ setcookie('vnVisit', $row['visit'], [
+ 'expires' => time() + 31536000, // 1 Year
+ 'samesite' => 'Lax'
+ ]);
$_SESSION['access'] = $row['access'];
} else
$_SESSION['skipVisit'] = TRUE;