Builder refactor, various fixes
gitea/hedera-web/pipeline/head This commit looks good Details

This commit is contained in:
Juan Ferrer 2022-06-18 23:04:34 +02:00
parent d1819118d8
commit fd6f39371a
31 changed files with 745 additions and 763 deletions

2
debian/changelog vendored
View File

@ -1,4 +1,4 @@
hedera-web (1.407.79) stable; urgency=low hedera-web (1.407.80) stable; urgency=low
* Initial Release. * Initial Release.

View File

@ -60,7 +60,7 @@
<htk-button <htk-button
icon="delete" icon="delete"
tip="_RemoveAddress" tip="_RemoveAddress"
on-click="this.onRemoveAddressClick($.address)"/> on-click="this.onRemoveAddressClick($iter)"/>
<htk-button <htk-button
icon="edit" icon="edit"
tip="_EditAddress" tip="_EditAddress"

View File

@ -30,7 +30,7 @@
<htk-bar-button <htk-bar-button
icon="check" icon="check"
tip="_Accept" tip="_Accept"
on-click="$.iter.performOperations()"/> on-click="iter.performOperations()"/>
</div> </div>
<div id="form" class="address"> <div id="form" class="address">
<div class="form box vn-w-sm vn-pa-lg"> <div class="form box vn-w-sm vn-pa-lg">

View File

@ -1,22 +1,18 @@
<vn> <vn>
<vn-group> <vn-group>
<db-form id="password-form"> <db-form v-model="passwordForm">
<db-model property="model"> <db-model property="model">
<custom>
SELECT length, nAlpha, nUpper, nDigits, nPunct SELECT length, nAlpha, nUpper, nDigits, nPunct
FROM account.userPassword FROM account.userPassword
</custom>
</db-model> </db-model>
</db-form> </db-form>
<db-form id="user-form"> <db-form id="user-form">
<db-model property="model" id="user-model" updatable="true"> <db-model property="model" id="user-model" updatable="true">
<custom>
SELECT u.id, u.name, u.email, u.nickname, SELECT u.id, u.name, u.email, u.nickname,
u.lang, c.isToBeMailed, c.id clientFk u.lang, c.isToBeMailed, c.id clientFk
FROM account.myUser u FROM account.myUser u
LEFT JOIN myClient c LEFT JOIN myClient c
ON u.id = c.id ON u.id = c.id
</custom>
</db-model> </db-model>
</db-form> </db-form>
</vn-group> </vn-group>
@ -27,7 +23,7 @@
<htk-bar-button <htk-bar-button
icon="place" icon="place"
tip="_Addresses" tip="_Addresses"
on-click="$.hash.setAll({form: 'account/address-list'})"/> on-click="hash.setAll({form: 'account/address-list'})"/>
<htk-bar-button <htk-bar-button
icon="lock_reset" icon="lock_reset"
tip="_Change password" tip="_Change password"
@ -119,24 +115,19 @@
</h5> </h5>
<ul> <ul>
<li> <li>
<htk-text form="password-form" column="length"/> {{passwordForm.length}} <t>characters long</t>
<t>characters long</t>
</li> </li>
<li> <li>
<htk-text form="password-form" column="nAlpha"/> {{passwordForm.nAlpha}} <t>alphabetic characters</t>
<t>alphabetic characters</t>
</li> </li>
<li> <li>
<htk-text form="password-form" column="nUpper"/> {{passwordForm.nUpper}} <t>capital letters</t>
<t>capital letters</t>
</li> </li>
<li> <li>
<htk-text form="password-form" column="nDigits"/> {{passwordForm.nDigits}} <t>digits</t>
<t>digits</t>
</li> </li>
<li> <li>
<htk-text form="password-form" column="nPunct"/> {{passwordForm.nPunct}} <t>symbols</t>
<t>symbols</t>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -1,6 +1,6 @@
<vn> <vn>
<vn-group> <vn-group>
<db-form id="user"> <db-form v-model="user">
<db-model property="model" lot="hash"> <db-model property="model" lot="hash">
SELECT u.id, u.name user, u.nickname, u.email, c.phone, r.name role SELECT u.id, u.name user, u.nickname, u.email, c.phone, r.name role
FROM account.user u FROM account.user u
@ -16,11 +16,11 @@
<div id="form" class="access-log"> <div id="form" class="access-log">
<div class="box vn-w-xs vn-pa-lg"> <div class="box vn-w-xs vn-pa-lg">
<div class="form"> <div class="form">
<h4><htk-text form="user" column="nickname"/></h4> <h4>{{user.nickname}}</h4>
<p>#<htk-text form="user" column="id"/> - <htk-text form="user" column="user"/></p> <p>#{{user.id}} - {{user.user}}</p>
<p><htk-text form="user" column="role"/></p> <p>{{user.role}}</p>
<p><htk-text form="user" column="email"/></p> <p>{{user.email}}</p>
<p><htk-text form="user" column="phone"/></p> <p>{{user.phone}}</p>
</div> </div>
</div> </div>
<htk-repeater form-id="iter" class="box vn-w-xs htk-list vn-mt-md"> <htk-repeater form-id="iter" class="box vn-w-xs htk-list vn-mt-md">

