diff --git a/debian/changelog b/debian/changelog index 9c4a7909..fcca71be 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -hedera-web (1.407.76) stable; urgency=low +hedera-web (1.407.77) stable; urgency=low * Initial Release. diff --git a/forms/account/address-list/address-list.js b/forms/account/address-list/address-list.js index 5a73cee4..a75df3b8 100644 --- a/forms/account/address-list/address-list.js +++ b/forms/account/address-list/address-list.js @@ -9,7 +9,7 @@ Hedera.AddressList = new Class } ,onAddAddressClick: function() { - this.hash.set({ + this.hash.setAll({ form: 'account/address', address: 0 }); @@ -33,7 +33,7 @@ Hedera.AddressList = new Class } ,onEditAddressClick: function(id) { - this.hash.set({ + this.hash.setAll({ form: 'account/address', address: id }); diff --git a/forms/account/address-list/locale/ca.yml b/forms/account/address-list/locale/ca.yml index cf957550..26601428 100644 --- a/forms/account/address-list/locale/ca.yml +++ b/forms/account/address-list/locale/ca.yml @@ -1,4 +1,4 @@ -Addresses: Direccions +Addresses: Adreces Return: Tornar AddAddress: Afegir adreça SetAsDefault: Establir com per defecte diff --git a/forms/account/address/address.js b/forms/account/address/address.js index 2e77b3f0..649deaee 100644 --- a/forms/account/address/address.js +++ b/forms/account/address/address.js @@ -9,20 +9,12 @@ Hedera.Address = new Class({ }, onStatusChange: function() { - if (this.$.iter.ready && this.$.address.value == 0) + if (this.$.iter.ready && this.hash.$.address == 0) this.$.iter.insertRow(); }, onOperationsDone: function() { Htk.Toast.showMessage(_('AddressChangedSuccessfully')); - this.onReturnClick(); - }, - - onAcceptClick: function() { - this.$.iter.performOperations(); - }, - - onReturnClick: function() { - window.history.back(); + window.history.back() } }); diff --git a/forms/account/address/ui.xml b/forms/account/address/ui.xml index 6a1b5aff..1e596cf6 100644 --- a/forms/account/address/ui.xml +++ b/forms/account/address/ui.xml @@ -1,24 +1,21 @@ - - + + + SELECT a.id, a.street, a.nickname, a.city, a.postalCode, a.provinceFk, p.countryFk FROM myAddress a LEFT JOIN vn.province p ON p.id = a.provinceFk WHERE a.id = #address - - - - - @@ -29,11 +26,11 @@ + on-click="window.history.back()"/> + on-click="$.iter.performOperations()"/>
@@ -62,13 +59,9 @@
- + placeholder="_Country" + id="country" + one-way="true"> SELECT id, country FROM vn.country ORDER BY country @@ -80,15 +73,10 @@ placeholder="_Province" column="provinceFk" form="iter"> - + SELECT id, name FROM vn.province - WHERE countryFk = #country + WHERE countryFk = #id ORDER BY name - - - - -
diff --git a/forms/account/conf/conf.js b/forms/account/conf/conf.js index 143e4f50..e15c2ca7 100644 --- a/forms/account/conf/conf.js +++ b/forms/account/conf/conf.js @@ -9,7 +9,7 @@ Hedera.Conf = new Class({ if (this.hash.$.verificationToken) this.onPassChangeClick(); } - + ,onPassChangeClick: function() { this.$.oldPassword.value = ''; this.$.newPassword.value = ''; @@ -75,9 +75,5 @@ Hedera.Conf = new Class({ ,onPassInfoClick: function() { this.$.passwordInfo.show(); } - - ,onAddressesClick: function() { - this.hash.set({form: 'account/address-list'}); - } }); diff --git a/forms/account/conf/ui.xml b/forms/account/conf/ui.xml index d8f3119a..28136d09 100644 --- a/forms/account/conf/ui.xml +++ b/forms/account/conf/ui.xml @@ -27,7 +27,7 @@ + on-click="$.hash.setAll({form: 'account/address-list'})"/> - - - - - - SELECT u.id, u.name user, u.nickname, u.email, c.phone, r.name role - FROM account.user u - JOIN account.role r ON r.id = u.role - LEFT JOIN vn.client c ON c.id = u.id - WHERE u.id = #user - - - - - - + + + SELECT u.id, u.name user, u.nickname, u.email, c.phone, r.name role + FROM account.user u + JOIN account.role r ON r.id = u.role + LEFT JOIN vn.client c ON c.id = u.id + WHERE u.id = #user @@ -25,29 +16,22 @@
-

-

# -

-

-

-

+

+

# -

+

+

+

- - - SELECT u.stamp, a.platform, a.browser, a.version, a.javascript, a.cookies - FROM visitUser u - JOIN visitAccess c ON c.id = u.accessFk - JOIN visitAgent a ON a.id = c.agentFk - WHERE u.userFk = #user - ORDER BY u.stamp DESC - LIMIT 8 - - - - - - + + SELECT u.stamp, a.platform, a.browser, a.version, a.javascript, a.cookies + FROM visitUser u + JOIN visitAccess c ON c.id = u.accessFk + JOIN visitAgent a ON a.id = c.agentFk + WHERE u.userFk = #user + ORDER BY u.stamp DESC + LIMIT 8
diff --git a/forms/admin/connections/connections.js b/forms/admin/connections/connections.js index 2bacd32c..ef09f125 100644 --- a/forms/admin/connections/connections.js +++ b/forms/admin/connections/connections.js @@ -25,7 +25,7 @@ Hedera.Connections = new Class({ } ,_onUserSupplant: function() { - this.hash.set({form: 'ecomerce/orders'}); + this.hash.setAll({form: 'ecomerce/orders'}); } ,sessionsFunc: function() { diff --git a/forms/admin/connections/ui.xml b/forms/admin/connections/ui.xml index 6482b5c4..65b25336 100644 --- a/forms/admin/connections/ui.xml +++ b/forms/admin/connections/ui.xml @@ -23,17 +23,15 @@ property="model" id="sessions" on-status-changed="this.onModelStatusChange()"> - - SELECT vu.userFk userId, vu.stamp, u.nickname, s.lastUpdate, - a.platform, a.browser, a.version, u.name user - FROM userSession s - JOIN visitUser vu ON vu.id = s.userVisitFk - JOIN visitAccess ac ON ac.id = vu.accessFk - JOIN visitAgent a ON a.id = ac.agentFk - JOIN visit v ON v.id = a.visitFk - JOIN account.user u ON u.id = vu.userFk - ORDER BY lastUpdate DESC - + SELECT vu.userFk userId, vu.stamp, u.nickname, s.lastUpdate, + a.platform, a.browser, a.version, u.name user + FROM userSession s + JOIN visitUser vu ON vu.id = s.userVisitFk + JOIN visitAccess ac ON ac.id = vu.accessFk + JOIN visitAgent a ON a.id = ac.agentFk + JOIN visit v ON v.id = a.visitFk + JOIN account.user u ON u.id = vu.userFk + ORDER BY lastUpdate DESC - - - -

Items

- +
- + SELECT i.id, i.longName, i.size, i.category, i.value5, i.value6, i.value7, i.image, im.updated @@ -22,14 +18,9 @@ LEFT JOIN image im ON im.collectionFk = 'catalog' AND im.name = i.image - WHERE i.longName LIKE CONCAT('%', #filter, '%') - OR i.id = #filter + WHERE i.longName LIKE CONCAT('%', #search, '%') + OR i.id = #search ORDER BY i.longName LIMIT 50 - - - - -
diff --git a/forms/admin/users/ui.xml b/forms/admin/users/ui.xml index 722187fb..922e88ff 100644 --- a/forms/admin/users/ui.xml +++ b/forms/admin/users/ui.xml @@ -1,34 +1,22 @@ - - - -

User management

- +
- - - +
- - - +
@@ -50,27 +54,19 @@ class="box htk-list" form-id="iter" empty-message="_Select date interval"> - - - SELECT browser, - MIN(CAST(version AS DECIMAL(4,1))) minVersion, - MAX(CAST(version AS DECIMAL(4,1))) maxVersion, - MAX(c.stamp) lastVisit, - COUNT(DISTINCT c.id) visits, - SUM(a.firstAccessFk = c.id AND v.firstAgentFk = a.id) newVisits - FROM visitUser e - JOIN visitAccess c ON c.id = e.accessFk - JOIN visitAgent a ON a.id = c.agentFk - JOIN visit v ON v.id = a.visitFk - WHERE c.stamp BETWEEN TIMESTAMP(#from,'00:00:00') AND TIMESTAMP(#to,'23:59:59') - GROUP BY browser ORDER BY visits DESC - - - - - - - + + SELECT browser, + MIN(CAST(version AS DECIMAL(4,1))) minVersion, + MAX(CAST(version AS DECIMAL(4,1))) maxVersion, + MAX(c.stamp) lastVisit, + COUNT(DISTINCT c.id) visits, + SUM(a.firstAccessFk = c.id AND v.firstAgentFk = a.id) newVisits + FROM visitUser e + JOIN visitAccess c ON c.id = e.accessFk + JOIN visitAgent a ON a.id = c.agentFk + JOIN visit v ON v.id = a.visitFk + WHERE c.stamp BETWEEN TIMESTAMP(#from,'00:00:00') AND TIMESTAMP(#to,'23:59:59') + GROUP BY browser ORDER BY visits DESC
diff --git a/forms/admin/visits/visits.js b/forms/admin/visits/visits.js index ffd35638..24d54cdb 100644 --- a/forms/admin/visits/visits.js +++ b/forms/admin/visits/visits.js @@ -3,16 +3,11 @@ Hedera.Visits = new Class({ Extends: Hedera.Form ,activate: function() { - this.$.from.value = new Date(); - this.$.to.value = new Date(); - } - - ,onRefreshClick: function() { - this.$.visits.refresh(); - } - - ,onSessionsClick: function() { - this.hash.set({form: 'admin/connections'}); + if (!this.hash.$.to) + this.hash.assign({ + from: new Date(), + to: new Date() + }); } }); diff --git a/forms/agencies/packages/packages.js b/forms/agencies/packages/packages.js index 14c16e14..d86d89c6 100644 --- a/forms/agencies/packages/packages.js +++ b/forms/agencies/packages/packages.js @@ -3,7 +3,7 @@ Hedera.Packages = new Class({ Extends: Hedera.Form ,onShowClick: function(column, agencyId) { - this.hash.set({ + this.hash.setAll({ form: 'agencies/provinces', agency: agencyId }); diff --git a/forms/agencies/provinces/provinces.js b/forms/agencies/provinces/provinces.js index 723fc15c..b441f598 100644 --- a/forms/agencies/provinces/provinces.js +++ b/forms/agencies/provinces/provinces.js @@ -1,6 +1,5 @@ -Hedera.Provinces = new Class -({ +Hedera.Provinces = new Class({ Extends: Hedera.Form }); diff --git a/forms/agencies/provinces/ui.xml b/forms/agencies/provinces/ui.xml index d9a3bcbf..46fd7c04 100644 --- a/forms/agencies/provinces/ui.xml +++ b/forms/agencies/provinces/ui.xml @@ -1,23 +1,12 @@ - - - -

ByProvince

- - - CALL vn2008.desglose_volume(#agency) - - - - - - + + CALL vn2008.desglose_volume(#agency) diff --git a/forms/cms/home/ui.xml b/forms/cms/home/ui.xml index 417b8131..77424b3c 100644 --- a/forms/cms/home/ui.xml +++ b/forms/cms/home/ui.xml @@ -7,7 +7,7 @@ class="start-order" icon="add_shopping_cart" tip="_Start order" - on-click="this.hash.set({form: 'ecomerce/catalog'})"/> + on-click="$.hash.setAll({form: 'ecomerce/catalog'})"/>
diff --git a/forms/ecomerce/basket/basket.js b/forms/ecomerce/basket/basket.js index 9dd2b623..e967ef52 100644 --- a/forms/ecomerce/basket/basket.js +++ b/forms/ecomerce/basket/basket.js @@ -6,7 +6,7 @@ Hedera.Basket = new Class({ this.close(); this.isOpen = true; - Hedera.BasketChecker.check(this.conn, + Hedera.BasketChecker.check(this.conn, this.hash, this.onBasketCheck.bind(this)); } @@ -21,7 +21,7 @@ Hedera.Basket = new Class({ ,onConfigureClick: function() { Htk.Toast.showWarning(_('RememberReconfiguringImpact')); - this.hash.set({form: 'ecomerce/checkout'}); + this.hash.setAll({form: 'ecomerce/checkout'}); } ,onDeleteClick: function(form) { diff --git a/forms/ecomerce/basket/ui.xml b/forms/ecomerce/basket/ui.xml index be46122f..6d05ae55 100644 --- a/forms/ecomerce/basket/ui.xml +++ b/forms/ecomerce/basket/ui.xml @@ -10,11 +10,11 @@ + on-click="this.hash.setAll({form: 'ecomerce/catalog'})"/> + on-click="this.hash.setAll({form: 'ecomerce/confirm'})"/>
diff --git a/forms/ecomerce/catalog/catalog.js b/forms/ecomerce/catalog/catalog.js index 3c7b052b..71aa2a21 100644 --- a/forms/ecomerce/catalog/catalog.js +++ b/forms/ecomerce/catalog/catalog.js @@ -8,8 +8,8 @@ Hedera.Catalog = new Class({ this.close(); this.isOpen = true; - if (!localStorage.getItem('hederaGuest')) { - Hedera.BasketChecker.check(this.conn, + if (!localStorage.getItem('hederaGuest')) { + Hedera.BasketChecker.check(this.conn, this.hash, this.onBasketCheck.bind(this)); } else { var query = 'CALL mybasket_configureForGuest'; @@ -31,7 +31,7 @@ Hedera.Catalog = new Class({ else this.setView(Hedera.Catalog.View.GRID); - this.onRealmChange(); + this.onFilterChange(); } ,deactivate: function() { @@ -40,77 +40,80 @@ Hedera.Catalog = new Class({ Vn.Node.remove(this.$.rightPanel); } + ,getFilter(params, tags, currentTag) { + if (params.search == null && params.type == null) + return null; + + const $ = this.$; + + params = Object.assign({}, params); + const filter = new Sql.Operation({ + type: Sql.Operation.Type.AND + }); + + let idSearch = false; + if (params.search != null) { + idSearch = /^\d+$/.test(params.search); + + if (!idSearch) { + filter.push($.searchOp); + params.search = `%${params.search}%`; + } else + filter.push($.idOp); + } + + if (!idSearch) { + if (params.realm != null) + filter.push($.realmOp); + if (params.type != null) + filter.push($.typeOp); + + for (const tag of tags) + if (tag != currentTag && params[tag] != null) + filter.push($[`${tag}Op`]); + } + + params.filter = filter; + const lot = new Vn.Lot(); + lot.setAll(params); + + return lot; + } + ,onFilterChange: function() { const $ = this.$; - const params = [ - 'search', - 'realm', - 'type', + const params = $.params.$; + + this.refreshTitle(); + const hasRealm = params.realm != null; + $.filters.style.display = hasRealm ? 'block' : 'none'; + $.realmMsg.style.display = hasRealm ? 'none' : 'block'; + + const tags = [ 'color', 'origin', 'category', 'producer' ]; - const lot = {}; - for (const param of params) - lot[param] = this.$[param].value; - if (lot.search != null || lot.type != null) { - const filter = new Sql.Operation({ - type: Sql.Operation.Type.AND - }); - const exprs = filter.exprs; - - let idSearch = false; - if (lot.search != null) { - idSearch = /^\d+$/.test(lot.search); - exprs.add(idSearch ? $.idOp : $.nameOp); - } - - if (!idSearch) { - if (lot.realm != null) - exprs.add($.realmOp); - if (lot.type != null) - exprs.add($.typeOp); - - const tags = [ - 'color', - 'origin', - 'category', - 'producer' - ]; - for (const tag of tags) - if (lot[tag] != null) - exprs.add($[`${tag}Op`]); - } - - const batch = new Sql.Batch(); - batch.addObject('filter', filter); - batch.block(); - $.items.batch = batch; + const lot = this.getFilter(params, tags); + if (lot) { + $.items.lot = lot; + $.items.refresh(); } else - $.items.batch = null; - } - - ,onRealmChange: function() { - const hasRealm = this.$.realm.value != null; - this.$.filters.style.display = hasRealm ? 'block' : 'none'; - this.$.realmMsg.style.display = hasRealm ? 'none' : 'block'; - this.onFilterChange(); - this.refreshTitle(); - } + $.items.clean(); - ,onTypeChange: function() { - this.onFilterChange(); - this.refreshTitle(); + for (const tag of tags) + $[`${tag}s`].lot = this.getFilter(params, tags, tag); } ,refreshTitle: function() { + const hash = this.hash.$; const types = this.$.types; const realms = this.$.realms; - const type = this.$.type.value; - const realm = this.$.realm.value; + const type = hash.type; + const realm = hash.realm; let typeName; let realmName; @@ -234,14 +237,14 @@ Hedera.Catalog = new Class({ if (this.isGuest()) return; - this.hash.set({form: 'ecomerce/basket'}); + this.hash.setAll({form: 'ecomerce/basket'}); } ,onConfigureClick: function() { if (this.isGuest()) return; - this.hash.set({form: 'ecomerce/checkout'}); + this.hash.setAll({form: 'ecomerce/checkout'}); } ,onAddItemClick: function(event, form) { @@ -251,7 +254,7 @@ Hedera.Catalog = new Class({ this.onEraseClick(); this.$.card.row = form.row; - this.$.cardItem.value = form.$.id; + this.$.cardLot.assign({item: form.$.id}); this.$.cardPopup.show(event.currentTarget); } @@ -280,7 +283,6 @@ Hedera.Catalog = new Class({ ,onConfirmClick: function() { var sql = ''; - var batch = new Sql.Batch(); var query = new Sql.String({query: 'CALL myBasket_addItem(#warehouse, #item, #amount);'}); var amountSum = 0; @@ -288,10 +290,12 @@ Hedera.Catalog = new Class({ var amount = this.items[warehouse]; amountSum += amount; - batch.addValue('warehouse', warehouse); - batch.addValue('item', this.$.cardItem.value); - batch.addValue('amount', amount); - sql += query.render(batch); + const params = { + warehouse: warehouse, + item: this.$.cardLot.$.item, + amount: amount + } + sql += query.render(params); } if (amountSum > 0) { @@ -299,7 +303,7 @@ Hedera.Catalog = new Class({ var itemName = this.$.card.get('item'); Htk.Toast.showMessage( - sprintf(_('Added%dOf%s'), amountSum, itemName)); + Vn.Value.sprintf(_('Added%dOf%s'), amountSum, itemName)); } this.$.cardPopup.hide(); @@ -313,7 +317,7 @@ Hedera.Catalog = new Class({ ,onPopupClose: function() { this.onEraseClick(); this.$.card.row = -1; - this.$.cardItem.value = undefined; + this.$.cardLot.value = undefined; } ,onCardLoad: function() { @@ -327,163 +331,3 @@ Hedera.Catalog.extend({ GRID: 1 } }); - -Vn.Filter = new Class({ - Extends: Htk.Field - ,Tag: 'vn-filter' - ,Child: 'model' - ,Properties: { - model: { - type: Db.Model - ,set: function(x) { - x.batch = this._batch; - this._model = x; - this._select.model = x; - } - ,get: function() { - return this._model; - } - }, - placeholder: { - type: String - ,set: function(x) { - this._select.placeholder = x; - this._placeholder = x; - } - ,get: function() { - return this._placeholder; - } - }, - filter: { - type: Sql.Filter - ,set: function(x) { - this._filter = x; - this._batch.addObject('filter', x); - } - ,get: function() { - return this._filter; - } - } - } - - ,_valueColumnIndex: 0 - ,_showColumnIndex: 1 - - ,initialize: function(props) { - var node = this.createRoot('div'); - node.className = 'vn-filter'; - - this._select = new Htk.Select(); - this._select.on('mousedown', this._onMouseDown, this); - this._select.on('changed', this._onChange, this); - this._select.on('ready', this._onReady, this); - this.node.appendChild(this._select.node); - - this._ul = this.createElement('ul'); - this.node.appendChild(this._ul); - - this._batch = new Sql.Batch(); - this.parent(props); - } - - ,_onMouseDown: function() { - if (this._model && this._model.status === Db.Model.Status.CLEAN) - this._model.refresh(); - } - - ,_onCloseClick: function() { - this._removeSelectionNode(); - this._changeValue(undefined); - } - - ,_removeSelectionNode: function() { - if (this._lastLi) { - Vn.Node.remove(this._lastLi); - this._lastLi = null; - this._label = null; - } - } - - ,_onChange: function() { - if (this._select.value === null - || this._select.value === undefined) - return; - - this._realSetValue(this._select.value); - } - - ,_onReady: function() { - if (this._emptyLabel) - this._refreshLabel(); - } - - ,_changeValue: function(newValue) { - this._batch.block(); - this.value = newValue; - this._batch.unblock(); - } - - ,_onTimeout: function() { - this._select.value = null; - } - - ,putValue: function(value) { - this._onMouseDown(); - this._realSetValue(value); - } - - ,_realSetValue: function(value) { - this._removeSelectionNode(); - - if (value === null || value === undefined) - return; - - var li = this._lastLi = this.createElement('li'); - this._ul.appendChild(li); - - var button = this.createElement('button'); - button.addEventListener('click', - this._onCloseClick.bind(this, li)); - li.appendChild(button); - - var icon = new Htk.Icon({ - name: 'close', - alt: _('Close') - }); - button.appendChild(icon.node); - - var text = this._label = this.createTextNode(''); - li.appendChild(text); - - setTimeout(this._onTimeout.bind(this)); - - this._changeValue(value); - this._refreshLabel(); - } - - ,_refreshLabel: function() { - if (!this._label) - return; - - let row = -1; - const model = this._model; - let showValue = ''; - this._emptyLabel = true; - - if (model) { - if (model.ready) { - row = model.searchByIndex(this._valueColumnIndex, this._value); - - if (row != -1) { - var label = model.getByIndex(row, this._showColumnIndex); - showValue = label; - this._emptyLabel = false; - } - } else - showValue = _('Loading...'); - } - - this._label.nodeValue = showValue; - } -}); - diff --git a/forms/ecomerce/catalog/style.css b/forms/ecomerce/catalog/style.css index 1ec8cdef..56b00e37 100644 --- a/forms/ecomerce/catalog/style.css +++ b/forms/ecomerce/catalog/style.css @@ -88,41 +88,7 @@ padding: 0; width: 100%; } -.right-panel .vn-filter, -.right-panel select { - margin: 0 auto; - margin-bottom: .7em; - display: block; -} -.vn-filter > ul { - margin: 0; - list-style-type: none; - text-align: left; - color: #666; - padding-left: .8em; -} -.vn-filter li { - margin: 0; - margin-top: .3em; - line-height: 2em; - max-width: 90%; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} -.vn-filter li > button { - vertical-align: middle; - text-align: center; - padding: .2em; - margin: 0; - margin-right: .2em; -} -.vn-filter li > button > span { - display: block; -} .right-panel .filters > button { - display: block; - margin: 0 auto; margin-top: 1em; } @@ -157,7 +123,7 @@ font-size: 1em; text-overflow: ellipsis; overflow: hidden; - max-height: 2.8em; + max-height: 3em; } .item-info > p { margin: 0; @@ -247,7 +213,7 @@ flex-direction: column; width: 240px; - height: 405px; + height: 410px; overflow: hidden; } .grid-view .item-box:hover { diff --git a/forms/ecomerce/catalog/ui.xml b/forms/ecomerce/catalog/ui.xml index cacab56f..7a209c51 100644 --- a/forms/ecomerce/catalog/ui.xml +++ b/forms/ecomerce/catalog/ui.xml @@ -4,8 +4,7 @@
- +
- - + + + + + + - - - - - - - - - - - - - - + type="EQUAL" + target="i" + field="id" + param="search"/> - - + id="search-op" + type="OR"> + + - - - - - + - - - - + - - - - - - - - + - - - - + + - - - + type="EQUAL" + target="i" + field="producerFk" + param="producer"/> @@ -96,6 +97,7 @@ CREATE TEMPORARY TABLE tmp.item @@ -125,11 +127,7 @@ LIMIT 5000; - - - - - +
@@ -241,14 +239,16 @@

Filter by

- CALL myBasket_getAvailable; @@ -257,21 +257,21 @@ JOIN vn.itemType t ON t.id = i.typeFk JOIN tmp.itemAvailable a ON a.id = i.id JOIN vn.itemTypeL10n l ON l.id = t.id - WHERE t.`order` >= 0 AND #filter + WHERE t.`order` >= 0 + AND t.categoryFk = #realm ORDER BY t.`order`, l.name - - - - - - - - + - + form="params" + column="color" + on-mousedown="$.colors.lazyRefresh()"> + CALL myBasket_getAvailable; SELECT DISTINCT l.id, l.name FROM vn.item i @@ -281,12 +281,17 @@ WHERE #filter ORDER BY name - - + - + form="params" + column="producer" + on-mousedown="$.producers.lazyRefresh()"> + CALL myBasket_getAvailable; SELECT DISTINCT p.id, p.name FROM vn.item i @@ -296,12 +301,17 @@ WHERE #filter ORDER BY name - - + - + form="params" + column="origin" + on-mousedown="$.origins.lazyRefresh()"> + CALL myBasket_getAvailable; SELECT DISTINCT o.id, l.name, o.code FROM vn.item i @@ -312,12 +322,17 @@ WHERE #filter ORDER BY name - - + - + form="params" + column="category" + on-mousedown="$.categorys.lazyRefresh()"> + CALL myBasket_getAvailable; SELECT DISTINCT i.category, i.category FROM vn.item i @@ -326,7 +341,7 @@ WHERE #filter ORDER BY category - +

Order by

@@ -383,7 +398,7 @@ SELECT i.description, o.name origin FROM vn.item i @@ -421,7 +436,7 @@ SELECT l.name, it.value FROM vn.itemTag it @@ -445,7 +460,7 @@ property="model" result-index="1" on-status-changed-after="onCardLoad" - batch="card-batch"> + lot="card-lot"> CALL myBasket_calcCatalogFromItem(#item); SELECT l.warehouseFk, w.name warehouse, p.`grouping`, p.price, p.priceKg, p.rate, l.available diff --git a/forms/ecomerce/checkout/checkout.js b/forms/ecomerce/checkout/checkout.js index 0343ccb0..386d0eb1 100644 --- a/forms/ecomerce/checkout/checkout.js +++ b/forms/ecomerce/checkout/checkout.js @@ -40,12 +40,13 @@ Hedera.Checkout = new Class({ date.setDate(date.getDate() + addDays); } - - this.$.date.value = date; - this.$.method.value = row.deliveryMethod; - this.$.agency.value = row.agencyModeFk; - this.$.address.value = row.addressFk; + this.$.lot.assign({ + date: date, + method: row.deliveryMethod, + agency: row.agencyModeFk, + address: row.addressFk + }); this.autoStepLocked = false; }, @@ -57,15 +58,8 @@ Hedera.Checkout = new Class({ this.disableButtons(true); var query = 'CALL myBasket_configure(#date, #method, #agency, #address)'; - - var batch = new Sql.Batch(); - batch.addParam('method', this.$.method); - batch.addParam('date', this.$.date); - batch.addParam('agency', this.$.agency); - batch.addParam('address', this.$.address); - this.conn.execQuery(query, - this.onBasketConfigured.bind(this), batch); + this.onBasketConfigured.bind(this), this.$.lot.$); }, onBasketConfigured: function(resultSet) { @@ -79,14 +73,14 @@ Hedera.Checkout = new Class({ else Htk.Toast.showMessage(_('OrderStarted')); - this.hash.set({form: 'ecomerce/catalog'}); + this.hash.setAll({form: 'ecomerce/catalog'}); }, onCancelClick: function() { if (this.$.orderForm.numRows > 0) window.history.back(); else - this.hash.set({form: 'ecomerce/orders'}); + this.hash.setAll({form: 'ecomerce/orders'}); }, agencySteps: ['method', 'date', 'address', 'agency', 'confirm-delivery'], @@ -157,13 +151,8 @@ Hedera.Checkout = new Class({ this.$.assistant.moveNext(); }, - addressRenderer: function(builder, form) { - builder.$.address.addEventListener('click', - this.onAddressClick.bind(this, form.$.id)); - }, - onAddressClick: function(addressId) { - this.$.address.value = addressId; + this.$.lot.set('address', addressId); this.goNextStep(); }, @@ -171,7 +160,7 @@ Hedera.Checkout = new Class({ if (this.selectedNode) Vn.Node.removeClass(this.selectedNode, 'selected'); - var row = this.$.addresses.search('id', this.$.address.value); + var row = this.$.addresses.search('id', this.$.lot.$.address); if (row != -1) { var builder = this.$.repeater.getBuilder(row); @@ -201,7 +190,7 @@ Hedera.Checkout = new Class({ } this.autoStepLocked = true; - this.$.agency.value = agency; + this.$.lot.assign({agency}); this.autoStepLocked = false; } else Htk.Toast.showError(_('NoAgeciesAvailableForDate')); diff --git a/forms/ecomerce/checkout/ui.xml b/forms/ecomerce/checkout/ui.xml index ec6db8da..a78b9313 100644 --- a/forms/ecomerce/checkout/ui.xml +++ b/forms/ecomerce/checkout/ui.xml @@ -1,9 +1,6 @@ - - - - + SELECT deliveryMethod, agencyModeFk, addressFk, defaultAgencyFk @@ -19,6 +16,7 @@ CALL vn.zone_getAgency(#address, #date); @@ -30,15 +28,10 @@ AND a.isVisible ORDER BY a.description; DROP TEMPORARY TABLE tmp.zoneGetAgency; - - - - - - CALL vn.zone_getAgency(#address, #date); @@ -50,12 +43,6 @@ AND a.isVisible ORDER BY a.description; DROP TEMPORARY TABLE tmp.zoneGetAgency; - - - - - -
@@ -85,7 +72,8 @@
@@ -125,8 +114,7 @@ + on-change="onAddressChange"> SELECT a.id, a.nickname, p.name province, a.city, a.street, a.isActive, c.country FROM myAddress a @@ -135,13 +123,12 @@ WHERE a.isActive -
-

- -

-

- -

+
+

{{iter.nickname}}

+

{{iter.street}}

@@ -155,7 +142,8 @@
@@ -168,7 +156,8 @@
@@ -179,7 +168,7 @@

Arrival - +

@@ -197,7 +186,7 @@

Pickup - +

Warehouse diff --git a/forms/ecomerce/confirm/confirm.js b/forms/ecomerce/confirm/confirm.js index 3218a47a..36ec2fee 100644 --- a/forms/ecomerce/confirm/confirm.js +++ b/forms/ecomerce/confirm/confirm.js @@ -6,7 +6,7 @@ Hedera.Confirm = new Class({ this.close(); this.isOpen = true; - Hedera.BasketChecker.check(this.conn, + Hedera.BasketChecker.check(this.conn, this.hash, this.onBasketCheck.bind(this)); }, @@ -139,10 +139,13 @@ Hedera.Confirm = new Class({ else var payAmount = this.$.totalAmount.value; - var tpv = new Hedera.Tpv({conn: this.conn}); + var tpv = new Hedera.Tpv({ + conn: this.conn, + hash: this.hash + }); tpv.pay(payAmount, this.$.orderForm.$.companyFk); } else - this.hash.set({'form': 'ecomerce/orders'}); + this.hash.setAll({'form': 'ecomerce/orders'}); } }); diff --git a/forms/ecomerce/orders/orders.js b/forms/ecomerce/orders/orders.js index 81dd4562..47346e9d 100644 --- a/forms/ecomerce/orders/orders.js +++ b/forms/ecomerce/orders/orders.js @@ -3,7 +3,10 @@ Hedera.Orders = new Class({ Extends: Hedera.Form, activate: function() { - this.tpv = new Hedera.Tpv({conn: this.conn}); + this.tpv = new Hedera.Tpv({ + conn: this.conn, + hash: this.hash + }); this.tpv.check(this._onTpvCheck.bind(this)); }, @@ -13,7 +16,7 @@ Hedera.Orders = new Class({ }, onBasketClick: function() { - this.hash.set({form: 'ecomerce/basket'}); + this.hash.setAll({form: 'ecomerce/basket'}); }, repeaterFunc: function(res, form) { diff --git a/forms/ecomerce/ticket/ticket.js b/forms/ecomerce/ticket/ticket.js index 686d541e..c845fa4b 100644 --- a/forms/ecomerce/ticket/ticket.js +++ b/forms/ecomerce/ticket/ticket.js @@ -6,9 +6,8 @@ Hedera.Ticket = new Class({ if (!ticket.value) return; - var batch = new Sql.Batch(); - batch.addValue('ticket', ticket.value); - this.conn.execQuery('CALL myTicket_logAccess(#ticket)', null, batch); + var params = {ticket: ticket.value}; + this.conn.execQuery('CALL myTicket_logAccess(#ticket)', null, params); }, onTicketReady: function(form) { diff --git a/forms/ecomerce/ticket/ui.xml b/forms/ecomerce/ticket/ui.xml index aaf31dc5..2a3c4687 100644 --- a/forms/ecomerce/ticket/ui.xml +++ b/forms/ecomerce/ticket/ui.xml @@ -1,18 +1,10 @@ - - - - - - - - - - - CALL myTicket_get(#ticket) - - - + + + + + CALL myTicket_get(#ticket) +

OrderDetail

@@ -20,7 +12,7 @@ + on-click="this.onPrintClick()"/>
@@ -67,7 +59,7 @@ + lot="params"> CALL myTicket_getRows(#ticket) @@ -108,7 +100,7 @@ + lot="params"> CALL myTicket_getServices(#ticket) @@ -133,7 +125,7 @@ + lot="params"> CALL myTicket_getPackages(#ticket) diff --git a/forms/news/new/new.js b/forms/news/new/new.js index 6dfa57c3..d3b68aec 100644 --- a/forms/news/new/new.js +++ b/forms/news/new/new.js @@ -55,7 +55,7 @@ Hedera.New = new Class({ }, onStatusChange: function() { - if (this.$.newId.value == 0) + if (this.hash.$.new == 0) this.$.iter.insertRow(); }, @@ -71,10 +71,6 @@ Hedera.New = new Class({ onAcceptClick: function() { this.$.iter.set('text', this.editor.getContent()); this.$.iter.performOperations(); - }, - - onReturnClick: function() { - this.hash.set({form: 'news/news'}); } }); }); diff --git a/forms/news/new/ui.xml b/forms/news/new/ui.xml index 982aed46..b9b54964 100644 --- a/forms/news/new/ui.xml +++ b/forms/news/new/ui.xml @@ -1,22 +1,14 @@ - - - - SELECT id, title, text, tag, priority, image - FROM news WHERE id = #new - - - - - - + SELECT id, title, text, tag, priority, image + FROM news WHERE id = #new @@ -28,7 +20,7 @@ + on-click="this.hash.setAll({form: 'news/news'});"/> Tag - - SELECT name, description FROM newsTag - ORDER BY description - + SELECT name, description FROM newsTag + ORDER BY description
diff --git a/forms/news/news/news.js b/forms/news/news/news.js index 54c5d1da..39d4f041 100644 --- a/forms/news/news/news.js +++ b/forms/news/news/news.js @@ -3,7 +3,7 @@ Hedera.News = new Class({ Extends: Hedera.Form ,editNew: function(newId) { - this.hash.set({ + this.hash.setAll({ form: 'news/new', new: newId }); diff --git a/forms/reports/items-form/items-form.js b/forms/reports/items-form/items-form.js index c2f4d85d..defdf2e3 100644 --- a/forms/reports/items-form/items-form.js +++ b/forms/reports/items-form/items-form.js @@ -3,18 +3,15 @@ Hedera.ItemsForm = new Class({ Extends: Hedera.Form ,activate: function() { - this.$.warehouse.value = 7; - this.$.realm.value = null; + this.$.lot.assign({ + warehouse: 7, + realm: null + }); } ,onPreviewClick: function() { - var batch = new Sql.Batch(); - batch.addValues({ - warehouse: this.$.warehouse.value - ,realm: this.$.realm.value - ,rate: this.$.rate.value - }); - this.gui.openReport('items-report', batch); + this.$.lot.set('rate', this.$.rate.value); + this.gui.openReport('items-report', this.$.lot); } }); diff --git a/forms/reports/items-form/ui.xml b/forms/reports/items-form/ui.xml index b735cf7a..8b6462db 100644 --- a/forms/reports/items-form/ui.xml +++ b/forms/reports/items-form/ui.xml @@ -1,4 +1,5 @@ +

Item list

@@ -12,25 +13,19 @@
- - + - - SELECT id, name FROM vn2008.warehouse - WHERE reserve ORDER BY name - + SELECT id, name FROM vn2008.warehouse + WHERE reserve ORDER BY name
- - + - - SELECT id, reino FROM vn2008.reinos - WHERE display != FALSE ORDER BY reino - + SELECT id, reino FROM vn2008.reinos + WHERE display != FALSE ORDER BY reino
diff --git a/forms/reports/shelves/shelves.js b/forms/reports/shelves/shelves.js index 7387b7c1..86cbe00b 100644 --- a/forms/reports/shelves/shelves.js +++ b/forms/reports/shelves/shelves.js @@ -3,49 +3,18 @@ Hedera.Shelves = new Class({ Extends: Hedera.Form ,activate: function() { - this.$.date.value = new Date(); - this.$.useIds.value = false; + this.$.lot.assign({ + date: new Date(), + useIds: false + }); } ,onConfigChange: function() { - const fields = [ - 'realm' - ,'family' - ,'warehouse' - ,'shelf' - ,'namePrefix' - ,'maxAmount' - ,'reportTitle' - ,'showPacking' - ,'stack' - ]; - const config = this.$.config.$; - - if (config) - for (const field of fields) - this.$[field].value = config[field]; + this.$.lot.assignLot(this.$.config); } ,onPreviewClick: function() { - var fields = [ - 'family' - ,'warehouse' - ,'shelf' - ,'namePrefix' - ,'maxAmount' - ,'reportTitle' - ,'showPacking' - ,'stack' - ,'useIds' - ,'date' - ]; - - var batch = new Sql.Batch(); - - for (const field of fields) - batch.addValue(field, this.$[field].value); - - this.gui.openReport('shelves-report', batch); + this.gui.openReport('shelves-report', this.$.lot); } }); diff --git a/forms/reports/shelves/ui.xml b/forms/reports/shelves/ui.xml index 8bb26e4f..376a1bff 100644 --- a/forms/reports/shelves/ui.xml +++ b/forms/reports/shelves/ui.xml @@ -1,14 +1,8 @@ - - - - SELECT c.id, c.name reportTitle, c.namePrefix, c.warehouse, c.family, - c.shelf, c.maxAmount, c.showPacking, c.stack, t.reino_id realm - FROM shelfConfig c - JOIN vn2008.Tipos t ON t.tipo_id = c.family - - - + + + +

Shelves

@@ -25,89 +19,84 @@ + on-ready="this.onConfigChange()"> + + SELECT c.id, c.name reportTitle, c.namePrefix, c.warehouse, c.family, + c.shelf, c.maxAmount, c.showPacking, c.stack, t.reino_id realm + FROM shelfConfig c + JOIN vn2008.Tipos t ON t.tipo_id = c.family + +
- +
- - - - SELECT id, reino FROM vn2008.reinos - WHERE display != FALSE ORDER BY reino - + + + SELECT id, reino FROM vn2008.reinos + WHERE display != FALSE ORDER BY reino
- - - - SELECT tipo_id, Tipo FROM vn2008.Tipos - WHERE reino_id = #realm ORDER BY Tipo - - - - - - + + + SELECT tipo_id, Tipo FROM vn2008.Tipos + WHERE reino_id = #realm ORDER BY Tipo
- - - - SELECT id, name FROM vn2008.warehouse - WHERE reserve ORDER BY name - + + + SELECT id, name FROM vn2008.warehouse + WHERE reserve ORDER BY name
- - - - SELECT id, name FROM shelf - + + + SELECT id, name FROM shelf
- +
- +
- +
diff --git a/js/db/connection.js b/js/db/connection.js index 06e40210..90b16d67 100644 --- a/js/db/connection.js +++ b/js/db/connection.js @@ -12,15 +12,13 @@ var Connection = new Class(); module.exports = Connection; -var Flag = -{ +var Flag = { NOT_NULL : 1 ,PRI_KEY : 2 ,AI : 512 | 2 | 1 }; -var Type = -{ +var Type = { BOOLEAN : 1 ,INTEGER : 3 ,DOUBLE : 4 @@ -53,10 +51,10 @@ Connection.implement({ * * @param {Sql.Stmt} stmt The statement * @param {Function} callback The function to call when operation is done - * @param {Sql.Batch} batch The batch used to set the parameters + * @param {Object} params The query params */ - ,execStmt: function(stmt, callback, batch) { - this.execSql(stmt.render(batch), callback); + ,execStmt: function(stmt, callback, params) { + this.execSql(stmt.render(params), callback); } /** @@ -64,10 +62,10 @@ Connection.implement({ * * @param {String} query The SQL statement * @param {Function} callback The function to call when operation is done - * @param {Sql.Batch} batch The batch used to set the parameters + * @param {Object} params The query params */ - ,execQuery: function(query, callback, batch) { - this.execStmt(new Sql.String({query: query}), callback, batch); + ,execQuery: function(query, callback, params) { + this.execStmt(new Sql.String({query: query}), callback, params); } /* diff --git a/js/db/db-lot.js b/js/db/db-lot.js new file mode 100644 index 00000000..67a3b3c6 --- /dev/null +++ b/js/db/db-lot.js @@ -0,0 +1,109 @@ + +var Connection = require('./connection'); +var Model = require('./model'); + +module.exports = new Class({ + Extends: Vn.Form + ,Tag: 'db-lot' + ,Properties: { + /** + * The connection used to execute the statement. + */ + conn: { + type: Connection + ,set: function(x) { + this.model.conn = x; + } + ,get: function() { + return this.model.conn; + } + }, + /** + * The model query. + */ + query: { + type: String + ,set: function(x) { + this.model.query = x; + } + ,get: function() { + return this.model.query; + } + }, + /** + * The model select statement. + */ + stmt: { + type: Sql.Stmt + ,set: function(x) { + this.model.stmt = x; + } + ,get: function() { + return this.model.stmt; + } + }, + /** + * The lot used to execute the statement. + */ + lot: { + type: Vn.Lot + ,set: function(x) { + this.model.lot = x; + } + ,get: function() { + return this.model.lot; + } + } + } + + ,initialize: function(props) { + this.model = new Model(); + Vn.Form.prototype.initialize.call(this, props); + } + + ,appendChild: function(child) { + if (child.nodeType === Node.TEXT_NODE) + this.query = child.textContent; + } + + ,refresh: function() { + if (this._model) + this._model.refresh(); + } + + ,performOperations: function() { + if (this._model) + this._model.performOperations(); + } + + /** + * Get the index of the column from its name. + * + * @param {string} columnName The column name + * @return {integer} The column index or -1 if column not exists + */ + ,getColumnIndex: function(columnName) { + return this._model ? + this._model.getColumnIndex(columnName) : -1; + } + + /** + * Gets a value from the form using the column index. + * + * @param {string} columnName The column index + * @return {Object} The value + */ + ,getByIndex: function(column) { + return this._model.getByIndex(this._row, column); + } + + /** + * Sets a value on the form using the column index. + * + * @param {string} columnName The column index + * @param {Object} value The new value + */ + ,setByIndex: function(column, value) { + return this._model.setByIndex(this._row, column, value); + } +}); diff --git a/js/db/db.js b/js/db/db.js index e1ba4ec9..073799ae 100644 --- a/js/db/db.js +++ b/js/db/db.js @@ -1,17 +1,17 @@ -require ('sql/sql'); +require('sql/sql'); Db = module.exports = { - Connection : require ('./connection') - ,Result : require ('./result') - ,ResultSet : require ('./result-set') - ,Model : require ('./model') - ,Iterator : require ('./iterator') - ,SimpleIterator : require ('./simple-iterator') - ,Form : require ('./form') - ,Param : require ('./param') - ,Query : require ('./query') - ,Calc : require ('./calc') - ,CalcSum : require ('./calc-sum') + Connection : require('./connection') + ,Result : require('./result') + ,ResultSet : require('./result-set') + ,Model : require('./model') + ,Iterator : require('./iterator') + ,SimpleIterator : require('./simple-iterator') + ,Form : require('./form') + ,Query : require('./query') + ,Calc : require('./calc') + ,CalcSum : require('./calc-sum') + ,Lot : require('./db-lot') }; diff --git a/js/db/form.js b/js/db/form.js index dce50d30..5df50d51 100644 --- a/js/db/form.js +++ b/js/db/form.js @@ -67,7 +67,7 @@ module.exports = new Class({ $: { type: Object ,get: function() { - return this._model.getObject(this._row); + return this._model && this._model.getObject(this._row); } } } @@ -85,13 +85,13 @@ module.exports = new Class({ this.lastRow = this._row; this._ready = ready; - this.signalEmit('status-changed'); + this.emit('status-changed'); if (this._row == -1) this.row = this.lastRow; if (ready) - this.signalEmit('ready'); + this.emit('ready'); this.iterChanged(); } diff --git a/js/db/iterator.js b/js/db/iterator.js index e0303ebd..98f3078e 100644 --- a/js/db/iterator.js +++ b/js/db/iterator.js @@ -44,10 +44,10 @@ module.exports = new Class({ } /** - * Emits the 'iter-changed' signal on the form. + * Emits the 'change' signal on the form. */ ,iterChanged: function() { - this.signalEmit('iter-changed'); + this.emit('change'); } /** @@ -90,6 +90,16 @@ module.exports = new Class({ return this._model.getObject(this._row); } + /** + * Sets a value on the form. + * + * @param {String} columnName The column name + */ + ,get: function(columnName) { + if (!this._model) return undefined; + return this._model.get(this._row, columnName); + } + /** * Sets a value on the form. * diff --git a/js/db/model.js b/js/db/model.js index 4b2e60ee..942b9fec 100644 --- a/js/db/model.js +++ b/js/db/model.js @@ -73,16 +73,16 @@ Model.implement({ } }, /** - * The batch used to execute the statement. + * The lot used to execute the statement. */ - batch: { - type: Sql.Batch + lot: { + type: Vn.LotIface ,set: function(x) { - this.link({_batch: x}, {'changed': this._autoLoad}); - this._autoLoad(); + this.link({_lot: x}, {'change': this._onLotChange}); + this._onLotChange(); } ,get: function() { - return this._batch; + return this._lot; } }, /** @@ -189,7 +189,7 @@ Model.implement({ ,_conn: null ,_resultIndex: 0 - ,_batch: null + ,_lot: null ,_stmt: null ,_status: Status.CLEAN ,data: null @@ -197,6 +197,7 @@ Model.implement({ ,columns: null ,columnMap: null ,_updatable: false + ,_paramsChanged: true ,_requestedSortIndex: -1 ,_requestedSortName: null @@ -213,7 +214,7 @@ Model.implement({ ,_requestedMainTable: null ,initialize: function(props) { - this.parent(props); + Vn.Object.prototype.initialize.call(this, props); this._cleanData(); this._setStatus(Status.CLEAN); } @@ -224,14 +225,66 @@ Model.implement({ } ,loadXml: function(builder, node) { - this.parent(builder, node); + Vn.Object.prototype.loadXml.call(this, builder, node); var query = node.firstChild.nodeValue; if (query) this.query = query; } - + + ,_getHolders(stmt) { + if (!stmt) return null; + let holders = this._stmt.findHolders(); + if (!holders) return null; + + if (this._lot) { + const params = this._lot.params; + for (const holder of holders) + if (params[holder] instanceof Sql.Object) { + const paramHolders = params[holder].findHolders(); + if (paramHolders) + holders = holders.concat(paramHolders); + } + } + + return holders; + } + + ,_getHolderValues: function() { + let holders = this._getHolders(this._stmt); + if (!holders) return null; + + const lotParams = this._lot ? this._lot.params : {}; + const params = {}; + for (const holder of holders) + if (!(lotParams[holder] instanceof Sql.Object)) + params[holder] = lotParams[holder]; + + return params; + } + + ,_getHolderParams: function() { + let holders = this._getHolders(this._stmt); + if (!holders) return null; + + const lotParams = this._lot ? this._lot.params : {}; + const params = {}; + for (const holder of holders) + params[holder] = lotParams[holder]; + + return params; + } + + ,_onLotChange: function() { + const params = this._getHolderValues(); + this._paramsChanged = !Vn.Value.equals(params, this._lastParams); + this._lastParams = params; + + if (this.autoLoad) + this.lazyRefresh(); + } + ,_autoLoad: function() { if (this.autoLoad) this.refresh(); @@ -239,36 +292,37 @@ Model.implement({ this.clean(); } + ,_isReady: function(params) { + if (!this._stmt || !this._conn) + return false; + + for (const param in params) + if (params[param] === undefined) + return false; + + return true; + } + + ,lazyRefresh: function() { + if (this._paramsChanged) + this.refresh(); + } + /** * Refresh the model data reexecuting the query on the database. */ ,refresh: function() { - var ready = false; - - if (this._stmt && this._conn) { - var ids = this._stmt.findHolders(); - - if (ids) { - if (this._batch && this._batch.isReady()) { - ready = true; + const params = this._getHolderParams(); - for (var i = 0; i < ids.length; i++) - if (!this._batch.get(ids[i])) { - ready = false; - break; - } - } - } else - ready = true; - } - - if (ready) { + if (this._isReady(params)) { this._setStatus(Status.LOADING); - this._conn.execStmt(this._stmt, this._selectDone.bind(this), this._batch); + this._paramsChanged = false; + this._conn.execStmt(this._stmt, + this._selectDone.bind(this), params); } else this.clean(); } - + ,clean: function() { this._cleanData(); this._setStatus(Status.CLEAN); @@ -344,7 +398,7 @@ Model.implement({ this._updatable = this._mainTable !== null && this._requestedUpdatable; if (oldValue != this._updatable) - this.signalEmit('updatable-changed'); + this.emit('updatable-changed'); } ,_refreshMainTable: function() { @@ -492,7 +546,7 @@ Model.implement({ */ ,get: function(rowIndex, columnName) { if (this.checkRowExists(rowIndex)) - return this.data[rowIndex][columnName]; + return this.data[rowIndex][columnName]; } /** @@ -541,9 +595,9 @@ Model.implement({ && op.oldValues[columnName] === undefined) op.oldValues[columnName] = row[columnName]; - this.signalEmit('row-updated-before', rowIndex); + this.emit('row-updated-before', rowIndex); row[columnName] = value; - this.signalEmit('row-updated', rowIndex, [columnName]); + this.emit('row-updated', rowIndex, [columnName]); if (this.mode == Mode.ON_CHANGE && !(op.type & Operation.INSERT)) @@ -596,12 +650,12 @@ Model.implement({ op.type |= Operation.DELETE; if (!this._requestedMainTable) { - this.signalEmit('row-deleted-before', rowIndex); + this.emit('row-deleted-before', rowIndex); this.data.splice(rowIndex, 1); - this.signalEmit('row-deleted', rowIndex); + this.emit('row-deleted', rowIndex); this._refreshRowIndexes(rowIndex); } else { - this.signalEmit('row-updated-before', rowIndex); + this.emit('row-updated-before', rowIndex); if (!op.oldValues) op.oldValues = []; @@ -619,7 +673,7 @@ Model.implement({ updatedCols.push(i); } - this.signalEmit('row-updated', rowIndex, updatedCols); + this.emit('row-updated', rowIndex, updatedCols); } if (this.mode === Mode.ON_CHANGE) @@ -650,7 +704,7 @@ Model.implement({ var op = this._createOperation(rowIndex); op.type |= Operation.INSERT; - this.signalEmit('row-inserted', rowIndex); + this.emit('row-inserted', rowIndex); return rowIndex; } @@ -662,14 +716,14 @@ Model.implement({ var ops = this._operations; if (ops.length === 0) { - this.signalEmit('operations-done'); + this.emit('operations-done'); return; } var stmts = new Sql.MultiStmt(); var query = new Sql.String({query: 'START TRANSACTION'}); - stmts.addStmt(query); + stmts.push(query); for (var i = 0; i < ops.length; i++) { query = null; @@ -690,12 +744,12 @@ Model.implement({ for (var tableIndex in op.tables) { var stmt = this._createDmlQuery(op, parseInt(tableIndex)); - query.addStmt(stmt); + query.push(stmt); } } if (query) { - stmts.addStmt(query); + stmts.push(query); } else { console.warn('Db.Model: %s', _('ErrorSavingChanges')); return; @@ -703,7 +757,7 @@ Model.implement({ } var query = new Sql.String({query: 'COMMIT'}); - stmts.addStmt(query); + stmts.push(query); this._conn.execStmt(stmts, this._onOperationsDone.bind(this, ops)); @@ -764,8 +818,8 @@ Model.implement({ dmlQuery.addTarget(target); - multiStmt.addStmt(dmlQuery); - multiStmt.addStmt(select); + multiStmt.push(dmlQuery); + multiStmt.push(select); return multiStmt; } @@ -795,7 +849,7 @@ Model.implement({ if (op.type & Operation.DELETE) { resultSet.fetchResult(); } else if (op.type & (Operation.INSERT | Operation.UPDATE)) { - this.signalEmit('row-updated-before', row.index); + this.emit('row-updated-before', row.index); var updatedCols = []; var cols = this.columns; @@ -823,14 +877,14 @@ Model.implement({ } } - this.signalEmit('row-updated', row.index, updatedCols); + this.emit('row-updated', row.index, updatedCols); } } resultSet.fetchResult(); // if (isOperation) - this.signalEmit('operations-done'); + this.emit('operations-done'); } /** @@ -844,9 +898,9 @@ Model.implement({ if (op.type & Operation.DELETE && !(op.type & Operation.INSERT)) { this.data.splice(row.index, 0, row); - this.signalEmit('row-inserted', row.index); + this.emit('row-inserted', row.index); } else if (op.type & Operation.UPDATE) { - this.signalEmit('row-updated-before', row.index); + this.emit('row-updated-before', row.index); var updatedCols = []; var cols = this.columns; @@ -858,7 +912,7 @@ Model.implement({ updatedCols.push(i); } - this.signalEmit('row-updated', row.index, updatedCols); + this.emit('row-updated', row.index, updatedCols); } } @@ -1064,8 +1118,8 @@ Model.implement({ ,_setStatus: function(status) { this._status = status; - this.signalEmit('status-changed', status); - this.signalEmit('status-changed-after', status); + this.emit('status-changed', status); + this.emit('status-changed-after', status); } ,_createTarget: function(tableIndex) { @@ -1088,8 +1142,8 @@ Model.implement({ const column = this.columnMap[pk]; const equalOp = new Sql.Operation({type: Sql.Operation.Type.EQUAL}); - equalOp.exprs.add(new Sql.Field({name: column.orgname})); - where.exprs.add(equalOp); + equalOp.push(new Sql.Field({name: column.orgname})); + where.push(equalOp); let pkValue = null; @@ -1100,9 +1154,9 @@ Model.implement({ pkValue = op.row[pk]; if (pkValue) - equalOp.exprs.add(new Sql.Value({value: pkValue})); + equalOp.push(new Sql.Value({value: pkValue})); else if (column.flags & Connection.Flag.AI && !useOldValues) - equalOp.exprs.add(new Sql.Function({name: 'LAST_INSERT_ID'})); + equalOp.push(new Sql.Function({name: 'LAST_INSERT_ID'})); else return null; } diff --git a/js/db/param.js b/js/db/param.js deleted file mode 100644 index 032a7231..00000000 --- a/js/db/param.js +++ /dev/null @@ -1,105 +0,0 @@ - -var Form = require('./form'); - -module.exports = new Class({ - Extends: Vn.Param - ,Tag: 'db-param' - ,Parent: 'form' - ,Properties: - { - /** - * The form field referenced by this param. - */ - column: - { - type: String - ,set: function(x) { - this._columnName = x; - this.refresh(); - } - ,get: function() { - this._columnName; - } - }, - /** - * The form referenced by this param. - */ - form: - { - type: Form - ,set: function(x) { - this.link({_form: x}, - { - 'status-changed': this.onFormChange - ,'iter-changed': this.onIterChange - }); - - this.refresh(); - } - ,get: function() { - return this._form; - } - }, - /** - * Determines whether the link to the form is unidirectional, ie, a - * change in the form updates the parameter but not vice versa. - */ - oneWay: - { - type: Boolean - ,set: function(x) { - this._oneWay = x; - } - ,get: function() { - return this._oneWay; - } - } - } - - ,_columnName: null - ,_form: null - ,_formLock: false - ,_columnIndex: -1 - ,_oneWay: false - ,_formValue: null - - ,initialize: function(props) { - this.parent(props); - this.on('changed', this.onChange, this); - } - - ,refresh: function() { - if (this._form) { - this.onFormChange(); - this.onIterChange(); - } - } - - ,onFormChange: function() { - if (this._columnName != null) - this._columnIndex = this._form.getColumnIndex(this._columnName); - } - - ,onIterChange: function() { - if (this._oneWay && this.value != null) - return; - - this._formLock = true; - - var formValue; - - if (this._columnIndex !== -1) - formValue = this._form.getByIndex(this._columnIndex); - else - formValue = undefined; - - this.value = formValue; - this._formLock = false; - } - - ,onChange: function() { - if (!this._formLock && this._columnIndex != -1 && !this.oneWay) - this._form.setByIndex(this._columnIndex, this._value); - } -}); - diff --git a/js/db/query.js b/js/db/query.js index eca97c8e..95685a25 100644 --- a/js/db/query.js +++ b/js/db/query.js @@ -1,100 +1,78 @@ -var Connection = require ('./connection'); +var Connection = require('./connection'); -module.exports = new Class -({ +module.exports = new Class({ Extends: Vn.Object ,Tag: 'db-query' - ,Properties: - { + ,Properties: { /** * The connection used to execute the statement. */ - conn: - { + conn: { type: Connection - ,set: function (x) - { + ,set: function(x) { this._conn = x; - this.onChange (); + this.onChange(); } - ,get: function () - { + ,get: function() { return this._conn; } }, /** * The model query. */ - query: - { + query: { type: String - ,set: function (x) - { - this._stmt = new Sql.String ({query: x}); - this.onChange (); + ,set: function(x) { + this._stmt = new Sql.String({query: x}); + this.onChange(); } - ,get: function () - { - return this._stmt.render (null); + ,get: function() { + return this._stmt.render(null); } }, /** * The model select statement. */ - stmt: - { + stmt: { type: Sql.Stmt - ,set: function (x) - { + ,set: function(x) { this._stmt = x; - this.onChange (); + this.onChange(); } - ,get: function () - { + ,get: function() { return this._stmt; } }, /** - * The batch used to execute the statement. + * The lot used to execute the statement. */ - batch: - { - type: Sql.Batch - ,set: function (x) - { - this.link ({_batch: x}, {'changed': this.onChange}); - this.onChange (); + lot: { + type: Vn.LotIface + ,set: function(x) { + this.link({_lot: x}, {'change': this.onChange}); + this.onChange(); } - ,get: function () - { - return this._batch; + ,get: function() { + return this._lot; } }, /** * Wether to execute automatically de query que it's ready. */ - autoLoad: - { + autoLoad: { type: Boolean, value: false } } - ,initialize: function (props) - { - this.parent (props); - } - - ,appendChild: function (child) - { + ,appendChild: function(child) { if (child.nodeType === Node.TEXT_NODE) this.query = child.textContent; } - ,loadXml: function (builder, node) - { - this.parent (builder, node); + ,loadXml: function(builder, node) { + Vn.Object.prototype.loadXml.call(this, builder, node); var query = node.firstChild.nodeValue; @@ -102,21 +80,18 @@ module.exports = new Class this.query = query; } - ,execute: function () - { - this._conn.execStmt (this._stmt, - this.onQueryDone.bind (this), this._batch); + ,execute: function() { + this._conn.execStmt(this._stmt, + this.onQueryDone.bind(this), this._lot); } - ,onQueryDone: function (resultSet) - { - this.signalEmit ('ready', resultSet); + ,onQueryDone: function(resultSet) { + this.emit('ready', resultSet); } - ,onChange: function () - { - if (this.autoLoad && this._conn && this._stmt && this._batch) - this.execute (); + ,onChange: function() { + if (this.autoLoad && this._conn && this._stmt && this._lot) + this.execute(); } }); diff --git a/js/hedera/app.js b/js/hedera/app.js index faded048..0c325c4a 100644 --- a/js/hedera/app.js +++ b/js/hedera/app.js @@ -16,7 +16,7 @@ module.exports = new Class({ ,initialize: function() { window.onerror = this._onWindowError.bind(this); window.onunload = this._onWindowUnload.bind(this); - Vn.Hash.initialize(); + this._hash = new Vn.Hash({window: window}); var conn = new Db.Connection(); this.link({_conn: conn}, {'error': this._onConnError}); @@ -28,7 +28,10 @@ module.exports = new Class({ if (this.tryAutoLogin()) return; - var login = this._login = new Login({conn: this._conn}); + var login = this._login = new Login({ + conn: this._conn, + hash: this._hash + }); login.on('login', this._onLogin, this); login.show(); } @@ -39,7 +42,10 @@ module.exports = new Class({ if (this._gui) return; - var gui = this._gui = new Gui({conn: this._conn}); + var gui = this._gui = new Gui({ + conn: this._conn, + hash: this._hash + }); gui.on('logout', this._onLogout, this); gui.show(); } @@ -164,6 +170,7 @@ module.exports = new Class({ this._freeGui(); this.deinitAutoLogin(); this._conn.unref(); + this._hash.unref(); } // Auto login functionality @@ -171,15 +178,17 @@ module.exports = new Class({ ,_firstLogin: true ,initAutoLogin: function() { - var isGuest = new Vn.HashParam({ + var isGuest = new Vn.Param({ + lot: this._hash, type: Boolean, - key: 'guest' + name: 'guest' }); this.link({_isGuest: isGuest}, {'changed': this._onGuestChange}); - var token = new Vn.HashParam({ + var token = new Vn.Param({ + lot: this._hash, type: String, - key: 'token' + name: 'token' }); this.link({_token: token}, {'changed': this._onTokenChange}); } diff --git a/js/hedera/basket-checker.js b/js/hedera/basket-checker.js index abde2365..624a76ff 100644 --- a/js/hedera/basket-checker.js +++ b/js/hedera/basket-checker.js @@ -1,6 +1,7 @@ module.exports = { - check: function(conn, callback) { + check: function(conn, hash, callback) { + this.hash = hash; conn.execQuery('CALL myBasket_check', this._onBasketCheck.bind(this, callback)); }, @@ -18,7 +19,7 @@ module.exports = { if (callback) callback(isOk); if (!isOk) - Vn.Hash.set({form: 'ecomerce/checkout'}); + this.hash.setAll({form: 'ecomerce/checkout'}); } }; diff --git a/js/hedera/form.js b/js/hedera/form.js index c49484f5..63f273e4 100644 --- a/js/hedera/form.js +++ b/js/hedera/form.js @@ -15,25 +15,33 @@ module.exports = new Class({ ,loadUi: function() { if (!this.isOpen) return; + + const conn = this.conn; + const hash = this.hash; - var builder = new Vn.Builder(); + const builder = new Vn.Builder(); builder.compileFile('forms/'+ this.formInfo.path +'/ui.xml'); - var scope = this.builder = builder.load(null, this); + const scope = this.builder = builder.load(null, this); this.$ = scope.$; - scope.link(null, {conn: this.conn}); - + scope.link(null, {conn, hash}); this.node = scope.$.form; - var models = scope.getByTagName('db-model'); + const paramsLot = this.$.params; + if (paramsLot) { + paramsLot.source = hash; + this.params = paramsLot; + } - for (var i = 0; i < models.length; i++) - models[i].conn = this.conn; + function setConnection(tagName) { + const objects = scope.getByTagName(tagName); + for (let i = 0; i < objects.length; i++) + objects[i].conn = conn; + } - var queries = scope.getByTagName('db-query'); - - for (var i = 0; i < queries.length; i++) - queries[i].conn = this.conn; + const tags = ['db-model', 'db-query', 'db-lot']; + for (let i = 0; i < tags.length; i++) + setConnection(tags[i]); if (this.node) { this.gui.setForm(this.node); @@ -95,7 +103,7 @@ module.exports = new Class({ ,_destroy: function() { this.close(); - this.parent(); + Vn.Object.prototype._destroy.call(this); } }); diff --git a/js/hedera/gui.js b/js/hedera/gui.js index 982c9ac8..355ea778 100644 --- a/js/hedera/gui.js +++ b/js/hedera/gui.js @@ -5,7 +5,7 @@ var Tpl = require('./gui.xml').default; require('./gui.scss'); module.exports = new Class({ - Extends: Htk.Component, + Extends: Vn.Component, Properties: { conn: { type: Db.Connection @@ -30,7 +30,7 @@ module.exports = new Class({ ,_navbarVisible: true ,initialize: function(props) { - this.builderInitString(Tpl); + this.loadTemplateFromString(Tpl); this.loadingCount = 0; this.$.background.onclick = function() {}; @@ -39,7 +39,7 @@ module.exports = new Class({ event.stopPropagation(); }); - this.parent(props); + Vn.Component.prototype.initialize.call(this, props); var sql = 'SELECT id, name, nickname FROM account.myUser;' +'SELECT defaultForm FROM config;' @@ -64,8 +64,10 @@ module.exports = new Class({ window.addEventListener('scroll', this._onScrollHandler ); } - this.hash = Vn.Hash; - this.formParam = new Vn.HashParam({key: 'form'}); + this.formParam = new Vn.Param({ + lot: this.hash, + name: 'form', + }); this.formParam.on('changed', this._onFormChange, this); if (!localStorage.getItem('hederaCookies')) { @@ -101,7 +103,7 @@ module.exports = new Class({ } ,_onConnClose: function() { - this.signalEmit('logout'); + this.emit('logout'); } ,_onConnLoadChange: function(conn, isLoading) { @@ -206,7 +208,7 @@ module.exports = new Class({ var a = this.createElement('a'); if (row.path) { - a.href = Vn.Hash.make({form: row.path}); + a.href = this.hash.make({form: row.path}); this.menuOptions[row.path] = a; } @@ -445,14 +447,14 @@ module.exports = new Class({ //++++++++++++++++++++++++++++++++++++++++++++++++++++++ Reports - ,openReport: function(reportName, batch) { + ,openReport: function(reportName, lot) { this.loaderPush(); var module = new Module('reports', reportName); - module.addCallback(this._onReportLoad.bind(this, batch)); + module.addCallback(this._onReportLoad.bind(this, lot)); } - ,_onReportLoad: function(batch, module) { + ,_onReportLoad: function(lot, module) { this.loaderPop(); if (module.error) { @@ -461,7 +463,7 @@ module.exports = new Class({ } var report = new module.klass(module, this); - report.open(batch); + report.open(lot); } //++++++++++++++++++++++++++++++++++++++++++++++++++++++ Supplant @@ -510,7 +512,6 @@ module.exports = new Class({ ,_destroy: function() { this.hide(); - this.parent(); + Vn.Component.prototype.initialize.call(this); } }); - diff --git a/js/hedera/hedera.js b/js/hedera/hedera.js index 0b36aa01..5389daea 100644 --- a/js/hedera/hedera.js +++ b/js/hedera/hedera.js @@ -1,7 +1,5 @@ require('htk/htk'); -require('./responsive.scss'); -require('./style.scss'); Hedera = module.exports = { Login : require('./login') diff --git a/js/hedera/locale/ca.yml b/js/hedera/locale/ca.yml index c2ca263f..4de7b265 100644 --- a/js/hedera/locale/ca.yml +++ b/js/hedera/locale/ca.yml @@ -61,4 +61,4 @@ Training: Formació Agencies: Agències Configuration: Configuració Account: Compte -Addresses: Direccions +Addresses: Adreces diff --git a/js/hedera/login.js b/js/hedera/login.js index e47cdf9c..36fba7c7 100644 --- a/js/hedera/login.js +++ b/js/hedera/login.js @@ -3,7 +3,7 @@ require('./login.scss'); var Tpl = require('./login.xml').default; module.exports = new Class({ - Extends: Htk.Component, + Extends: Vn.Component, Properties: { conn: @@ -19,8 +19,8 @@ module.exports = new Class({ } ,initialize: function(props) { - this.parent(props); - this.builderInitString(Tpl); + Vn.Component.prototype.initialize.call(this, props); + this.loadTemplateFromString(Tpl); //this.$.socialBar.conn = this._conn; @@ -69,7 +69,7 @@ module.exports = new Class({ if (user) localStorage.setItem('hederaLastUser', user); - this.signalEmit('login'); + this.emit('login'); } else { this._focusUserInput(); throw error; diff --git a/js/hedera/module.js b/js/hedera/module.js index 2810921b..6961ef61 100644 --- a/js/hedera/module.js +++ b/js/hedera/module.js @@ -1,6 +1,5 @@ -module.exports = new Class -({ +module.exports = new Class({ basePath: null ,path: null ,moduleName: null @@ -11,84 +10,74 @@ module.exports = new Class ,error: false ,ready: false - ,initialize: function (basePath, path) - { + ,initialize: function(basePath, path) { var absPath = basePath +'/'+ path; - var aux = path.split ('/'); + var aux = path.split('/'); var moduleName = aux[aux.length - 1]; - Vn.Locale.load (absPath, - this.onLocaleReady.bind (this)); - Vn.includeJs (absPath +'/'+ moduleName +'.js', - this.onJsReady.bind (this)); - Vn.loadXml (absPath +'/ui.xml', - this.onUiReady.bind (this)); + Vn.Locale.load(absPath, + this.onLocaleReady.bind(this)); + Vn.includeJs(absPath +'/'+ moduleName +'.js', + this.onJsReady.bind(this)); + Vn.loadXml(absPath +'/ui.xml', + this.onUiReady.bind(this)); this.basePath = basePath; this.path = path; this.moduleName = moduleName; } - ,addCallback: function (callback) - { + ,addCallback: function(callback) { if (!this.ready) - this.callbacks.push (callback); + this.callbacks.push(callback); else - callback (this); + callback(this); } - ,onLocaleReady: function (success) - { + ,onLocaleReady: function() { this.localeReady = true; - this.onReady (); + this.onReady(); } - ,onJsReady: function (success) - { + ,onJsReady: function(success) { this.jsReady = true; this.error = !success; - this.onReady (); + this.onReady(); } - ,onUiReady: function (success) - { + ,onUiReady: function(success) { this.uiReady = true; this.error = !success; - this.onReady (); + this.onReady(); } - ,onReady: function () - { + ,onReady: function() { if (!(this.localeReady && this.jsReady && this.uiReady)) return; this.ready = true; - var klassName = this.toCamelCase (this.moduleName); + var klassName = this.toCamelCase(this.moduleName); try { this.klass = Hedera[klassName]; - } - catch (e) - { + } catch (e) { this.error = true; - console.error (e); + console.error(e); } var callbacks = this.callbacks; this.callbacks = null; for (var i = 0; i < callbacks.length; i++) - callbacks[i] (this); + callbacks[i](this); } - ,toCamelCase: function (dashedName) - { - var camelCase = dashedName.charAt (0).toUpperCase (); - camelCase += dashedName.substr (1).replace (/\w\-\w/g, function (token) - { - return token.charAt (0) + token.charAt (2).toUpperCase (); + ,toCamelCase: function(dashedName) { + var camelCase = dashedName.charAt(0).toUpperCase(); + camelCase += dashedName.substr(1).replace(/\w-\w/g, function(token) { + return token.charAt(0) + token.charAt(2).toUpperCase(); }); return camelCase; } diff --git a/js/hedera/report.js b/js/hedera/report.js index 6be4e667..06c53530 100644 --- a/js/hedera/report.js +++ b/js/hedera/report.js @@ -6,11 +6,11 @@ module.exports = new Class({ this.info = moduleInfo; this.gui = gui; this.conn = gui.conn; - this.parent(null); + Vn.Object.prototype.initialize.call(this); } - ,open: function(batch) { - this.batch = batch; + ,open: function(lot) { + this.lot = lot; this.createWindow(); } @@ -70,7 +70,7 @@ module.exports = new Class({ var scope = this.scope = builder.load(this.doc, this); scope.link(null, { - batch: this.batch, + lot: this.lot, conn: this.conn }); this.$ = scope.$; diff --git a/js/hedera/social-bar.js b/js/hedera/social-bar.js index d082068c..b70bf398 100644 --- a/js/hedera/social-bar.js +++ b/js/hedera/social-bar.js @@ -1,33 +1,26 @@ +require('./social-bar.scss'); -module.exports = new Class -({ - Extends: Htk.Widget +module.exports = new Class({ + Extends: Vn.Component ,Tag: 'htk-social-bar' - ,Properties: - { - conn: - { + ,Properties: { + conn: { type: Db.Connection - ,set: function (x) - { + ,set: function(x) { this._conn = x; - this._refresh (); + this._refresh(); } - ,get: function () - { + ,get: function() { return this._conn; } }, - priority: - { + priority: { type: Number - ,set: function (x) - { + ,set: function(x) { this._priority = x; - this._refresh (); + this._refresh(); } - ,get: function () - { + ,get: function() { return this._priority; } } @@ -35,42 +28,37 @@ module.exports = new Class ,_priority: 0 - ,initialize: function () - { - var node = this.createRoot ('div'); + ,initialize: function() { + var node = this.createRoot('div'); node.className = 'htk-social-bar'; } - ,_refresh: function () - { + ,_refresh: function() { if (!this._conn || this._priority === null) return; - var batch = new Sql.Batch (); - batch.addValue ('priority', this._priority); + const params = {priority: this._priority}; var query = 'SELECT title, link, icon FROM social ' +'WHERE priority >= #priority ORDER BY priority'; - this._conn.execQuery (query, this._onQueryDone.bind (this), batch); + this._conn.execQuery(query, this._onQueryDone.bind(this), params); } - ,_onQueryDone: function (resultSet) - { - Vn.Node.removeChilds (this._node); - var res = resultSet.fetchResult (); + ,_onQueryDone: function(resultSet) { + Vn.Node.removeChilds(this._node); + var res = resultSet.fetchResult(); - while (res.next ()) - { - var a = this.createElement ('a'); - a.href = res.get ('link'); + while (res.next()) { + var a = this.createElement('a'); + a.href = res.get('link'); a.target = '_blank'; - this._node.appendChild (a); + this._node.appendChild(a); - var img = this.createElement ('img'); - img.src = 'image/social/'+ res.get ('icon'); - img.alt = res.get ('title'); - img.title = res.get ('title'); - a.appendChild (img); + var img = this.createElement('img'); + img.src = 'image/social/'+ res.get('icon'); + img.alt = res.get('title'); + img.title = res.get('title'); + a.appendChild(img); } } }); diff --git a/js/hedera/social-bar.scss b/js/hedera/social-bar.scss new file mode 100644 index 00000000..e540930a --- /dev/null +++ b/js/hedera/social-bar.scss @@ -0,0 +1,13 @@ + +.htk-social-bar { + text-align: center; + + a { + display: inline-block; + margin: 0 .1em; + } + img { + height: 1.8em; + width: 1.8em; + } +} diff --git a/js/hedera/tpv.js b/js/hedera/tpv.js index 3dd6e795..0ad62275 100644 --- a/js/hedera/tpv.js +++ b/js/hedera/tpv.js @@ -6,16 +6,16 @@ module.exports = new Class({ ,tpvStatus: null ,check: function(callback) { - this.tpvOrder = Vn.Hash.$.tpvOrder; - this.tpvStatus = Vn.Hash.$.tpvStatus; + this.tpvOrder = this.hash.$.tpvOrder; + this.tpvStatus = this.hash.$.tpvStatus; if (this.tpvStatus) { - var batch = new Sql.Batch(); - batch.addValue('transaction', this.tpvOrder); - batch.addValue('status', this.tpvStatus); - + const params = { + transaction: this.tpvOrder, + status: this.tpvStatus + }; var query = 'CALL myTpvTransaction_end(#transaction, #status)'; - this.conn.execQuery(query, null, batch); + this.conn.execQuery(query, null, params); } if (callback) @@ -66,15 +66,14 @@ module.exports = new Class({ } ,retryPay: function() { - var batch = new Sql.Batch(); - batch.addValue('transaction', parseInt(this.tpvOrder)); + const params = {transaction: parseInt(this.tpvOrder)}; var query = 'SELECT t.amount, m.companyFk ' +'FROM myTpvTransaction t ' +'JOIN tpvMerchant m ON m.id = t.merchantFk ' +'WHERE t.id = #transaction'; this.conn.execQuery(query, - this._onRetryPayDone.bind(this), batch); + this._onRetryPayDone.bind(this), params); } ,_onRetryPayDone: function(resultSet) { diff --git a/js/htk/assistant-bar.js b/js/htk/assistant-bar/index.js similarity index 96% rename from js/htk/assistant-bar.js rename to js/htk/assistant-bar/index.js index 0f944549..eb9859fe 100644 --- a/js/htk/assistant-bar.js +++ b/js/htk/assistant-bar/index.js @@ -1,9 +1,9 @@ - -var Widget = require('./widget'); -var Assistant = require('./assistant'); +require('./style.scss'); +var Component = require('vn/component'); +var Assistant = require('../assistant'); module.exports = new Class({ - Extends: Widget, + Extends: Component, Tag: 'htk-assistant-bar', Properties: { assistant: { diff --git a/js/htk/assistant-bar/style.scss b/js/htk/assistant-bar/style.scss new file mode 100644 index 00000000..4b6877d3 --- /dev/null +++ b/js/htk/assistant-bar/style.scss @@ -0,0 +1,66 @@ + +.htk-assistant-bar { + position: relative; + padding: .8em; + display: flex; + align-items: center; + justify-content: space-between; + box-sizing: border-box; + width: 100%; + + & > button { + border-radius: 50%; + padding: .5em; + margin: 0; + text-align: center; + + & > img { + display: block; + width: 1.8em; + padding: .5em; + } + } + & > .end { + display: none; + color: #8cc63f; + + & > .icon { + font-size: 1.6rem; + } + } + & > .steps { + display: flex; + align-items: center; + + & > div { + background-color: #AAA; + width: .5em; + height: .5em; + cursor: pointer; + border-radius: 50%; + margin: .5em; + + transition-property: width, height; + transition-duration: 100ms; + transition-timing-function: ease-in-out; + + &.selected { + background-color: #666; + width: 1em; + height: 1em; + } + &:hover { + opacity: .7; + } + } + & > img { + width: 1.3em; + margin: 0 .2em; + cursor: pointer; + + &:hover { + opacity: .7; + } + } + } +} diff --git a/js/htk/assistant.js b/js/htk/assistant/index.js similarity index 91% rename from js/htk/assistant.js rename to js/htk/assistant/index.js index 1a17dc68..dd9c411c 100644 --- a/js/htk/assistant.js +++ b/js/htk/assistant/index.js @@ -1,13 +1,13 @@ - -var Widget = require('./widget'); -var Step = require('./step'); -var Toast = require('./toast'); +require('./style.scss'); +var Component = require('vn/component'); +var Step = require('../step'); +var Toast = require('../toast'); /** * A simple assistant with steps. */ module.exports = new Class({ - Extends: Widget, + Extends: Component, Tag: 'htk-assistant', Properties: { /** @@ -42,7 +42,7 @@ module.exports = new Class({ set: function(x) { this._stepsIndex = x; this.setStep(this._stepIndex); - this.signalEmit('steps-change'); + this.emit('steps-change'); }, get: function() { return this._stepsIndex; @@ -83,7 +83,7 @@ module.exports = new Class({ initialize: function(props) { var node = this.createRoot('div'); node.className = 'htk-assistant'; - this.parent(props); + Component.prototype.initialize.call(this, props); }, appendChild: function(step) { @@ -132,7 +132,7 @@ module.exports = new Class({ this._stepName = stepName; this.currentStep = step; step.show(); - this.signalEmit('step-change', stepIndex); + this.emit('step-change', stepIndex); if (this.stepFunc) this.stepFunc(step); diff --git a/js/htk/assistant/style.scss b/js/htk/assistant/style.scss new file mode 100644 index 00000000..b5261194 --- /dev/null +++ b/js/htk/assistant/style.scss @@ -0,0 +1,14 @@ + +.htk-assistant > div { + display: none; + + & > h2 { + text-align: center; + font-weight: normal; + font-size: 1.5rem; + margin: 0; + padding: 0; + margin-bottom: 1em; + font-weight: bold; + } +} \ No newline at end of file diff --git a/js/htk/field/bar-button.js b/js/htk/bar-button/index.js similarity index 73% rename from js/htk/field/bar-button.js rename to js/htk/bar-button/index.js index b923f17a..aad3e764 100644 --- a/js/htk/field/bar-button.js +++ b/js/htk/bar-button/index.js @@ -1,5 +1,5 @@ -var Button = require('./button'); +var Button = require('../button'); module.exports = new Class({ Extends: Button diff --git a/js/htk/field/button.js b/js/htk/button/index.js similarity index 97% rename from js/htk/field/button.js rename to js/htk/button/index.js index 7d105b1c..f3d1ddb0 100644 --- a/js/htk/field/button.js +++ b/js/htk/button/index.js @@ -1,3 +1,4 @@ +require('./style.scss'); module.exports = new Class({ Extends: Htk.Field diff --git a/js/htk/button/style.scss b/js/htk/button/style.scss new file mode 100644 index 00000000..4ac5ef43 --- /dev/null +++ b/js/htk/button/style.scss @@ -0,0 +1,10 @@ + +.htk-button { + display: flex; + align-items: center; + gap: 8px; + + & > .htk-icon { + display: block; + } +} \ No newline at end of file diff --git a/js/htk/field/calendar.js b/js/htk/calendar/index.js similarity index 98% rename from js/htk/field/calendar.js rename to js/htk/calendar/index.js index f1e54421..15c28a17 100644 --- a/js/htk/field/calendar.js +++ b/js/htk/calendar/index.js @@ -1,16 +1,15 @@ +require('./style.scss'); module.exports = new Class({ Extends: Htk.Field ,Tag: 'htk-calendar' - ,Properties: - { - restrictFunc: - { + ,Properties: { + restrictFunc: { type: Function ,set: function(x) { this._restrictFunc = x; } - ,get: function(x) { + ,get: function() { return this._restrictFunc; } } diff --git a/js/htk/calendar/style.scss b/js/htk/calendar/style.scss new file mode 100644 index 00000000..56761078 --- /dev/null +++ b/js/htk/calendar/style.scss @@ -0,0 +1,94 @@ +@import "../style/variables"; +@import "../style/classes"; + +.htk-calendar { + @extend %box; + width: 20em; + + table { + border-collapse: collapse; + } + thead tr, + tfoot tr { + font-weight: normal; + vertical-align: middle; + text-align: center; + height: 3em; + } + thead > tr { + &.weekdays > th { + font-weight: normal; + color: #999; + text-transform: lowercase; + } + & > th { + &.previous, &.next { + font-size: .8rem; + + button { + border-radius: 50%; + padding: 10px; + display: block; + margin: 0 auto; + + & > .htk-icon { + font-size: 1rem; + } + } + } + &.month-year { + font-size: 1.2rem; + text-transform: lowercase; + } + } + } + tfoot tr { + border-top: none; + } + th.button { + display: table-cell; + } + th.button:hover { + cursor: pointer; + background-color: rgba(1, 1, 1, 0.2); + } + col { + width: 14.2%; + } + tr { + height: 2em; + } + tbody td { + text-align: right; + } + tbody td > div { + height: 2em; + width: 2em; + line-height: 2em; + text-align: center; + border-radius: 2em; + padding: 0.3em; + margin: 0 auto; + color: #555; + } + div { + &.disabled { + color: #bbb; + } + &.today { + font-weight: bold; + color: black; + } + &.selected { + color: white; + background-color: $color-primary; + } + &.enabled { + @extend %clickable; + + &:hover { + background-color: rgba(140, 198, 63, 0.8); + } + } + } +} diff --git a/js/htk/check/index.js b/js/htk/check/index.js new file mode 100644 index 00000000..56d4f8e8 --- /dev/null +++ b/js/htk/check/index.js @@ -0,0 +1,26 @@ + +module.exports = new Class({ + Extends: Htk.Field + ,Tag: 'htk-check' + + ,render: function() { + var node = this.createRoot('input'); + node.type = 'checkbox'; + node.addEventListener('change', this.changed.bind(this)); + } + + ,changed: function() { + this.valueChanged(this.node.checked); + } + + ,putValue: function(value) { + if (value) + this.node.checked = true; + else + this.node.checked = false; + } + + ,setEditable: function(editable) { + this.node.disabled = !editable; + } +}); diff --git a/js/htk/column/check.js b/js/htk/column/check.js deleted file mode 100644 index 950187d0..00000000 --- a/js/htk/column/check.js +++ /dev/null @@ -1,34 +0,0 @@ - -module.exports = new Class -({ - Extends: Htk.Column - ,Tag: 'htk-column-check' - - ,initialize: function (props) - { - this._cssClass = 'cell-check'; - this.parent (props); - } - - ,render: function (tr) - { - var checkButton = this.createElement ('input'); - checkButton.type = 'checkbox'; - checkButton.checked = this.value; - - if (this.editable) - checkButton.addEventListener ('changed', - this.inputChanged.bind (this, tr, node)); - else - checkButton.disabled = true; - - var td = this.parent (tr); - td.appendChild (checkButton); - return td; - } - - ,inputChanged: function (tr, node) - { - this.changed (tr, node.value); - } -}); diff --git a/js/htk/column.js b/js/htk/column/index.js similarity index 89% rename from js/htk/column.js rename to js/htk/column/index.js index 782e68c5..e5f9385b 100644 --- a/js/htk/column.js +++ b/js/htk/column/index.js @@ -1,5 +1,5 @@ -var NodeBuilder = require('../vn/node-builder'); +var NodeBuilder = require('../../vn/node-builder'); /** * Represents a grid column. This is an abstract class and should not be @@ -8,8 +8,7 @@ var NodeBuilder = require('../vn/node-builder'); module.exports = new Class({ Extends: NodeBuilder ,Tag: 'htk-column' - ,Properties: - { + ,Properties: { value: { type: String ,value: null @@ -57,7 +56,7 @@ module.exports = new Class({ * Initializes the column. */ ,initialize: function(props) { - this.parent(props); + NodeBuilder.prototype.initialize.call(this, props); this.td = this.createElement('td'); } @@ -90,7 +89,7 @@ module.exports = new Class({ } ,changed: function(tr, newValue) { - this.signalEmit('changed', tr.rowIndex - 1, newValue); + this.emit('changed', tr.rowIndex - 1, newValue); } }); diff --git a/js/htk/column/radio.js b/js/htk/column/radio.js deleted file mode 100644 index a10235d2..00000000 --- a/js/htk/column/radio.js +++ /dev/null @@ -1,39 +0,0 @@ - -module.exports = new Class -({ - Extends: Htk.Column - ,Tag: 'htk-column-radio' - ,Properties: - { - param: - { - type: Vn.Param - ,set: function (x) - { - this.radioGroup.master = x; - } - } - } - - ,initialize: function (props) - { - this._cssClass = 'cell-radio'; - this.radioGroup = new Htk.RadioGroup (this.doc); - this.parent (props); - } - - ,render: function (tr) - { - var td = this.parent (tr); - - var radio = this.radioGroup.createButton (this.value); - td.appendChild (radio); - - return td; - } - - ,clear: function () - { - this.radioGroup.clear (); - } -}); diff --git a/js/htk/column/button.js b/js/htk/columns/button/index.js similarity index 85% rename from js/htk/column/button.js rename to js/htk/columns/button/index.js index b35dc65a..552a39e5 100644 --- a/js/htk/column/button.js +++ b/js/htk/columns/button/index.js @@ -1,3 +1,4 @@ +require('./style.scss'); module.exports = new Class({ Extends: Htk.Column, @@ -36,11 +37,11 @@ module.exports = new Class({ initialize: function(props) { this._cssClass = 'cell-button'; - this.parent(props); + Htk.Column.prototype.initialize.call(this, props); }, render: function(tr) { - const td = this.parent(tr); + const td = Htk.Column.prototype.render.call(this, tr); let button; @@ -69,6 +70,6 @@ module.exports = new Class({ }, buttonClicked: function(value, tr, button) { - this.signalEmit('clicked', value, tr.rowIndex - 1, button); + this.emit('clicked', value, tr.rowIndex - 1, button); } }); diff --git a/js/htk/columns/button/style.scss b/js/htk/columns/button/style.scss new file mode 100644 index 00000000..787ca01d --- /dev/null +++ b/js/htk/columns/button/style.scss @@ -0,0 +1,34 @@ +@import "../../style/classes"; + +td.cell-button { + max-width: 20px; + text-align: center; + + & > button, + & > a { + @extend %clickable; + display: block; + height: 44px; + width: 44px; + margin: 0 auto; + border-radius: 50%; + padding: 10px; + border: none; + background-color: transparent; + box-sizing: border-box; + + & > .htk-icon { + display: block; + } + &:hover { + background-color: rgba(1, 1, 1, 0.1); + } + } + img { + height: 1.5em; + width: 1.5em; + display: block; + margin: auto; + padding: 0; + } +} diff --git a/js/htk/columns/check/index.js b/js/htk/columns/check/index.js new file mode 100644 index 00000000..0face4d1 --- /dev/null +++ b/js/htk/columns/check/index.js @@ -0,0 +1,31 @@ +require('./style.scss'); + +module.exports = new Class({ + Extends: Htk.Column + ,Tag: 'htk-column-check' + + ,initialize: function(props) { + this._cssClass = 'cell-check'; + Htk.Column.prototype.initialize.call(this, props); + } + + ,render: function(tr) { + var checkButton = this.createElement('input'); + checkButton.type = 'checkbox'; + checkButton.checked = this.value; + + if (this.editable) + checkButton.addEventListener('changed', + this.inputChanged.bind(this, tr, node)); + else + checkButton.disabled = true; + + var td = Htk.Column.prototype.render.call(this, tr); + td.appendChild(checkButton); + return td; + } + + ,inputChanged: function(tr, node) { + this.changed(tr, node.value); + } +}); diff --git a/js/htk/columns/check/style.scss b/js/htk/columns/check/style.scss new file mode 100644 index 00000000..bdee6773 --- /dev/null +++ b/js/htk/columns/check/style.scss @@ -0,0 +1,5 @@ + +th.cell-check, +th.cell-radio { + text-align: center; +} diff --git a/js/htk/column/date.js b/js/htk/columns/date/index.js similarity index 83% rename from js/htk/column/date.js rename to js/htk/columns/date/index.js index f215c755..336573fc 100644 --- a/js/htk/column/date.js +++ b/js/htk/columns/date/index.js @@ -24,13 +24,13 @@ module.exports = new Class ,initialize: function(props) { this._cssClass = 'cell-date'; - this.parent(props); + Htk.Column.prototype.initialize.call(this, props); } ,render: function(tr) { var text = Vn.Date.strftime(this.value, this._format); - var td = this.parent(tr); + var td = Htk.Column.prototype.render.call(this, tr); td.appendChild(this.createTextNode(text)); return td; diff --git a/js/htk/column/image.js b/js/htk/columns/image/index.js similarity index 77% rename from js/htk/column/image.js rename to js/htk/columns/image/index.js index acf246cd..3542f238 100644 --- a/js/htk/column/image.js +++ b/js/htk/columns/image/index.js @@ -1,46 +1,41 @@ +require('./style.scss'); -module.exports = new Class -({ +module.exports = new Class({ Extends: Htk.Column ,Tag: 'htk-column-image' - ,Properties: - { + ,Properties: { /** * The directory where the images are allocated. */ - directory: - { + directory:{ type: String ,value: null }, /** * The subdirectory where the images are allocated. */ - subdir: - { + subdir:{ type: String ,value: null }, /** * Subdirectory where full images are allocated. */ - fullDir: - { + fullDir:{ type: String ,value: null }, /** * The REST connection used to upload the image. */ - conn: - { + conn:{ type: Vn.JsonConnection } } ,initialize: function(props) { this._cssClass = 'cell-image'; - this.parent(props); + Htk.Column.prototype.initialize.call(this, props); } ,render: function(tr) { @@ -53,7 +48,7 @@ module.exports = new Class ,doc: this.doc }); - var td = this.parent(tr); + var td = Htk.Column.prototype.render.call(this, tr); td.appendChild(image.node); return td; } diff --git a/js/htk/columns/image/style.scss b/js/htk/columns/image/style.scss new file mode 100644 index 00000000..9be8f215 --- /dev/null +++ b/js/htk/columns/image/style.scss @@ -0,0 +1,11 @@ + +td.cell-image { + text-align: center; + + .htk-image { + max-width: 2.5em; + max-height: 2.5em; + display: block; + margin: auto; + } +} diff --git a/js/htk/column/link.js b/js/htk/columns/link/index.js similarity index 78% rename from js/htk/column/link.js rename to js/htk/columns/link/index.js index 62b45f85..23b44895 100644 --- a/js/htk/column/link.js +++ b/js/htk/columns/link/index.js @@ -1,23 +1,19 @@ -module.exports = new Class -({ +module.exports = new Class({ Extends: Htk.Column ,Tag: 'htk-column-link' - ,Properties: - { + ,Properties: { /** * The link url. */ - href: - { + href:{ type: String ,value: null }, /** * the target where the link is opened. */ - target: - { + target:{ type: String ,value: null } @@ -31,7 +27,7 @@ module.exports = new Class if (this.target) link.target = this.target; - var td = this.parent(tr); + var td = Htk.Column.prototype.render.call(this, tr); td.appendChild(link); return td; } diff --git a/js/htk/columns/radio/index.js b/js/htk/columns/radio/index.js new file mode 100644 index 00000000..29b753e4 --- /dev/null +++ b/js/htk/columns/radio/index.js @@ -0,0 +1,32 @@ + +module.exports = new Class({ + Extends: Htk.Column + ,Tag: 'htk-column-radio' + ,Properties: { + param:{ + type: Vn.Param + ,set: function(x) { + this.radioGroup.master = x; + } + } + } + + ,initialize: function(props) { + this._cssClass = 'cell-radio'; + this.radioGroup = new Htk.RadioGroup(this.doc); + Htk.Column.prototype.initialize.call(this, props); + } + + ,render: function(tr) { + var td = Htk.Column.prototype.render.call(this, tr); + + var radio = this.radioGroup.createButton(this.value); + td.appendChild(radio); + + return td; + } + + ,clear: function() { + this.radioGroup.clear(); + } +}); diff --git a/js/htk/column/spin.js b/js/htk/columns/spin/index.js similarity index 81% rename from js/htk/column/spin.js rename to js/htk/columns/spin/index.js index a115d739..85022c4e 100644 --- a/js/htk/column/spin.js +++ b/js/htk/columns/spin/index.js @@ -1,15 +1,13 @@ +require('./style.scss'); -module.exports = new Class -({ +module.exports = new Class({ Extends: Htk.Column ,Tag: 'htk-column-spin' - ,Properties: - { + ,Properties: { /** * The text to append to the number. */ - unit: - { + unit:{ type: String ,value: null }, @@ -24,17 +22,17 @@ module.exports = new Class ,initialize: function(props) { this._cssClass = 'cell-spin'; - this.parent(props); + Htk.Column.prototype.initialize.call(this, props); } ,renderHeader: function() { - var th = this.parent(); + var th = Htk.Column.prototype.renderHeader.call(this, ); th.className = 'cell-spin'; return th; } ,render: function(tr) { - var td = this.parent(tr); + var td = Htk.Column.prototype.render.call(this, tr); var valueString = null; diff --git a/js/htk/columns/spin/style.scss b/js/htk/columns/spin/style.scss new file mode 100644 index 00000000..33603fb3 --- /dev/null +++ b/js/htk/columns/spin/style.scss @@ -0,0 +1,8 @@ + +th.cell-spin { + text-align: right; +} +td.cell-spin { + width: 2.5em; + text-align: right; +} diff --git a/js/htk/column/text.js b/js/htk/columns/text/index.js similarity index 82% rename from js/htk/column/text.js rename to js/htk/columns/text/index.js index fabfd4bf..749823c1 100644 --- a/js/htk/column/text.js +++ b/js/htk/columns/text/index.js @@ -1,15 +1,12 @@ -module.exports = new Class -({ +module.exports = new Class({ Extends: Htk.Column ,Tag: 'htk-column-text' - ,Properties: - { + ,Properties: { /** * Format that applies to the value. */ - format: - { + format:{ type: String ,set: function(x) { this._format = _(x); @@ -24,7 +21,7 @@ module.exports = new Class ,initialize: function(props) { this._cssClass = 'cell-text'; - this.parent(props); + Htk.Column.prototype.initialize.call(this, props); } ,render: function(tr) { @@ -42,7 +39,7 @@ module.exports = new Class node = this.createTextNode( Vn.Value.format(this.value, this._format)); - var td = this.parent(tr); + var td = Htk.Column.prototype.render.call(this, tr); td.appendChild(node); return td; } diff --git a/js/htk/component.js b/js/htk/component.js deleted file mode 100644 index c536e3ae..00000000 --- a/js/htk/component.js +++ /dev/null @@ -1,36 +0,0 @@ - -const Widget = require('./widget'); - -module.exports = new Class({ - Extends: Widget - - ,scope: null - - ,builderInit: function(path) { - const builder = new Vn.Builder(); - builder.compileFile(path); - this.builderResultInit(builder); - } - - ,builderInitString: function(xmlString) { - const builder = new Vn.Builder(); - builder.compileString(xmlString); - this.builderResultInit(builder); - } - - ,builderResultInit: function(builder) { - const scope = this.scope = builder.load(this.doc, this); - scope.link(); - - this.$ = scope.$; - this._node = scope.$.main; - } - - ,_destroy: function() { - if (this.scope) - this.scope.unref(); - - this.parent(); - } -}); - diff --git a/js/htk/field/date-chooser.js b/js/htk/date-chooser/index.js similarity index 94% rename from js/htk/field/date-chooser.js rename to js/htk/date-chooser/index.js index 579c4a44..a32b14cc 100644 --- a/js/htk/field/date-chooser.js +++ b/js/htk/date-chooser/index.js @@ -1,8 +1,7 @@ +require('./style.scss'); +var Calendar = require('../calendar'); -var Calendar = require('./calendar'); - -module.exports = new Class -({ +module.exports = new Class({ Extends: Htk.Field ,Tag: 'htk-date-chooser' diff --git a/js/htk/date-chooser/style.scss b/js/htk/date-chooser/style.scss new file mode 100644 index 00000000..5a464056 --- /dev/null +++ b/js/htk/date-chooser/style.scss @@ -0,0 +1,17 @@ + +.htk-date-chooser { + display: flex; + align-items: center; + font-weight: normal; + + & > span { + flex: 1; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + & > .htk-icon { + flex: none; + color: #666; + } +} \ No newline at end of file diff --git a/js/htk/dialog.js b/js/htk/dialog/index.js similarity index 94% rename from js/htk/dialog.js rename to js/htk/dialog/index.js index f8d0f30b..5ac11fd2 100644 --- a/js/htk/dialog.js +++ b/js/htk/dialog/index.js @@ -1,5 +1,5 @@ - -var Popup = require('./popup'); +require('./style.scss'); +var Popup = require('../popup'); /** * Class to show message dialogs with buttons. @@ -97,7 +97,7 @@ Dialog.implement({ ,_buttons: Button.ACCEPT ,open: function() { - this.parent(); + Popup.prototype.open.call(this); // Dialog body @@ -146,8 +146,8 @@ Dialog.implement({ ,hide(response) { if (!this._isOpen) return; - this.parent(); - this.signalEmit('response', response); + Popup.prototype.hide.call(this); + this.emit('response', response); } ,createButton: function(label, response) { diff --git a/js/htk/dialog/style.scss b/js/htk/dialog/style.scss new file mode 100644 index 00000000..e2073089 --- /dev/null +++ b/js/htk/dialog/style.scss @@ -0,0 +1,25 @@ + +.htk-dialog { + padding: 1.5em; + max-width: 20em; + font-weight: normal; + color: #555; + + p { + margin: 0; + } + img { + float: left; + height: 3em; + margin-top: 0; + margin-right: 1em; + } + p { + padding: 0; + } + .button-bar > button { + float: right; + margin-left: 1em; + margin-top: .5em; + } +} \ No newline at end of file diff --git a/js/htk/field/entry.js b/js/htk/entry/index.js similarity index 93% rename from js/htk/field/entry.js rename to js/htk/entry/index.js index 6f6fbd9b..fed77876 100644 --- a/js/htk/field/entry.js +++ b/js/htk/entry/index.js @@ -1,10 +1,8 @@ -module.exports = new Class -({ +module.exports = new Class({ Extends: Htk.Field ,Tag: 'htk-entry' - ,Properties: - { + ,Properties: { /** * Displayed text when there is no content. */ @@ -52,7 +50,7 @@ module.exports = new Class node.addEventListener('change', this._onChange.bind(this)); } - ,_onChange: function(event) { + ,_onChange: function() { var newValue; if (this.node.value == '') diff --git a/js/htk/field/check.js b/js/htk/field/check.js deleted file mode 100644 index 2cfc9b5e..00000000 --- a/js/htk/field/check.js +++ /dev/null @@ -1,31 +0,0 @@ - -module.exports = new Class -({ - Extends: Htk.Field - ,Tag: 'htk-check' - - ,render: function () - { - var node = this.createRoot ('input'); - node.type = 'checkbox'; - node.addEventListener ('change', this.changed.bind (this)); - } - - ,changed: function () - { - this.valueChanged (this.node.checked); - } - - ,putValue: function (value) - { - if (value) - this.node.checked = true; - else - this.node.checked = false; - } - - ,setEditable: function (editable) - { - this.node.disabled = !editable; - } -}); diff --git a/js/htk/field.js b/js/htk/field/index.js similarity index 55% rename from js/htk/field.js rename to js/htk/field/index.js index 0a7e42a1..81b66f90 100644 --- a/js/htk/field.js +++ b/js/htk/field/index.js @@ -1,12 +1,12 @@ -var Widget = require('./widget'); +var Component = require('vn/component'); module.exports = new Class({ - Extends: Widget + Extends: Component + ,Implements: Vn.ParamIface ,Tag: 'htk-field' ,Child: 'param' - ,Properties: - { + ,Properties: { value: { type: String ,set: function(x) { @@ -18,21 +18,48 @@ module.exports = new Class({ this.valueChanged(x); this.putValue(x); + this._notifyChanges(); } - ,get: function(x) { + ,get: function() { return this._value; } }, param: { - type: Vn.Param + type: Vn.ParamIface ,set: function(x) { - this.link({_param: x}, {'changed': this.onParamChange}); - this.onParamChange(); + this._setParam(x); } ,get: function() { return this._param; } }, + lot: { + type: Vn.LotIface + ,set: function(x) { + this._setLot(x); + } + ,get: function() { + return this._lot; + } + }, + name: { + type: String + ,set: function(x) { + this._setName(x); + } + ,get: function() { + return this._name; + } + }, + oneWay: { + type: Boolean + ,set: function(x) { + this._oneWay = x; + } + ,get: function() { + return this._oneWay; + } + }, editable: { type: Boolean ,set: function(x) { @@ -48,21 +75,19 @@ module.exports = new Class({ form: { type: Db.Iterator ,set: function(x) { - this._form = x; - this.bindToForm(); + this.lot = x; } ,get: function() { - return this._form; + return this._lot; } }, column: { type: String ,set: function(x) { - this._paramName = x; - this.bindToForm(); + this.name = x; } ,get: function() { - return this._paramName; + return this._name; } }, conditionalFunc: { @@ -71,27 +96,24 @@ module.exports = new Class({ } } - ,_value: undefined - ,_param: null ,_editable: true - ,_blockParamChange: false - ,_blockValueChange: false + ,_lockField: false - ,onParamChange: function() { - if (!this._blockValueChange) { - this._blockParamChange = true; - this.value = this._param.value; - this._blockParamChange = false; - } - } - - ,bindToForm: function() { - if (this._form && this._paramName) - this.param = new Db.Param - ({ - form: this._form - ,column: this._paramName - }); + ,_setValue: function(newValue) { + if (!this._putValue(newValue)) + return; + + if (!this._lockField) + this.putValue(newValue); + + if (this.conditionalFunc) + this.conditionalFunc(this, newValue); + + const node = this.node; + if (node && node.nodeType === Node.ELEMENT_NODE) + node.classList.toggle('filled', newValue !== undefined); + + this._notifyChanges(); } /** @@ -100,7 +122,7 @@ module.exports = new Class({ * * @param {Boolean} editable Whether the user is allowed to edit the entry */ - ,setEditable: function(editable) {} + ,setEditable: function() {} /** * Virtual method that must be implemented by class childs to put the value @@ -108,7 +130,7 @@ module.exports = new Class({ * * @param {Object} value The new value for the entry */ - ,putValue: function(value) {} + ,putValue: function() {} /** * Protected method that should be called from class childs when the value @@ -117,18 +139,9 @@ module.exports = new Class({ * @param {Object} value The new entry value */ ,valueChanged: function(value) { - this._value = value; - - if (this.conditionalFunc) - this.conditionalFunc(this, value); - - if (this._param && !this._blockParamChange) { - this._blockValueChange = true; - this._param.value = value; - this._blockValueChange = false; - } - - this.signalEmit('changed'); + this._lockField = true; + this._setValue(value); + this._lockField = false; } }); diff --git a/js/htk/field/spin.js b/js/htk/field/spin.js deleted file mode 100644 index 102aff33..00000000 --- a/js/htk/field/spin.js +++ /dev/null @@ -1,46 +0,0 @@ - -module.exports = new Class -({ - Extends: Htk.Field - ,Tag: 'htk-spin' - - ,render: function () - { - var input = this.createRoot ('input'); - //setInputTypeNumber (input); - this.node.type = 'number'; - input.addEventListener ('change', this._onChange.bind (this)); - - this.unit = null; - this.digits = 0; - } - - ,_onChange: function () - { - var newValue = (this.node.value == '') ? null : parseFloat (this.node.value); - this.node.value = newValue; - this.valueChanged (newValue); - } - - ,putValue: function (value) - { - var text; - - if (value != null) - { - text = (new Number (value)).toFixed (this.digits); - - if (this.unit != null) - text += ' ' + this.unit; - } - else - text = ''; - - this.node.value = text; - } - - ,setEditable: function (editable) - { - this.node.readOnly = !editable; - } -}); diff --git a/js/htk/field/table.js b/js/htk/field/table.js deleted file mode 100644 index b1b39464..00000000 --- a/js/htk/field/table.js +++ /dev/null @@ -1,58 +0,0 @@ - -var Entry = require ('./entry'); -var ColumnRadio = require ('../column/radio'); - -module.exports = new Class -({ - Extends: Entry - ,Tag: 'htk-table' - - ,render: function () - { - var tv = new Htk.TreeView (); - this.node.appendChild (tv.node); - - var renderer = new ColumnRadio (); - tv.appendColumn (0, renderer, ''); - - var rbGroup = renderer.rbGroup; - rbGroup.addSignal ('changed', this.changed, this); - - this.treeview = tv; - this.rbGroup = rbGroup; - } - - ,setModel: function (model) - { - this.treeview.setModel (model); - model.addSignal ('status-changed', this.modelRefresh, this); - this.selectValue (); - } - - ,changed: function (rbGroup) - { - this.realValue = this.rbGroup.getValue (); - this.signalEmit ('changed'); - } - - ,selectValue: function () - { - this.rbGroup.setValue (this.realValue); - } - - ,setRealValue: function () - { - this.selectValue (); - } - - ,modelRefresh: function (model, status) - { - if (status == Db.Model.Status.READY) - this.selectValue (); - } - - ,setEditable: function (editable) - { - this.rbGroup.setEditable (editable); - } -}); diff --git a/js/htk/full-image.js b/js/htk/full-image/index.js similarity index 94% rename from js/htk/full-image.js rename to js/htk/full-image/index.js index b4e0d00e..cea3810e 100644 --- a/js/htk/full-image.js +++ b/js/htk/full-image/index.js @@ -1,6 +1,6 @@ - -var Popup = require('./popup'); -var Spinner = require('./spinner'); +require('./style.scss'); +var Popup = require('../popup'); +var Spinner = require('../spinner'); module.exports = new Class ({ diff --git a/js/htk/full-image/style.scss b/js/htk/full-image/style.scss new file mode 100644 index 00000000..8795818b --- /dev/null +++ b/js/htk/full-image/style.scss @@ -0,0 +1,12 @@ + +.htk-full-image { + img { + display: block; + cursor: pointer; + } + .htk-spinner { + background-color: #FFF; + margin: .6em; + display: block; + } +} diff --git a/js/htk/grid.js b/js/htk/grid/index.js similarity index 98% rename from js/htk/grid.js rename to js/htk/grid/index.js index 2e025501..5cdc88f9 100644 --- a/js/htk/grid.js +++ b/js/htk/grid/index.js @@ -1,8 +1,8 @@ - -var Widget = require('./widget'); +require('./style.scss'); +var Component = require('vn/component'); module.exports = new Class({ - Extends: Widget + Extends: Component ,Tag: 'htk-grid' ,Child: 'model' ,Properties: diff --git a/js/htk/grid/style.scss b/js/htk/grid/style.scss new file mode 100644 index 00000000..a2a608db --- /dev/null +++ b/js/htk/grid/style.scss @@ -0,0 +1,63 @@ +@import "../style/variables"; + +.htk-grid { + margin: auto; + border-collapse: collapse; + + & > thead > tr, + & > tfoot > tr { + background-color: $color-primary; + vertical-align: middle; + height: 3em; + } + th { + color: white; + cursor: pointer; + font-weight: normal; + padding: 0 0.4em; + } + th:hover { + background-color: rgba(1, 1, 1, 0.2); + } + tr { + height: 3.5em; + } + & > tfoot a, + & > thead a { + color: black; + } + tr.pair-row { + background-color: transparent; + } + & > tbody tr { + border-top: 1px solid #DDD; + } + & > tbody tr:first-child { + border-top: none; + } + & > tbody td { + margin: 0; + padding: 0 0.5em; + } + th, + td { + text-align: left; + } + td:first-child, + th:first-child { + padding-left: 1em; + } + td:last-child, + th:last-child { + padding-right: 1em; + } + .message { + padding: 1.5em; + text-align: center; + } + .message > * { + display: inline-block; + vertical-align: middle; + padding-right: .8em; + } +} diff --git a/js/htk/htk.js b/js/htk/htk.js index 1fbd0362..5cc21c26 100644 --- a/js/htk/htk.js +++ b/js/htk/htk.js @@ -3,9 +3,7 @@ require('db/db'); require('./style/index.scss'); Htk = module.exports = { - Widget : require('./widget') - ,Component : require('./component') - ,Popup : require('./popup') + Popup : require('./popup') ,Dialog : require('./dialog') ,Toast : require('./toast') ,Repeater : require('./repeater') @@ -18,36 +16,37 @@ Htk = module.exports = { ,AssistantBar : require('./assistant-bar') ,Step : require('./step') ,Loader : require('./loader') + ,List : require('./list') ,Field : require('./field') ,Column : require('./column') }; var Fields = { - Text : require('./field/text') - ,Html : require('./field/html') - ,Entry : require('./field/entry') - ,RadioGroup : require('./field/radio-group') - ,Radio : require('./field/radio') - ,Label : require('./field/label') - ,TextArea : require('./field/text-area') - ,Spin : require('./field/spin') - ,Check : require('./field/check') - ,Select : require('./field/select') - ,Calendar : require('./field/calendar') - ,DateChooser : require('./field/date-chooser') - ,Image : require('./field/image') - ,Button : require('./field/button') - ,BarButton : require('./field/bar-button') - ,Table : require('./field/table') - ,SearchEntry : require('./field/search-entry') - ,ColumnButton : require('./column/button') - ,ColumnLink : require('./column/link') - ,ColumnDate : require('./column/date') - ,ColumnImage : require('./column/image') - ,ColumnRadio : require('./column/radio') - ,ColumnSpin : require('./column/spin') - ,ColumnText : require('./column/text') - ,ColumnCheck : require('./column/check') + Text : require('./text') + ,Html : require('./html') + ,Entry : require('./entry') + ,RadioGroup : require('./radio/radio-group') + ,Radio : require('./radio') + ,Label : require('./label') + ,TextArea : require('./text-area') + ,Spin : require('./spin') + ,Check : require('./check') + ,Select : require('./select') + ,Calendar : require('./calendar') + ,DateChooser : require('./date-chooser') + ,Image : require('./image') + ,Button : require('./button') + ,BarButton : require('./bar-button') + ,Table : require('./table') + ,SearchEntry : require('./search-entry') + ,ColumnButton : require('./columns/button') + ,ColumnLink : require('./columns/link') + ,ColumnDate : require('./columns/date') + ,ColumnImage : require('./columns/image') + ,ColumnRadio : require('./columns/radio') + ,ColumnSpin : require('./columns/spin') + ,ColumnText : require('./columns/text') + ,ColumnCheck : require('./columns/check') }; for (var field in Fields) diff --git a/js/htk/field/html.js b/js/htk/html/index.js similarity index 100% rename from js/htk/field/html.js rename to js/htk/html/index.js diff --git a/js/htk/icon.js b/js/htk/icon/index.js similarity index 92% rename from js/htk/icon.js rename to js/htk/icon/index.js index 75ed807f..ad12013a 100644 --- a/js/htk/icon.js +++ b/js/htk/icon/index.js @@ -1,8 +1,8 @@ -var Widget = require('./widget'); +var Component = require('vn/component'); module.exports = new Class({ - Extends: Widget + Extends: Component ,Tag: 'htk-icon' ,Properties: { diff --git a/js/htk/image-editor.js b/js/htk/image-editor/index.js similarity index 76% rename from js/htk/image-editor.js rename to js/htk/image-editor/index.js index cd0bcde2..3ba053ae 100644 --- a/js/htk/image-editor.js +++ b/js/htk/image-editor/index.js @@ -1,7 +1,7 @@ - -var Component = require('./component'); -var Toast = require('./toast'); -var Tpl = require('./image-editor.xml').default; +require('./style.scss'); +var Component = require('vn/component'); +var Toast = require('../toast'); +var Tpl = require('./ui.xml').default; /** * A form to handle the image database, it allows to add new images or @@ -19,14 +19,14 @@ module.exports = new Class({ }, initialize: function(props) { - this.builderInitString(Tpl); + this.loadTemplateFromString(Tpl); var self = this; this.$.form.onsubmit = function() { self._onSubmit(); return false; }; - this.parent(props); + Component.prototype.initialize.call(this, props); }, onNameChange: function() { @@ -35,7 +35,7 @@ module.exports = new Class({ if (!newValue) newValue = null - this.signalEmit('name-changed', newValue); + this.emit('name-changed', newValue); }, _onSubmit: function() { @@ -55,7 +55,7 @@ module.exports = new Class({ throw error; Toast.showMessage(_('ImageAdded')); - this.signalEmit('file-uploaded', this.$.name.value); + this.emit('file-uploaded', this.$.name.value); }, setData: function(image, directory) { diff --git a/js/htk/image-editor/style.scss b/js/htk/image-editor/style.scss new file mode 100644 index 00000000..22921146 --- /dev/null +++ b/js/htk/image-editor/style.scss @@ -0,0 +1,33 @@ + +.htk-image-editor { + width: 18em; + margin: 0 auto; + padding: 1.5em; + + h2 { + color: white; + background-color: #009688; + text-align: left; + font-size: 1.3em; + line-height: 1.7em; + font-weight: normal; + padding: 0.6em 0.8em; + margin: 0; + } + iframe { + display: none; + } + .footer { + margin-top: 2em; + + & > .htk-spinner { + padding-right: 1.2em; + height: 1.3em; + width: 1.3em; + } + & > .htk-spinner, + & > input { + float: right; + } + } +} diff --git a/js/htk/image-editor.xml b/js/htk/image-editor/ui.xml similarity index 100% rename from js/htk/image-editor.xml rename to js/htk/image-editor/ui.xml diff --git a/js/htk/field/image.js b/js/htk/image/index.js similarity index 99% rename from js/htk/field/image.js rename to js/htk/image/index.js index 07441da1..76b084ca 100644 --- a/js/htk/field/image.js +++ b/js/htk/image/index.js @@ -1,3 +1,5 @@ +require('./style.scss'); + /** * Class to display or edit an image. Also it allows to show it's full version. */ diff --git a/js/htk/image/style.scss b/js/htk/image/style.scss new file mode 100644 index 00000000..fdfceb41 --- /dev/null +++ b/js/htk/image/style.scss @@ -0,0 +1,35 @@ + +.htk-image { + position: relative; + overflow: hidden; + transition: opacity 250ms ease-out; + + &.clickable:hover { + cursor: pointer; + opacity: 0.85; + } + & > img { + display: block; + height: 100%; + width: 100%; + } + & > button { + position: absolute; + top: 0; + left: 0; + padding: 8px; + margin: 4px; + display: none; + background-color: rgba(255, 255, 255, .6); + + &:hover { + background-color: rgba(255, 255, 255, .8); + } + } + &:hover > button { + display: block; + } + & > button > .htk-icon { + display: block; + } +} diff --git a/js/htk/field/label.js b/js/htk/label/index.js similarity index 100% rename from js/htk/field/label.js rename to js/htk/label/index.js diff --git a/js/htk/list/index.js b/js/htk/list/index.js new file mode 100644 index 00000000..1fe6bd72 --- /dev/null +++ b/js/htk/list/index.js @@ -0,0 +1 @@ +require('./style.scss'); diff --git a/js/htk/list/style.scss b/js/htk/list/style.scss new file mode 100644 index 00000000..70669d0c --- /dev/null +++ b/js/htk/list/style.scss @@ -0,0 +1,56 @@ +@import "../style/classes"; + +.htk-list { + a.item, + .item.clickable { + @extend %clickable; + } + .item { + padding: 20px; + border-bottom: 1px solid #DDD; + display: flex; + align-items: center; + + & > .side { + flex: none; + } + & > .content { + flex: 1; + overflow: hidden; + + & > .important { + font-weight: bold; + font-size: 1rem; + margin-bottom: .5em; + } + & > p { + margin: .1em 0; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + } + & > .actions { + flex: none; + display: none; + align-items: center; + + & > .htk-button { + margin: 0; + } + & > * { + display: inline-block; + vertical-align: middle; + } + & > input { + margin: .6em; + } + } + &:hover > .actions { + display: flex; + } + &:last-child { + border-bottom: none; + } + } +} diff --git a/js/htk/loader.js b/js/htk/loader.js deleted file mode 100644 index 8ca67313..00000000 --- a/js/htk/loader.js +++ /dev/null @@ -1,79 +0,0 @@ - -var Widget = require ('./widget'); - -module.exports = new Class -({ - Tag: 'htk-loader' - ,Extends: Widget - ,Properties: { - form: - { - type: Db.Form - ,set: function (x) - { - this.link ({_form: x}, {'status-changed': this.onFormChange}); - this.onFormChange (); - } - ,get: function () - { - return this._form; - } - } - } - - ,initialize: function () - { - var node = this.createRoot ('div'); - node.className = 'htk-loader'; - - var div = this.createElement ('div'); - div.className = 'spinner'; - - var spinner = new Htk.Spinner (); - div.appendChild (spinner.node); - - var childs = this.createElement ('div'); - - this.spinner = spinner; - this.div = div; - this.childs = childs; - this.isLoading = true; - this.stop (); - } - - ,appendChild: function (child) - { - this.childs.appendChild (child); - } - - ,stop: function () - { - if (!this.isLoading) - return; - - this.isLoading = false; - this.spinner.stop (); - Vn.Node.removeChilds (this.node); - this.node.appendChild (this.childs); - } - - ,start: function () - { - if (this.isLoading) - return; - - this.isLoading = true; - this.spinner.start (); - Vn.Node.removeChilds (this.node); - this.node.appendChild (this.div); - } - - ,onFormChange: function () - { - if (this._form.ready) - this.stop (); - else - this.start (); - } -}); - diff --git a/js/htk/loader/index.js b/js/htk/loader/index.js new file mode 100644 index 00000000..d0034c9e --- /dev/null +++ b/js/htk/loader/index.js @@ -0,0 +1,70 @@ +require('./style.scss'); +var Component = require('vn/component'); + +module.exports = new Class({ + Tag: 'htk-loader' + ,Extends: Component + ,Properties: { + form: { + type: Db.Form + ,set: function(x) { + this.link({_form: x}, {'status-changed': this.onFormChange}); + this.onFormChange(); + } + ,get: function() { + return this._form; + } + } + } + + ,initialize: function() { + var node = this.createRoot('div'); + node.className = 'htk-loader'; + + var div = this.createElement('div'); + div.className = 'spinner'; + + var spinner = new Htk.Spinner(); + div.appendChild(spinner.node); + + var childs = this.createElement('div'); + + this.spinner = spinner; + this.div = div; + this.childs = childs; + this.isLoading = true; + this.stop(); + } + + ,appendChild: function(child) { + this.childs.appendChild(child); + } + + ,stop: function() { + if (!this.isLoading) + return; + + this.isLoading = false; + this.spinner.stop(); + Vn.Node.removeChilds(this.node); + this.node.appendChild(this.childs); + } + + ,start: function() { + if (this.isLoading) + return; + + this.isLoading = true; + this.spinner.start(); + Vn.Node.removeChilds(this.node); + this.node.appendChild(this.div); + } + + ,onFormChange: function() { + if (this._form.ready) + this.stop(); + else + this.start(); + } +}); + diff --git a/js/htk/loader/style.scss b/js/htk/loader/style.scss new file mode 100644 index 00000000..5c42e10b --- /dev/null +++ b/js/htk/loader/style.scss @@ -0,0 +1,4 @@ + +.htk-loader > .spinner { + text-align: center; +} diff --git a/js/htk/locale/ca.yml b/js/htk/locale/ca.yml index ea06ed41..3bb55c1d 100644 --- a/js/htk/locale/ca.yml +++ b/js/htk/locale/ca.yml @@ -27,3 +27,4 @@ Next: Següent Confirm: Confirmar Search: Cercar Search...: Cercar... +Erase: Esborrar diff --git a/js/htk/locale/en.yml b/js/htk/locale/en.yml index e1f455d6..58547015 100644 --- a/js/htk/locale/en.yml +++ b/js/htk/locale/en.yml @@ -27,3 +27,4 @@ Next: Next Confirm: Confirm Search: Search Search...: Search... +Erase: Erase diff --git a/js/htk/locale/es.yml b/js/htk/locale/es.yml index 76a005ee..4210301e 100644 --- a/js/htk/locale/es.yml +++ b/js/htk/locale/es.yml @@ -27,3 +27,4 @@ Next: Siguiente Confirm: Confirmar Search: Buscar Search...: Buscar... +Erase: Borrar diff --git a/js/htk/locale/fr.yml b/js/htk/locale/fr.yml index 1d10627b..4bd0b2ca 100644 --- a/js/htk/locale/fr.yml +++ b/js/htk/locale/fr.yml @@ -27,3 +27,4 @@ Next: Suivant Confirm: Confirmer Search: Recherche Search...: Recherche... +Erase: Effacer diff --git a/js/htk/locale/pt.yml b/js/htk/locale/pt.yml index 67775386..17167706 100644 --- a/js/htk/locale/pt.yml +++ b/js/htk/locale/pt.yml @@ -27,3 +27,4 @@ Next: Seguinte Confirm: Confirmar Search: Procurar Search...: Procurar... +Erase: Apagar diff --git a/js/htk/popup.js b/js/htk/popup/index.js similarity index 91% rename from js/htk/popup.js rename to js/htk/popup/index.js index 72dfd9ec..5afca3bd 100644 --- a/js/htk/popup.js +++ b/js/htk/popup/index.js @@ -1,21 +1,18 @@ - -var Widget = require('./widget'); +require('./style.scss'); +var Component = require('vn/component'); /** * Class to handle popups. */ -module.exports = new Class -({ - Extends: Widget +module.exports = new Class({ + Extends: Component ,Tag: 'htk-popup' - ,Properties: - { + ,Properties: { /** * The popup child. */ - child: - { - type: Widget + child: { + type: Component ,set: function(x) { this._child = x; this._setChildNode(x.node); @@ -27,8 +24,7 @@ module.exports = new Class /** * The popup child Node. */ - ,childNode: - { + ,childNode: { type: Object ,set: function(x) { this._child = null; @@ -41,8 +37,7 @@ module.exports = new Class /** * Indicates how the dialog must be displayed. */ - ,modal: - { + ,modal: { type: Boolean ,set: function(x) { this._modal = x; @@ -60,7 +55,7 @@ module.exports = new Class ,initialize: function(props) { this._bgMouseDownHandler = this._bgMouseDown.bind(this); - this.parent(props); + Component.prototype.initialize.call(this, props); } ,render: function() { @@ -73,8 +68,9 @@ module.exports = new Class this.node.appendChild(childNode); } - ,show: function(parent) { + ,show: function(parent, event) { this._parent = parent; + this._lastEvent = event; this.open(); } @@ -185,7 +181,7 @@ module.exports = new Class Vn.Node.remove(this._node); this._parent = null; this._isOpen = false; - this.signalEmit('closed'); + this.emit('closed'); } ,_bgMouseDown: function(e) { diff --git a/js/htk/popup/style.scss b/js/htk/popup/style.scss new file mode 100644 index 00000000..c89d5967 --- /dev/null +++ b/js/htk/popup/style.scss @@ -0,0 +1,32 @@ + +.htk-popup { + z-index: 200; + display: block; + position: fixed; + overflow: hidden; + background-color: white; + border-radius: 10px; + box-shadow: 0 0 0.4em rgba(1, 1, 1, 0.6); + box-sizing: content-box; + + &.modal { + position: absolute; + font-size: 1.2em; + top: 50%; + left: 50%; + } + & > * { + border-radius: 0.1em; + } +} +.htk-background { + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: 190; + background-color: rgba(1, 1, 1, 0.7); + opacity: 0; + transition: opacity 200ms ease-in-out; +} diff --git a/js/htk/field/radio.js b/js/htk/radio/index.js similarity index 100% rename from js/htk/field/radio.js rename to js/htk/radio/index.js diff --git a/js/htk/field/radio-group.js b/js/htk/radio/radio-group.js similarity index 65% rename from js/htk/field/radio-group.js rename to js/htk/radio/radio-group.js index 9ed79344..59673b4f 100644 --- a/js/htk/field/radio-group.js +++ b/js/htk/radio/radio-group.js @@ -1,35 +1,30 @@ var htkRadioGroupUid = 0; -module.exports = new Class -({ +module.exports = new Class({ Extends: Htk.Field ,Tag: 'htk-radio-group' ,radioLock: false - ,initialize: function (props) - { - this.clear (); - this.parent (props); + ,initialize: function(props) { + this.clear(); + Htk.Field.prototype.initialize.call(this, props); } - ,clear: function () - { + ,clear: function() { this.name = htkRadioGroupUid++; this.buttons = []; } - ,createButton: function (value) - { - var button = Vn.Browser.createRadio (this.name, this.doc); + ,createButton: function(value) { + var button = Vn.Browser.createRadio(this.name, this.doc); button.value = value; button.radioGroup = this; return button; } - ,removeButton: function (button) - { + ,removeButton: function(button) { for (var i = 0; i < this.buttons.length; i++) if (this.buttons === button) { this.buttons.splice(i, 1); @@ -40,7 +35,7 @@ module.exports = new Class /** * @return %true if there is an option selected, otherwise it returns %false */ - ,isSelected: function () { + ,isSelected: function() { for (var i = 0; i < this.buttons.length; i++) if (this.buttons[i].value == this.value) return true; diff --git a/js/htk/repeater.js b/js/htk/repeater/index.js similarity index 95% rename from js/htk/repeater.js rename to js/htk/repeater/index.js index 86aa0348..53b3f3b6 100644 --- a/js/htk/repeater.js +++ b/js/htk/repeater/index.js @@ -1,8 +1,8 @@ - -var Widget = require('./widget'); +require('./style.scss'); +var Component = require('vn/component'); module.exports = new Class({ - Extends: Widget + Extends: Component ,Tag: 'htk-repeater' ,Child: 'model' ,Properties: @@ -91,7 +91,7 @@ module.exports = new Class({ } ,loadXml: function(scope, node) { - this.parent(scope, node); + Component.prototype.loadXml.call(this, scope, node); this._parentScope = scope; var builder = this._builder = new Vn.Builder(); @@ -169,7 +169,7 @@ module.exports = new Class({ } this.node.appendChild(this._container); - this.signalEmit('change'); + this.emit('change'); } ,_showNoRecordsFound: function() { @@ -233,7 +233,7 @@ module.exports = new Class({ ,destroy: function() { this._freeChildsData(); - this.parent(); + Component.prototype.destroy.call(this); } }); diff --git a/js/htk/repeater/style.scss b/js/htk/repeater/style.scss new file mode 100644 index 00000000..fc353a74 --- /dev/null +++ b/js/htk/repeater/style.scss @@ -0,0 +1,16 @@ + +.htk-repeater { + & > .message { + padding: 1.5em; + text-align: center; + + & > * { + vertical-align: middle; + } + & > span, + & > .htk-spinner { + display: inline-block; + padding-right: 10px; + } + } +} diff --git a/js/htk/field/search-entry.js b/js/htk/search-entry/index.js similarity index 97% rename from js/htk/field/search-entry.js rename to js/htk/search-entry/index.js index e87fed09..c2b91fa0 100644 --- a/js/htk/field/search-entry.js +++ b/js/htk/search-entry/index.js @@ -1,3 +1,4 @@ +require('./style.scss'); module.exports = new Class({ Extends: Htk.Field diff --git a/js/htk/search-entry/style.scss b/js/htk/search-entry/style.scss new file mode 100644 index 00000000..071f7d32 --- /dev/null +++ b/js/htk/search-entry/style.scss @@ -0,0 +1,33 @@ + +.htk-search-entry { + display: flex; + align-items: center; + gap: 6px; + background-color: white; + height: 40px; + border-radius: 20px; + padding: 0 12px; + overflow: hidden; + + & > * { + display: inline-block; + vertical-align: middle; + } + & > .htk-icon { + display: block; + margin: 0; + color: gray; + } + & > .entry { + margin: 0; + border: none; + width: 80px; + box-shadow: none; + padding-right: 0; + padding-left: 0; + height: inherit; + } + & > .entry:focus { + background-color: initial; + } +} \ No newline at end of file diff --git a/js/htk/field/select.js b/js/htk/select/index.js similarity index 81% rename from js/htk/field/select.js rename to js/htk/select/index.js index d7f773a1..1899cb82 100644 --- a/js/htk/field/select.js +++ b/js/htk/select/index.js @@ -1,5 +1,5 @@ - -var ColumnText = require('../column/text'); +require('./style.scss'); +var ColumnText = require('../columns/text'); module.exports = new Class({ Extends: Htk.Field @@ -40,8 +40,7 @@ module.exports = new Class({ /** * The number of rows in the form. */ - numRows: - { + numRows:{ type: Number ,get: function() { if (this._model) @@ -53,8 +52,7 @@ module.exports = new Class({ /** * Checks if the form data is ready. */ - ready: - { + ready:{ type: Boolean ,get: function() { return this._model && this._model.ready; @@ -63,8 +61,7 @@ module.exports = new Class({ /** * Checks if the form data is ready. */ - placeholder: - { + placeholder:{ type: String ,set: function(x) { this._placeholder = x; @@ -77,8 +74,7 @@ module.exports = new Class({ /** * Wether to allow null values. */ - notNull: - { + notNull:{ type: Boolean ,set: function(x) { this._notNull = x; @@ -93,7 +89,13 @@ module.exports = new Class({ $: { type: Object ,get: function() { - return this._model.getObject(this._row); + return this._model.getObject(this._row) || {}; + } + }, + params: { + type: Object + ,get: function() { + return this.$; } } } @@ -111,11 +113,21 @@ module.exports = new Class({ const button = this.createRoot('button'); button.type = 'button'; button.className = 'htk-select input'; - button.addEventListener('mousedown', this._onButtonMouseDown.bind(this)); + button.addEventListener('mousedown', + e => this._onButtonMouseDown(e)); this.label = this.createElement('span'); button.appendChild(this.label); + const erase = new Htk.Icon({ + name: 'close', + title: _('Erase') + }); + erase.classList.add('erase'); + erase.addEventListener('mousedown', + e => this._onEraseMouseDown(e)); + button.appendChild(erase.node); + const dropDown = new Htk.Icon({ name: 'expand_more' }); @@ -127,12 +139,22 @@ module.exports = new Class({ this._refreshShowText(); this.iterChanged(); } + + ,_onEraseMouseDown(event) { + if (event.defaultPrevented) return; + event.preventDefault(); + this._setRow(-1); + this.valueChanged(undefined); + } - ,_onButtonMouseDown: function(e) { + ,_onButtonMouseDown: function(event) { if (this._popup) { this._popup.hide(); return; } + + if (event.defaultPrevented) return; + event.preventDefault(); var model = this._model; @@ -151,11 +173,9 @@ module.exports = new Class({ var popup = this._popup = new Htk.Popup({childNode: menu}); popup.on('closed', this._onPopupClose.bind(this)); - popup.show(this.node); + popup.show(this.node, event); - this.signalEmit('menu-show'); - - e.stopPropagation(); + this.emit('menu-show'); } ,_onGridClicked: function(grid, e) { @@ -184,7 +204,7 @@ module.exports = new Class({ ,_onPopupClose: function() { this._popup = null; - this.signalEmit('menu-hide'); + this.emit('menu-hide'); } ,_refreshShowText: function() { @@ -204,14 +224,14 @@ module.exports = new Class({ ,_onModelChange: function() { var model = this._model; - this.signalEmit('status-changed'); + this.emit('status-changed'); if (this._popup) this._popup.reset(); if (model && model.ready) { this._selectOption(); - this.signalEmit('ready'); + this.emit('ready'); } else this._setRow(-1); } diff --git a/js/htk/select/style.scss b/js/htk/select/style.scss new file mode 100644 index 00000000..58b1e7f7 --- /dev/null +++ b/js/htk/select/style.scss @@ -0,0 +1,53 @@ +@import "../style/variables"; + +.htk-select { + display: flex; + align-items: center; + font-weight: normal; + width: 100%; + + & > span { + flex: 1; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + & > .htk-icon { + flex: none; + color: #666; + + &.erase { + display: none + } + } + &:not(.filled) > span { + color: #666; + } + &.filled:hover > .htk-icon.erase { + display: block; + } +} +.htk-select-menu { + height: 100%; + max-height: 80em; + overflow: auto; + min-width: 14em; + + tbody > tr { + border-top: none; + height: 2.5em; + } + td.message { + padding: 1em; + } + tr:hover { + background-color: rgba(1, 1, 1, 0.1); + cursor: pointer; + } + td { + max-width: 11em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} \ No newline at end of file diff --git a/js/htk/spin/index.js b/js/htk/spin/index.js new file mode 100644 index 00000000..6de72ed9 --- /dev/null +++ b/js/htk/spin/index.js @@ -0,0 +1,39 @@ + +module.exports = new Class({ + Extends: Htk.Field + ,Tag: 'htk-spin' + + ,render: function() { + var input = this.createRoot('input'); + //setInputTypeNumber (input); + this.node.type = 'number'; + input.addEventListener('change', this._onChange.bind(this)); + + this.unit = null; + this.digits = 0; + } + + ,_onChange: function() { + var newValue = (this.node.value == '') ? null : parseFloat(this.node.value); + this.node.value = newValue; + this.valueChanged(newValue); + } + + ,putValue: function(value) { + var text; + + if (value != null) { + text = (new Number(value)).toFixed(this.digits); + + if (this.unit != null) + text += ' ' + this.unit; + } else + text = ''; + + this.node.value = text; + } + + ,setEditable: function(editable) { + this.node.readOnly = !editable; + } +}); diff --git a/js/htk/spinner.js b/js/htk/spinner.js deleted file mode 100644 index f7edcd39..00000000 --- a/js/htk/spinner.js +++ /dev/null @@ -1,38 +0,0 @@ - -var Widget = require ('./widget'); - -module.exports = new Class -({ - Extends: Widget - ,Tag: 'htk-spinner' - - ,_started: false - - ,render: function () - { - var loader = this.createRoot ('div'); - loader.className = 'htk-spinner'; - - var spin = this.spin = this.createElement ('div'); - loader.appendChild (spin); - } - - ,start: function () - { - if (this._started) - return; - - Vn.Node.addClass (this.spin, 'spinner'); - this._started = true; - } - - ,stop: function () - { - if (!this._started) - return; - - Vn.Node.removeClass (this.spin, 'spinner'); - this._started = false; - } -}); - diff --git a/js/htk/spinner/index.js b/js/htk/spinner/index.js new file mode 100644 index 00000000..326a68d9 --- /dev/null +++ b/js/htk/spinner/index.js @@ -0,0 +1,34 @@ +require('./style.scss'); +var Component = require('vn/component'); + +module.exports = new Class({ + Extends: Component + ,Tag: 'htk-spinner' + + ,_started: false + + ,render: function() { + var loader = this.createRoot('div'); + loader.className = 'htk-spinner'; + + var spin = this.spin = this.createElement('div'); + loader.appendChild(spin); + } + + ,start: function() { + if (this._started) + return; + + Vn.Node.addClass(this.spin, 'spinner'); + this._started = true; + } + + ,stop: function() { + if (!this._started) + return; + + Vn.Node.removeClass(this.spin, 'spinner'); + this._started = false; + } +}); + diff --git a/js/htk/spinner/style.scss b/js/htk/spinner/style.scss new file mode 100644 index 00000000..df630787 --- /dev/null +++ b/js/htk/spinner/style.scss @@ -0,0 +1,31 @@ + +.htk-spinner { + width: 1.8em; + height: 1.8em; + position: relative; + display: inline-block; + + & > .spinner { + left: 0; + position: absolute; + width: inherit; + height: inherit; + box-sizing: border-box; + border-radius: 50%; + border: 2px solid transparent; + border-top-color: #666; + border-left-color: #666; + animation: spinner 1s linear infinite; + -webkit-animation: spinner 1s linear infinite; + } + &.dark > .spinner { + border-top-color: white; + border-left-color: white; + } +} +@keyframes spinner { + to {transform: rotate(360deg);} +} +@-webkit-keyframes spinner { + to {-webkit-transform: rotate(360deg);} +} diff --git a/js/htk/step.js b/js/htk/step/index.js similarity index 82% rename from js/htk/step.js rename to js/htk/step/index.js index 724266a4..86ce2a2f 100644 --- a/js/htk/step.js +++ b/js/htk/step/index.js @@ -1,8 +1,8 @@ -var Widget = require('./widget'); +var Component = require('vn/component'); module.exports = new Class({ - Extends: Widget, + Extends: Component, Tag: 'htk-step', Properties: { name: { @@ -22,7 +22,7 @@ module.exports = new Class({ initialize: function(props) { var node = this.createRoot('div'); node.className = 'htk-step'; - this.parent(props); + Component.prototype.initialize.call(this, props); }, show: function() { @@ -42,7 +42,7 @@ module.exports = new Class({ }, appendChild: function(child) { - if (child instanceof Widget) + if (child instanceof Component) child = child.node; this.node.appendChild(child); } diff --git a/js/htk/style/components.scss b/js/htk/style/components.scss deleted file mode 100644 index 35822fbd..00000000 --- a/js/htk/style/components.scss +++ /dev/null @@ -1,708 +0,0 @@ - -@import "./variables"; - -/* Icon */ - -.htk-icon {} - -/* Button */ - -.htk-button { - display: flex; - align-items: center; - gap: 8px; - - & > .htk-icon { - display: block; - } -} - -/* Grid */ - -.htk-grid { - margin: auto; - border-collapse: collapse; - - & > thead > tr, - & > tfoot > tr { - background-color: $color-primary; - vertical-align: middle; - height: 3em; - } - th { - color: white; - cursor: pointer; - font-weight: normal; - padding: 0 0.4em; - } - th:hover { - background-color: rgba(1, 1, 1, 0.2); - } - tr { - height: 3.5em; - } - & > tfoot a, - & > thead a { - color: black; - } - tr.pair-row { - background-color: transparent; - } - & > tbody tr { - border-top: 1px solid #DDD; - } - & > tbody tr:first-child { - border-top: none; - } - & > tbody td { - margin: 0; - padding: 0 0.5em; - } - th, - td { - text-align: left; - } - td:first-child, - th:first-child { - padding-left: 1em; - } - td:last-child, - th:last-child { - padding-right: 1em; - } - .message { - padding: 1.5em; - text-align: center; - } - .message > * { - display: inline-block; - vertical-align: middle; - padding-right: .8em; - } -} - -/* Repater */ - -.htk-repeater { - & > .message { - padding: 1.5em; - text-align: center; - - & > * { - vertical-align: middle; - } - & > span, - & > .htk-spinner { - display: inline-block; - padding-right: 10px; - } - } -} - -/* Grid cells */ - -th.cell-spin { - text-align: right; -} -td.cell-spin { - width: 2.5em; - text-align: right; -} -th.cell-check, -th.cell-radio { - text-align: center; -} -td.cell-button { - max-width: 20px; - text-align: center; -} -td.cell-button > button, -td.cell-button > a { - @extend %clickable; - display: block; - height: 44px; - width: 44px; - margin: 0 auto; - border-radius: 50%; - padding: 10px; - border: none; - background-color: transparent; - box-sizing: border-box; - - & > .htk-icon { - display: block; - } -} -td.cell-button > button:hover, -td.cell-button > a:hover { - background-color: rgba(1, 1, 1, 0.1); -} -td.cell-button img { - height: 1.5em; - width: 1.5em; - display: block; - margin: auto; - padding: 0; -} -td.cell-image { - text-align: center; -} -td.cell-image .htk-image { - max-width: 2.5em; - max-height: 2.5em; - display: block; - margin: auto; -} - -/* Select */ - -.htk-select, -.htk-date-chooser { - display: flex; - align-items: center; - font-weight: normal; - - & > span { - flex: 1; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - & > .htk-icon { - flex: none; - color: #666; - } -} -.htk-select { - width: 100%; -} -.htk-select-menu { - height: 100%; - max-height: 80em; - overflow: auto; - min-width: 14em; - - tbody > tr { - border-top: none; - height: 2.5em; - } - td.message { - padding: 1em; - } - tr:hover { - background-color: rgba(1, 1, 1, 0.1); - cursor: pointer; - } - td { - max-width: 11em; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -} - -/* List */ - -.htk-list { - a.item, - .item.clickable { - @extend %clickable; - } - .item { - padding: 20px; - border-bottom: 1px solid #DDD; - display: flex; - align-items: center; - - & > .side { - flex: none; - } - & > .content { - flex: 1; - overflow: hidden; - - & > .important { - font-weight: bold; - font-size: 1rem; - margin-bottom: .5em; - } - & > p { - margin: .1em 0; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - } - & > .actions { - flex: none; - display: none; - align-items: center; - - & > .htk-button { - margin: 0; - } - & > * { - display: inline-block; - vertical-align: middle; - } - & > input { - margin: .6em; - } - } - } - .item:hover > .actions { - display: flex; - } - .item:last-child { - border-bottom: none; - } -} - -/* Calendar */ - -.htk-calendar { - @extend %box; - width: 20em; - - table { - border-collapse: collapse; - } - thead tr, - tfoot tr { - font-weight: normal; - vertical-align: middle; - text-align: center; - height: 3em; - } - thead > tr { - &.weekdays > th { - font-weight: normal; - color: #999; - text-transform: lowercase; - } - & > th { - &.previous, &.next { - font-size: .8rem; - - button { - border-radius: 50%; - padding: 10px; - display: block; - margin: 0 auto; - - & > .htk-icon { - font-size: 1rem; - } - } - } - &.month-year { - font-size: 1.2rem; - text-transform: lowercase; - } - } - } - tfoot tr { - border-top: none; - } - th.button { - display: table-cell; - } - th.button:hover { - cursor: pointer; - background-color: rgba(1, 1, 1, 0.2); - } - col { - width: 14.2%; - } - tr { - height: 2em; - } - tbody td { - text-align: right; - } - tbody td > div { - height: 2em; - width: 2em; - line-height: 2em; - text-align: center; - border-radius: 2em; - padding: 0.3em; - margin: 0 auto; - color: #555; - } - div { - &.disabled { - color: #bbb; - } - &.today { - font-weight: bold; - color: black; - } - &.selected { - color: white; - background-color: $color-primary; - } - &.enabled { - @extend %clickable; - - &:hover { - background-color: rgba(140, 198, 63, 0.8); - } - } - } -} - -/* Image */ - -.htk-image { - position: relative; - overflow: hidden; - transition: opacity 250ms ease-out; - - &.clickable:hover { - cursor: pointer; - opacity: 0.85; - } - & > img { - display: block; - height: 100%; - width: 100%; - } - & > button { - position: absolute; - top: 0; - left: 0; - padding: 8px; - margin: 4px; - display: none; - background-color: rgba(255, 255, 255, .6); - - &:hover { - background-color: rgba(255, 255, 255, .8); - } - } - &:hover > button { - display: block; - } - & > button > .htk-icon { - display: block; - } -} - -/* Full image */ - -.htk-full-image { - img { - display: block; - cursor: pointer; - } - .htk-spinner { - background-color: #FFF; - margin: .6em; - display: block; - } -} - -/* Image editor */ - -.htk-image-editor { - width: 18em; - margin: 0 auto; - padding: 1.5em; - - h2 { - color: white; - background-color: #009688; - text-align: left; - font-size: 1.3em; - line-height: 1.7em; - font-weight: normal; - padding: 0.6em 0.8em; - margin: 0; - } - iframe { - display: none; - } - .footer { - margin-top: 2em; - - & > .htk-spinner { - padding-right: 1.2em; - height: 1.3em; - width: 1.3em; - } - & > .htk-spinner, - & > input { - float: right; - } - } -} - -/* Toast */ - -.htk-toast { - z-index: 210; - display: block; - position: fixed; - left: 50%; - top: 4em; - width: 21em; - margin-left: -10.5em; - text-align: center; - overflow: auto; - max-height: 40em; - overflow: visible; - - & > div { - margin: .5em 0; - padding: .5em 2%; - border-radius: 0.1em; - box-shadow: 0 0 0.4em rgba(1, 1, 1, 0.6); - width: 96%; - - opacity: 0; - transform: translateZ(0) translateY(-1em); - -webkit-transform: translateZ(0) translateY(-1em); - - transition-property: opacity, transform; - transition-duration: 200ms; - transition-timing-function: ease-out; - - &.show { - opacity: 1; - transform: translateZ(0) translateY(0em); - -webkit-transform: translateZ(0) translateY(0em); - } - } - & > .message { - background-color: #BBFFBB; - color: #363; - } - & > .warning { - background-color: #FFE0B2; - color: #C30; - } - & > .error { - background-color: #FFCDD2; - color: #A00; - } -} - -/* Popup */ - -.htk-background { - position: fixed; - left: 0; - right: 0; - top: 0; - bottom: 0; - z-index: 190; - background-color: rgba(1, 1, 1, 0.7); - opacity: 0; - transition: opacity 200ms ease-in-out; -} -.htk-popup { - z-index: 200; - display: block; - position: fixed; - overflow: hidden; - background-color: white; - border-radius: 10px; - box-shadow: 0 0 0.4em rgba(1, 1, 1, 0.6); - box-sizing: content-box; - - &.modal { - position: absolute; - font-size: 1.2em; - top: 50%; - left: 50%; - } - & > * { - border-radius: 0.1em; - } -} - -/* Dialog */ - -.htk-dialog { - padding: 1.5em; - max-width: 20em; - font-weight: normal; - color: #555; - - p { - margin: 0; - } - img { - float: left; - height: 3em; - margin-top: 0; - margin-right: 1em; - } - p { - padding: 0; - } - .button-bar > button { - float: right; - margin-left: 1em; - margin-top: .5em; - } -} - -/* Assistant */ - -.htk-assistant > div { - display: none; - - & > h2 { - text-align: center; - font-weight: normal; - font-size: 1.5rem; - margin: 0; - padding: 0; - margin-bottom: 1em; - font-weight: bold; - } -} - -/* Assistant bar */ - -.htk-assistant-bar { - position: relative; - padding: .8em; - display: flex; - align-items: center; - justify-content: space-between; - box-sizing: border-box; - width: 100%; - - & > button { - border-radius: 50%; - padding: .5em; - margin: 0; - text-align: center; - - & > img { - display: block; - width: 1.8em; - padding: .5em; - } - } - & > .end { - display: none; - color: #8cc63f; - - & > .icon { - font-size: 1.6rem; - } - } - & > .steps { - display: flex; - align-items: center; - - & > div { - background-color: #AAA; - width: .5em; - height: .5em; - cursor: pointer; - border-radius: 50%; - margin: .5em; - - transition-property: width, height; - transition-duration: 100ms; - transition-timing-function: ease-in-out; - - &.selected { - background-color: #666; - width: 1em; - height: 1em; - } - &:hover { - opacity: .7; - } - } - & > img { - width: 1.3em; - margin: 0 .2em; - cursor: pointer; - - &:hover { - opacity: .7; - } - } - } -} - -/* Search entry */ - -.htk-search-entry { - display: flex; - align-items: center; - gap: 6px; - background-color: white; - height: 40px; - border-radius: 20px; - padding: 0 12px; - overflow: hidden; - - & > * { - display: inline-block; - vertical-align: middle; - } - & > .htk-icon { - display: block; - margin: 0; - color: gray; - } - & > .entry { - margin: 0; - border: none; - width: 80px; - box-shadow: none; - padding-right: 0; - padding-left: 0; - height: inherit; - } - & > .entry:focus { - background-color: initial; - } -} - -/* Spinner */ - -.htk-spinner { - width: 1.8em; - height: 1.8em; - position: relative; - display: inline-block; - - & > .spinner { - left: 0; - position: absolute; - width: inherit; - height: inherit; - box-sizing: border-box; - border-radius: 50%; - border: 2px solid transparent; - border-top-color: #666; - border-left-color: #666; - animation: spinner 1s linear infinite; - -webkit-animation: spinner 1s linear infinite; - } - &.dark > .spinner { - border-top-color: white; - border-left-color: white; - } -} -@keyframes spinner { - to {transform: rotate(360deg);} -} -@-webkit-keyframes spinner { - to {-webkit-transform: rotate(360deg);} -} - -/* Loader */ - -.htk-loader > .spinner { - text-align: center; -} diff --git a/js/htk/style/index.scss b/js/htk/style/index.scss index 90999008..6bd86f10 100644 --- a/js/htk/style/index.scss +++ b/js/htk/style/index.scss @@ -4,5 +4,6 @@ @import "./spacing"; @import "./text"; @import "./width"; -@import "./components"; +@import "./responsive"; @import "./material-symbols"; +@import "./style"; diff --git a/js/hedera/opensans.ttf b/js/htk/style/opensans.ttf similarity index 100% rename from js/hedera/opensans.ttf rename to js/htk/style/opensans.ttf diff --git a/js/hedera/poppins.ttf b/js/htk/style/poppins.ttf similarity index 100% rename from js/hedera/poppins.ttf rename to js/htk/style/poppins.ttf diff --git a/js/hedera/responsive.scss b/js/htk/style/responsive.scss similarity index 89% rename from js/hedera/responsive.scss rename to js/htk/style/responsive.scss index 444471a6..8b55c2ad 100644 --- a/js/hedera/responsive.scss +++ b/js/htk/style/responsive.scss @@ -14,19 +14,19 @@ /* Desktop - FHD 1920x1080 */ @media (max-resolution: 119dpi) and (min-device-width: 1900px) { - body { font-size: 13pt; } + body { font-size: 10pt; } } /* Mobile - Low DPI */ @media (min-resolution: 120dpi), (-webkit-min-device-pixel-ratio: 1.5) { - body { font-size: 9pt; } + body { font-size: 10pt; } } @media (min-resolution: 144dpi), (-webkit-min-device-pixel-ratio: 1.5) { - body { font-size: 11pt; } + body { font-size: 10pt; } } /* Mobile - Normal DPI */ @@ -38,7 +38,7 @@ @media (min-device-width: 384px) and (min-resolution: 192dpi), (min-device-width: 384px) and (-webkit-min-device-pixel-ratio: 2) { - body { font-size: 11pt; } + body { font-size: 10pt; } } /* Mobile - High DPI */ @@ -50,5 +50,5 @@ @media (min-device-width: 412px) and (min-resolution: 249dpi), (min-device-width: 412px) and (-webkit-min-device-pixel-ratio: 3) { - body { font-size: 11pt; } + body { font-size: 10pt; } } diff --git a/js/hedera/roboto.ttf b/js/htk/style/roboto.ttf similarity index 100% rename from js/hedera/roboto.ttf rename to js/htk/style/roboto.ttf diff --git a/js/hedera/style.scss b/js/htk/style/style.scss similarity index 95% rename from js/hedera/style.scss rename to js/htk/style/style.scss index 6a8f4aa5..86dfc3db 100644 --- a/js/hedera/style.scss +++ b/js/htk/style/style.scss @@ -1,5 +1,5 @@ -@import "../htk/style/classes"; +@import "./classes"; @font-face { font-family: 'Poppins'; @@ -173,7 +173,7 @@ button, padding: 10px; border-radius: 22px; margin: -0.5em; - font-weight: bold; + font-weight: normal; &:disabled { background-color: rgba(0, 0, 0, .1); @@ -313,18 +313,3 @@ img.icon { @media screen and (min-width: 2000px) { .masonry-box { width: 25%; } } - -/* Social bar */ - -.htk-social-bar { - text-align: center; - - a { - display: inline-block; - margin: 0 .1em; - } - img { - height: 1.8em; - width: 1.8em; - } -} diff --git a/js/htk/table/index.js b/js/htk/table/index.js new file mode 100644 index 00000000..26ed5fad --- /dev/null +++ b/js/htk/table/index.js @@ -0,0 +1,50 @@ + +var Entry = require('../entry'); +var ColumnRadio = require('../columns/radio'); + +module.exports = new Class({ + Extends: Entry + ,Tag: 'htk-table' + + ,render: function() { + var tv = new Htk.TreeView(); + this.node.appendChild(tv.node); + + var renderer = new ColumnRadio(); + tv.appendColumn(0, renderer, ''); + + var rbGroup = renderer.rbGroup; + rbGroup.addSignal('changed', this.changed, this); + + this.treeview = tv; + this.rbGroup = rbGroup; + } + + ,setModel: function(model) { + this.treeview.setModel(model); + model.addSignal('status-changed', this.modelRefresh, this); + this.selectValue(); + } + + ,changed: function() { + this.realValue = this.rbGroup.getValue(); + this.emit('changed'); + } + + ,selectValue: function() { + this.rbGroup.setValue(this.realValue); + } + + ,setRealValue: function() { + this.selectValue(); + } + + ,modelRefresh: function(model, status) { + if (status == Db.Model.Status.READY) + this.selectValue(); + } + + ,setEditable: function(editable) { + this.rbGroup.setEditable(editable); + } +}); diff --git a/js/htk/field/text-area.js b/js/htk/text-area/index.js similarity index 100% rename from js/htk/field/text-area.js rename to js/htk/text-area/index.js diff --git a/js/htk/field/text.js b/js/htk/text/index.js similarity index 100% rename from js/htk/field/text.js rename to js/htk/text/index.js diff --git a/js/htk/toast.js b/js/htk/toast/index.js similarity index 99% rename from js/htk/toast.js rename to js/htk/toast/index.js index e08f142d..6a19ccd0 100644 --- a/js/htk/toast.js +++ b/js/htk/toast/index.js @@ -1,3 +1,5 @@ +require('./style.scss'); + /** * Class to show toast messages. */ diff --git a/js/htk/toast/style.scss b/js/htk/toast/style.scss new file mode 100644 index 00000000..4334fa08 --- /dev/null +++ b/js/htk/toast/style.scss @@ -0,0 +1,47 @@ +.htk-toast { + z-index: 210; + display: block; + position: fixed; + left: 50%; + top: 4em; + width: 21em; + margin-left: -10.5em; + text-align: center; + overflow: auto; + max-height: 40em; + overflow: visible; + + & > div { + margin: .5em 0; + padding: .5em 2%; + border-radius: 0.1em; + box-shadow: 0 0 0.4em rgba(1, 1, 1, 0.6); + width: 96%; + + opacity: 0; + transform: translateZ(0) translateY(-1em); + -webkit-transform: translateZ(0) translateY(-1em); + + transition-property: opacity, transform; + transition-duration: 200ms; + transition-timing-function: ease-out; + + &.show { + opacity: 1; + transform: translateZ(0) translateY(0em); + -webkit-transform: translateZ(0) translateY(0em); + } + } + & > .message { + background-color: #BBFFBB; + color: #363; + } + & > .warning { + background-color: #FFE0B2; + color: #C30; + } + & > .error { + background-color: #FFCDD2; + color: #A00; + } +} \ No newline at end of file diff --git a/js/htk/widget.js b/js/htk/widget.js deleted file mode 100644 index f78860e0..00000000 --- a/js/htk/widget.js +++ /dev/null @@ -1,146 +0,0 @@ - -const NodeBuilder = require('../vn/node-builder'); - -const Widget = new Class({ - Extends: NodeBuilder - ,Properties: { - /** - * Main HTML node that represents the widget - */ - node: { - type: Object - ,get: function() { - this.renderBase(); - return this._node; - } - }, - /** - * CSS syle. - */ - style: { - type: String - ,set: function(x) { - this.node.style = x; - } - ,get: function() { - return this._node.style; - } - }, - /** - * CSS classes to be appendend to the node classes. - */ - class: { - type: String - ,set: function(x) { - this._cssClass = x; - this._refreshClass(); - } - ,get: function() { - return this._node.className; - } - }, - /** - * CSS class list. - */ - classList: { - type: Object - ,get: function() { - return this._node.classList; - } - }, - /** - * Title of the element. - */ - title: { - type: String - ,set: function(x) { - this._node.title = x; - } - ,get: function() { - return this._node.title; - } - }, - /** - * The HTML id of the element. - */ - htmlId: - { - type: String - ,set: function(x) { - this._htmlId = x; - this._node.id = x; - } - ,get: function() { - return this._htmlId; - } - }, - } - - ,_node: null - - ,initialize: function(props) { - this.doc = document; - this.renderBase(); - this.parent(props); - } - - ,createRoot: function(tagName) { - return this._node = this.createElement(tagName); - } - - ,renderBase: function() { - if (this._node) - return; - - this.render(); - this._refreshClass(); - } - - ,_refreshClass: function() { - if (this._node && this._cssClass) - this._node.className = this._cssClass +' '+ this._node.className; - } - - ,remove: function() { - Vn.Node.remove(this._node); - } - - ,on: function(id, callback, instance) { - if (htmlEventMap[id]) { - this.node.addEventListener(id, - callback.bind(instance, this)); - } else - this.parent(id, callback, instance); - } -}); - -htmlEventMap = {}; -htmlEvents = [ - 'click', - 'dblclick', - 'keydown', - 'keypress', - 'keyup', - 'mousedown', - 'mousemove', - 'mouseout', - 'mouseover', - 'mouseup', - 'mousewheel', - 'wheel', - 'focus', - 'focusout', - 'focusin' -]; -htmlEvents.forEach(x => htmlEventMap[x] = true); - -htmlMethods = [ - 'addEventListener' -]; -htmlMethods.forEach(method => { - Widget[method] = function() { - this.node.apply(this.node, arguments); - }; -}); - -module.exports = Widget; diff --git a/js/sql/batch.js b/js/sql/batch.js deleted file mode 100644 index 6071e48e..00000000 --- a/js/sql/batch.js +++ /dev/null @@ -1,142 +0,0 @@ - -var Object = require('./object'); -var Value = require('./value'); - -/** - * A map container for many Sql.Object - */ -module.exports = new Class({ - Extends: Object - ,Tag: 'sql-batch' - ,Properties: { - blocked: { - type: Boolean - ,set: function(x) { - this._blocked = x; - } - ,get: function() { - return this._blocked; - } - } - } - - ,objects: {} - ,_blocked: false - - ,loadXml: function(scope, node) { - this.parent(scope, node); - - var childs = node.childNodes; - - for (var i = 0; i < childs.length; i++) - if (childs[i].tagName && childs[i].tagName.toLowerCase() == 'item') { - var object; - var id = childs[i].getAttribute('name'); - - if (id) { - if (object = scope.getById(childs[i].getAttribute('param'))) - this.addParam(id, object); - else if (object = scope.getById(childs[i].getAttribute('object'))) - this.addObject(id, object); - } - } - } - - ,get: function(id) { - if (this.objects[id]) - return this.objects[id]; - - return null; - } - - ,add: function(id) { - if (!this.objects[id]) - this.objects[id] = null; - } - - ,_addObject: function(id, object) { - this.remove(id); - this.objects[id] = object; - object.on('changed', this.emitChanged, this); - this.emitChanged(); - } - - ,addObject: function(id, object) { - this._addObject(id, object.ref()); - } - - ,addValue: function(id, value) { - this._addObject(id, - new Value({value: value})); - } - - ,addValues: function(values) { - for (var id in values) - this.addValue(id, values[id]); - } - - ,addParam: function(id, param) { - this._addObject(id, - new Value({param: param})); - } - - ,getValue: function(id) { - var object = this.objects[id]; - - if (object instanceof Value) - return object.value; - - return null; - } - - ,addParams: function(params) { - for (var id in params) - this.addParam(id, params[id]); - } - - ,remove: function(id) { - if (this.objects[id]) { - this._unrefObject(this.objects[id]); - delete this.objects[id]; - } - } - - ,block: function() { - this._blocked = true; - } - - ,unblock: function() { - this._blocked = false; - } - - ,emitChanged: function() { - if (!this._blocked) - this.signalEmit('changed'); - } - - ,changed: function() { - this.signalEmit('changed'); - } - - ,isReady: function() { - for (var id in this.objects) - if (!(this.objects[id] && this.objects[id].isReady())) - return false; - - return true; - } - - ,_unrefObject: function(object) { - if (object) { - object.disconnect('changed', this.emitChanged, this); - object.unref(); - } - } - - ,_destroy: function() { - for (var id in this.objects) - this._unrefObject(this.objects[id]); - - this.parent(); - } -}); diff --git a/js/sql/delete.js b/js/sql/delete.js index 41ee95f5..53447391 100644 --- a/js/sql/delete.js +++ b/js/sql/delete.js @@ -4,19 +4,14 @@ var Stmt = require('./stmt'); /** * The equivalent of a SQL delete. */ -module.exports = new Class -({ +module.exports = new Class({ Extends: Stmt + ,Tag: 'sql-delete' - ,render: function(batch) { - var sql = 'DELETE FROM ' + this.renderTarget(batch); - - if (this.where) - sql += ' WHERE ' + this.where.render(batch); - - sql += ' LIMIT 1'; // Only for security. - - return sql; + ,render: function(params) { + return 'DELETE FROM' + + this.renderTarget(params) + + this.renderIfSet(this.where, 'WHERE', params) + + this.renderLimit(params); } }); - diff --git a/js/sql/dml.js b/js/sql/dml.js index 31f2bab8..00ad03f5 100644 --- a/js/sql/dml.js +++ b/js/sql/dml.js @@ -13,13 +13,13 @@ module.exports = new Class({ ,expr: [] ,addSet: function(fieldName, value) { - this.expr.push(new Value({value: value})); this.field.push(new Field({name: fieldName})); + this.expr.push(new Value({value: value})); } ,addExpr: function(fieldName, expr) { - this.expr.push(expr); this.field.push(new Field({name: fieldName})); + this.expr.push(expr); } ,delSet: function() { diff --git a/js/sql/field.js b/js/sql/field.js index 315d5af1..79839d48 100644 --- a/js/sql/field.js +++ b/js/sql/field.js @@ -6,26 +6,28 @@ var Expr = require('./expr'); * * @param {string} taget The name of the owner table */ -module.exports = new Class -({ +module.exports = new Class({ Extends: Expr ,Tag: 'sql-field' - ,Properties: - { - name: - { + ,Properties: { + /** + * The column name. + */ + name: { type: String ,value: null }, - target: - { + /** + * The source table name or its alias if it has been specified. + */ + target: { type: String ,value: null } } - ,render: function(batch) { - var sql = (this.target) ? '`' + this.target + '`.' : ''; - return sql + '`' + this.name + '`'; + ,render: function() { + return this.renderPreIdent(this.target) + + this.renderIdent(this.name); } }); diff --git a/js/sql/filter-item.js b/js/sql/filter-item.js index a8d08d8e..343eebb3 100644 --- a/js/sql/filter-item.js +++ b/js/sql/filter-item.js @@ -1,17 +1,62 @@ var Operation = require('./operation'); +var Value = require('./value'); +var Field = require('./field'); /** - * The equivalent of a SQL operation. + * Objects to be used as an operands of @Sql.Filter. It represents a two + * expressions basic operation composed by a table column, the operator and the + * value extracted from the rendering paramerers. */ module.exports = new Class({ Extends: Operation ,Tag: 'sql-filter-item' ,Properties: { - primary: { - type: Boolean + /** + * The column name. + */ + field: { + type: String, + value: null + }, + /** + * The source table name or its alias if it has been specified. + */ + target: { + type: String, + value: null + }, + /** + * The parameter name. + */ + param: { + type: String, + value: null } } - - ,primary: true + + /** + * Checks if parameter name has been defined and if it has a value. + */ + ,isReady: function(params) { + return this.param != null && params != null && params[this.param] != null; + } + + ,render: function(params) { + var newOp = new Operation({type: this.type}); + + newOp.push(new Field({ + name: this.field, + target: this.target + })); + + var value = params[this.param]; + newOp.push(new Value({value: value})); + + return newOp.render(params); + } + + ,findHolders: function() { + return this.param ? [this.param] : null; + } }); diff --git a/js/sql/filter.js b/js/sql/filter.js index 8b515905..111a6914 100644 --- a/js/sql/filter.js +++ b/js/sql/filter.js @@ -1,44 +1,49 @@ var Operation = require('./operation'); +var Value = require('./value'); /** - * The equivalent of a SQL operation. + * The equivalent of a SQL filter expression. It allows to automatically build + * SQL filters based on lot parameters. */ module.exports = new Class({ Extends: Operation ,Tag: 'sql-filter' - ,Properties: { - alwaysReady: { - type: Boolean - } - } - ,isReady: function() { - if (this.alwaysReady) + /** + * Checks if any of filters childs are ready. + */ + ,isReady: function(params) { + var exprs = this.exprs; + for (var i = exprs.length; i--;) + if (exprs[i].isReady(params)) return true; - - var e = this.exprs.getArray(); - for (var i = 0; i < e.length; i++) - if (e[i].isReady() && e[i].primary) - return true; - + return false; } - ,render: function(batch) { - var isReady = false; - var newOp = new Operation({type: this.type}); + /** + * Renders the filter as an SQL expression. If any of its childs isn't + * ready is ommitted from the expression. If all of its childs aren't ready + * renders the TRUE expression. + */ + ,render: function(params) { + var newOp; + var newExprs = []; + + this.exprs.forEach(function(expr) { + if (expr.isReady(params)) + newExprs.push(expr); + }) - var e = this.exprs.getArray(); - for (var i = 0; i < e.length; i++) - if (e[i].isReady()) { - newOp.exprs.add(e[i]); - isReady = true; - } + if (newExprs.length > 0) + newOp = new Operation({ + type: this.type, + exprs: newExprs + }); + else + newOp = new Value({value: true}); - if (!isReady) - return 'TRUE'; - - return newOp.render(batch); + return newOp.render(params); } }); diff --git a/js/sql/function.js b/js/sql/function.js index b24df9d9..50fc7c0a 100644 --- a/js/sql/function.js +++ b/js/sql/function.js @@ -1,38 +1,48 @@ -var Expr = require ('./expr'); -var List = require ('./list'); +var Expr = require('./expr'); +var ListHolder = require('./list-holder'); /** * The equivalent of a SQL function. - * - * @param {string} funcName The name of the function - * @param {Array#Sql.Expr} param Array with function parameters */ -module.exports = new Class -({ +module.exports = new Class({ Extends: Expr - ,Properties: - { - name: - { + ,Tag: 'sql-function' + ,Implements: ListHolder + ,Properties: { + /** + * The function name. + */ + name: { type: String ,value: null }, - schema: - { + /** + * The function schema. + */ + schema: { type: String ,value: null }, - params: - { - type: List - ,value: null + /** + * The function parameters. + */ + params: { + type: Array + ,set: function(x) { + this.list = x; + } + ,get: function() { + return this.list; + } } } - ,render: function (batch) - { - var sql = (this.schema) ? '`' + this.schema + '`.' : ''; - return sql + '`' + this.name + '`()'; + ,render: function(params) { + return this.renderPreIdent(this.schema) + + this.renderIdent(this.name) + + '(' + + this.renderListWs(this.list, params, ', ') + + ')'; } }); diff --git a/js/sql/holder.js b/js/sql/holder.js index 797731e2..67083cfa 100644 --- a/js/sql/holder.js +++ b/js/sql/holder.js @@ -1,26 +1,32 @@ -var Object = require('./object'); +var SqlObject = require('./object'); +var Value = require('./value'); /** * A holder for another object. */ -module.exports = new Class -({ - Extends: Object - ,Properties: - { - id: - { +module.exports = new Class({ + Extends: SqlObject + ,Properties: { + id: { type: String ,value: null } } - ,render: function(batch) { - var object; + ,render: function(params) { + if (params) { + var object = params[this.id]; - if (batch && (object = batch.get(this.id))) - return object.render(batch); + if (object !== undefined) { + if (!(object instanceof SqlObject)) { + var sqlValue = new Value(); + sqlValue.value = object; + return sqlValue.render(); + } else + return object.render(params); + } + } return '#'+ this.id; } diff --git a/js/sql/insert.js b/js/sql/insert.js index a74d29ae..6e6dd4c7 100644 --- a/js/sql/insert.js +++ b/js/sql/insert.js @@ -1,38 +1,19 @@ -var Dml = require ('./dml'); +var Dml = require('./dml'); /** * The equivalent of a SQL insert. */ -module.exports = new Class -({ +module.exports = new Class({ Extends: Dml - ,render: function (batch) - { - var sql; - var n; - - sql = 'INSERT INTO ' + this.renderTarget (batch) + ' ('; - - for (n = 0; n < this.field.length; n++) - { - if (n > 0) - sql += ', '; - sql += this.field[n].render (batch); - } - - sql += ') VALUES ('; - - for (n = 0; n < this.field.length; n++) - { - if (n > 0) - sql += ', '; - sql += this.expr[n].render(batch); - } - - sql += ')'; - - return sql; + ,render: function(params) { + return 'INSERT INTO' + + this.renderTarget(params) + + ' (' + + this.renderListWs(this.field, params, ', ') + + ') VALUES (' + + this.renderListWs(this.expr, params, ', ') + + ')'; } }) diff --git a/js/sql/join-item.js b/js/sql/join-item.js new file mode 100644 index 00000000..3e63cc9a --- /dev/null +++ b/js/sql/join-item.js @@ -0,0 +1,48 @@ + +var Target = require('./target'); +var Expr = require('./expr'); +var SqlObject = require('./object'); +var Type = require('./join').Type; + +var TypeSql = [ + 'INNER', + 'LEFT', + 'RIGHT' +]; + +/** + * The equivalent of a SQL join. + */ +module.exports = new Class({ + Extends: SqlObject + ,Tag: 'sql-join-table' + ,Properties: { + /** + * The join type. + */ + type: { + enumType: Type + ,value: 0 + }, + /** + * The right target. + */ + target: { + type: Target + ,value: null + }, + /** + * The join on condition. + */ + condition: { + type: Expr + ,value: null + } + } + + ,render: function(params) { + return TypeSql[this.type] +' JOIN ' + + this.target.render(params) + + this.renderIfSet(this.condition, 'ON', params); + } +}); diff --git a/js/sql/join.js b/js/sql/join.js new file mode 100644 index 00000000..37fcb8c8 --- /dev/null +++ b/js/sql/join.js @@ -0,0 +1,47 @@ + +var Target = require('./target'); +var ListHolder = require('./list-holder'); + +/** + * The equivalent of a SQL join. + */ +var Klass = new Class(); +module.exports = Klass; + +var Type = { + INNER : 0, + LEFT : 1, + RIGHT : 2 +}; + +Klass.extend({ + Type: Type +}); + +Klass.implement({ + Extends: Target + ,Implements: ListHolder + ,Tag: 'sql-join' + ,Properties: { + /** + * The right targets. + */ + targets: { + type: Array + ,set: function(x) { + this.list = x; + } + ,get: function() { + return this.list; + } + } + } + + ,render: function(params) { + return '(' + + this.target.render(params) + + ' ' + + this.renderList(this.list, params) + + ')'; + } +}); diff --git a/js/sql/list-holder.js b/js/sql/list-holder.js new file mode 100644 index 00000000..470ee8bb --- /dev/null +++ b/js/sql/list-holder.js @@ -0,0 +1,61 @@ +/** + * Interface for array holders. + */ +module.exports = new Class({ + Properties: { +/* list: { + type: Array + ,set: function (x) { + this._list = x; + } + ,get: function () { + return this._list; + } + } +*/ + } + + ,list: [] + + ,appendChild: function(child) { + this.list.push(child); + } + + /** + * Adds an element to the list. + * + * @param {SqlObject} element The element to add + */ + ,push: function(element) { + this.list.push(element); + } + + /** + * Removes an element from the list. + * + * @param {Number} i The element index + */ + ,splice: function(i) { + this.list.splice(i); + } + + /** + * Adds an element to the list. + * + * @param {Number} i The element index + */ + ,get: function(i) { + return this.list[i]; + } + + ,findHolders() { + let ids = []; + + for (const object of this.list){ + holders = object.findHolders(); + if (holders) ids = ids.concat(holders); + } + + return ids; + } +}); diff --git a/js/sql/list.js b/js/sql/list.js index df73cac9..b9c7e697 100644 --- a/js/sql/list.js +++ b/js/sql/list.js @@ -1,11 +1,11 @@ -var Object = require('./object'); +var SqlObject = require('./object'); /** * List of Sql.Object */ module.exports = new Class({ - Extends: Object + Extends: SqlObject ,objects: [] @@ -29,7 +29,7 @@ module.exports = new Class({ } ,_onObjectChange: function() { - this.signalEmit('changed'); + this.emit('changed'); } ,isReady: function() { @@ -44,6 +44,17 @@ module.exports = new Class({ return true; } + + ,findHolders() { + let ids = []; + + for (const object of this.objects){ + holders = object.findHolders(); + if (holders) ids = ids.concat(holders); + } + + return ids; + } ,_unrefObject: function(object) { object.disconnect('changed', this._onObjectChange, this); @@ -54,7 +65,7 @@ module.exports = new Class({ for (var i = 0; i < this.objects.length; i++) this._unrefObject(this.objects[i]); - this.parent(); + SqlObject.prototype._destroy.call(this); } }); diff --git a/js/sql/multi-stmt.js b/js/sql/multi-stmt.js index ecd91a91..60504a28 100644 --- a/js/sql/multi-stmt.js +++ b/js/sql/multi-stmt.js @@ -1,49 +1,30 @@ -var Stmt = require ('./stmt'); +var Stmt = require('./stmt'); +var ListHolder = require('./list-holder'); /** * The equivalent of a SQL multi statement. */ -module.exports = new Class -({ +module.exports = new Class({ Extends: Stmt - - ,stmts: [] - - ,addStmt: function (stmt) - { - return this.stmts.push (stmt); - } - - ,getStmt: function (stmtIndex) - { - return this.stmts[index]; - } - - ,isReady: function () - { - if (this.stmts.length == 0) - return false; - - for (var i = 0; i < this.stmts.length; i++) - if (!this.stmts[i].isReady ()) - return false; - - return true; - } - - ,render: function (batch) - { - var sql = ''; - - for (var i = 0; i < this.stmts.length; i++) - { - if (i > 0) - sql += ";\n"; - - sql += this.stmts[i].render (batch); + ,Implements: ListHolder + ,Tag: 'sql-multi-stmt' + ,Properties: { + /** + * The statements list. + */ + stmts: { + type: Array + ,set: function(x) { + this.list = x; + } + ,get: function() { + return this.list; + } } + } - return sql; + ,render: function(params) { + return this.renderListWs(this.list, params, ";\n"); } }); diff --git a/js/sql/object.js b/js/sql/object.js index 0da96489..848ac8ce 100644 --- a/js/sql/object.js +++ b/js/sql/object.js @@ -3,18 +3,11 @@ */ module.exports = new Class({ Extends: Vn.Object - - /** - * Renders the object as an SQL string. - * - * @param {Sql.Batch} batch The batch used to render the object - * @return {String} The SQL string - */ - ,render: function(batch) {} /** * Gets if the object is ready to be rendered. * + * @param {Object} params The query parameters * @return {boolean} %true if the object is ready, %false otherwise */ ,isReady: function() { @@ -22,9 +15,90 @@ module.exports = new Class({ } /** - * Through the query looking for containers and adds it to the batch. + * Through the query looking for containers. * - * @return {Sql.Batch} batch The batch + * @return {Array} An array with the names of the found parameters */ - ,findHolders: function(batch) {} + ,findHolders: function() {} + + /** + * Renders the object as an SQL string. + * + * @param {Object} params The params used to render the object + * @return {string} The SQL string + */ + ,render: function() {} + + /** + * Renders an objects array. + * + * @param {Array} list The objects array + * @param {Object} params The parameters + * @return {string} The rendered SQL string + */ + ,renderList: function(list, params) { + var sql = ''; + + list.forEach(function(item) { + sql += item.render(params); + }) + + return sql; + } + + /** + * Renders an objects array using a separator. + * + * @param {Array} list The objects array + * @param {Object} params The parameters + * @param {String} separator The separator between items + * @return {string} The rendered SQL string + */ + ,renderListWs: function(list, params, separator) { + var sql = ''; + + list.forEach(function(item, i) { + if (i > 0) + sql += separator; + sql += item.render(params); + }) + + return sql; + } + + /** + * Renders a quoted SQL identifier. + * + * @param {String} identifier The identifier + * @return {string} The quoted identifier + */ + ,renderIdent: function(identifier) { + return '`'+ identifier +'`'; + } + + /** + * Renders a quoted SQL identifier. + * + * @param {String} identifier The identifier + * @return {string} The quoted identifier + */ + ,renderPreIdent: function(identifier) { + if (identifier) + return this.renderIdent(identifier) +'.'; + else + return ''; + } + + /** + * Renders the object if it's defined. + * + * @param {String} prefix The rendered string prefix + * @return {string} The rendered object with its prefix + */ + ,renderIfSet: function(object, prefix, params) { + if (object) + return ' '+ prefix +' '+ object.render(params); + else + return ''; + } }); diff --git a/js/sql/operation.js b/js/sql/operation.js index a87a11c8..9f491f4a 100644 --- a/js/sql/operation.js +++ b/js/sql/operation.js @@ -1,14 +1,15 @@ var Expr = require('./expr'); +var ListHolder = require('./list-holder'); /** * The equivalent of a SQL operation between exprs. * - * @param {Array#Sql.Expr} expr Array with the exprs - * @param {Sql..Operation.Type} type The type of the operation + * @param {Array#Expr} exprs Array with the exprs + * @param {Type} type The type of the operation */ -var Operation = new Class(); -module.exports = Operation; +var Klass = new Class(); +module.exports = Klass; var Type = { EQUAL : 0 @@ -16,6 +17,16 @@ var Type = { ,AND : 2 ,OR : 3 ,REGEXP : 4 + ,LOWER : 5 + ,UPPER : 6 + ,LE : 7 + ,UE : 8 + ,PLUS : 9 + ,MINUS : 10 + ,MULT : 11 + ,DIV : 12 + ,NE : 13 + ,MOD : 14 }; var Operators = [ @@ -24,55 +35,51 @@ var Operators = [ ,'AND' ,'OR' ,'REGEXP' + ,'<' + ,'>' + ,'<=' + ,'>=' + ,'+' + ,'-' + ,'*' + ,'/' + ,'<>' + ,'MOD' ]; -Operation.extend({ +Klass.extend({ Type: Type ,Operators: Operators }); -Operation.implement({ +Klass.implement({ Extends: Expr + ,Implements: ListHolder ,Tag: 'sql-operation' ,Properties: { type: { enumType: Type ,value: -1 + }, + target: { + type: String + ,value: null + }, + exprs: { + type: Array + ,set: function(x) { + this.list = x; + } + ,get: function() { + return this.list; + } } } - ,initialize: function(props) { - this.parent(props); - this.link({exprs: new Sql.List()}, {'changed': this.onListChange}); - } - - ,appendChild: function(child) { - this.exprs.add(child); - } - - ,onListChange: function() { - this.signalEmit('changed'); - } - - ,isReady: function() { - return this.exprs.isReady(); - } - - ,render: function(batch) { - var sql = '('; + ,render: function(params) { var operator = ' '+ Operators[this.type] +' '; - var e = this.exprs.getArray(); - - for (var i = 0; i < e.length; i++) { - if (i > 0) - sql += operator; - - sql += e[i].render(batch); - } - - sql += ')'; - - return sql; + return '(' + + this.renderListWs(this.list, params, operator) + + ')'; } }); - diff --git a/js/sql/search-tags.js b/js/sql/search-tags.js deleted file mode 100644 index 4c24ff80..00000000 --- a/js/sql/search-tags.js +++ /dev/null @@ -1,19 +0,0 @@ - -var Value = require('./value'); - -/** - * The equivalent of a SQL value. - */ -module.exports = new Class -({ - Extends: Value - ,Tag: 'sql-search-tags' - - ,render: function(batch) { - if (typeof this._value == 'string') { - var value = this._value.replace(/^|\s+|$/g, '%'); - return "'" + value.replace(this.regexp, this.replaceFunc) + "'"; - } else - return this.parent(); - } -}); diff --git a/js/sql/select.js b/js/sql/select.js index 4a0db861..12840246 100644 --- a/js/sql/select.js +++ b/js/sql/select.js @@ -1,37 +1,25 @@ -var Stmt = require ('./stmt'); -var Field = require ('./field'); +var Stmt = require('./stmt'); +var Field = require('./field'); /** * The equivalent of a SQL select. */ -module.exports = new Class -({ +module.exports = new Class({ Extends: Stmt ,expr: [] - ,addField: function (fieldName) - { - this.expr.push (new Field ({name: fieldName})); + ,addField: function(fieldName) { + this.expr.push(new Field({name: fieldName})); } - ,render: function (batch) - { - var sql = 'SELECT ' - - for (var i = 0; i < this.expr.length; i++) - { - if (i > 0) - sql += ', '; - sql += this.expr[i].render(batch); - } - - sql += ' FROM ' + this.renderTarget (batch); - - if (this.where) - sql += ' WHERE ' + this.where.render (batch); - - return sql; + ,render: function(params) { + return 'SELECT ' + + this.renderListWs(this.expr, params, ', ') + + ' FROM' + + this.renderTarget(params) + + this.renderIfSet(this.where, 'WHERE', params) + + this.renderLimit(params); } }); diff --git a/js/sql/sql.js b/js/sql/sql.js index 7de038f9..068b8231 100644 --- a/js/sql/sql.js +++ b/js/sql/sql.js @@ -1,28 +1,29 @@ -require ('vn/vn'); +require('vn/vn'); Sql = module.exports = { - Object : require ('./object') - ,Holder : require ('./holder') - ,Batch : require ('./batch') - ,List : require ('./list') - ,Expr : require ('./expr') - ,Value : require ('./value') - ,Field : require ('./field') - ,Function : require ('./function') - ,Operation : require ('./operation') - ,Target : require ('./target') - ,Table : require ('./table') - ,Stmt : require ('./stmt') - ,Dml : require ('./dml') - ,String : require ('./string') - ,Delete : require ('./delete') - ,Insert : require ('./insert') - ,Select : require ('./select') - ,Update : require ('./update') - ,MultiStmt : require ('./multi-stmt') - ,Filter : require ('./filter') - ,FilterItem : require ('./filter-item') - ,SearchTags : require ('./search-tags') + Object : require('./object') + ,Holder : require('./holder') + ,List : require('./list') + ,ListHolder : require('./list-holder') + ,Expr : require('./expr') + ,Value : require('./value') + ,Field : require('./field') + ,Function : require('./function') + ,Operation : require('./operation') + ,Target : require('./target') + ,Table : require('./table') + ,Join : require('./join') + ,JoinItem : require('./join-item') + ,Stmt : require('./stmt') + ,Dml : require('./dml') + ,String : require('./string') + ,Delete : require('./delete') + ,Insert : require('./insert') + ,Select : require('./select') + ,Update : require('./update') + ,MultiStmt : require('./multi-stmt') + ,Filter : require('./filter') + ,FilterItem : require('./filter-item') }; diff --git a/js/sql/stmt.js b/js/sql/stmt.js index e447db74..a6d3e564 100644 --- a/js/sql/stmt.js +++ b/js/sql/stmt.js @@ -1,47 +1,40 @@ -var Object = require ('./object'); -var Expr = require ('./expr'); +var Object = require('./object'); +var Expr = require('./expr'); /** * The equivalent of a SQL statement. */ -module.exports = new Class -({ +module.exports = new Class({ Extends: Object - ,Properties: - { - where: - { + ,Properties: { + where: { type: Expr ,value: null + }, + limit: { + type: Number + ,value: null } } ,target: [] - ,addTarget: function (target) - { - this.target.push (target); + ,addTarget: function(target) { + this.target.push(target); } - ,renderTarget: function (batch) - { - var sql; - var len = this.target.length; - - if (len > 0) - { - sql = ' '; - - for (var n = 0; n < len; n++) - { - if (n > 0) sql += ', '; - sql += this.target[n].render (batch); - } - } + ,renderTarget: function(params) { + if (this.target.length > 0) + return ' '+ this.renderListWs(this.target, params, ', '); else - sql += 'DUAL'; - - return sql; - } + return ' DUAL'; + } + + ,renderLimit: function() { + if (this.limit != null) + return ' LIMIT '+ parseInt(this.limit); + else + return ''; + } }); diff --git a/js/sql/string.js b/js/sql/string.js index aa9fefb7..ed8989b4 100644 --- a/js/sql/string.js +++ b/js/sql/string.js @@ -1,45 +1,45 @@ -var Stmt = require ('./stmt'); -var Holder = require ('./holder'); +var Stmt = require('./stmt'); +var Holder = require('./holder'); /** * Literal SQL string. */ -module.exports = new Class -({ +module.exports = new Class({ Extends: Stmt - ,Properties: - { - query: - { + ,Tag: 'sql-string' + ,Properties: { + query: { type: String ,value: null } } ,regexp: /#\w+/g - - ,replaceFunc: function (batch, token) - { - var holder = new Holder ({id: token.substr (1)}); - return holder.render (batch); + + ,appendChild: function(child) { + if (child.nodeType === Node.TEXT_NODE) + this.query = child.textContent; } - ,render: function (batch) - { + ,render: function(params) { if (!this.query) return null; + + function replaceFunc(token) { + var holder = new Holder({id: token.substr(1)}); + return holder.render(params); + } - return this.query.replace (this.regexp, this.replaceFunc.bind (this, batch)); + return this.query.replace(this.regexp, replaceFunc); } - ,findHolders: function () - { - var ids = this.query.match (this.regexp); + ,findHolders: function() { + var ids = this.query.match(this.regexp); if (ids) for (var i = 0; i < ids.length; i++) - ids[i] = ids[i].substr (1); + ids[i] = ids[i].substr(1); return ids; } diff --git a/js/sql/table.js b/js/sql/table.js index 3ab4cf9f..e97c8eec 100644 --- a/js/sql/table.js +++ b/js/sql/table.js @@ -4,26 +4,30 @@ var Target = require('./target'); /** * Represents a database table. */ -module.exports = new Class -({ +module.exports = new Class({ Extends: Target - ,Properties: - { - name: - { + ,Properties: { + name: { type: String ,value: null }, - schema: - { + alias: { + type: String + ,value: null + }, + schema: { type: String ,value: null } } - ,render: function(batch) { - var sql = this.schema ? '`' + this.schema + '`.' : ''; - sql += '`' + this.name + '`'; + ,render: function() { + var sql = this.renderPreIdent(this.schema) + + this.renderIdent(this.name); + + if (this.alias) + sql += ' AS '+ this.renderIdent(this.alias); + return sql; } }); diff --git a/js/sql/target.js b/js/sql/target.js index e45fda9a..bb19092c 100644 --- a/js/sql/target.js +++ b/js/sql/target.js @@ -4,7 +4,6 @@ var Object = require('./object'); /** * The equivalent of a SQL target. */ -module.exports = new Class -({ +module.exports = new Class({ Extends: Object }); diff --git a/js/sql/update.js b/js/sql/update.js index 89d4ae8e..8c91eab8 100644 --- a/js/sql/update.js +++ b/js/sql/update.js @@ -1,32 +1,28 @@ -var Dml = require ('./dml'); +var Dml = require('./dml'); /** * The equivalent of a SQL update. */ -module.exports = new Class -({ +module.exports = new Class({ Extends: Dml - ,render: function (batch) - { - var sql; - var n; + ,render: function(params) { + var sql = 'UPDATE' + + this.renderTarget(params) + + ' SET '; - sql = 'UPDATE ' + this.renderTarget (batch) + ' SET '; - - for (n = 0; n < this.field.length; n++) - { - if (n > 0) + this.field.forEach(function(field, i) { + if (i > 0) sql += ', '; - sql += this.field[n].render () + ' = ' + this.expr[n].render(batch); - } + + sql += field.render(params) + + ' = ' + + this.expr[i].render(params); + }, this); - if (this.where) - sql += ' WHERE ' + this.where.render (batch); - - sql += ' LIMIT 1'; // Only for security. - + sql += this.renderIfSet(this.where, 'WHERE', params) + + this.renderLimit(params); return sql; } }); diff --git a/js/sql/value.js b/js/sql/value.js index c2523ef6..1650eead 100644 --- a/js/sql/value.js +++ b/js/sql/value.js @@ -5,64 +5,69 @@ var Expr = require('./expr'); * The equivalent of a SQL value. */ module.exports = new Class({ - Extends: Expr - ,Tag: 'sql-value' + Extends: Expr + ,Implements: Vn.ParamIface + ,Tag: 'sql-value' ,Properties: { - /** - * The master param. - */ - param: { - type: Vn.Param + value: { + type: null ,set: function(x) { - this.link({_param: x}, {'changed': this.onParamChange}); - this.onParamChange(); + this._setValue(x); + } + ,get: function() { + return this._value; + } + }, + type: { + type: Type + ,set: function(x) { + this._setType(x); + } + ,get: function() { + return this._type; + } + }, + param: { + type: Vn.ParamIface + ,set: function(x) { + this._setParam(x); } ,get: function() { return this._param; } }, - /** - * The value. - */ - value: { - type: String + lot: { + type: Vn.LotIface ,set: function(x) { - if (Vn.Value.compare(x, this._value)) - return; - - if (x instanceof Date) - x = x.clone(); - - this._value = x; - - if (this._param && !this.paramLock) { - this.paramLock = true; - this._param.value = x; - this.paramLock = false; - } - - this.signalEmit('changed'); + this._setLot(x); } ,get: function() { - return this._value; + return this._lot; + } + }, + name: { + type: String + ,set: function(x) { + this._name = x; + this._onLotChange(); + } + ,get: function() { + return this._name; + } + }, + oneWay: { + type: Boolean + ,set: function(x) { + this._oneWay = x; + } + ,get: function() { + return this._oneWay; } } } - ,_value: undefined - ,_param: null ,regexp: new RegExp('(\\\\)|\'', 'g') - ,paramLock: false - - ,onParamChange: function() { - if (this.paramLock || !this._param) - return; - - this.paramLock = true; - this.value = this._param.value; - this.paramLock = false; - } ,isReady: function() { return this._value !== undefined; diff --git a/js/vn/browser.js b/js/vn/browser.js index fde58c69..92986ab0 100644 --- a/js/vn/browser.js +++ b/js/vn/browser.js @@ -1,36 +1,29 @@ -module.exports = -{ - getPageYOffset: function () - { +module.exports = { + getPageYOffset: function() { return window.pageYOffset; }, - getPageXOffset: function () - { + getPageXOffset: function() { return window.pageXOffset; }, - getInnerHeight: function () - { + getInnerHeight: function() { return window.innerHeight; }, - getInnerWidth: function () - { + getInnerWidth: function() { return window.innerWidth; }, - createRadio: function (uid, doc) - { - var radio = doc.createElement ('input'); + createRadio: function(uid, doc) { + var radio = doc.createElement('input'); radio.type = 'radio'; radio.name = uid; return radio; }, - setInputTypeNumber: function (input) - { + setInputTypeNumber: function(input) { input.type = 'number'; } }; diff --git a/js/vn/builder.js b/js/vn/builder.js index 5041ba14..437340e4 100644 --- a/js/vn/builder.js +++ b/js/vn/builder.js @@ -1,7 +1,8 @@ const VnObject = require('./object'); -const Widget = require('../htk/widget'); +const Component = require('./component'); const VnNode = require('./node'); const Scope = require('./scope'); +const Type = require('./type'); const kebabToCamel = require('./string-util').kebabToCamel; /** @@ -488,6 +489,9 @@ module.exports = new Class({ case Function: context.funcProps[propName] = this._getMethod(value); break; + case Type: + newValue = window[value]; + break; default: if (propInfo.enumType) newValue = propInfo.enumType[value]; @@ -511,7 +515,7 @@ module.exports = new Class({ const object = new context.klass(); object.setProperties(context.props); - if (context.nodeId && object instanceof Widget) { + if (context.nodeId && object instanceof Component) { var id = context.nodeId; object.htmlId = scope.getHtmlId(id); object.className = '_'+ id +' '+ object.className; @@ -642,7 +646,7 @@ module.exports = new Class({ for (var i = 0; i < childs.length; i++) { let child = objects[childs[i]]; - if (child instanceof Widget) + if (child instanceof Component) child = child.node; if (child instanceof Node) object.appendChild(child); diff --git a/js/vn/component.js b/js/vn/component.js new file mode 100644 index 00000000..2d72fa39 --- /dev/null +++ b/js/vn/component.js @@ -0,0 +1,206 @@ + +const NodeBuilder = require('./node-builder'); + +const Klass = new Class(); +module.exports = Klass; + +Klass.extend({Classes: ''}); + +const ComponentClass = { + Extends: NodeBuilder, + Tag: 'vn-component', + Properties: { + /** + * Main HTML node that represents the component + */ + node: { + type: Object + ,get: function() { + this.renderBase(); + return this._node; + } + }, + /** + * CSS syle. + */ + style: { + type: String + ,set: function(x) { + this.node.style = x; + } + ,get: function() { + return this._node.style; + } + }, + /** + * CSS classes to be appendend to the node classes. + */ + class: { + type: String + ,set: function(x) { + this._className = x; + this._refreshClass(); + } + ,get: function() { + return this._className; + } + }, + /** + * CSS classes to be appendend to the node classes. + */ + className: { + type: String + ,set: function(x) { + this._className = x; + this._refreshClass(); + } + ,get: function() { + return this._className; + } + }, + /** + * CSS class list. + */ + classList: { + type: Object + ,get: function() { + return this._node.classList; + } + }, + /** + * Title of the element. + */ + title: { + type: String + ,set: function(x) { + this._node.title = x; + } + ,get: function() { + return this._node.title; + } + }, + /** + * The HTML id of the element. + */ + htmlId: + { + type: String + ,set: function(x) { + this._htmlId = x; + if (this._node) + this._node.id = x; + } + ,get: function() { + return this._htmlId; + } + }, + } + + ,_node: null + ,scope: null + + ,initialize: function(props) { + this.doc = document; + this.renderBase(); + NodeBuilder.prototype.initialize.call(this, props); + } + + ,createRoot: function(tagName) { + const node = this._node = this.createElement(tagName); + if (this._htmlId) + node.id = this._htmlId; + if (this.$constructor.Classes) + node.className = this.$constructor.Classes; + return node; + } + + ,appendChild: function(child) { + if (child instanceof Klass) + this._node.appendChild(child.node); + else if (child instanceof Element) + this._node.appendChild(child); + } + + ,renderBase: function() { + if (this._node) + return; + + this.render(); + this._refreshClass(); + } + + ,_refreshClass: function() { + if (this._node && this._className) + this._node.className = this._className +' '+ this._node.className; + } + + ,remove: function() { + Vn.Node.remove(this._node); + } + + ,on: function(id, callback, instance) { + if (htmlEventMap[id]) { + this.addEventListener(id, + callback.bind(instance, this)); + } else + NodeBuilder.prototype.on.call(this, id, callback, instance); + } + + ,loadTemplateFromFile: function(path) { + const builder = new Vn.Builder(); + builder.compileFile(path); + this.loadScope(builder); + } + + ,loadTemplateFromString: function(xmlString) { + const builder = new Vn.Builder(); + builder.compileString(xmlString); + this.loadScope(builder); + } + + ,loadScope: function(builder) { + const scope = this.scope = builder.load(this.doc, this); + scope.link(); + + this.$ = scope.$; + this._node = scope.$.main; + } + + ,_destroy: function() { + if (this.scope) + this.scope.unref(); + + NodeBuilder.prototype._destroy.call(this, ); + } +}; + +htmlEventMap = {}; +htmlEvents = [ + 'click', + 'dblclick', + 'keydown', + 'keypress', + 'keyup', + 'mousedown', + 'mousemove', + 'mouseout', + 'mouseover', + 'mouseup', + 'mousewheel', + 'wheel', + 'focus', + 'focusout', + 'focusin' +]; +htmlEvents.forEach(x => htmlEventMap[x] = true); + +htmlMethods = [ + 'addEventListener' +]; +htmlMethods.forEach(method => { + ComponentClass[method] = function() { + this.node[method].apply(this.node, arguments); + }; +}); + +Klass.implement(ComponentClass); diff --git a/js/vn/date.js b/js/vn/date.js index 2c0d887a..24fb6598 100644 --- a/js/vn/date.js +++ b/js/vn/date.js @@ -8,8 +8,7 @@ Date.prototype.clone = function() { module.exports = { - WDays: - [ + WDays: [ 'Sunday' ,'Monday' ,'Tuesday' @@ -18,8 +17,7 @@ module.exports = ,'Friday' ,'Saturday' ] - ,AbrWDays: - [ + ,AbrWDays: [ 'Su' ,'Mo' ,'Tu' @@ -28,8 +26,7 @@ module.exports = ,'Fr' ,'Sa' ] - ,Months: - [ + ,Months: [ 'January' ,'February' ,'March' @@ -43,8 +40,7 @@ module.exports = ,'November' ,'December' ] - ,AbrMonths: - [ + ,AbrMonths: [ 'Jan' ,'Feb' ,'Mar' diff --git a/js/vn/enum.js b/js/vn/enum.js new file mode 100644 index 00000000..3c48b4aa --- /dev/null +++ b/js/vn/enum.js @@ -0,0 +1,4 @@ +/** + * Base type for ennumerations. + */ + module.exports = function() {}; diff --git a/js/vn/form.js b/js/vn/form.js new file mode 100644 index 00000000..208bc25b --- /dev/null +++ b/js/vn/form.js @@ -0,0 +1,72 @@ + +var Iterator = require('./iterator'); +var ModelIface = require('./model-iface'); + +module.exports = new Class({ + Extends: Iterator + ,Tag: 'db-form' + ,Properties: { + model: { + type: ModelIface + ,set: function(x) { + this.link({_model: x}, + { + 'status-changed': this.onModelChange + ,'row-updated': this.onModelRowUpdate + }); + } + ,get: function() { + return this._model; + } + }, + /** + * The row where the form positioned, has -1 if the row is unselected. + */ + row: { + type: Number + ,set: function(x) { + if (!this._model || this._model.numRows <= x || x < -1) + x = -1; + if (x == this._row) + return; + + this._row = x; + this.rowChanged(); + } + ,get: function() { + return this._row; + } + } + } + + ,initialize: function(props) { + Object.assign(this, { + _lastRow: 0 + ,_lastReady: false + }); + Iterator.prototype.initialize.call(this, props); + } + + ,onModelChange: function() { + var ready = this._model && this._model.ready; + + if (ready != this._lastReady) { + if (this._row != -1) + this._lastRow = this._row; + + this._lastReady = ready; + this.emit('status-changed'); + + if (this._row == -1) + this.row = this._lastRow; + + if (ready) + this.emit('ready'); + } + } + + ,onModelRowUpdate: function(model, row) { + if (row == this._row) + this.rowChanged(); + } +}); diff --git a/js/vn/hash-listener.js b/js/vn/hash-listener.js deleted file mode 100644 index 2a554675..00000000 --- a/js/vn/hash-listener.js +++ /dev/null @@ -1,14 +0,0 @@ - -var Object = require('./object'); - -/** - * Class to handle the URL. - */ -module.exports = new Class -({ - Extends: Object - - ,changed: function() { - this.signalEmit('changed'); - } -}); diff --git a/js/vn/hash-param.js b/js/vn/hash-param.js deleted file mode 100644 index 40d23d15..00000000 --- a/js/vn/hash-param.js +++ /dev/null @@ -1,127 +0,0 @@ - -var Object = require('./object'); -var Param = require('./param'); -var Hash = require('./hash'); - -module.exports = new Class({ - Extends: Object - ,Tag: 'vn-hash-param' - ,Child: 'param' - ,Properties: { - param: { - type: Param - ,set: function(x) { - this.link({_param: x}, {'changed': this._onParamChange}); - this._refreshParam(); - } - ,get: function() { - return this._param; - } - }, - key: { - type: String - ,set: function(x) { - this._key = x; - this._onHashChange(); - } - ,get: function() { - return this._key; - } - }, - value: { - type: Object - ,set: function(x) { - this._setValue(x, true); - } - ,get: function() { - return this._value; - } - }, - type: { - type: Object - ,set: function(x) { - this._type = x; - this._onHashChange(); - } - ,get: function() { - return this._type; - } - } - } - - ,_hashLock: false - ,_paramLock: false - ,_value: undefined - ,_key: null - ,_type: null - - ,initialize: function(props) { - this.parent(props); - var listener = Hash.getListener(); - this.link({_listener: listener}, {'changed': this._onHashChange}); - this._onHashChange(); - } - - ,_onHashChange: function() { - if (this._hashLock || !this._key || !this._listener) - return; - - var newValue = Hash.get(this._key); - - if (newValue === '') - newValue = null; - - if (this._type && newValue !== undefined && newValue !== null) - switch (this._type) { - case Boolean: - newValue = (/^(true|1)$/i).test(newValue); - break; - case Number: - newValue = 0 + new Number(newValue); - break; - } - - this._hashLock = true; - this._setValue(newValue, true); - this._hashLock = false; - } - - ,_setValue: function(newValue, signal) { - if (newValue == this._value) - return; - - this._value = newValue; - - if (this._key && !this._hashLock) { - this._hashLock = true; - - var map = {}; - map[this._key] = newValue; - Hash.add(map); - - this._hashLock = false; - } - - this._refreshParam(); - - if (signal) - this.signalEmit('changed', newValue); - } - - ,_refreshParam: function() { - if (this._param && !this._paramLock) { - this._paramLock = true; - this._param.value = this._value; - this._paramLock = false; - } - } - - ,_onParamChange: function() { - if (this._paramLock) - return; - - this._paramLock = true; - this._setValue(this._param.value); - this._paramLock = false; - } -}); diff --git a/js/vn/hash.js b/js/vn/hash.js index 25bf22ef..146bb690 100644 --- a/js/vn/hash.js +++ b/js/vn/hash.js @@ -1,137 +1,164 @@ -var HashListener = require('./hash-listener'); +var VnDate = require('./date'); +var Lot = require('./lot'); /** - * Class to handle the URL. + * Class to handle the hash part of the URL as a key-value javascript object. + * It also handles dates and objects as a value. */ -module.exports = { - path: null - ,_hash: null - ,_hashMap: {} - ,_listener: null - - ,initialize: function() { - this.$ = this._hasMap; - this._listener = new HashListener(); - - this._hashChangedHandler = this._hashChanged.bind(this); - window.addEventListener('hashchange', this._hashChangedHandler); - this._hashChanged(); - } - - ,destroy: function() { - window.removeEventListener('hashchange', this._hashChangedHandler); - } - - ,getListener: function() { - return this._listener; - } - - /** - * Gets the hash part of the URL. - * - * @param {string} key The variable name - */ - ,get: function(key) { - return this._hashMap[key]; - } - - /** - * Unsets a hash key. - * - * @param {string} key The variable name - */ - ,unset: function(key) { - this.add({[key]: undefined}); - } - - /** - * Sets the hash part of the URL, respecting the current hash variables. - * - * @param {Object} map A key-value map - */ - ,add: function(map) { - var newMap = this._hashMap; - - for (var key in map) - newMap[key] = map[key]; - - this.set(newMap); - } - - /** - * Sets the hash part of the URL. - * - * @param {Object} map A key-value map - */ - ,set: function(map) { - if (map) - for (var key in map) - if (map[key] === null || map[key] === undefined) - delete map[key]; - - var newHash = this.make(map); - - if (!map) - map = {}; - - if (newHash !== this._hash) { - this._hashMap = map; - this.$ = map; - this._hash = newHash; - - this._blockChanged = true; - location.hash = newHash; - this._blockChanged = false; - - this._listener.changed(); +module.exports = new Class({ + Extends: Lot + ,Properties: { + /** + * The main window object. + */ + window: + { + type: Window + ,set: function(x) { + this._window = x; + x.addEventListener('hashchange', this._hashChangedHandler); + this._hashChanged(); + } + ,get: function() { + return this._window; + } } } + ,initialize: function(props) { + Object.assign(this, { + _hash: null + ,_hashLock: false + ,_window: null + ,_hashChangedHandler: this._hashChanged.bind(this) + }); + Lot.prototype.initialize.call(this, props); + } + + ,_paramsChanged: function() { + this.updateHash(); + } + /** * Creates a URL with the given hash data. * - * @param {Object} map A key-value map - * @param {boolean} add %true to combine with the current map, %false otherwise - * @return {String} The URL + * @param {Object} params A key-value object + * @param {boolean} add %true to combine with the current params, %false otherwise + * @return {string} The URL */ - ,make: function(map, add) { - var hash = '#!'; - - if (add && map) - for (var key in this._hashMap) - if (!map[key]) - map[key] = this._hashMap[key]; + ,make: function(params, add) { + if (add) { + params = Object.assign({}, params); - for (var key in map) { + for (var key in this._params) + if (!params[key]) + params[key] = this._params[key]; + } + + return this.renderHash(params); + } + + /** + * Updates the window hash with current params. + */ + ,updateHash: function() { + if (this._hashLock) + return; + + this._hash = this.renderHash(this._params); + + this._hashLock = true; + location.hash = this._hash; + this._hashLock = false; + } + + /* + * Called when window hash changes. + */ + ,_hashChanged: function() { + var newHash = location.hash; + + if (this._hashLock || this._hash === newHash) + return; + + this._hash = newHash; + + this._hashLock = true; + this.params = this.parseHash(newHash); + this._hashLock = false; + } + + /** + * Creates a URL with the given hash data. + * + * @param {Object} params The key-value object + * @return {string} The URL + */ + ,renderHash: function(params) { + var hash = '#!'; + + for (var key in params) + if (params[key] !== undefined) { if (hash.length > 2) hash += '&'; - hash += encodeURIComponent(key) +'='+ encodeURIComponent(map[key]); + hash += encodeURIComponent(key) +'='+ this.renderValue(params[key]); } return hash; } - ,_hashChanged: function() { - var newHash = location.hash; - - if (this._blockChanged || newHash === this._hash) - return; - + /** + * Parses a hash string to a key-value object. + * + * @param {string} hashString The hash string + * @return {Object} The key-value object + */ + ,parseHash: function(hashString) { var newMap = hashMap = {}; - var kvPairs = newHash.substring(2).split('&'); + var kvPairs = hashString.substr(2).split('&'); for (var i = 0; i < kvPairs.length; i++) { var kvPair = kvPairs[i].split('=', 2); if (kvPair[0]) - newMap[decodeURIComponent(kvPair[0])] = decodeURIComponent(kvPair[1]); + newMap[decodeURIComponent(kvPair[0])] = this.parseValue(kvPair[1]); } - this._hashMap = newMap; - this.$ = newMap; - this._hash = newHash; - this._listener.changed(); + return newMap; } -}; + + ,renderValue: function(v) { + if (v == null) + return ''; + + switch (typeof v) { + case 'object': + if (v instanceof Date) + return VnDate.strftime(v, '%Y-%m-%d'); + else + return JSON.stringify(v) + } + + return v; + } + + ,parseValue: function(v) { + if (v == null) + return v; + + v = decodeURIComponent(v); + + if (v === '') + return null; + + return v; + } + + ,_destroy: function() { + this._window.removeEventListener('hashchange', this._hashChangedHandler); + this._window = null; + Lot.prototype._destroy.call(this); + } +}); diff --git a/js/vn/ie.js b/js/vn/ie.js index cb8e7add..29b451f9 100644 --- a/js/vn/ie.js +++ b/js/vn/ie.js @@ -1,48 +1,40 @@ -module.exports = -{ - getPageYOffset: function () - { +module.exports = { + getPageYOffset: function() { if (document.documentElement.scrollTop) return document.documentElement.scrollTop; else return document.body.scrollTop; }, - getPageXOffset: function () - { + getPageXOffset: function() { if (document.documentElement.scrollLeft) return document.documentElement.scrollLeft; else return document.body.scrollLeft; }, - getInnerHeight: function () - { + getInnerHeight: function() { if (document.documentElement.clientHeight) return document.documentElement.clientHeight; else return document.body.clientHeight; }, - getInnerWidth: function () - { + getInnerWidth: function() { if (document.documentElement.clientWidth) return document.documentElement.clientWidth; else return document.body.clientWidth; }, - createRadio: function (radioName) - { + createRadio: function(radioName) { var radio; try { - radio = document.createElement (''); - } - catch (e) - { - radio = document.createElement ('input'); + radio = document.createElement(''); + } catch (e) { + radio = document.createElement('input'); radio.type = 'radio'; radio.name = radioName; } @@ -50,30 +42,26 @@ module.exports = return radio; }, - setInputTypeNumber: function (input) - { + setInputTypeNumber: function(input) { input.type = 'text'; } }; -if (!Function.bind) -{ - Function.prototype.bind = function () - { +if (!Function.bind) { + Function.prototype.bind = function() { var bindFunc = this; var bindThis = arguments[0]; var bindArgs = arguments; - var IE_bind = function () - { - var args = new Array (); + var IE_bind = function() { + var args = new Array(); for (var i = 1; i < bindArgs.length; i++) - args.push (bindArgs[i]); + args.push(bindArgs[i]); for (var i = 0; i < arguments.length; i++) - args.push (arguments[i]); + args.push(arguments[i]); - bindFunc.apply (bindThis, args); + bindFunc.apply(bindThis, args); } return IE_bind; @@ -82,33 +70,28 @@ if (!Function.bind) // attachEvent -> addEventListener -if (window.attachEvent && !window.addEventListener) -{ - function IE_addEventListener (signal, func, capture) - { +if (window.attachEvent && !window.addEventListener) { + function IE_addEventListener(signal, func, capture) { var obj = this; - func.IE_eventHandler = function (event) - { + func.IE_eventHandler = function(event) { event.target = event.srcElement; event.layerY = event.clientY; event.layerX = event.clientX; event.pageX = event.offsetX; event.pageY = event.offsetY; - event.stopPropagation = function () - { + event.stopPropagation = function() { this.cancelBubble = true; } - func.call (obj, event); + func.call(obj, event); } - this.attachEvent ('on' + signal, func.IE_eventHandler); + this.attachEvent('on' + signal, func.IE_eventHandler); } - function IE_removeEventListener (signal, func, capture) - { - this.detachEvent ('on' + signal, func.IE_eventHandler); + function IE_removeEventListener(signal, func, capture) { + this.detachEvent('on' + signal, func.IE_eventHandler); } window.addEventListener = IE_addEventListener; @@ -118,9 +101,8 @@ if (window.attachEvent && !window.addEventListener) var IE_createElement = document.createElement; - document.createElement = function (tagName) - { - var node = IE_createElement (tagName); + document.createElement = function(tagName) { + var node = IE_createElement(tagName); node.addEventListener = IE_addEventListener; node.removeEventListener = IE_removeEventListener; return node; @@ -129,11 +111,9 @@ if (window.attachEvent && !window.addEventListener) // ActiveXObject ('Microsoft.XMLHTTP') -> XMLHttpRequest -if (!window.XMLHttpRequest && window.ActiveXObject) -{ - function XMLHttpRequest () - { - return new ActiveXObject ('Microsoft.XMLHTTP'); +if (!window.XMLHttpRequest && window.ActiveXObject) { + function XMLHttpRequest() { + return new ActiveXObject('Microsoft.XMLHTTP'); } } diff --git a/js/vn/iterator-iface.js b/js/vn/iterator-iface.js new file mode 100644 index 00000000..c01e0e8e --- /dev/null +++ b/js/vn/iterator-iface.js @@ -0,0 +1,67 @@ + +var LotIface = require('./lot-iface'); +var ModelIface = require('./model-iface'); + +module.exports = new Class({ + Implements: LotIface + ,Properties: { + /** + * The model associated to this form. + */ + model: { + type: ModelIface + }, + /** + * The row where the form positioned, has -1 if the row is unselected. + */ + row: { + type: Number + }, + /** + * The number of rows in the form. + */ + numRows: { + type: Number + }, + /** + * Checks if the form data is ready. + */ + ready: { + type: Boolean + } + } + + ,insertRow: function() { + if (this._model) + this.row = this._model.insertRow(); + } + + /** + * Removes the current row. + */ + ,deleteRow: function() { + if (this._row >= 0) + this._model.deleteRow(this._row); + } + + /** + * Gets a value from the form. + * + * @param {string} columnName The column name + * @return {Object} The value + */ + ,get: function(columnName) { + return this._model ? + this._model.get(this._row, columnName) : undefined; + } + + /** + * Sets a value on the form. + * + * @param {string} columnName The column name + * @param {Object} value The new value + */ + ,set: function(columnName, value) { + this._model.set(this._row, columnName, value); + } +}); diff --git a/js/vn/iterator.js b/js/vn/iterator.js new file mode 100644 index 00000000..01888c7a --- /dev/null +++ b/js/vn/iterator.js @@ -0,0 +1,75 @@ + +var Lot = require('./lot'); +var IteratorIface = require('./iterator-iface'); +var ModelIface = require('./model-iface'); + +/** + * A light iterator for models. It assumes that its row and model properties + * are always valid. + */ +module.exports = new Class({ + Extends: Lot + ,Implements: IteratorIface + ,Properties: { + model: { + type: ModelIface + ,set: function(x) { + this._model = x; + } + ,get: function() { + return this._model; + } + }, + row: { + type: Number + ,set: function(x) { + this._row = x; + this.rowChanged(); + } + ,get: function() { + return this._row; + } + }, + numRows: { + type: Number + ,get: function() { + return this._model ? + this._model.numRows : 0; + } + }, + ready: { + type: Boolean + ,get: function() { + return this._model ? + this._model.ready : false; + } + } + } + + ,_model: null + + ,initialize: function(props) { + Object.assign(this, { + _row: -1 + ,_rowLock: false + }); + Lot.prototype.initialize.call(this, props); + } + + ,_paramsChanged: function(diff) { + if (!this._rowLock) + for (var key in diff) + this._model.set(this._row, key, diff[key]); + } + + ,rowChanged: function() { + var row; + + if (this._model) + row = this._model.getObject(this._row); + + this._rowLock = true; + this.params = row != null ? row : {}; + this._rowLock = false; + } +}); diff --git a/js/vn/json-connection.js b/js/vn/json-connection.js index 9e012eb8..f6655927 100644 --- a/js/vn/json-connection.js +++ b/js/vn/json-connection.js @@ -1,12 +1,12 @@ -var Object = require('./object'); +var VnObject = require('./object'); var JsonException = require('./json-exception'); /** * Handler for JSON rest connections. */ module.exports = new Class({ - Extends: Object + Extends: VnObject ,_connected: false ,_requestsCount: 0 @@ -16,7 +16,7 @@ module.exports = new Class({ * Initilizes the connection object. */ ,initialize: function() { - this.parent(); + VnObject.prototype.initialize.call(this); this.fetchToken(); } @@ -70,7 +70,7 @@ module.exports = new Class({ var storage = remember ? localStorage : sessionStorage; storage.setItem('vnToken', this.token); - this.signalEmit('openned'); + this.emit('openned'); } else this._closeClient(); @@ -93,7 +93,7 @@ module.exports = new Class({ * Called when close operation is done. */ ,_onClose: function(callback, json, error) { - this.signalEmit('closed'); + this.emit('closed'); if (callback) callback(this, null, error); @@ -221,7 +221,7 @@ module.exports = new Class({ this._requestsCount++; if (this._requestsCount === 1) - this.signalEmit('loading-changed', true); + this.emit('loading-changed', true); } ,_onStateChange: function(request, callback) { @@ -231,7 +231,7 @@ module.exports = new Class({ this._requestsCount--; if (this._requestsCount === 0) - this.signalEmit('loading-changed', false); + this.emit('loading-changed', false); var data = null; var error = null; @@ -314,7 +314,7 @@ module.exports = new Class({ if (error.exception == 'SessionExpired') this.clearToken(); - this.signalEmit('error', error); + this.emit('error', error); } } }); diff --git a/js/vn/lot-iface.js b/js/vn/lot-iface.js new file mode 100644 index 00000000..7639a16e --- /dev/null +++ b/js/vn/lot-iface.js @@ -0,0 +1,83 @@ + +/** + * Holds a plain key-value javascript object and monitorizes changes over it. + */ +module.exports = new Class({ + Properties: { + /** + * The internal object with the params, this is the lot internal object + * and should be used for read-only purposes. + */ + params: { + type: Object + } + /** + * Shortcut for params property. + */ + ,$: { + type: Object + } + } + + /** + * Gets a value from the lot. + * + * @param {string} field The field name + * @return {*} The field value + */ + ,get: function(field) { + return this.params[field]; + } + + /** + * Sets a value on the lot. + * + * @param {string} field The field name + * @param {*} value The new field value + */ + ,set: function(field, value) { + var params = {}; + params[field] = value; + this.assign(params); + } + + /** + * Returns an array with the lot keys. + * + * @return {Array} The lot keys + */ + ,keys: function() {} + + /** + * Emits the 'change' signal on the lot. + * + * @param {Object} changes The changed params and its values + */ + ,changed: function(changes) { + this.emit('change', changes); + } + + /** + * Copies all values from another lot. + * + * @param {Object} object The source object + */ + ,assign: function() {} + + /** + * Copies all values from another lot. + * + * @param {LotIface} lot The source lot + */ + ,assignLot: function(lot) { + this.assign(lot.$); + } + + /** + * Resets all values. + */ + ,reset: function() { + this.params = {}; + } + }); + \ No newline at end of file diff --git a/js/vn/lot-query.js b/js/vn/lot-query.js new file mode 100644 index 00000000..63981d7d --- /dev/null +++ b/js/vn/lot-query.js @@ -0,0 +1,122 @@ + +var Lot = require('./lot'); +var LotIface = require('./lot-iface'); +var Spec = require('./spec'); +var Value = require('./value'); + +module.exports = new Class({ + Extends: Lot + ,Tag: 'vn-lot-query' + ,Properties: { + fields: { + type: Array + ,set: function(x) { + this._fields = x; + this._onSourceChange(); + } + ,get: function() { + return this._fields; + } + }, + source: { + type: LotIface + ,set: function(x) { + this.link({_source: x}, {change: this._onSourceChange}); + this._onSourceChange(); + } + ,get: function() { + return this._source; + } + } + } + + ,initialize: function(props) { + Object.assign(this, { + _fields: null, + _source: null, + _lockSource: false, + _specs: {} + }); + Lot.prototype.initialize.call(this, props); + } + + ,appendChild: function(child) { + if (!(child instanceof Spec)) + throw new Error('VnLotQuery: Child must be a Vn.Spec instance'); + + this._specs[child.name] = child; + this._onSourceChange(); + } + + ,_onSourceChange: function() { + if (this._lockSource) + return; + + var params = this._source ? this._source.params : {}; + var myParams = {}; + + for (var key in this._specs) + myParams[key] = Value.simpleClone(params[key]); + + this.assign(myParams); + } + + ,assign: function(params) { + params = this.transformParams(params); + var diff = Value.partialDiff(this._params, params); + this._assign(diff); + } + + ,setAll: function(params) { + params = this.transformParams(params); + var diff = Value.diff(this._params, params); + this._assign(diff); + } + + ,_assign: function(diff) { + if (diff) { + Object.assign(this._params, diff); + + if (this.source) { + this._lockSource = true; + this.source.assign(diff); + this._lockSource = false; + } + + this._paramsChanged(diff); + this.changed(diff); + } + } + + ,transformParams: function(params) { + var newParams = {}; + + for (var key in this._specs) { + var spec = this._specs[key]; + if (params[key]) + newParams[key] = cast(params[key], spec.type); + } + + return Object.assign(params, newParams); + } +}); + +function cast(value, type) { + switch (type) { + case Boolean: + return (/^(true|1)$/i).test(value); + case Number: + return 0 + new Number(value); + case Date: + var date = new Date(value); + date.setHours(0, 0, 0, 0); + return date; + case String: + return value; + default: + if (type instanceof Object) + return JSON.parse(value); + else + return value; + } +} diff --git a/js/vn/lot.js b/js/vn/lot.js new file mode 100644 index 00000000..bd6bbe09 --- /dev/null +++ b/js/vn/lot.js @@ -0,0 +1,65 @@ + +var VnObject = require('./object'); +var LotIface = require('./lot-iface'); +var Value = require('./value'); + +module.exports = new Class({ + Extends: VnObject + ,Implements: LotIface + ,Tag: 'vn-lot' + ,Properties: { + $: { + type: Object + ,set: function(x) { + this.setAll(x); + } + ,get: function() { + return this._params; + } + } + ,params: { + type: Object + ,set: function(x) { + this.$ = x; + } + ,get: function() { + return this.$; + } + } + } + + ,initialize: function(props) { + this._params = {}; + VnObject.prototype.initialize.call(this, props); + } + + ,keys: function() { + return Object.keys(this._params); + } + + ,assign: function(params) { + var diff = Value.partialDiff(this._params, params); + this._assign(diff); + } + + ,setAll: function(params) { + var diff = Value.diff(this._params, params); + this._assign(diff); + } + + ,_assign: function(diff) { + if (diff) { + Object.assign(this._params, diff); + this._paramsChanged(diff); + this.changed(diff); + } + } + + /** + * Called when lot params changes, can be implemented by child classes to be + * notified about changes. + * + * @param {Object} diff Changed parameters and its new values + */ + ,_paramsChanged: function() {} +}); diff --git a/js/vn/model-iface.js b/js/vn/model-iface.js new file mode 100644 index 00000000..7b4abdb9 --- /dev/null +++ b/js/vn/model-iface.js @@ -0,0 +1,127 @@ + +/** + * Readable data model. + */ + module.exports = new Class({ + Properties: { + /** + * The number of rows in the model. + */ + numRows: { + type: Number + }, + /** + * The current status of the model. + */ + status: { + type: Number + }, + /** + * Checks if the model data is ready. + */ + ready: { + type: Boolean + } + } + + /** + * Checks if the column exists. + * + * @param {integer} column The column index + * @return {boolean} %true if column exists, %false otherwise + */ + ,checkColExists: function() {} + + /** + * Checks if the row exists. + * + * @param {integer} rowIndex The row index + * @return {boolean} %true if row exists, %false otherwise + */ + ,checkRowExists: function() {} + + /** + * Get the index of the column from its name. + * + * @param {string} columnName The column name + * @return {number} The column index or -1 if column not exists + */ + ,getColumnIndex: function() {} + + /** + * Gets a value from the model. + * + * @param {number} rowIndex The row index + * @param {string} columnName The column name + * @return {*} The value, or %undefined + */ + ,get: function() {} + + /** + * Gets a value using the column index. + * + * @param {number} rowIndex The row index + * @param {number} column The column index + * @return {*} The value + */ + ,getByIndex: function() {} + + /** + * Gets a row as an object using the column index. + * + * @param {number} rowIndex The row index + * @return {Object} The row as an object + */ + ,getObject: function() {} + + /** + * Orders the model by the specified column name. + * + * @param {number} column The column name + * @param {SortWay} way The sort way + */ + ,sortByName: function() {} + + /** + * Orders the model by the specified column. + * + * @param {number} column The column index + * @param {SortWay} way The sort way + */ + ,sort: function() {} + + /** + * Searchs a value on the model and returns the row index of the first + * ocurrence. + * If an index have been built on that column, it will be used, for more + * information see the indexColumn() method. + * + * @param {string} column The column name + * @param {Object} value The value to search + * @return {number} The column index + */ + ,search: function() {} + + /** + * Searchs a value on the model and returns the row index of the first + * ocurrence. + * + * @param {number} col The column index + * @param {Object} value The value to search + * @return {number} The column index + */ + ,searchByIndex: function() {} + + /** + * Builds an internal hash index for the specified column, this speeds + * significantly searches on that column, specially when model has a lot of + * rows. + * + * FIXME: Not fully implemented. + * + * @param {string} column The column name + */ + ,indexColumn: function() {} + }); + + \ No newline at end of file diff --git a/js/vn/model-proxy.js b/js/vn/model-proxy.js new file mode 100644 index 00000000..04fabcd0 --- /dev/null +++ b/js/vn/model-proxy.js @@ -0,0 +1,80 @@ + +var ModelIface = require('./object'); + +/** + * Monitorizes all changes made to a model. + */ +var Klass = new Class(); +module.exports = Klass; + +var Mode = { + ON_CHANGE : 1 + ,ON_DEMAND : 2 +}; + +var Operation = { + INSERT : 1 << 1 + ,UPDATE : 1 << 2 + ,DELETE : 1 << 3 +}; + +Klass.extend({ + Mode: Mode + ,Operation: Operation +}); + +Klass.implement({ + Implements: ModelIface + ,Properties: { + /** + * Update mode. + */ + mode: { + enumType: Mode + ,value: Mode.ON_CHANGE + } + } + + /** + * Updates a value on the model. + * + * @param {number} rowIndex The row index + * @param {string} columnName The column name + * @param {*} value The new value + */ + ,set: function() {} + + /** + * Updates a value on the model using the column index. + * + * @param {number} rowIndex The row index + * @param {number} col The column index + * @param {*} value The new value + */ + ,setByIndex: function() {} + + /** + * Deletes a row from the model. + * + * @param {number} rowIndex The row index + */ + ,deleteRow: function() {} + + /** + * Inserts a new row on the model. + * + * @return The index of the inserted row + */ + ,insertRow: function() {} + + /** + * Cleans the model data. + */ + ,clean: function() {} + + /** + * Performs all model changes on the database. + */ + ,performOperations: function() {} +}); + diff --git a/js/vn/model.js b/js/vn/model.js new file mode 100644 index 00000000..d33ead43 --- /dev/null +++ b/js/vn/model.js @@ -0,0 +1,350 @@ + +var VnObject = require('./object'); +var simpleEquals = require('./value').simpleEquals; + +var Klass = new Class(); +module.exports = Klass; + +var Status = { + CLEAN : 1 + ,LOADING : 2 + ,READY : 3 + ,ERROR : 4 +}; + +var SortWay = { + ASC : 1 + ,DESC : 2 +}; + +Klass.extend({ + Status + ,SortWay +}); + +Klass.implement({ + Extends: VnObject + ,Tag: 'vn-model' + ,Properties: { + /** + * The model internal data. + */ + data: { + type: Array + ,get: function() { + return this._data; + } + ,set: function(x) { + this._setData(x); + } + }, + /** + * The number of rows in the model. + */ + numRows: { + type: Number + ,get: function() { + if (this._data) + return this._data.length; + + return 0; + } + }, + /** + * The current status of the model. + */ + status: { + type: Number + ,get: function() { + return this._status; + } + }, + /** + * Checks if the model data is ready. + */ + ready: { + type: Boolean + ,get: function() { + return this._status === Status.READY; + } + } + } + + ,_data: null + ,_status: Status.CLEAN + + ,_requestedSortName: null + ,_sortColumn: null + ,_sortWay: null + + ,_requestedIndexes: {} + ,_indexes: [] + + //+++++++++++++++++++++++++++++ Data hadling + + ,_setData: function(data) { + this._data = data; + this._refreshRowIndexes(0); + + if (this._requestedSortName != null) + this._realSort(this._requestedSortName, this._sortWay); + for (column in this._requestedIndexes) + this._buildIndex(column); + + this._setStatus(Status.READY); + } + + /** + * Checks if the row exists. + * + * @param {Integer} rowIndex The row index + * @return {Boolean} %true if row exists, %false otherwise + */ + ,checkRowExists: function(rowIndex) { + return this._data != null + && rowIndex >= 0 + && rowIndex < this._data.length; + } + + /** + * Gets a value from the model. + * + * @param {Number} rowIndex The row index + * @param {Number} columnName The column name + * @return {*} The value + */ + ,get: function(rowIndex, columnName) { + if (!this.checkRowExists(rowIndex)) + return undefined; + + return this._data[rowIndex][columnName]; + } + + /** + * Updates a value on the model. + * + * @param {Number} rowIndex The row index + * @param {Number} columnName The column name + * @param {*} value The new value + */ + ,set: function(rowIndex, columnName, value) { + if (!this.checkRowExists(rowIndex)) + return; + + this.emit('row-updated-before', rowIndex); + this._data[rowIndex][columnName] = value; + this.emit('row-updated', rowIndex, [columnName]); + } + + /** + * Gets a row as an object using the column index. + * + * @param {Number} rowIndex The row index + * @return {Object} The row as an object + */ + ,getObject: function(rowIndex) { + return this.checkRowExists(rowIndex) ? + this._data[rowIndex] : null; + } + + ,_setStatus: function(status) { + this._status = status; + this.emit('status-changed', status); + this.emit('status-changed-after', status); + } + + /** + * Inserts a new row on the model. + * + * @return The index of the inserted row + */ + ,insertRow: function(newRow) { + var rowIndex = this._data.push(newRow) - 1; + newRow.index = rowIndex; + this.emit('row-inserted', rowIndex); + return rowIndex; + } + + /** + * Deletes a row from the model. + * + * @param {Number} rowIndex The row index + */ + ,deleteRow: function(rowIndex) { + if (!this.checkRowExists(rowIndex)) + return; + + this.emit('row-deleted-before', rowIndex); + this._data.splice(rowIndex, 1); + this.emit('row-deleted', rowIndex); + this._refreshRowIndexes(rowIndex); + } + + //+++++++++++++++++++++++++++++ Sorting + + /** + * Orders the model by the specified column name. + * + * @param {String} columnName The column name + * @param {SortWay} way The sort way + */ + ,sort: function(columnName, way) { + this._requestedSortName = columnName; + this._sort(columnName, way); + } + + ,_sort: function(columnName, way) { + var status = this._status; + this._setStatus(Status.LOADING); + this._realSort(columnName, way); + this._setStatus(status); + } + + ,_realSort: function(columnName, way) { + if (!this._data) + return; + + if (columnName !== this._sortColumn) { + if (way === SortWay.DESC) + var sortFunction = this.sortFunctionDesc; + else + var sortFunction = this.sortFunctionAsc; + + this._data.sort(sortFunction.bind(this, columnName)); + } else if (way !== this._sortWay) + this._data.reverse(); + + this._sortColumn = columnName; + this._sortWay = way; + + this._refreshRowIndexes(0); + } + + ,_refreshRowIndexes: function(start) { + var data = this._data; + + for (var i = start; i < data.length; i++) + data[i].index = i; + } + + /* + * Function used to sort the model ascending. + */ + ,sortFunctionAsc: function(column, a, b) { + if (a[column] < b[column]) + return -1; + else if (a[column] > b[column]) + return 1; + + return 0; + } + + /* + * Function used to sort the model descending. + */ + ,sortFunctionDesc: function(column, a, b) { + if (a[column] > b[column]) + return -1; + else if (a[column] < b[column]) + return 1; + + return 0; + } + + //+++++++++++++++++++++++++++++ Searching + + /** + * Builds an internal hash index for the specified column, this speeds + * significantly searches on that column, specially when model has a lot of + * rows. + * + * FIXME: Not fully implemented. + * + * @param {String} column The column name + */ + ,indexColumn: function(column) { + this._requestedIndexes[column] = true; + + if (this._status === Status.READY) + this._buildIndex(column); + } + + ,getHashValue: function(value) { + if (value instanceof Date) + return value.getTime(); + else + return value; + } + + ,_buildIndex: function(columnName) { + if (this.columnMap[columnName] === undefined) + return; + + var data = this._data; + var values = {}; + var nulls = []; + + for (var i = 0; i < data.length; i++) { + var value = data[i][columnName]; + + if (value == null) { + nulls.push(data[i]); + continue; + } + + index[this.getHashValue(value)] = data[i]; + } + + this._indexes[columnName] = { + values: values, + index: index + }; + } + + /** + * Searchs a value on the model and returns the row index of the first + * ocurrence. + * + * @param {Number} columnName The column name + * @param {Object} value The value to search + * @return {Number} The column index + */ + ,search: function(columnName, value) { + var data = this._data; + + if (data == null) + return -1; + + // Searchs the value using an internal index. + + var index = this._indexes[columnName]; + + if (index) { + if (value == null) { + if (index.nulls[0] !== undefined) + return index.nulls[0].index; + } else { + var row = index.values[this.getHashValue(value)]; + + if (rowIndex !== undefined) + return row.index; + } + + return -1; + } + + // Searchs the value using a loop. + + for (var i = 0; i < data.length; i++) + if (simpleEquals(data[i][columnName], value)) + return i; + + return -1; + } + + //+++++++++++++++++++++++++++++ Virtual + + ,refresh: function() { + this._setStatus(Status.READY); + } +}); diff --git a/js/vn/mutators.js b/js/vn/mutators.js index 6ee6576e..0c681e0c 100644 --- a/js/vn/mutators.js +++ b/js/vn/mutators.js @@ -1,29 +1,33 @@ vnCustomTags = {}; -var Mutators = Class.Mutators; +const Mutators = Class.Mutators; +const _Extends = Mutators.Extends; -var _Extends = Mutators.Extends; - -Mutators.Extends = function () -{ - _Extends.apply (this, arguments); - this.implement ({Properties: {}}); +Mutators.Extends = function() { + _Extends.apply(this, arguments); + this.implement({Properties: {}}); } -Mutators.Tag = function (tagName) -{ +Mutators.Tag = function(tagName) { vnCustomTags[tagName] = this; - this.extend ({Tag: tagName}); + this.extend({Tag: tagName}); + + const parent = this.parent; + if (parent && parent.Classes !== undefined) { + let Classes = tagName + + if (parent.Classes !== '') + Classes += ' '+ parent.Classes; + this.extend({Classes}); + } }; -Mutators.Properties = function (props) -{ - var parentProps; +Mutators.Properties = function(props) { + let parentProps; - for (var propName in props) - { - var prop = props[propName]; + for (const propName in props) { + const prop = props[propName]; prop.configurable = true; if (!prop.get && !prop.set && prop.writable === undefined) @@ -31,21 +35,10 @@ Mutators.Properties = function (props) } if (this.parent && (parentProps = this.parent.Properties)) - for (var propName in parentProps) + for (const propName in parentProps) if (!props[propName]) props[propName] = parentProps[propName]; - this.extend ({Properties: props}); - Object.defineProperties (this.prototype, props); + this.extend({Properties: props}); + Object.defineProperties(this.prototype, props); }; - -Mutators.Parent = function (propName) -{ - this.extend ({Parent: propName}); -}; - -Mutators.Child = function (propName) -{ - this.extend ({Child: propName}); -}; - diff --git a/js/vn/node-builder.js b/js/vn/node-builder.js index 7727285c..30b8c004 100644 --- a/js/vn/node-builder.js +++ b/js/vn/node-builder.js @@ -11,11 +11,11 @@ module.exports = new Class({ } ,createElement: function(tagName) { - return document.createElement(tagName); + return this.doc.createElement(tagName); } ,createTextNode: function(text) { - return document.createTextNode(text); + return this.doc.createTextNode(text); } ,render: function() {} diff --git a/js/vn/object.js b/js/vn/object.js index c607e995..2b2c3668 100644 --- a/js/vn/object.js +++ b/js/vn/object.js @@ -1,69 +1,110 @@ /** - * The main base class. Manages the signal system. - * - * @param signals Map with all connected signal handlers + * The main base class. Manages the signal system. Objects based on this class + * can be instantiated declaratively using XML. */ -module.exports = new Class({ - Tag: 'vn-object' - ,Properties: {} +module.exports = class VnObject { + /** + * Tag to be used when the class instance is defined via XML. All classes + * must define this attribute, even if it is not used. + */ + static Tag = 'vn-object'; - ,_refCount: 1 - ,_signalData: null + /** + * Class public properties. + */ + static Properties = {}; - ,initialize: function(props) { + /* + * Reference count. + */ + _refCount = 1; + + /* + * Signal handlers data. + */ + _thisArg = null; + + /** + * Initializes the object and sets all properties passed to the class + * constructor. + * + * @param {Object} props The properties passed to the contructor + */ + constructor(props) { + this.setProperties(props); + } + + initialize(props) { this.setProperties(props); } - ,setProperties: function(props) { + /** + * Sets a group of object properties. + * + * @param {Object} props Properties + */ + setProperties(props) { for (var prop in props) this[prop] = props[prop]; } - ,ref: function() { + /** + * Increases the object reference count. + */ + ref() { this._refCount++; return this; } - ,unref: function() { + /** + * Decreases the object reference count. + */ + unref() { this._refCount--; if (this._refCount === 0) this._destroy(); } - - ,loadXml: function(builder, node) {} - ,appendChild: function(child) {} - - ,_signalInit: function() { - if (!this._signalData) - this._signalData = { - signals: {}, - links: {} - }; - } + + /** + * Called from @Vn.Builder when it finds a custom tag as a child of the + * element. + * + * @param {Vn.Scope} scope The scope instance + * @param {Node} node The custom tag child nodes + */ + loadXml() {} + + /** + * Called from @Vn.Builder when it finds a a child tag that isn't + * associated to any property. + * + * @param {Object} child The child object instance + */ + appendChild() {} /** * Conects a signal with a function. * - * @param {String} id The signal identifier - * @param {Function} callback The callback + * @param {string} id The signal identifier + * @param {function} callback The callback * @param {Object} instance The instance */ - ,on: function(id, callback, instance) { + on(id, callback, instance) { if (!(callback instanceof Function)) { console.warn('Vn.Object: Invalid callback for signal \'%s\'', id); return; } this._signalInit(); - var callbacks = this._signalData.signals[id]; + var callbacks = this._thisArg.signals[id]; if (!callbacks) - callbacks = this._signalData.signals[id] = []; + callbacks = this._thisArg.signals[id] = []; callbacks.push({ - blocked: false + blocked: false ,callback: callback ,instance: instance }); @@ -72,15 +113,15 @@ module.exports = new Class({ /** * Locks/Unlocks a signal emission to the specified object. * - * @param {String} id The signal identifier - * @param {Function} callback The callback - * @param {Boolean} block %true for lock the signal, %false for unlock + * @param {string} id The signal identifier + * @param {function} callback The callback + * @param {boolean} block %true for lock the signal, %false for unlock */ - ,blockSignal: function(id, callback, block, instance) { - if (!this._signalData) + blockSignal(id, callback, block, instance) { + if (!this._thisArg) return; - var callbacks = this._signalData.signals[id]; + var callbacks = this._thisArg.signals[id]; if (!callbacks) return; @@ -92,15 +133,15 @@ module.exports = new Class({ } /** - * Emits a signal in the current object. + * Emits a signal in the object. * - * @param {String} id The signal identifier + * @param {string} id The signal identifier */ - ,signalEmit: function(id) { - if (!this._signalData) + emit(id) { + if (!this._thisArg) return; - var callbacks = this._signalData.signals[id]; + var callbacks = this._thisArg.signals[id]; if (!callbacks) return; @@ -119,23 +160,21 @@ module.exports = new Class({ /** * Disconnects a signal from current object. * - * @param {String} id The signal identifier - * @param {Function} callback The connected callback + * @param {string} id The signal identifier + * @param {function} callback The connected callback * @param {Object} instance The instance */ - ,disconnect: function(id, callback, instance) { - if (!this._signalData) + disconnect(id, callback, instance) { + if (!this._thisArg) return; - var callbacks = this._signalData.signals[id]; + var callbacks = this._thisArg.signals[id]; - if (!callbacks) - return; - - for (var i = 0; i < callbacks.length; i++) - if (callbacks[i].callback == callback - && callbacks[i].instance == instance) - callbacks.splice(i--, 1); + if (callbacks) + for (var i = callbacks.length; i--;) + if (callbacks[i].callback === callback + && callbacks[i].instance === instance) + callbacks.splice(i, 1); } /** @@ -143,49 +182,55 @@ module.exports = new Class({ * * @param {Object} instance The instance */ - ,disconnectByInstance: function(instance) { - if (!this._signalData) + disconnectByInstance(instance) { + if (!this._thisArg) return; - var signals = this._signalData.signals; + var signals = this._thisArg.signals; for (var signalId in signals) { var callbacks = signals[signalId]; - for (var i = 0; i < callbacks.length; i++) - if (callbacks[i].instance == instance) - callbacks.splice(i--, 1); + if (callbacks) + for (var i = callbacks.length; i--;) + if (callbacks[i].instance === instance) + callbacks.splice(i, 1); } } /** * Destroys the object, this method should only be called before losing - * the last reference to the object. + * the last reference to the object. It can be overwritten by child classes + * but should always call the parent method. */ - ,_destroy: function() { - if (!this._signalData) + _destroy() { + if (!this._thisArg) return; - var links = this._signalData.links; + var links = this._thisArg.links; for (var key in links) - links[key].disconnectByInstance(this); + this._unlink(links[key]); - this._signalData = null; + this._thisArg = null; } - ,link: function(prop, handlers) { + /** + * Links the object with another object. + * + * @param {Object} prop The linked property + * @param {Object} handlers The object events to listen with + */ + link(prop, handlers) { this._signalInit(); - var links = this._signalData.links; + var links = this._thisArg.links; for (var key in prop) { var newObject = prop[key]; var oldObject = this[key]; - if (oldObject) { - oldObject.disconnectByInstance(this); - oldObject.unref(); - } + if (oldObject) + this._unlink(oldObject); this[key] = newObject; @@ -195,8 +240,20 @@ module.exports = new Class({ for (var signal in handlers) newObject.on(signal, handlers[signal], this); } else if (oldObject) - delete links[key]; + links[key] = undefined; } } -}); + _unlink(object) { + object.disconnectByInstance(this); + object.unref(); + } + + _signalInit() { + if (!this._thisArg) + this._thisArg = { + signals: {}, + links: {} + }; + } +} diff --git a/js/vn/param-iface.js b/js/vn/param-iface.js new file mode 100644 index 00000000..792de330 --- /dev/null +++ b/js/vn/param-iface.js @@ -0,0 +1,136 @@ + +var LotIface = require('./lot-iface'); +var Type = require('./type'); +var Value = require('./value'); + +/** + * A value holder, it emits the changed signal when value is changed. + * Also it can be linked with a lot value or another parameter. + */ +module.exports = new Class({ + Properties: { + /** + * The parameter value. + */ + value: { + type: null + }, + /** + * The parameter type. + */ + type: { + type: Type + }, + /** + * Another parameter to bind with. + */ + param: { + type: Object + }, + /** + * A lot to bind with. + */ + lot: { + type: LotIface + }, + /** + * The field name in the lot. + */ + name: { + type: String + }, + /** + * Determines whether the link to the lot is unidirectional, ie, a + * change in the lot updates the parameter but not viceversa. + */ + oneWay: { + type: Boolean + } + } + + ,_value: undefined + ,_type: null + ,_param: null + ,_paramLock: false + ,_lot: null + ,_name: null + ,_lotLock: false + ,_oneWay: false + + ,_setValue: function(newValue) { + if (this._putValue(newValue)) + this._notifyChanges(); + } + + ,_putValue: function(newValue) { + if (Value.simpleEquals(newValue, this._value)) + return false; + + this._value = Value.simpleClone(newValue); + return true; + } + + ,_notifyChanges: function() { + this._refreshLot(); + this._refreshParam(); + this.emit('changed', this._value); + } + + ,_setType: function(type) { + this._type = type; + this._onLotChange(); + } + + ,_setParam: function(param) { + this.link({_param: param}, {changed: this._onParamChange}); + this._refreshParam(); + } + + ,_onParamChange: function() { + if (this._paramLock || !this._param) + return; + + this._paramLock = true; + this._setValue(this._param.value); + this._paramLock = false; + } + + ,_refreshParam: function() { + if (this._paramLock || !this._param) + return; + + this._paramLock = true; + this._param.value = this._value; + this._paramLock = false; + } + + ,_setLot: function(lot) { + this.link({_lot: lot}, {change: this._onLotChange}); + this._onLotChange(); + } + + ,_onLotChange: function() { + if (this._lotLock || !this._name || !this._lot) + return; + + var newValue = this._lot.get(this._name, this._type); + + this._lotLock = true; + this._setValue(newValue); + this._lotLock = false; + } + + ,_refreshLot: function() { + if (this._lotLock || !this._name || !this._lot || this._oneWay) + return; + + this._lotLock = true; + this._lot.set(this._name, this._value); + this._lotLock = false; + } + + ,_setName: function(name) { + this._name = name; + this._onLotChange(); + } +}); diff --git a/js/vn/param.js b/js/vn/param.js index 7001198c..1962e427 100644 --- a/js/vn/param.js +++ b/js/vn/param.js @@ -1,71 +1,79 @@ -var Object = require ('./object'); -var Param = require ('./param'); -var Value = require ('./value'); +var VnObject = require('./object'); +var Type = require('./type'); +var ParamIface = require('./param-iface'); +var LotIface = require('./lot-iface'); /** - * Simply a linkable value holder. + * A simple implementation of @ParamIface. */ -module.exports = new Class -({ - Extends: Object +module.exports = new Class({ + Extends: VnObject + ,Implements: ParamIface ,Tag: 'vn-param' - ,Properties: - { - value: - { - type: String - ,set: function (x) - { - if (Value.compare (x, this._value)) - return; - - if (x instanceof Date) - x = x.clone (); - - this._value = x; - - if (this._master && !this.masterLock) - { - this.masterLock = true; - this._master.value = x; - this.masterLock = false; - } - - this.signalEmit ('changed', this._value); + ,Properties: { + value: { + type: null + ,set: function(x) { + this._setValue(x); } - ,get: function () - { + ,get: function() { return this._value; } }, - master: - { - type: Param - ,set: function (x) - { - this.link ({_master: x}, {'changed': this._onMasterChange}); - this._onMasterChange (); + type: { + type: Type + ,set: function(x) { + this._setType(x); } - ,get: function () - { - return this._master; + ,get: function() { + return this._type; + } + }, + param: { + type: ParamIface + ,set: function(x) { + this._setParam(x); + } + ,get: function() { + return this._param; + } + }, + master: { + type: ParamIface + ,set: function(x) { + this._setParam(x); + } + ,get: function() { + return this._param; + } + }, + lot: { + type: LotIface + ,set: function(x) { + this._setLot(x); + } + ,get: function() { + return this._lot; + } + }, + name: { + type: String + ,set: function(x) { + this._setName(x); + } + ,get: function() { + return this._name; + } + }, + oneWay: { + type: Boolean + ,set: function(x) { + this._oneWay = x; + } + ,get: function() { + return this._oneWay; } } } - - ,_value: undefined - ,_master: null - ,masterLock: false - - ,_onMasterChange: function () - { - if (this.masterLock) - return; - - this.masterLock = true; - this.value = this._master.value; - this.masterLock = false; - } }); - diff --git a/js/vn/scope.js b/js/vn/scope.js index bf92ab14..57b9771b 100644 --- a/js/vn/scope.js +++ b/js/vn/scope.js @@ -53,6 +53,6 @@ module.exports = new Class({ if (objects[i] instanceof VnObject) objects[i].unref(); - this.parent(); + VnObject.prototype._destroy.call(this); } }); diff --git a/js/vn/spec.js b/js/vn/spec.js new file mode 100644 index 00000000..f24bc6da --- /dev/null +++ b/js/vn/spec.js @@ -0,0 +1,37 @@ + +var VnObject = require('./object'); +var Type = require('./type'); + +/** + * Paramter specification. + */ +module.exports = new Class({ + Extends: VnObject + ,Tag: 'vn-spec' + ,Properties: { + /** + * The parameter name. + */ + name: { + type: String + ,set: function(x) { + this._name = x; + } + ,get: function() { + return this._name; + } + }, + /** + * The parameter type. + */ + type: { + type: Type + ,set: function(x) { + this._type = x; + } + ,get: function() { + return this._type; + } + } + } +}); diff --git a/js/vn/type.js b/js/vn/type.js new file mode 100644 index 00000000..b380e90a --- /dev/null +++ b/js/vn/type.js @@ -0,0 +1,4 @@ +/** + * Type that references another type. + */ + module.exports = function() {}; \ No newline at end of file diff --git a/js/vn/value.js b/js/vn/value.js index 50dcb287..b45bf17f 100644 --- a/js/vn/value.js +++ b/js/vn/value.js @@ -1,65 +1,191 @@ -var VnDate = require ('./date'); +var VnDate = require('./date'); -module.exports = -{ - regexpNumber: /%\.([0-9]+)d/g - ,regexpString: /%s/g +/** + * Clones a simple value. A simple value is any value that is not an array, + * object or function. If non-simple value is passed, the same object reference + * is returned. + * + * @param {*} value The value to be copied + * @return {*} The value copy + */ +function simpleClone(value) { + if (value instanceof Date) + return value.clone(); - ,compare: function (a, b) - { - if (a === b) - return true; - if (typeof a === typeof b && a instanceof Date) - return a.getTime () === b.getTime (); - - return false; - } - - ,format: function (value, format) - { - if (value === null || value === undefined) - return ''; - - if (format) - switch (typeof value) - { - case 'number': - return format.replace (this.regexpNumber, - this.replaceNumber.bind (null, value)); - case 'string': - return format.replace (this.regexpString, - this.replaceString.bind (null, value)); - case 'object': - if (value instanceof Date) - return VnDate.strftime (value, format); - } - - return value; - } - - ,replaceNumber: function (value, token, digits) - { - return new Number (value).toFixed (parseInt (digits)); - } - - ,replaceString: function (value) - { - return value; - } -}; + return value; +} -window.sprintf = function (formatString) -{ +/** + * Checks if two simple values are equal using the strict equality operator. For + * information about simple values see simpleClone() function. + * + * @param {*} a Value to compare to + * @param {*} b Value to compare with + * @return {boolean} %true if they are equal, %false otherwise + */ +function simpleEquals(a, b) { + if (a === b) + return true; + if (a instanceof Date && b instanceof Date) + return a.getTime() === b.getTime(); + + return false; +} + +/** + * Calculates differences between two simple key-value objects. + * + * @param {Object} orgObject Value to compare to + * @param {Object} newObject Value to compare with + * @return {Object} The differences or %null if there are no differences + */ +function diff(orgObject, newObject) { + var diff = {}; + + for (var key in orgObject) + if (!simpleEquals(orgObject[key], newObject[key])) + diff[key] = simpleClone(newObject[key]); + + for (var key in newObject) + if (orgObject[key] === undefined && newObject[key] !== undefined) + diff[key] = simpleClone(newObject[key]); + + if (Object.keys(diff).length > 0) + return diff; + + return null; +} + +/** + * Calculates new differences between two simple key-value objects. + * + * @param {Object} orgObject Value to compare to + * @param {Object} newObject Value to compare with + * @return {Object} The differences or %null if there are no differences + */ +function partialDiff(orgObject, newObject) { + var diff = {}; + + for (var key in newObject) + if (!simpleEquals(orgObject[key], newObject[key])) + diff[key] = simpleClone(newObject[key]); + + if (Object.keys(diff).length > 0) + return diff; + + return null; +} + +/** + * Clones a simple key-value object in wich properties are simple values. For + * information about simple values see simpleClone() function. + * + * @param {*} object The object to be cloned + * @return The cloned object + */ +function kvClone(object) { + var copy = {}; + + for (var key in object) + copy[key] = simpleClone(object[key]); + + return copy; +} + +/** + * Checks if two values are equal, it also checks objects. Basic values are + * compared using the strict equality operator. + * + * @param {*} a Value to compare to + * @param {*} b Value to compare with + * @return {boolean} %true if they are equal, %false otherwise + */ +function equals(a, b) { + if (a === b) + return true; + if (a instanceof Date && b instanceof Date) + return a.getTime() === b.getTime(); + if (a && b && (typeof a === 'object') && (typeof b === 'object')) { + for (var key in a) + if (!equals(a[key], b[key])) + return false; + + for (var key in b) + if (a[key] === undefined && b[key] !== undefined) + return false; + + return true; + } + + return false; +} + +/** + * Returns a formated string. + * + * @param {Object} formatString The base string template + * @param {...} arguments Format parameters + * @return {string} The formated string + */ +function sprintf(formatString) { var args = arguments; if (args.length <= 1) return formatString; var i = 1; - return formatString.replace (/%[s|d]/g, function () - { + return formatString.replace(/%[s|d]/g, function() { return args[i++]; }); } +module.exports = { + regexpNumber: /%\.([0-9]+)d/g + ,regexpString: /%s/g + + ,equals + ,diff + ,partialDiff + ,kvClone + ,simpleClone + ,simpleEquals + ,sprintf + + ,compare: function(a, b) { + if (a === b) + return true; + if (a instanceof Date && b instanceof Date) + return a.getTime() === b.getTime(); + + return false; + } + + ,format: function(value, format) { + if (value === null || value === undefined) + return ''; + + if (format) + switch (typeof value) { + case 'number': + return format.replace(this.regexpNumber, + this.replaceNumber.bind(null, value)); + case 'string': + return format.replace(this.regexpString, + this.replaceString.bind(null, value)); + case 'object': + if (value instanceof Date) + return VnDate.strftime(value, format); + } + + return value; + } + + ,replaceNumber: function(value, token, digits) { + return new Number(value).toFixed(parseInt(digits)); + } + + ,replaceString: function(value) { + return value; + } +}; diff --git a/js/vn/vn.js b/js/vn/vn.js index d6536319..5e272e9d 100644 --- a/js/vn/vn.js +++ b/js/vn/vn.js @@ -3,20 +3,31 @@ require('mootools'); Vn = module.exports = { Locale : require('./locale') - ,Enum : function() {} + ,Enum : require('./enum') + ,Type : require('./type') ,Object : require('./object') + ,Mutators : require('./mutators') ,Browser : require('./browser') ,Cookie : require('./cookie') ,Date : require('./date') ,Value : require('./value') ,Url : require('./url') - ,Mutators : require('./mutators') - ,Param : require('./param') - ,HashListener : require('./hash-listener') + ,LotIface : require('./lot-iface') + ,Lot : require('./lot') + ,LotQuery : require('./lot-query') ,Hash : require('./hash') - ,HashParam : require('./hash-param') + ,ParamIface : require('./param-iface') + ,Param : require('./param') + ,Spec : require('./spec') + ,Model : require('./model') + ,ModelIface : require('./model-iface') + ,ModelProxy : require('./model-proxy') + ,IteratorIface : require('./iterator-iface') + ,Iterator : require('./iterator') + ,Form : require('./form') ,Node : require('./node') ,NodeBuilder : require('./node-builder') + ,Component : require('./component') ,Builder : require('./builder') ,JsonException : require('./json-exception') ,JsonConnection : require('./json-connection') @@ -374,4 +385,3 @@ Vn = module.exports = { return this.isMobileCached; } }; - diff --git a/package.json b/package.json index 08c0c95a..fd3a3f8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hedera-web", - "version": "1.407.76", + "version": "1.407.77", "description": "Verdnatura web page", "license": "GPL-3.0", "repository": { diff --git a/reports/delivery-note/ui.xml b/reports/delivery-note/ui.xml index 23f95654..eb7d3db5 100644 --- a/reports/delivery-note/ui.xml +++ b/reports/delivery-note/ui.xml @@ -1,7 +1,7 @@ - + CALL myTicket_get(#ticket) @@ -47,7 +47,7 @@
- + CALL myTicket_getRows(#ticket) @@ -64,7 +64,7 @@ property="model" id="services" conn="conn" - batch="batch" + lot="lot" on-status-changed="onServicesChanged"> CALL myTicket_getServices(#ticket) @@ -77,7 +77,7 @@ CALL myTicket_getPackages(#ticket) diff --git a/reports/items-report/items-report.js b/reports/items-report/items-report.js index 76963065..cae507db 100644 --- a/reports/items-report/items-report.js +++ b/reports/items-report/items-report.js @@ -1,5 +1,4 @@ -Hedera.ItemsReport = new Class -({ +Hedera.ItemsReport = new Class({ Extends: Hedera.Report }); diff --git a/reports/items-report/ui.xml b/reports/items-report/ui.xml index 5eb2f1a9..ac2d2383 100644 --- a/reports/items-report/ui.xml +++ b/reports/items-report/ui.xml @@ -6,7 +6,7 @@ CALL item_getList(#warehouse, CURDATE(), #realm, #rate) diff --git a/reports/shelves-report/shelves-report.js b/reports/shelves-report/shelves-report.js index 64068259..e681ef81 100644 --- a/reports/shelves-report/shelves-report.js +++ b/reports/shelves-report/shelves-report.js @@ -7,20 +7,15 @@ Hedera.ShelvesReport = new Class({ ,trayThickness: 2 ,trayMargin: 5 - ,open: function(batch) { - this.batch = batch; - this.title = batch.getValue('reportTitle'); - this.maxAmount = batch.getValue('maxAmount'); - this.showPacking = batch.getValue('showPacking'); - this.stack = batch.getValue('stack'); - this.useIds = batch.getValue('useIds'); + ,open: function(lot) { + this.lot = lot; var query = 'SELECT id, name, nTrays, topTrayHeight, trayHeight, width, depth '+ 'FROM shelf WHERE id = #shelf; '+ 'CALL item_listAllocation(#warehouse, #date, #family, #namePrefix, #useIds)'; - this.conn.execQuery(query, this.onQueryExec.bind(this), this.batch); + this.conn.execQuery(query, this.onQueryExec.bind(this), lot.$); } ,onQueryExec: function(resultSet) {