View File

@ -6,7 +6,7 @@
<htk-bar-button <htk-bar-button
icon="refresh" icon="refresh"
tip="_Refresh" tip="_Refresh"
on-click="$.sessions.refresh()"/> on-click="sessions.refresh()"/>
<div class="connections-sum"> <div class="connections-sum">
<htk-text> <htk-text>
<db-calc-sum <db-calc-sum

View File

@ -26,7 +26,7 @@
<div class="item"> <div class="item">
<div class="side vn-mr-md"> <div class="side vn-mr-md">
<htk-image <htk-image
form="iter" form="$iter"
column="image" column="image"
stamp-column="updated" stamp-column="updated"
class="photo" class="photo"

View File

@ -10,7 +10,7 @@
<htk-bar-button <htk-bar-button
icon="refresh" icon="refresh"
tip="_Refresh" tip="_Refresh"
on-click="$.visits.refresh()"/> on-click="visits.refresh()"/>
<htk-bar-button <htk-bar-button
icon="visibility" icon="visibility"
tip="_Connections" tip="_Connections"

View File

@ -7,7 +7,7 @@
class="start-order" class="start-order"
icon="add_shopping_cart" icon="add_shopping_cart"
tip="_Start order" tip="_Start order"
on-click="$.hash.setAll({form: 'ecomerce/catalog'})"/> on-click="hash.setAll({form: 'ecomerce/catalog'})"/>
</div> </div>
<div id="form" class="home"> <div id="form" class="home">
<div class="column mansonry" id="news-column"> <div class="column mansonry" id="news-column">

View File

@ -44,19 +44,16 @@
class="delete" class="delete"
tip="_Remove" tip="_Remove"
icon="delete" icon="delete"
on-click="this.onDeleteClick($.iter)"/> on-click="this.onDeleteClick($iter)"/>
<htk-image <htk-image
form="iter" value="{{iter.image}}"
column="image"
stamp-column="updated" stamp-column="updated"
class="photo" class="photo"
directory="catalog" directory="catalog"
subdir="200x200" subdir="200x200"
full-dir="1600x900"/> full-dir="1600x900"/>
<div class="info"> <div class="info">
<h2> <h2>{{iter.item}}</h2>
<htk-text form="iter" column="item"/>
</h2>
<p class="tags"> <p class="tags">
{{iter.value5}} {{iter.value6}} {{iter.value7}} {{iter.value5}} {{iter.value6}} {{iter.value7}}
</p> </p>

View File

@ -169,13 +169,6 @@ Hedera.Catalog = new Class({
Hedera.Catalog.View.GRID : Hedera.Catalog.View.LIST); Hedera.Catalog.View.GRID : Hedera.Catalog.View.LIST);
} }
,onBasketReady: function(form) {
if (form.$.method != 'PICKUP')
Vn.Node.setText(this.$.method, _('Agency'));
else
Vn.Node.setText(this.$.method, _('Warehouse'));
}
,onItemsChange: function(model, status) { ,onItemsChange: function(model, status) {
if (status !== Db.Model.Status.CLEAN) if (status !== Db.Model.Status.CLEAN)
this.$.order.style.display = 'block'; this.$.order.style.display = 'block';
@ -255,7 +248,7 @@ Hedera.Catalog = new Class({
if (this.isGuest()) return; if (this.isGuest()) return;
this.onEraseClick(); this.onEraseClick();
this.$.card.row = form.row; this.$.$card.row = form.row;
this.$.cardLot.assign({item: form.$.id}); this.$.cardLot.assign({item: form.$.id});
this.$.cardPopup.show(event.currentTarget); this.$.cardPopup.show(event.currentTarget);
} }
@ -303,7 +296,7 @@ Hedera.Catalog = new Class({
if (amountSum > 0) { if (amountSum > 0) {
this.conn.execQuery(sql); this.conn.execQuery(sql);
var itemName = this.$.card.get('item'); var itemName = this.$.$card.get('item');
Htk.Toast.showMessage( Htk.Toast.showMessage(
Vn.Value.sprintf(_('Added%dOf%s'), amountSum, itemName)); Vn.Value.sprintf(_('Added%dOf%s'), amountSum, itemName));
} }
@ -318,7 +311,7 @@ Hedera.Catalog = new Class({
,onPopupClose: function() { ,onPopupClose: function() {
this.onEraseClick(); this.onEraseClick();
this.$.card.row = -1; this.$.$card.row = -1;
this.$.cardLot.value = undefined; this.$.cardLot.value = undefined;
} }

View File

@ -87,7 +87,7 @@
param="producer"/> param="producer"/>
</vn-group> </vn-group>
<vn-group> <vn-group>
<db-form id="basket" on-ready="onBasketReady"> <db-form v-model="basket">
<db-model property="model"> <db-model property="model">
SELECT b.id, b.sent, a.description agency, m.code method SELECT b.id, b.sent, a.description agency, m.code method
FROM myBasket b FROM myBasket b
@ -108,7 +108,7 @@
JOIN vn.itemType t ON t.id = i.typeFk JOIN vn.itemType t ON t.id = i.typeFk
WHERE #filter; WHERE #filter;
CALL myBasket_calcCatalogFull; CALL myBasket_calcCatalogFull;
SELECT i.id, i.description, i.longName item, i.subName, SELECT i.id, i.longName item, i.subName,
i.tag5, i.value5, i.tag6, i.value6, i.tag7, i.value7, i.tag5, i.value5, i.tag6, i.value6, i.tag7, i.value7,
i.relevancy, i.size, i.category, i.relevancy, i.size, i.category,
k.name ink, p.name producer, o.name origin, k.name ink, p.name producer, o.name origin,
@ -126,7 +126,7 @@
ORDER BY i.relevancy DESC, i.name, i.size ORDER BY i.relevancy DESC, i.name, i.size
LIMIT 5000; LIMIT 5000;
</db-model> </db-model>
<db-form id="card" model="items"/> <db-form id="$card" v-model="card" model="items"/>
<vn-lot id="card-lot"/> <vn-lot id="card-lot"/>
</vn-group> </vn-group>
<div id="form" class="catalog"> <div id="form" class="catalog">
@ -135,17 +135,17 @@
id="grid-view" id="grid-view"
empty-message="_Choose filter from right menu" empty-message="_Choose filter from right menu"
form-id="item" form-id="item"
model="items" > model="items">
<custom> <custom>
<div <div
id="item-box" id="item-box"
class="item-box clickable" class="item-box clickable"
title="{{_('AddToBasket')}}" title="{{_('AddToBasket')}}"
on-click="this.onAddItemClick($event, $.item)"> on-click="this.onAddItemClick($event, $iter)">
<htk-image <htk-image
directory="catalog" directory="catalog"
subdir="200x200" subdir="200x200"
form="item" form="$iter"
column="image" column="image"
stamp-column="updated" stamp-column="updated"
full-dir="1600x900" full-dir="1600x900"
@ -193,12 +193,10 @@
</div> </div>
<div id="right-panel" class="right-panel" on-click="onRightPanelClick"> <div id="right-panel" class="right-panel" on-click="onRightPanelClick">
<div class="basket-info"> <div class="basket-info">
<p>{{Vn.Value.format(basket.sent, '%D')}}</p>
<p> <p>
<htk-text form="basket" column="sent" format="%D"/> {{_(basket.method != 'PICKUP' ? 'Agency' : 'Warehouse')}}
</p> {{basket.agency}}
<p>
<span id="method"/>
<htk-text form="basket" column="agency"/>
</p> </p>
<button class="thin" on-click="this.onConfigureClick()"> <button class="thin" on-click="this.onConfigureClick()">
<t>Modify</t> <t>Modify</t>
@ -266,7 +264,7 @@
placeholder="_Color" placeholder="_Color"
form="params" form="params"
column="color" column="color"
on-mousedown="$.colors.lazyRefresh()"> on-mousedown="colors.lazyRefresh()">
<db-model <db-model
id="colors" id="colors"
property="model" property="model"
@ -286,7 +284,7 @@
placeholder="_Producer" placeholder="_Producer"
form="params" form="params"
column="producer" column="producer"
on-mousedown="$.producers.lazyRefresh()"> on-mousedown="producers.lazyRefresh()">
<db-model <db-model
id="producers" id="producers"
property="model" property="model"
@ -306,7 +304,7 @@
placeholder="_Origin" placeholder="_Origin"
form="params" form="params"
column="origin" column="origin"
on-mousedown="$.origins.lazyRefresh()"> on-mousedown="origins.lazyRefresh()">
<db-model <db-model
id="origins" id="origins"
property="model" property="model"
@ -327,7 +325,7 @@
placeholder="_Category" placeholder="_Category"
form="params" form="params"
column="category" column="category"
on-mousedown="$.categorys.lazyRefresh()"> on-mousedown="categorys.lazyRefresh()">
<db-model <db-model
id="categorys" id="categorys"
property="model" property="model"
@ -395,7 +393,7 @@
modal="true" modal="true"
on-closed="onPopupClose"> on-closed="onPopupClose">
<div property="child-node" class="item-card"> <div property="child-node" class="item-card">
<db-form id="card-extend"> <db-form vModel="extendedCard">
<db-model <db-model
property="model" property="model"
lot="card-lot" lot="card-lot"
@ -410,29 +408,19 @@
<htk-image <htk-image
directory="catalog" directory="catalog"
subdir="200x200" subdir="200x200"
form="card" form="$card"
column="image" column="image"
stamp-column="updated" stamp-column="updated"
full-dir="1600x900" full-dir="1600x900"
conn="conn" conn="conn"
editable="true"/> editable="true"/>
<div class="item-info"> <div class="item-info">
<h2> <h2>{{card.item}}</h2>
<htk-text form="card" column="item"/> <p class="sub-name">{{card.subname}}</p>
</h2> <p>#{{card.id}}</p>
<p class="sub-name"> <p>{{Vn.Value.format(card.stems, _('%.0d Units'))}}</p>
<htk-text form="card" column="subName"/>
</p>
<p>
#<htk-text form="card" column="id"/>
</p>
<p>
<htk-text form="card" column="stems" format="_%.0d Units"/>
</p>
</div> </div>
<p class="desc"> <p class="desc">{{extendedCard.description}}</p>
<htk-text form="card-extend" column="description" id="desc"/>
</p>
<htk-repeater show-status="false" form-id="tag" class="tags"> <htk-repeater show-status="false" form-id="tag" class="tags">
<db-model <db-model
property="model" property="model"
@ -448,8 +436,8 @@
</db-model> </db-model>
<custom> <custom>
<tr> <tr>
<td><htk-text form="tag" column="name"/></td> <td>{{tag.name}}</td>
<td><htk-text form="tag" column="value"/></td> <td>{{tag.value}}</td>
</tr> </tr>
</custom> </custom>
</htk-repeater> </htk-repeater>

View File

@ -14,9 +14,7 @@
column="debt"> column="debt">
<db-form property="form"> <db-form property="form">
<db-model property="model"> <db-model property="model">
<custom>
SELECT -myClient_getDebt(NULL) debt SELECT -myClient_getDebt(NULL) debt
</custom>
</db-model> </db-model>
</db-form> </db-form>
</htk-text> </htk-text>
@ -42,9 +40,7 @@
form-id="iter" form-id="iter"
renderer="repeaterFunc"> renderer="repeaterFunc">
<db-model property="model" id="tickets"> <db-model property="model" id="tickets">
<custom> CALL myTicket_list(NULL, NULL);
CALL myTicket_list (NULL, NULL);
</custom>
</db-model> </db-model>
<custom> <custom>
<a id="link" class="item" title="{{_('SeeOrder')}}"> <a id="link" class="item" title="{{_('SeeOrder')}}">

View File

@ -10,13 +10,6 @@ Hedera.Ticket = new Class({
this.conn.execQuery('CALL myTicket_logAccess(#ticket)', null, params); this.conn.execQuery('CALL myTicket_logAccess(#ticket)', null, params);
}, },
onTicketReady: function(form) {
if (form.$.method != 'PICKUP')
Vn.Node.setText(this.$.method, _('Agency'));
else
Vn.Node.setText(this.$.method, _('Warehouse'));
},
onPrintClick: function() { onPrintClick: function() {
let params = Vn.Url.makeUri({ let params = Vn.Url.makeUri({
authorization: this.conn.token, authorization: this.conn.token,
@ -27,24 +20,17 @@ Hedera.Ticket = new Class({
window.open(`/api/report/delivery-note?${params}`); window.open(`/api/report/delivery-note?${params}`);
}, },
repeaterFunc: function(res, form) { repeaterFunc: function(scope, form) {
var discount = res.$.discount; scope.$.discount.style.display = form.$.discount ? 'inline' : 'none';
discount.style.display = form.$.discount ? 'inline' : 'none';
res.$.discountSubtotal.value = this.discountSubtotal(form);
res.$.subtotal.value = this.subtotal(form);
}, },
discountSubtotal: function(form) { discountSubtotal: function(line) {
return form.$.quantity * form.$.price; return line.quantity * line.price;
}, },
subtotal: function(form) { subtotal: function(line) {
var discount = form.$.discount; var discount = line.discount;
return this.discountSubtotal(form) * ((100 - discount) / 100); return this.discountSubtotal(line) * ((100 - discount) / 100);
},
servicesFunc: function(res, form) {
res.$.subtotal.value = form.$.quantity * form.$.price;
}, },
onServicesChanged: function(model) { onServicesChanged: function(model) {

View File

@ -2,7 +2,7 @@
<vn-lot-query id="params"> <vn-lot-query id="params">
<vn-spec name="ticket" type="Number"/> <vn-spec name="ticket" type="Number"/>
</vn-lot-query> </vn-lot-query>
<db-lot id="ticket" lot="params" on-ready="onTicketReady"> <db-lot id="ticket-form" lot="params" v-model="ticket">
CALL myTicket_get(#ticket) CALL myTicket_get(#ticket)
</db-lot> </db-lot>
<div id="title"> <div id="title">
@ -16,42 +16,32 @@
</div> </div>
<div id="form" class="ticket"> <div id="form" class="ticket">
<div class="box vn-w-sm vn-pa-lg"> <div class="box vn-w-sm vn-pa-lg">
<htk-loader class="head" form="ticket"> <htk-loader class="head" form="ticket-form">
<h5>#<htk-text column="id" form="ticket"/></h5> <h5>#{{ticket.id}}</h5>
<div> <div>
<h6><t>ShippingInformation</t></h6> <h6><t>ShippingInformation</t></h6>
<p> <p>
<t>Preparation</t> <htk-text form="ticket" column="shipped" format="%D"/> <t>Preparation</t> {{Vn.Value.format(ticket.shipped, '%D')}}
</p> </p>
<p> <p>
<t>Delivery</t> <htk-text form="ticket" column="landed" format="%D"/> <t>Delivery</t> {{Vn.Value.format(ticket.landed, '%D')}}
</p> </p>
<p> <p>
<span id="method"></span> <htk-text form="ticket" column="agency"/> {{_(ticket.method != 'PICKUP' ? 'Agency' : 'Warehouse')}} {{ticket.agency}}
</p> </p>
</div> </div>
<div class="address"> <div class="address">
<h6><t>DeliveryAddress</t></h6> <h6><t>DeliveryAddress</t></h6>
<p> <p>{{ticket.nickname}}</p>
<htk-text form="ticket" column="nickname"/> <p>{{ticket.street}}</p>
</p> <p>{{ticket.postalCode}} {{ticket.city}} ({{ticket.province}})</p>
<p>
<htk-text form="ticket" column="street"/>
</p>
<p>
<htk-text form="ticket" column="postalCode"/>
<htk-text form="ticket" column="city"/>
(<htk-text form="ticket" column="province"/>)
</p>
</div> </div>
<div class="total"> <div class="total">
<p class="important total"> <p class="important total">
<t>Total</t> <t>Total</t> {{Vn.Value.format(ticket.taxBase, '%.2d€')}}
<htk-text format="%.2d€" form="ticket" column="taxBase"/>
</p> </p>
<p class="important total"> <p class="important total">
<t>Total + tax</t> <t>Total + tax</t> {{Vn.Value.format(ticket.total, '%.2d€')}}
<htk-text format="%.2d€" form="ticket" column="total"/>
</p> </p>
</div> </div>
</htk-loader> </htk-loader>
@ -65,38 +55,32 @@
<custom> <custom>
<div class="line"> <div class="line">
<htk-image <htk-image
form="iter" value="{{iter.image}}"
column="image"
stamp-column="updated" stamp-column="updated"
class="photo" class="photo"
directory="catalog" directory="catalog"
subdir="200x200" subdir="200x200"
full-dir="1600x900"/> full-dir="1600x900"/>
<div class="info"> <div class="info">
<h2> <h2>{{iter.concept}}</h2>
<htk-text form="iter" column="concept"/>
</h2>
<p class="tags"> <p class="tags">
<htk-text form="iter" column="value5"/> {{iter.value5}} {{iter.value6}} {{iter.value7}}
<htk-text form="iter" column="value6"/>
<htk-text form="iter" column="value7"/>
</p> </p>
<p class="amount"> <p class="amount">
<htk-text form="iter" column="quantity"/> x {{iter.quantity}} x {{Vn.Value.format(iter.price, '%.2d€')}}
<htk-text form="iter" column="price" format="%.2d€"/>
</p> </p>
<p class="subtotal"> <p class="subtotal">
<span class="discount" id="discount"> <span class="discount" id="discount">
<htk-text id="discount-subtotal" format="%.2d€"/> - {{Vn.Value.format(this.discountSubtotal(iter), '%.2d€')}} -
<htk-text form="iter" column="discount" format="%.0d%"/> = {{Vn.Value.format(iter.discount, '%.0d%')}} =
</span> </span>
<htk-text id="subtotal" format="%.2d€"/> {{Vn.Value.format(this.subtotal(iter), '%.2d€')}}
</p> </p>
</div> </div>
</div> </div>
</custom> </custom>
</htk-repeater> </htk-repeater>
<htk-repeater form-id="iter" id="services" class="packages" renderer="servicesFunc"> <htk-repeater form-id="iter" id="services" class="packages">
<db-model <db-model
property="model" property="model"
on-status-changed="onServicesChanged" on-status-changed="onServicesChanged"
@ -106,15 +90,12 @@
<custom> <custom>
<div class="line"> <div class="line">
<div class="info"> <div class="info">
<h2> <h2>{{iter.description}}</h2>
<htk-text form="iter" column="description"/>
</h2>
<p class="amount"> <p class="amount">
<htk-text form="iter" column="quantity"/> x {{iter.quantity}} x {{Vn.Value.format(iter.price, '%.2d€')}}
<htk-text form="iter" column="price" format="%.2d€"/>
</p> </p>
<p class="subtotal"> <p class="subtotal">
<htk-text id="subtotal" format="%.2d€"/> {{Vn.Value.format(iter.quantity * iter.price, '%.2d€')}}
</p> </p>
</div> </div>
<div class="clear"/> <div class="clear"/>
@ -131,22 +112,15 @@
<custom> <custom>
<div class="line"> <div class="line">
<htk-image <htk-image
form="iter" value="{{iter.image}}"
column="image"
class="photo" class="photo"
directory="catalog" directory="catalog"
subdir="200x200" subdir="200x200"
full-dir="1600x900"/> full-dir="1600x900"/>
<div class="info"> <div class="info">
<h2> <h2>{{iter.name}}</h2>
<htk-text form="iter" column="name"/> <p>#{{iter.id}}</p>
</h2> <p class="amount">{{iter.quantity}}</p>
<p>
#<htk-text form="iter" column="id"/>
</p>
<p class="amount">
<htk-text form="iter" column="quantity"/>
</p>
</div> </div>
<div class="clear"/> <div class="clear"/>
</div> </div>

View File

@ -24,7 +24,7 @@
title="_EditNew"> title="_EditNew">
<div class="side vn-mr-md"> <div class="side vn-mr-md">
<htk-image <htk-image
form="iter" form="$iter"
column="image" column="image"
class="photo" class="photo"
directory="news" directory="news"
@ -46,7 +46,7 @@
<htk-button <htk-button
tip="_Remove" tip="_Remove"
icon="delete" icon="delete"
on-click="this.onDeleteClick($.iter)"/> on-click="this.onDeleteClick($iter)"/>
</div> </div>
</a> </a>
</custom> </custom>

View File

@ -844,6 +844,7 @@ Model.implement({
if (!(op.type & Operation.DELETE if (!(op.type & Operation.DELETE
&& op.type & Operation.INSERT)) && op.type & Operation.INSERT))
// eslint-disable-next-line no-unused-vars
isOperation = true; isOperation = true;
if (op.type & Operation.DELETE) { if (op.type & Operation.DELETE) {

View File

@ -20,11 +20,11 @@ module.exports = new Class({
const hash = this.hash; const hash = this.hash;
const builder = new Vn.Builder(); 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); const scope = this.builder = builder.load(null, this);
this.$ = scope.$; this.$ = scope.$;
scope.link(null, {conn, hash}); scope.link({conn, hash});
this.node = scope.$.form; this.node = scope.$.form;
const paramsLot = this.$.params; const paramsLot = this.$.params;

View File

@ -69,7 +69,7 @@ module.exports = new Class({
builder.compileFile('reports/'+ this.info.path +'/ui.xml'); builder.compileFile('reports/'+ this.info.path +'/ui.xml');
var scope = this.scope = builder.load(this.doc, this); var scope = this.scope = builder.load(this.doc, this);
scope.link(null, { scope.link({
lot: this.lot, lot: this.lot,
conn: this.conn conn: this.conn
}); });

View File

@ -12,6 +12,8 @@ td.cell-button {
width: 44px; width: 44px;
margin: 0 auto; margin: 0 auto;
border: none; border: none;
border-radius: 50%;
padding: 10px;
background-color: transparent; background-color: transparent;
box-sizing: border-box; box-sizing: border-box;

View File

@ -5,13 +5,11 @@ module.exports = new Class({
Extends: Component Extends: Component
,Tag: 'htk-repeater' ,Tag: 'htk-repeater'
,Child: 'model' ,Child: 'model'
,Properties: ,Properties: {
{
/** /**
* The source data model. * The source data model.
*/ */
model: model: {
{
type: Db.Model type: Db.Model
,set: function(x) { ,set: function(x) {
this.link({_model: x}, { this.link({_model: x}, {
@ -30,8 +28,7 @@ module.exports = new Class({
/** /**
* The identifier for internal iterator. * The identifier for internal iterator.
*/ */
,formId: ,formId: {
{
type: String type: String
,set: function(x) { ,set: function(x) {
this._formId = x; this._formId = x;
@ -44,8 +41,7 @@ module.exports = new Class({
* {Function (Vn.BuilderResult, Db.Form)} Function to call after every * {Function (Vn.BuilderResult, Db.Form)} Function to call after every
* box rendering. * box rendering.
*/ */
,renderer: ,renderer: {
{
type: Function type: Function
,set: function(x) { ,set: function(x) {
this._renderer = x; this._renderer = x;
@ -57,8 +53,7 @@ module.exports = new Class({
/** /**
* Wether to show the model status. * Wether to show the model status.
*/ */
,showStatus: ,showStatus: {
{
type: Boolean type: Boolean
,set: function(x) { ,set: function(x) {
this._showStatus = x; this._showStatus = x;
@ -71,8 +66,7 @@ module.exports = new Class({
/** /**
* Message that should be displayed when source model is not ready. * Message that should be displayed when source model is not ready.
*/ */
,emptyMessage: ,emptyMessage: {
{
type: String type: String
,value: null ,value: null
} }
@ -105,7 +99,7 @@ module.exports = new Class({
} }
,getBuilder: function(index) { ,getBuilder: function(index) {
return this._childsData[index].builder; return this._childsData[index].scope;
} }
,getForm: function(index) { ,getForm: function(index) {
@ -119,14 +113,12 @@ module.exports = new Class({
}); });
var scope = this._builder.load(this.doc, null, this._parentScope); var scope = this._builder.load(this.doc, null, this._parentScope);
scope.link([set.getObject()], { scope.link({
[this._formId]: set $iter: set,
[this._formId]: set.getObject()
}); });
this._childsData.push({ this._childsData.push({scope, set});
builder: scope,
set: set
});
if (this._renderer) if (this._renderer)
this._renderer(scope, set); this._renderer(scope, set);
@ -228,7 +220,7 @@ module.exports = new Class({
,_unrefChildData: function(index) { ,_unrefChildData: function(index) {
var childData = this._childsData[index]; var childData = this._childsData[index];
childData.set.unref(); childData.set.unref();
childData.builder.unref(); childData.scope.unref();
} }
,destroy: function() { ,destroy: function() {

View File

@ -1,16 +1,22 @@
const VnObject = require('./object'); const VnObject = require('./object');
const Component = require('./component');
const VnNode = require('./node');
const Scope = require('./scope'); const Scope = require('./scope');
const Type = require('./type');
const kebabToCamel = require('./string-util').kebabToCamel; 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. * Creates a object from a XML specification.
*/ */
module.exports = new Class({ module.exports = new Class({
Extends: VnObject Extends: VnObject
,_contexts: null
/** /**
* Compiles an XML file. * Compiles an XML file.
@ -30,8 +36,8 @@ module.exports = new Class({
* @return {Boolean} %true on success, %false othersise * @return {Boolean} %true on success, %false othersise
*/ */
,compileString: function(xmlString) { ,compileString: function(xmlString) {
var parser = new DOMParser(); const parser = new DOMParser();
var doc = parser.parseFromString(xmlString, 'text/xml'); const doc = parser.parseFromString(xmlString, 'text/xml');
return this.compileDocument(doc); return this.compileDocument(doc);
} }
@ -41,12 +47,12 @@ module.exports = new Class({
* @param {Document} doc The DOM document * @param {Document} doc The DOM document
* @return {Boolean} %true on success, %false othersise * @return {Boolean} %true on success, %false othersise
*/ */
,compileDocument: function(doc, exprArgs) { ,compileDocument: function(doc) {
if (!doc) if (!doc)
return false; return false;
this._preCompile(exprArgs); this._preCompile();
var docElement = doc.documentElement; const docElement = doc.documentElement;
if (docElement.tagName !== 'vn') { if (docElement.tagName !== 'vn') {
this.showError('The toplevel tag should be named \'vn\''); this.showError('The toplevel tag should be named \'vn\'');
@ -54,9 +60,9 @@ module.exports = new Class({
return false; return false;
} }
var childs = docElement.childNodes; const childs = docElement.childNodes;
if (childs) if (childs)
for (var i = 0; i < childs.length; i++) for (let i = 0; i < childs.length; i++)
this._compile(childs[i]); this._compile(childs[i]);
this._postCompile(); this._postCompile();
@ -69,8 +75,8 @@ module.exports = new Class({
* @path Node The DOM node * @path Node The DOM node
* @return %true on success, %false othersise * @return %true on success, %false othersise
*/ */
,compileNode: function(node, exprArgs) { ,compileNode: function(node) {
this._preCompile(exprArgs); this._preCompile();
this._mainContext = this._compile(node).id; this._mainContext = this._compile(node).id;
this._postCompile(); this._postCompile();
return true; return true;
@ -79,28 +85,27 @@ module.exports = new Class({
/** /**
* Called before starting to compile nodes. * Called before starting to compile nodes.
*/ */
,_preCompile: function(exprArgs) { ,_preCompile: function() {
this._path = null; this._path = null;
this._tags = {}; this._tags = {};
this._contexts = []; this._contexts = [];
this._exprContexts = [];
this._contextMap = {}; this._contextMap = {};
this._links = []; this._links = [];
this._mainContext = null; this._mainContext = null;
this._baseExprArgs = ['_', '$']; this._compilers = [];
if (exprArgs) for (regCompiler of regCompilers)
this._baseExprArgs = this._baseExprArgs.concat(exprArgs); this._compilers.push(new regCompiler(this));
this._baseEventArgs = this._baseExprArgs.concat(['$event']);
this._exprArgs = this._baseExprArgs.join(',');
this._eventArgs = this._baseEventArgs.join(',');
} }
/** /**
* Called after all nodes have been compiled. * Called after all nodes have been compiled.
*/ */
,_postCompile: function() {} ,_postCompile: function() {
for (const compiler of this._compilers)
compiler.postCompile(this._contextMap);
}
/** /**
* Compiles a node. * Compiles a node.
@ -116,23 +121,23 @@ module.exports = new Class({
|| /^[\n\r\t]*$/.test(node.textContent)) || /^[\n\r\t]*$/.test(node.textContent))
return null; return null;
context = let i;
this.textCompile(node, tagName) const compilers = this._compilers;
|| this.objectCompile(node, tagName) for (i = 0; i < compilers.length && context === null; i++)
|| this.elementCompile(node, tagName); context = compilers[i].compile(this, node, tagName);
context.id = this._contexts.length; context.id = this._contexts.length;
context.compiler = compilers[i - 1];
if (isElement) { if (isElement) {
var nodeId = node.getAttribute('id'); const nodeId = node.getAttribute('id');
if (nodeId) { if (nodeId) {
this._contextMap[kebabToCamel(nodeId)] = context.id; this._contextMap[kebabToCamel(nodeId)] = context.id;
context.nodeId = nodeId; context.nodeId = nodeId;
} }
var tags = this._tags[tagName]; let tags = this._tags[tagName];
if (!tags) if (!tags)
this._tags[tagName] = tags = []; this._tags[tagName] = tags = [];
@ -143,537 +148,21 @@ module.exports = new Class({
return context; 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) { ,load: function(dstDocument, thisArg, parentScope) {
if (this._contexts === null) if (!this._contexts) return null;
return null;
const doc = dstDocument ? dstDocument : document; const doc = dstDocument ? dstDocument : document;
const contexts = this._contexts; const objects = new Array(this._contexts.length);
const len = contexts.length; const exprValues = new Array(this._exprContexts.length);
const objects = new Array(len); return new Scope(this, doc, objects, exprValues, thisArg, parentScope);
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;
}
}
}
} }
,showError: function(error) { ,showError: function(error) {
var path = this._path ? this._path : 'Node'; const path = this._path ? this._path : 'Node';
var logArgs = ['Vn.Builder: %s: '+ error, path]; 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]); logArgs.push(arguments[i]);
console.warn.apply(null, logArgs); 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);
}
}
}); });

85
js/vn/compiler-element.js Normal file
View File

@ -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);
}
}
});

216
js/vn/compiler-object.js Normal file
View File

@ -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();
}
});

33
js/vn/compiler-text.js Normal file
View File

@ -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;
}
});

150
js/vn/compiler.js Normal file
View File

@ -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;
}
});

View File

@ -29,7 +29,11 @@ module.exports = new Class({
} }
,initialize: function(props) { ,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); VnObject.prototype.initialize.call(this, props);
} }

View File

@ -6,21 +6,30 @@ let scopeUid = 0;
module.exports = new Class({ module.exports = new Class({
Extends: VnObject Extends: VnObject
,initialize: function(builder, objects, thisArg, parent) { ,initialize: function(builder, doc, objects, exprValues, thisArg, parent) {
this.builder = builder; this.builder = builder;
this.objects = objects; this.objects = objects;
this.exprValues = exprValues;
this.thisArg = thisArg; this.thisArg = thisArg;
this.parent = parent; this.parent = parent;
this.uid = ++scopeUid; this.uid = ++scopeUid;
this.$ = parent ? Object.create(parent.$) : {}; this.$ = parent ? Object.create(parent.$) : {};
if (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; 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; var contextMap = this.builder._contextMap;
for (var id in extraObjects) for (var id in extraObjects)
@ -28,26 +37,88 @@ module.exports = new Class({
for (var id in contextMap) for (var id in contextMap)
this.$[id] = this.objects[contextMap[id]]; this.$[id] = this.objects[contextMap[id]];
this.exprScope = [ const builder = this.builder;
_, const contexts = builder._contexts;
this.$ const objects = this.objects;
].concat(exprScope);
this.builder.link(this); for (const compiler of builder._compilers)
this.builder.digest(this); 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) for (const object of this.objects)
if (object.assignLot) if (object.assignLot)
object.on('change', this.onLotChange, this); object.on('change', this.onChange, this);
} }
,onLotChange() { ,digest() {
this.emit('lot-change'); const exprContexts = this.builder._exprContexts;
this.builder.digest(this); 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() { ,getMain: function() {
return this.builder.getMain(this); return this.objects[this.builder._mainContext];
} }
,getById: function(objectId) { ,getById: function(objectId) {
@ -56,7 +127,18 @@ module.exports = new Class({
} }
,getByTagName: function(tagName) { ,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) { ,getHtmlId: function(nodeId) {

View File

@ -1,6 +1,6 @@
{ {
"name": "hedera-web", "name": "hedera-web",
"version": "1.407.79", "version": "1.407.80",
"description": "Verdnatura web page", "description": "Verdnatura web page",
"license": "GPL-3.0", "license": "GPL-3.0",
"repository": { "repository": {

View File

@ -66,7 +66,7 @@ class HtmlService extends Service {
// Setting the version // Setting the version
setcookie('vnVersion', $this->getVersion()); setcookie('vnVersion', $this->getVersion(), ['samesite' => 'Lax']);
// Loading the requested page // Loading the requested page

View File

@ -124,7 +124,10 @@ abstract class Service {
); );
if (isset($row['access'])) { 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']; $_SESSION['access'] = $row['access'];
} else } else
$_SESSION['skipVisit'] = TRUE; $_SESSION['skipVisit'] = TRUE;