#659 - Unify model beta
This commit is contained in:
parent
7933607dd0
commit
ee73068e9e
|
@ -37,7 +37,7 @@
|
|||
id="claimDestinationFk"
|
||||
field="saleClaimed.claimDestinationFk"
|
||||
url="/claim/api/ClaimDestinations"
|
||||
select-fields="['id','description']"
|
||||
fields="['id','description']"
|
||||
value-field="id"
|
||||
show-field="description"
|
||||
on-change="$ctrl.setClaimDestination(saleClaimed.id, value)">
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
disabled="true"
|
||||
field="$ctrl.claim.workerFk"
|
||||
url="/client/api/Workers"
|
||||
select-fields="['firstName', 'name']"
|
||||
fields="['firstName', 'name']"
|
||||
value-field="id"
|
||||
label="Worker">
|
||||
<tpl-item>{{firstName}} {{name}}</tpl-item>
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
id="claimReason"
|
||||
field="claimDevelopment.claimReasonFk"
|
||||
data="claimReasons"
|
||||
select-fields="['id','description']"
|
||||
fields="['id', 'description']"
|
||||
show-field="description"
|
||||
vn-acl="salesAssistant">
|
||||
</vn-autocomplete>
|
||||
|
@ -59,7 +59,7 @@
|
|||
id="claimResult"
|
||||
field="claimDevelopment.claimResultFk"
|
||||
data="claimResults"
|
||||
select-fields="['id','description']"
|
||||
fields="['id', 'description']"
|
||||
show-field="description"
|
||||
vn-acl="salesAssistant">
|
||||
</vn-autocomplete>
|
||||
|
@ -69,7 +69,7 @@
|
|||
id="Responsible"
|
||||
field="claimDevelopment.claimResponsibleFk"
|
||||
data="claimResponsibles"
|
||||
select-fields="['id','description']"
|
||||
fields="['id', 'description']"
|
||||
show-field="description"
|
||||
vn-acl="salesAssistant">
|
||||
</vn-autocomplete>
|
||||
|
@ -88,7 +88,7 @@
|
|||
id="redelivery"
|
||||
field="claimDevelopment.claimRedeliveryFk"
|
||||
data="claimRedeliveries"
|
||||
select-fields="['id','description']"
|
||||
fields="['id', 'description']"
|
||||
show-field="description"
|
||||
vn-acl="salesAssistant">
|
||||
</vn-autocomplete>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
vn-acl="administrative, salesAssistant"
|
||||
field="$ctrl.client.payMethodFk"
|
||||
url="/client/api/PayMethods"
|
||||
select-fields="ibanRequired"
|
||||
fields="['ibanRequired']"
|
||||
initial-data="$ctrl.client.payMethod">
|
||||
</vn-autocomplete>
|
||||
<vn-textfield
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
vn-id="sampleType"
|
||||
field="$ctrl.clientSample.typeFk"
|
||||
model="ClientSample.typeFk"
|
||||
select-fields="['code','hasCompany']"
|
||||
fields="['code','hasCompany']"
|
||||
url="/client/api/Samples"
|
||||
show-field="description"
|
||||
value-field="id"
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
import ngModule from '../../module';
|
||||
import ModelProxy from '../model-proxy/model-proxy';
|
||||
|
||||
export default class ArrayModel extends ModelProxy {
|
||||
constructor($q, $filter) {
|
||||
super();
|
||||
this.$q = $q;
|
||||
this.$filter = $filter;
|
||||
this.autoLoad = true;
|
||||
this.limit = 0;
|
||||
this.userFilter = [];
|
||||
|
||||
this.clear();
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.autoRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the model is loading.
|
||||
*/
|
||||
get isLoading() {
|
||||
return false;
|
||||
}
|
||||
|
||||
autoRefresh() {
|
||||
if (this.autoLoad)
|
||||
return this.refresh();
|
||||
return this.$q.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the model data.
|
||||
*
|
||||
* @return {Promise} The request promise
|
||||
*/
|
||||
refresh() {
|
||||
this.data = this.proccessData(0);
|
||||
return this.$q.resolve();
|
||||
}
|
||||
|
||||
proccessData(skip) {
|
||||
if (!this.proxiedData) return null;
|
||||
let data = this.proxiedData.slice();
|
||||
|
||||
let filter = {
|
||||
order: this.order,
|
||||
limit: this.limit
|
||||
};
|
||||
|
||||
if (this.where)
|
||||
filter.where = [this.where];
|
||||
|
||||
filter = this.mergeFilters(this.userFilter, filter);
|
||||
|
||||
let where = filter.where;
|
||||
|
||||
if (where) {
|
||||
if (!Array.isArray(where))
|
||||
where = [where];
|
||||
|
||||
for (let subWhere of where)
|
||||
data = this.$filter('filter')(data, subWhere);
|
||||
}
|
||||
|
||||
let order = filter.order;
|
||||
|
||||
if (typeof order === 'string')
|
||||
order = order.split(/\s*,\s*/);
|
||||
|
||||
if (Array.isArray(order)) {
|
||||
let orderComp = [];
|
||||
|
||||
for (let field of order) {
|
||||
let split = field.split(/\s+/);
|
||||
orderComp.push({
|
||||
field: split[0],
|
||||
way: split[1] === 'DESC' ? -1 : 1
|
||||
});
|
||||
}
|
||||
|
||||
data.sort((a, b) => this.sortFunc(a, b, orderComp));
|
||||
} else if (typeof order === 'function')
|
||||
data.sort(order);
|
||||
|
||||
this.skip = skip;
|
||||
|
||||
if (filter.limit) {
|
||||
let end = skip + filter.limit;
|
||||
this.moreRows = end < data.length;
|
||||
data = data.slice(this.skip, end);
|
||||
} else
|
||||
this.moreRows = false;
|
||||
|
||||
this.currentFilter = filter;
|
||||
return data;
|
||||
}
|
||||
|
||||
applyFilter(userFilter, userParams) {
|
||||
this.userFilter = userFilter;
|
||||
this.userParams = userParams;
|
||||
return this.refresh();
|
||||
}
|
||||
|
||||
addFilter(userFilter, userParams) {
|
||||
this.userFilter = this.mergeFilters(userFilter, this.userFilter);
|
||||
Object.assign(this.userParams, userParams);
|
||||
return this.refresh();
|
||||
}
|
||||
|
||||
removeFilter() {
|
||||
return applyFilter(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* When limit is enabled, loads the next set of rows.
|
||||
*
|
||||
* @return {Promise} The request promise
|
||||
*/
|
||||
loadMore() {
|
||||
if (!this.moreRows)
|
||||
return this.$q.resolve();
|
||||
|
||||
let data = this.proccessData(this.skip + this.currentFilter.limit);
|
||||
this.data = this.data.concat(data);
|
||||
return this.$q.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the model, removing all it's data.
|
||||
*/
|
||||
clear() {
|
||||
this.data = null;
|
||||
this.userFilter = null;
|
||||
this.moreRows = false;
|
||||
this.skip = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves current changes on the server.
|
||||
*
|
||||
* @return {Promise} The save request promise
|
||||
*/
|
||||
save() {
|
||||
if (this.getChanges())
|
||||
this.orgData = this.data;
|
||||
|
||||
return this.$q.resolve();
|
||||
}
|
||||
|
||||
sortFunc(a, b, order) {
|
||||
for (let i of order) {
|
||||
let compRes = this.compareFunc(a[i.field], b[i.field]) * i.way;
|
||||
if (compRes !== 0)
|
||||
return compRes;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
compareFunc(a, b) {
|
||||
if (a === b)
|
||||
return 0;
|
||||
|
||||
let aType = typeof a;
|
||||
|
||||
if (aType === typeof b) {
|
||||
switch (aType) {
|
||||
case 'string':
|
||||
return a.localeCompare(b);
|
||||
case 'number':
|
||||
return a - b;
|
||||
case 'boolean':
|
||||
return a ? 1 : -1;
|
||||
case 'object':
|
||||
if (a instanceof Date && b instanceof Date)
|
||||
return a.getTime() - b.getTime();
|
||||
}
|
||||
}
|
||||
|
||||
if (a === undefined)
|
||||
return -1;
|
||||
if (b === undefined)
|
||||
return 1;
|
||||
if (a === null)
|
||||
return -1;
|
||||
if (b === null)
|
||||
return 1;
|
||||
|
||||
return a > b ? 1 : -1;
|
||||
}
|
||||
|
||||
mergeFilters(src, dst) {
|
||||
let mergedWhere = [];
|
||||
let wheres = [dst.where, src.where];
|
||||
|
||||
for (let where of wheres) {
|
||||
if (Array.isArray(where))
|
||||
mergedWhere = mergedWhere.concat(where);
|
||||
else if (where)
|
||||
mergedWhere.push(where);
|
||||
}
|
||||
|
||||
switch (mergedWhere.length) {
|
||||
case 0:
|
||||
mergedWhere = undefined;
|
||||
break;
|
||||
case 1:
|
||||
mergedWhere = mergedWhere[0];
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
where: mergedWhere,
|
||||
order: src.order || dst.order,
|
||||
limit: src.limit || dst.limit
|
||||
};
|
||||
}
|
||||
|
||||
undoChanges() {
|
||||
super.undoChanges();
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
ArrayModel.$inject = ['$q'];
|
||||
|
||||
ngModule.component('vnArrayModel', {
|
||||
controller: ArrayModel,
|
||||
bindings: {
|
||||
orgData: '<?',
|
||||
data: '=?',
|
||||
link: '<?',
|
||||
order: '@?',
|
||||
limit: '<?',
|
||||
autoLoad: '<?'
|
||||
}
|
||||
});
|
|
@ -20,5 +20,6 @@
|
|||
</div>
|
||||
<vn-drop-down
|
||||
vn-id="drop-down"
|
||||
on-select="$ctrl.onDropDownSelect(value)">
|
||||
on-select="$ctrl.onDropDownSelect(value)"
|
||||
on-data-ready="$ctrl.onDataReady()">
|
||||
</vn-drop-down>
|
|
@ -1,6 +1,6 @@
|
|||
import ngModule from '../../module';
|
||||
import Input from '../../lib/input';
|
||||
import asignProps from '../../lib/asign-props';
|
||||
import assignProps from '../../lib/assign-props';
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
|
@ -11,6 +11,8 @@ import './style.scss';
|
|||
* @property {Array} data Static data for the autocomplete
|
||||
* @property {Object} intialData A initial data to avoid the server request used to get the selection
|
||||
* @property {Boolean} multiple Wether to allow multiple selection
|
||||
*
|
||||
* @event change Thrown when value is changed
|
||||
*/
|
||||
export default class Autocomplete extends Input {
|
||||
constructor($element, $scope, $http, $transclude) {
|
||||
|
@ -20,9 +22,6 @@ export default class Autocomplete extends Input {
|
|||
|
||||
this._field = undefined;
|
||||
this._selection = null;
|
||||
this.valueField = 'id';
|
||||
this.showField = 'name';
|
||||
this._multiField = [];
|
||||
this.readonly = true;
|
||||
this.form = null;
|
||||
this.input = this.element.querySelector('.mdl-textfield__input');
|
||||
|
@ -31,15 +30,41 @@ export default class Autocomplete extends Input {
|
|||
this.element.querySelector('.mdl-textfield'));
|
||||
}
|
||||
|
||||
set url(value) {
|
||||
this._url = value;
|
||||
$postLink() {
|
||||
this.assignDropdownProps();
|
||||
this.showField = this.$.dropDown.showField;
|
||||
this.valueField = this.$.dropDown.valueField;
|
||||
this.linked = true;
|
||||
this.refreshSelection();
|
||||
}
|
||||
|
||||
get model() {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
set model(value) {
|
||||
this._model = value;
|
||||
this.assignDropdownProps();
|
||||
}
|
||||
|
||||
get data() {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
set data(value) {
|
||||
this._data = value;
|
||||
this.assignDropdownProps();
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
set url(value) {
|
||||
this._url = value;
|
||||
this.assignDropdownProps();
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {any} The autocomplete value.
|
||||
*/
|
||||
|
@ -53,9 +78,7 @@ export default class Autocomplete extends Input {
|
|||
|
||||
this._field = value;
|
||||
this.refreshSelection();
|
||||
|
||||
if (this.onChange)
|
||||
this.onChange({value});
|
||||
this.emit('change', {value});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,15 +94,6 @@ export default class Autocomplete extends Input {
|
|||
this.refreshDisplayed();
|
||||
}
|
||||
|
||||
set data(value) {
|
||||
this._data = value;
|
||||
this.refreshSelection();
|
||||
}
|
||||
|
||||
get data() {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
selectionIsValid(selection) {
|
||||
return selection
|
||||
&& selection[this.valueField] == this._field
|
||||
|
@ -92,16 +106,16 @@ export default class Autocomplete extends Input {
|
|||
|
||||
let value = this._field;
|
||||
|
||||
if (value && this.valueField && this.showField) {
|
||||
if (value && this.linked) {
|
||||
if (this.selectionIsValid(this.initialData)) {
|
||||
this.selection = this.initialData;
|
||||
return;
|
||||
}
|
||||
|
||||
let data = this.data;
|
||||
|
||||
if (!data && this.$.dropDown)
|
||||
data = this.$.dropDown.$.model.data;
|
||||
if (this.$.dropDown) {
|
||||
let data;
|
||||
if (this.$.dropDown.model)
|
||||
data = this.$.dropDown.model.orgData;
|
||||
|
||||
if (data)
|
||||
for (let i = 0; i < data.length; i++)
|
||||
|
@ -110,15 +124,14 @@ export default class Autocomplete extends Input {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.url) {
|
||||
this.requestSelection(value);
|
||||
return;
|
||||
}
|
||||
} else
|
||||
this.selection = null;
|
||||
}
|
||||
|
||||
requestSelection(value) {
|
||||
if (!this.url) return;
|
||||
let where = {};
|
||||
|
||||
if (this.multiple)
|
||||
|
@ -127,7 +140,7 @@ export default class Autocomplete extends Input {
|
|||
where[this.valueField] = value;
|
||||
|
||||
let filter = {
|
||||
fields: this.getFields(),
|
||||
fields: this.$.dropDown.getFields(),
|
||||
where: where
|
||||
};
|
||||
|
||||
|
@ -170,18 +183,6 @@ export default class Autocomplete extends Input {
|
|||
this.mdlUpdate();
|
||||
}
|
||||
|
||||
getFields() {
|
||||
let fields = [];
|
||||
fields.push(this.valueField);
|
||||
fields.push(this.showField);
|
||||
|
||||
if (this.selectFields)
|
||||
for (let field of this.selectFields)
|
||||
fields.push(field);
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
mdlUpdate() {
|
||||
let field = this.element.querySelector('.mdl-textfield');
|
||||
let mdlField = field.MaterialTextfield;
|
||||
|
@ -227,26 +228,34 @@ export default class Autocomplete extends Input {
|
|||
this.showDropDown();
|
||||
}
|
||||
|
||||
showDropDown(search) {
|
||||
Object.assign(this.$.dropDown.$.model, {
|
||||
url: this.url,
|
||||
staticData: this._data
|
||||
});
|
||||
onDataReady() {
|
||||
this.refreshSelection();
|
||||
}
|
||||
|
||||
asignProps(this, this.$.dropDown, [
|
||||
assignDropdownProps() {
|
||||
if (!this.$.dropDown) return;
|
||||
assignProps(this, this.$.dropDown, [
|
||||
'valueField',
|
||||
'showField',
|
||||
'showFilter',
|
||||
'multiple',
|
||||
'$transclude',
|
||||
'translateFields',
|
||||
'model',
|
||||
'data',
|
||||
'url',
|
||||
'fields',
|
||||
'include',
|
||||
'where',
|
||||
'order',
|
||||
'limit',
|
||||
'showFilter',
|
||||
'multiple',
|
||||
'$transclude'
|
||||
'searchFunction'
|
||||
]);
|
||||
}
|
||||
|
||||
this.$.dropDown.selectFields = this.getFields();
|
||||
this.$.dropDown.parent = this.input;
|
||||
this.$.dropDown.show(search);
|
||||
showDropDown(search) {
|
||||
this.assignDropdownProps();
|
||||
this.$.dropDown.show(this.input, search);
|
||||
}
|
||||
}
|
||||
Autocomplete.$inject = ['$element', '$scope', '$http', '$transclude'];
|
||||
|
@ -255,22 +264,23 @@ ngModule.component('vnAutocomplete', {
|
|||
template: require('./autocomplete.html'),
|
||||
controller: Autocomplete,
|
||||
bindings: {
|
||||
url: '@?',
|
||||
data: '<?',
|
||||
label: '@',
|
||||
field: '=?',
|
||||
disabled: '<?',
|
||||
showField: '@?',
|
||||
valueField: '@?',
|
||||
selectFields: '<?',
|
||||
disabled: '<?',
|
||||
where: '@?',
|
||||
order: '@?',
|
||||
label: '@',
|
||||
initialData: '<?',
|
||||
field: '=?',
|
||||
limit: '<?',
|
||||
showFilter: '<?',
|
||||
selection: '<?',
|
||||
multiple: '<?',
|
||||
onChange: '&?'
|
||||
data: '<?',
|
||||
url: '@?',
|
||||
fields: '<?',
|
||||
include: '<?',
|
||||
where: '<?',
|
||||
order: '@?',
|
||||
limit: '<?',
|
||||
searchFunction: '&?'
|
||||
},
|
||||
transclude: {
|
||||
tplItem: '?tplItem'
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import ngModule from '../../module';
|
||||
import ModelProxy from '../model-proxy/model-proxy';
|
||||
import {mergeWhere, mergeFilters} from 'vn-loopback/common/filter.js';
|
||||
|
||||
export default class CrudModel extends ModelProxy {
|
||||
constructor($http, $q) {
|
||||
constructor($q, $http) {
|
||||
super();
|
||||
this.$http = $http;
|
||||
this.$q = $q;
|
||||
|
@ -10,6 +11,10 @@ export default class CrudModel extends ModelProxy {
|
|||
this.autoLoad = true;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.autoRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the model is loading.
|
||||
*/
|
||||
|
@ -17,9 +22,21 @@ export default class CrudModel extends ModelProxy {
|
|||
return this.canceler != null;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
set url(url) {
|
||||
if (this._url === url) return;
|
||||
this._url = url;
|
||||
this.clear();
|
||||
this.autoRefresh();
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
autoRefresh() {
|
||||
if (this.autoLoad)
|
||||
this.refresh();
|
||||
return this.refresh();
|
||||
return this.$q.resolve();
|
||||
}
|
||||
|
||||
buildFilter() {
|
||||
|
@ -48,7 +65,8 @@ export default class CrudModel extends ModelProxy {
|
|||
* @return {Promise} The request promise
|
||||
*/
|
||||
refresh() {
|
||||
if (!this.url) return;
|
||||
if (!this._url)
|
||||
return this.$q.resolve();
|
||||
return this.sendRequest(this.buildFilter());
|
||||
}
|
||||
|
||||
|
@ -84,9 +102,7 @@ export default class CrudModel extends ModelProxy {
|
|||
* @return {Promise} The request promise
|
||||
*/
|
||||
removeFilter() {
|
||||
this.userFilter = null;
|
||||
this.userParams = null;
|
||||
return this.refresh();
|
||||
return applyFilter(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,12 +117,23 @@ export default class CrudModel extends ModelProxy {
|
|||
|
||||
/**
|
||||
* When limit is enabled, loads the next set of rows.
|
||||
*
|
||||
* @return {Promise} The request promise
|
||||
*/
|
||||
loadMore() {
|
||||
if (!this.moreRows) return;
|
||||
if (!this.moreRows)
|
||||
return this.$q.resolve();
|
||||
|
||||
let filter = Object.assign({}, this.currentFilter);
|
||||
filter.skip = this.orgData ? this.orgData.length : 0;
|
||||
this.sendRequest(filter, true);
|
||||
return this.sendRequest(filter, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the model, removing all it's data.
|
||||
*/
|
||||
clear() {
|
||||
this.orgData = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,27 +142,30 @@ export default class CrudModel extends ModelProxy {
|
|||
* @return {Object} The current changes
|
||||
*/
|
||||
getChanges() {
|
||||
if (!this.isChanged)
|
||||
return null;
|
||||
|
||||
let create = [];
|
||||
let update = [];
|
||||
let remove = [];
|
||||
|
||||
for (let row of this.removed)
|
||||
remove.push(row.$data[this.primaryKey]);
|
||||
remove.push(row[this.primaryKey]);
|
||||
|
||||
for (let row of this._data) {
|
||||
if (row.$isNew)
|
||||
create.push(row.$data);
|
||||
else if (row.$oldData)
|
||||
update.push(row.$data);
|
||||
if (row.$isNew) {
|
||||
let data = {};
|
||||
for (let prop in row)
|
||||
if (prop.charAt(0) !== '$')
|
||||
data[prop] = row[prop];
|
||||
create.push(data);
|
||||
} else if (row.$oldData) {
|
||||
let data = {};
|
||||
for (let prop in row.$oldData)
|
||||
data[prop] = row[prop];
|
||||
update.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
let isChanged =
|
||||
create.length > 0 ||
|
||||
update.length > 0 ||
|
||||
remove.length > 0;
|
||||
|
||||
if (!isChanged)
|
||||
return null;
|
||||
|
||||
let changes = {
|
||||
create: create,
|
||||
|
@ -157,9 +187,9 @@ export default class CrudModel extends ModelProxy {
|
|||
if (!changes)
|
||||
return this.$q.resolve();
|
||||
|
||||
let url = this.saveUrl ? this.saveUrl : `${this.url}/crud`;
|
||||
let url = this.saveUrl ? this.saveUrl : `${this._url}/crud`;
|
||||
return this.$http.post(url, changes)
|
||||
.then(() => this.resetChanges());
|
||||
.then(() => this.applyChanges());
|
||||
}
|
||||
|
||||
buildParams() {
|
||||
|
@ -186,7 +216,7 @@ export default class CrudModel extends ModelProxy {
|
|||
params: params
|
||||
};
|
||||
|
||||
return this.$http.get(this.url, options).then(
|
||||
return this.$http.get(this._url, options).then(
|
||||
json => this.onRemoteDone(json, filter, append),
|
||||
json => this.onRemoteError(json)
|
||||
);
|
||||
|
@ -202,9 +232,9 @@ export default class CrudModel extends ModelProxy {
|
|||
this.currentFilter = filter;
|
||||
}
|
||||
|
||||
this.data = this.proxiedData.slice();
|
||||
this.moreRows = filter.limit && data.length == filter.limit;
|
||||
this.onRequestEnd();
|
||||
this.dataChanged();
|
||||
}
|
||||
|
||||
onRemoteError(err) {
|
||||
|
@ -215,8 +245,13 @@ export default class CrudModel extends ModelProxy {
|
|||
onRequestEnd() {
|
||||
this.canceler = null;
|
||||
}
|
||||
|
||||
undoChanges() {
|
||||
super.undoChanges();
|
||||
this.data = this.proxiedData.slice();
|
||||
}
|
||||
}
|
||||
CrudModel.$inject = ['$http', '$q'];
|
||||
CrudModel.$inject = ['$q', '$http'];
|
||||
|
||||
ngModule.component('vnCrudModel', {
|
||||
controller: CrudModel,
|
||||
|
@ -224,12 +259,12 @@ ngModule.component('vnCrudModel', {
|
|||
orgData: '<?',
|
||||
data: '=?',
|
||||
onDataChange: '&?',
|
||||
fields: '<?',
|
||||
link: '<?',
|
||||
url: '@?',
|
||||
saveUrl: '@?',
|
||||
where: '<?',
|
||||
fields: '<?',
|
||||
include: '<?',
|
||||
where: '<?',
|
||||
order: '@?',
|
||||
limit: '<?',
|
||||
filter: '<?',
|
||||
|
@ -240,80 +275,3 @@ ngModule.component('vnCrudModel', {
|
|||
autoLoad: '<?'
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Passes a loopback fields filter to an object.
|
||||
*
|
||||
* @param {Object} fields The fields object or array
|
||||
* @return {Object} The fields as object
|
||||
*/
|
||||
function fieldsToObject(fields) {
|
||||
let fieldsObj = {};
|
||||
|
||||
if (Array.isArray(fields))
|
||||
for (let field of fields)
|
||||
fieldsObj[field] = true;
|
||||
else if (typeof fields == 'object')
|
||||
for (let field in fields)
|
||||
if (fields[field])
|
||||
fieldsObj[field] = true;
|
||||
|
||||
return fieldsObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two loopback fields filters.
|
||||
*
|
||||
* @param {Object|Array} src The source fields
|
||||
* @param {Object|Array} dst The destination fields
|
||||
* @return {Array} The merged fields as an array
|
||||
*/
|
||||
function mergeFields(src, dst) {
|
||||
let fields = {};
|
||||
Object.assign(fields,
|
||||
fieldsToObject(src),
|
||||
fieldsToObject(dst)
|
||||
);
|
||||
return Object.keys(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two loopback where filters.
|
||||
*
|
||||
* @param {Object|Array} src The source where
|
||||
* @param {Object|Array} dst The destination where
|
||||
* @return {Array} The merged wheres
|
||||
*/
|
||||
function mergeWhere(src, dst) {
|
||||
let and = [];
|
||||
if (src) and.push(src);
|
||||
if (dst) and.push(dst);
|
||||
return and.length > 1 ? {and} : and[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two loopback filters returning the merged filter.
|
||||
*
|
||||
* @param {Object} src The source filter
|
||||
* @param {Object} dst The destination filter
|
||||
* @return {Object} The result filter
|
||||
*/
|
||||
function mergeFilters(src, dst) {
|
||||
let res = Object.assign({}, dst);
|
||||
|
||||
if (!src)
|
||||
return res;
|
||||
|
||||
if (src.fields)
|
||||
res.fields = mergeFields(src.fields, res.fields);
|
||||
if (src.where)
|
||||
res.where = mergeWhere(res.where, src.where);
|
||||
if (src.include)
|
||||
res.include = src.include;
|
||||
if (src.order)
|
||||
res.order = src.order;
|
||||
if (src.limit)
|
||||
res.limit = src.limit;
|
||||
|
||||
return res;
|
||||
}
|
|
@ -1,7 +1,3 @@
|
|||
<vn-rest-model
|
||||
vn-id="model"
|
||||
on-data-change="$ctrl.onModelDataChange()">
|
||||
</vn-rest-model>
|
||||
<vn-popover
|
||||
vn-id="popover"
|
||||
on-open="$ctrl.onOpen()"
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
import './style.scss';
|
||||
import ngModule from '../../module';
|
||||
import Component from '../../lib/component';
|
||||
import './style.scss';
|
||||
import ArrayModel from '../array-model/array-model';
|
||||
import CrudModel from '../crud-model/crud-model';
|
||||
import {mergeWhere} from 'vn-loopback/common/filter.js';
|
||||
|
||||
/**
|
||||
* @event select Thrown when model item is selected
|
||||
* @event change Thrown when model data is ready
|
||||
*/
|
||||
export default class DropDown extends Component {
|
||||
constructor($element, $scope, $transclude, $timeout, $translate) {
|
||||
constructor($element, $scope, $transclude, $timeout, $translate, $http, $q, $filter) {
|
||||
super($element, $scope);
|
||||
this.$transclude = $transclude;
|
||||
this.$timeout = $timeout;
|
||||
this.$translate = $translate;
|
||||
this.$http = $http;
|
||||
this.$q = $q;
|
||||
this.$filter = $filter;
|
||||
|
||||
this.valueField = 'id';
|
||||
this.showField = 'name';
|
||||
|
@ -27,7 +37,7 @@ export default class DropDown extends Component {
|
|||
}
|
||||
|
||||
get shown() {
|
||||
return this.$.popover.shown;
|
||||
return this.$.popover && this.$.popover.shown;
|
||||
}
|
||||
|
||||
set shown(value) {
|
||||
|
@ -39,43 +49,47 @@ export default class DropDown extends Component {
|
|||
}
|
||||
|
||||
set search(value) {
|
||||
value = value == '' || value == null ? null : value;
|
||||
if (value === this._search && this.$.model.data != null) return;
|
||||
|
||||
let oldValue = this._search;
|
||||
this._search = value;
|
||||
this.$.model.clear();
|
||||
|
||||
if (!this.shown) return;
|
||||
|
||||
value = value == '' || value == null ? null : value;
|
||||
oldValue = oldValue == '' || oldValue == null ? null : oldValue;
|
||||
if (value === oldValue && this.modelData != null) return;
|
||||
|
||||
if (value != null)
|
||||
this._activeOption = 0;
|
||||
|
||||
this.$timeout.cancel(this.searchTimeout);
|
||||
|
||||
if (this.model) {
|
||||
this.model.clear();
|
||||
this.searchTimeout = this.$timeout(() => {
|
||||
this.refreshModel();
|
||||
this.searchTimeout = null;
|
||||
}, 350);
|
||||
}
|
||||
|
||||
this.buildList();
|
||||
}
|
||||
|
||||
get statusText() {
|
||||
let model = this.$.model;
|
||||
let data = model.data;
|
||||
let statusText = null;
|
||||
let model = this.model;
|
||||
let data = this.modelData;
|
||||
|
||||
if (!model)
|
||||
return 'No data';
|
||||
if (model.isLoading || this.searchTimeout)
|
||||
statusText = 'Loading...';
|
||||
else if (data == null)
|
||||
statusText = 'No data';
|
||||
else if (model.moreRows)
|
||||
statusText = 'Load More';
|
||||
else if (data.length === 0)
|
||||
statusText = 'No results found';
|
||||
return 'Loading...';
|
||||
if (data == null)
|
||||
return 'No data';
|
||||
if (model.moreRows)
|
||||
return 'Load More';
|
||||
if (data.length === 0)
|
||||
return 'No results found';
|
||||
|
||||
return statusText;
|
||||
}
|
||||
|
||||
get model() {
|
||||
return this.$.model;
|
||||
return null;
|
||||
}
|
||||
|
||||
get activeOption() {
|
||||
|
@ -86,14 +100,14 @@ export default class DropDown extends Component {
|
|||
* Shows the drop-down. If a parent is specified it is shown in a visible
|
||||
* relative position to it.
|
||||
*
|
||||
* @param {HTMLElement} parent The parent element to show drop down relative to
|
||||
* @param {String} search The initial search term or %null
|
||||
*/
|
||||
show(search) {
|
||||
show(parent, search) {
|
||||
this._activeOption = -1;
|
||||
this.search = search;
|
||||
this.buildList();
|
||||
this.$.popover.parent = this.parent;
|
||||
this.$.popover.show();
|
||||
this.$.popover.show(parent || this.parent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,7 +149,7 @@ export default class DropDown extends Component {
|
|||
if (this.activeLi)
|
||||
this.activeLi.className = '';
|
||||
|
||||
let data = this.$.model.data;
|
||||
let data = this.modelData;
|
||||
|
||||
if (option >= 0 && data && option < data.length) {
|
||||
this.activeLi = this.ul.children[option];
|
||||
|
@ -149,7 +163,7 @@ export default class DropDown extends Component {
|
|||
* @param {Number} option The option index
|
||||
*/
|
||||
selectOption(option) {
|
||||
let data = this.$.model.data;
|
||||
let data = this.modelData;
|
||||
let item = option != -1 && data ? data[option] : null;
|
||||
|
||||
if (item) {
|
||||
|
@ -168,47 +182,13 @@ export default class DropDown extends Component {
|
|||
this.field = value;
|
||||
}
|
||||
|
||||
if (this.onSelect)
|
||||
this.onSelect({value: value});
|
||||
this.emit('select', {value: value});
|
||||
}
|
||||
|
||||
if (!this.multiple)
|
||||
this.$.popover.hide();
|
||||
}
|
||||
|
||||
refreshModel() {
|
||||
this.$.model.filter = {
|
||||
fields: this.selectFields,
|
||||
where: this.getWhere(this._search),
|
||||
order: this.getOrder(),
|
||||
limit: this.limit || 8
|
||||
};
|
||||
this.$.model.refresh(this._search);
|
||||
}
|
||||
|
||||
getWhere(search) {
|
||||
if (search == '' || search == null)
|
||||
return undefined;
|
||||
|
||||
if (this.where) {
|
||||
let jsonFilter = this.where.replace(/search/g, search);
|
||||
return this.$.$eval(jsonFilter);
|
||||
}
|
||||
|
||||
let where = {};
|
||||
where[this.showField] = {like: `%${search}%`};
|
||||
return where;
|
||||
}
|
||||
|
||||
getOrder() {
|
||||
if (this.order)
|
||||
return this.order;
|
||||
else if (this.showField)
|
||||
return `${this.showField} ASC`;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
this.document.addEventListener('keydown', this.docKeyDownHandler);
|
||||
this.list.scrollTop = 0;
|
||||
|
@ -227,15 +207,15 @@ export default class DropDown extends Component {
|
|||
let list = this.list;
|
||||
let shouldLoad =
|
||||
list.scrollTop + list.clientHeight >= (list.scrollHeight - 40)
|
||||
&& !this.$.model.isLoading;
|
||||
&& !this.model.isLoading;
|
||||
|
||||
if (shouldLoad)
|
||||
this.$.model.loadMore();
|
||||
this.model.loadMore();
|
||||
}
|
||||
|
||||
onLoadMoreClick(event) {
|
||||
event.preventDefault();
|
||||
this.$.model.loadMore();
|
||||
this.model.loadMore();
|
||||
}
|
||||
|
||||
onContainerClick(event) {
|
||||
|
@ -244,14 +224,10 @@ export default class DropDown extends Component {
|
|||
if (index != -1) this.selectOption(index);
|
||||
}
|
||||
|
||||
onModelDataChange() {
|
||||
this.buildList();
|
||||
}
|
||||
|
||||
onDocKeyDown(event) {
|
||||
if (event.defaultPrevented) return;
|
||||
|
||||
let data = this.$.model.data;
|
||||
let data = this.modelData;
|
||||
let option = this.activeOption;
|
||||
let nOpts = data ? data.length - 1 : 0;
|
||||
|
||||
|
@ -283,11 +259,12 @@ export default class DropDown extends Component {
|
|||
}
|
||||
|
||||
buildList() {
|
||||
if (!this.shown) return;
|
||||
this.destroyList();
|
||||
|
||||
let hasTemplate = this.$transclude && this.$transclude.isSlotFilled('tplItem');
|
||||
let fragment = this.document.createDocumentFragment();
|
||||
let data = this.$.model.data;
|
||||
let data = this.modelData;
|
||||
|
||||
if (data) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
|
@ -339,11 +316,109 @@ export default class DropDown extends Component {
|
|||
this.scopes = [];
|
||||
}
|
||||
|
||||
getFields() {
|
||||
let fields = [];
|
||||
fields.push(this.valueField);
|
||||
fields.push(this.showField);
|
||||
|
||||
if (this.fields)
|
||||
for (let field of this.fields)
|
||||
fields.push(field);
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
$onDestroy() {
|
||||
this.destroyList();
|
||||
}
|
||||
|
||||
// Model related code
|
||||
|
||||
onDataChange() {
|
||||
if (this.model.orgData)
|
||||
this.emit('dataReady');
|
||||
this.buildList();
|
||||
}
|
||||
|
||||
get modelData() {
|
||||
return this._model ? this._model.data : null;
|
||||
}
|
||||
|
||||
get model() {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
set model(value) {
|
||||
this.linkEvents({_model: value}, {dataChange: this.onDataChange});
|
||||
this.onDataChange();
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
set url(value) {
|
||||
this._url = value;
|
||||
if (value) {
|
||||
this.model = new CrudModel(this.$q, this.$http);
|
||||
this.model.autoLoad = false;
|
||||
this.model.url = value;
|
||||
}
|
||||
}
|
||||
|
||||
get data() {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
set data(value) {
|
||||
this._data = value;
|
||||
if (value) {
|
||||
this.model = new ArrayModel(this.$q, this.$filter);
|
||||
this.model.autoLoad = false;
|
||||
this.model.orgData = value;
|
||||
}
|
||||
}
|
||||
|
||||
refreshModel() {
|
||||
let model = this.model;
|
||||
|
||||
let order;
|
||||
if (this.order)
|
||||
order = this.order;
|
||||
else if (this.showField)
|
||||
order = `${this.showField} ASC`;
|
||||
|
||||
let filter = {
|
||||
order,
|
||||
limit: this.limit || 8
|
||||
};
|
||||
|
||||
if (model instanceof CrudModel) {
|
||||
let searchExpr = this._search == null
|
||||
? null
|
||||
: this.searchFunction({$search: this._search});
|
||||
|
||||
Object.assign(filter, {
|
||||
fields: this.getFields(),
|
||||
include: this.include,
|
||||
where: mergeWhere(this.where, searchExpr)
|
||||
});
|
||||
} else if (model instanceof ArrayModel) {
|
||||
if (this._search != null)
|
||||
filter.where = this.searchFunction({$search: this._search});
|
||||
}
|
||||
|
||||
return this.model.applyFilter(filter);
|
||||
}
|
||||
|
||||
searchFunction(scope) {
|
||||
if (this.model instanceof CrudModel)
|
||||
return {[this.showField]: {like: `%${scope.$search}%`}};
|
||||
if (this.model instanceof ArrayModel)
|
||||
return {[this.showField]: scope.$search};
|
||||
}
|
||||
}
|
||||
DropDown.$inject = ['$element', '$scope', '$transclude', '$timeout', '$translate'];
|
||||
DropDown.$inject = ['$element', '$scope', '$transclude', '$timeout', '$translate', '$http', '$q', '$filter'];
|
||||
|
||||
/**
|
||||
* Gets the position of an event element relative to a parent.
|
||||
|
@ -374,15 +449,21 @@ ngModule.component('vnDropDown', {
|
|||
controller: DropDown,
|
||||
bindings: {
|
||||
field: '=?',
|
||||
data: '<?',
|
||||
selection: '=?',
|
||||
search: '<?',
|
||||
limit: '<?',
|
||||
showFilter: '<?',
|
||||
parent: '<?',
|
||||
multiple: '<?',
|
||||
onSelect: '&?',
|
||||
translateFields: '<?'
|
||||
translateFields: '<?',
|
||||
data: '<?',
|
||||
url: '@?',
|
||||
fields: '<?',
|
||||
include: '<?',
|
||||
where: '<?',
|
||||
order: '@?',
|
||||
limit: '<?',
|
||||
searchFunction: '&?'
|
||||
},
|
||||
transclude: {
|
||||
tplItem: '?tplItem'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import ngModule from '../../module';
|
||||
import Input from '../../lib/input';
|
||||
import asignProps from '../../lib/asign-props';
|
||||
import assignProps from '../../lib/assign-props';
|
||||
import './style.scss';
|
||||
|
||||
export default class IconMenu extends Input {
|
||||
|
@ -8,20 +8,6 @@ export default class IconMenu extends Input {
|
|||
super($element, $scope);
|
||||
this.$transclude = $transclude;
|
||||
this.input = this.element.querySelector('.mdl-button');
|
||||
this.valueField = 'id';
|
||||
this.showField = 'name';
|
||||
}
|
||||
|
||||
getFields() {
|
||||
let fields = [];
|
||||
fields.push(this.valueField);
|
||||
fields.push(this.showField);
|
||||
|
||||
if (this.selectFields)
|
||||
for (let field of this.selectFields)
|
||||
fields.push(field);
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
|
@ -38,26 +24,24 @@ export default class IconMenu extends Input {
|
|||
}
|
||||
|
||||
showDropDown() {
|
||||
Object.assign(this.$.dropDown.$.model, {
|
||||
url: this.url,
|
||||
staticData: this.data
|
||||
});
|
||||
|
||||
asignProps(this, this.$.dropDown, [
|
||||
assignProps(this, this.$.dropDown, [
|
||||
'valueField',
|
||||
'showField',
|
||||
'where',
|
||||
'order',
|
||||
'showFilter',
|
||||
'multiple',
|
||||
'limit',
|
||||
'$transclude',
|
||||
'translateFields'
|
||||
'translateFields',
|
||||
'model',
|
||||
'data',
|
||||
'url',
|
||||
'fields',
|
||||
'include',
|
||||
'where',
|
||||
'order',
|
||||
'limit',
|
||||
'searchFunction'
|
||||
]);
|
||||
|
||||
this.$.dropDown.selectFields = this.getFields();
|
||||
this.$.dropDown.parent = this.input;
|
||||
this.$.dropDown.show();
|
||||
this.$.dropDown.show(this.input);
|
||||
}
|
||||
}
|
||||
IconMenu.$inject = ['$element', '$scope', '$transclude'];
|
||||
|
@ -65,20 +49,20 @@ IconMenu.$inject = ['$element', '$scope', '$transclude'];
|
|||
ngModule.component('vnIconMenu', {
|
||||
template: require('./icon-menu.html'),
|
||||
bindings: {
|
||||
url: '@?',
|
||||
data: '<?',
|
||||
label: '@',
|
||||
showField: '@?',
|
||||
selection: '<?',
|
||||
valueField: '@?',
|
||||
selectFields: '<?',
|
||||
disabled: '<?',
|
||||
initialData: '<?',
|
||||
showFilter: '<?',
|
||||
field: '=?',
|
||||
url: '@?',
|
||||
data: '<?',
|
||||
where: '@?',
|
||||
order: '@?',
|
||||
label: '@',
|
||||
initialData: '<?',
|
||||
field: '=?',
|
||||
limit: '<?',
|
||||
showFilter: '<?',
|
||||
selection: '<?',
|
||||
multiple: '<?',
|
||||
onChange: '&?',
|
||||
icon: '@?',
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import './model-proxy/model-proxy';
|
||||
import './rest-model/crud-model';
|
||||
import './rest-model/rest-model';
|
||||
import './crud-model/crud-model';
|
||||
import './watcher/watcher';
|
||||
import './textfield/textfield';
|
||||
import './icon/icon';
|
||||
|
|
|
@ -1,8 +1,66 @@
|
|||
import ngModule from '../../module';
|
||||
import EventEmitter from '../../lib/event-emitter';
|
||||
|
||||
export default class ModelProxy {
|
||||
/**
|
||||
* A filtrable model.
|
||||
*
|
||||
* @property {Function} filter The filter function
|
||||
*/
|
||||
export class Filtrable {
|
||||
applyFilter(userFilter, userParams) {}
|
||||
addFilter(userFilter, userParams) {}
|
||||
removeFilter() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sortable model.
|
||||
*
|
||||
* @property {String|Array<String>|Function} order The sort specification
|
||||
*/
|
||||
export class Sortable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginable model.
|
||||
*
|
||||
* @property {Number} limit The page size
|
||||
*/
|
||||
export class Paginable {
|
||||
get isLoading() {}
|
||||
get moreRows() {}
|
||||
loadMore() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data model.
|
||||
*
|
||||
* @event dataChange Emitted when data property changes
|
||||
*/
|
||||
export class DataModel extends EventEmitter {
|
||||
get data() {}
|
||||
refresh() {}
|
||||
clear() {}
|
||||
}
|
||||
|
||||
ngModule.component('vnDataModel', {
|
||||
controller: DataModel,
|
||||
bindings: {
|
||||
data: '=?',
|
||||
autoLoad: '<?',
|
||||
autoSync: '<?',
|
||||
order: '@?',
|
||||
limit: '<?'
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* A data model that monitorizes changes to the data.
|
||||
*
|
||||
* @event dataChange Emitted when data property changes
|
||||
*/
|
||||
export default class ModelProxy extends DataModel {
|
||||
constructor() {
|
||||
this._data = [];
|
||||
super();
|
||||
this.resetChanges();
|
||||
}
|
||||
|
||||
|
@ -12,18 +70,16 @@ export default class ModelProxy {
|
|||
|
||||
set orgData(value) {
|
||||
this._orgData = value;
|
||||
// this._data.splice(0, this._data.length);
|
||||
|
||||
if (this.Row) {
|
||||
this._data = [];
|
||||
if (value) {
|
||||
this.proxiedData = new Array(value.length);
|
||||
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
let row = new this.Row(value[i], i);
|
||||
this._data.push(row);
|
||||
}
|
||||
for (let i = 0; i < value.length; i++)
|
||||
this.proxiedData[i] = this.createRow(Object.assign({}, value[i]), i);
|
||||
} else
|
||||
this._data = value;
|
||||
this.proxiedData = null;
|
||||
|
||||
this.data = null;
|
||||
this.resetChanges();
|
||||
}
|
||||
|
||||
|
@ -31,95 +87,122 @@ export default class ModelProxy {
|
|||
return this._data;
|
||||
}
|
||||
|
||||
set data(value) {}
|
||||
|
||||
get fields() {
|
||||
return this._fields;
|
||||
}
|
||||
|
||||
set fields(value) {
|
||||
this._fields = value;
|
||||
|
||||
let Row = function(data, index) {
|
||||
this.$data = data;
|
||||
this.$index = index;
|
||||
this.$oldData = null;
|
||||
this.$isNew = false;
|
||||
};
|
||||
|
||||
for (let prop of value) {
|
||||
Object.defineProperty(Row.prototype, prop, {
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
set: function(value) {
|
||||
if (!this.$isNew) {
|
||||
if (!this.$oldData)
|
||||
this.$oldData = {};
|
||||
if (!this.$oldData[prop])
|
||||
this.$oldData[prop] = this.$data[prop];
|
||||
}
|
||||
|
||||
this.$data[prop] = value;
|
||||
},
|
||||
get: function() {
|
||||
return this.$data[prop];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.Row = Row;
|
||||
set data(value) {
|
||||
this._data = value;
|
||||
this.emit('dataChange');
|
||||
}
|
||||
|
||||
remove(index) {
|
||||
let data = this._data;
|
||||
let [item] = this.data.splice(index, 1);
|
||||
|
||||
let item;
|
||||
[item] = data.splice(index, 1);
|
||||
|
||||
for (let i = index; i < data.length; i++)
|
||||
data[i].$index = i;
|
||||
let proxiedIndex = this.proxiedData.indexOf(item);
|
||||
this.proxiedData.splice(proxiedIndex, 1);
|
||||
|
||||
if (!item.$isNew)
|
||||
this.removed.push(item);
|
||||
|
||||
this.isChanged = true;
|
||||
this.emit('rowRemove', index);
|
||||
}
|
||||
|
||||
insert(data) {
|
||||
data = Object.assign(data || {}, this.link);
|
||||
let newRow = new this.Row(data, this._data.length);
|
||||
let newRow = this.createRow(data, null);
|
||||
newRow.$isNew = true;
|
||||
return this._data.push(newRow);
|
||||
let index = this.proxiedData.push(newRow) - 1;
|
||||
|
||||
if (this.data)
|
||||
this.data.push(newRow);
|
||||
|
||||
this.isChanged = true;
|
||||
this.emit('rowInsert', index);
|
||||
return index;
|
||||
}
|
||||
|
||||
createRow(obj, index) {
|
||||
let proxy = new Proxy(obj, {
|
||||
set: (obj, prop, value) => {
|
||||
if (prop.charAt(0) !== '$' && value !== obj[prop] && !obj.$isNew) {
|
||||
if (!obj.$oldData)
|
||||
obj.$oldData = {};
|
||||
if (!obj.$oldData[prop])
|
||||
obj.$oldData[prop] = value;
|
||||
this.isChanged = true;
|
||||
}
|
||||
return Reflect.set(obj, prop, value);
|
||||
}
|
||||
});
|
||||
Object.assign(proxy, {
|
||||
$orgIndex: index,
|
||||
$oldData: null,
|
||||
$isNew: false
|
||||
});
|
||||
return proxy;
|
||||
}
|
||||
|
||||
resetChanges() {
|
||||
this.removed = [];
|
||||
this.isChanged = false;
|
||||
|
||||
for (let row of this._data) {
|
||||
let data = this.proxiedData;
|
||||
if (data)
|
||||
for (let row of data)
|
||||
row.$oldData = null;
|
||||
row.$isNew = false;
|
||||
}
|
||||
|
||||
applyChanges() {
|
||||
let data = this.proxiedData;
|
||||
let orgData = this.orgData;
|
||||
if (!data) return;
|
||||
|
||||
for (let row of this.removed) {
|
||||
if (row.$isNew) {
|
||||
let data = {};
|
||||
for (let prop in row)
|
||||
if (prop.charAt(0) !== '$')
|
||||
data[prop] = row[prop];
|
||||
row.$orgIndex = orgData.push(data) - 1;
|
||||
row.$isNew = false;
|
||||
} else if (row.$oldData)
|
||||
for (let prop in row.$oldData)
|
||||
orgData[row.$orgIndex][prop] = row[prop];
|
||||
}
|
||||
|
||||
let removed = this.removed;
|
||||
|
||||
if (removed) {
|
||||
removed = removed.sort((a, b) => b.$orgIndex - a.$orgIndex);
|
||||
|
||||
for (let row of this.removed)
|
||||
orgData.splice(row.$orgIndex, 1);
|
||||
}
|
||||
|
||||
this.resetChanges();
|
||||
}
|
||||
|
||||
undoChanges() {
|
||||
let data = this._data;
|
||||
let data = this.proxiedData;
|
||||
if (!data) return;
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let row = data[i];
|
||||
|
||||
if (row.$oldData)
|
||||
Object.assign(row.$data, row.$oldData);
|
||||
Object.assign(row, row.$oldData);
|
||||
if (row.$isNew)
|
||||
data.splice(i--, 1);
|
||||
}
|
||||
|
||||
for (let row of this.removed)
|
||||
data.splice(row.$index, 0, row);
|
||||
let removed = this.removed;
|
||||
|
||||
this.resetChanges();
|
||||
if (removed) {
|
||||
removed = removed.sort((a, b) => a.$orgIndex - b.$orgIndex);
|
||||
|
||||
for (let row of this.removed)
|
||||
data.splice(row.$orgIndex, 0, row);
|
||||
}
|
||||
|
||||
dataChanged() {
|
||||
if (this.onDataChange)
|
||||
this.onDataChange();
|
||||
this.resetChanges();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,9 +210,6 @@ ngModule.component('vnModelProxy', {
|
|||
controller: ModelProxy,
|
||||
bindings: {
|
||||
orgData: '<?',
|
||||
data: '=?',
|
||||
fields: '<?',
|
||||
link: '<?',
|
||||
onDataChange: '&?'
|
||||
data: '=?'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,6 +4,11 @@ import './style.scss';
|
|||
|
||||
/**
|
||||
* A simple popover.
|
||||
*
|
||||
* @property {HTMLElement} parent The parent element to show drop down relative to
|
||||
*
|
||||
* @event open Thrown when popover is displayed
|
||||
* @event close Thrown when popover is hidden
|
||||
*/
|
||||
export default class Popover extends Component {
|
||||
constructor($element, $scope, $timeout, $transitions) {
|
||||
|
@ -61,10 +66,13 @@ export default class Popover extends Component {
|
|||
/**
|
||||
* Shows the popover. If a parent is specified it is shown in a visible
|
||||
* relative position to it.
|
||||
*
|
||||
* @param {HTMLElement} parent Overrides the parent property
|
||||
*/
|
||||
show() {
|
||||
show(parent) {
|
||||
if (this._shown) return;
|
||||
|
||||
if (parent) this.parent = parent;
|
||||
this._shown = true;
|
||||
this.element.style.display = 'block';
|
||||
this.$timeout.cancel(this.showTimeout);
|
||||
|
@ -78,9 +86,7 @@ export default class Popover extends Component {
|
|||
|
||||
this.deregisterCallback = this.$transitions.onStart({}, () => this.hide());
|
||||
this.relocate();
|
||||
|
||||
if (this.onOpen)
|
||||
this.onOpen();
|
||||
this.emit('open');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,9 +101,7 @@ export default class Popover extends Component {
|
|||
this.showTimeout = this.$timeout(() => {
|
||||
this.element.style.display = 'none';
|
||||
this.showTimeout = null;
|
||||
|
||||
if (this.onClose)
|
||||
this.onClose();
|
||||
this.emit('close');
|
||||
}, 250);
|
||||
|
||||
this.document.removeEventListener('keydown', this.docKeyDownHandler);
|
||||
|
@ -187,9 +191,5 @@ Popover.$inject = ['$element', '$scope', '$timeout', '$transitions'];
|
|||
ngModule.component('vnPopover', {
|
||||
template: require('./popover.html'),
|
||||
controller: Popover,
|
||||
transclude: true,
|
||||
bindings: {
|
||||
onOpen: '&?',
|
||||
onClose: '&?'
|
||||
}
|
||||
transclude: true
|
||||
});
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
import ngModule from '../../module';
|
||||
|
||||
export default class RestModel {
|
||||
constructor($http, $q, $filter) {
|
||||
this.$http = $http;
|
||||
this.$q = $q;
|
||||
this.$filter = $filter;
|
||||
this.filter = null;
|
||||
this.clear();
|
||||
}
|
||||
|
||||
set staticData(value) {
|
||||
this._staticData = value;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
get staticData() {
|
||||
return this._staticData;
|
||||
}
|
||||
|
||||
get isLoading() {
|
||||
return this.canceler != null;
|
||||
}
|
||||
|
||||
set url(url) {
|
||||
if (this._url != url) {
|
||||
this._url = url;
|
||||
this.clear();
|
||||
}
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
if (this.moreRows) {
|
||||
let filter = Object.assign({}, this.myFilter);
|
||||
filter.skip += filter.limit;
|
||||
this.sendRequest(filter, true);
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.cancelRequest();
|
||||
this.data = null;
|
||||
this.moreRows = false;
|
||||
this.dataChanged();
|
||||
}
|
||||
|
||||
refresh(search) {
|
||||
if (this.url) {
|
||||
let filter = Object.assign({}, this.filter);
|
||||
|
||||
if (filter.limit)
|
||||
filter.skip = 0;
|
||||
|
||||
this.clear();
|
||||
this.sendRequest(filter);
|
||||
} else if (this._staticData) {
|
||||
if (search)
|
||||
this.data = this.$filter('filter')(this._staticData, search);
|
||||
else
|
||||
this.data = this._staticData;
|
||||
this.dataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
cancelRequest() {
|
||||
if (this.canceler) {
|
||||
this.canceler.resolve();
|
||||
this.canceler = null;
|
||||
this.request = null;
|
||||
}
|
||||
}
|
||||
|
||||
sendRequest(filter, append) {
|
||||
this.cancelRequest();
|
||||
this.canceler = this.$q.defer();
|
||||
let options = {timeout: this.canceler.promise};
|
||||
let json = encodeURIComponent(JSON.stringify(filter));
|
||||
this.request = this.$http.get(`${this.url}?filter=${json}`, options).then(
|
||||
json => this.onRemoteDone(json, filter, append),
|
||||
json => this.onRemoteError(json)
|
||||
);
|
||||
}
|
||||
|
||||
onRemoteDone(json, filter, append) {
|
||||
let data = json.data;
|
||||
|
||||
if (append)
|
||||
this.data = this.data.concat(data);
|
||||
else
|
||||
this.data = data;
|
||||
|
||||
this.myFilter = filter;
|
||||
this.moreRows = filter.limit && data.length == filter.limit;
|
||||
this.onRequestEnd();
|
||||
this.dataChanged();
|
||||
}
|
||||
|
||||
onRemoteError(json) {
|
||||
this.onRequestEnd();
|
||||
}
|
||||
|
||||
onRequestEnd() {
|
||||
this.request = null;
|
||||
this.canceler = null;
|
||||
}
|
||||
|
||||
dataChanged() {
|
||||
if (this.onDataChange)
|
||||
this.onDataChange();
|
||||
}
|
||||
}
|
||||
RestModel.$inject = ['$http', '$q', '$filter'];
|
||||
|
||||
ngModule.component('vnRestModel', {
|
||||
controller: RestModel,
|
||||
bindings: {
|
||||
url: '@?',
|
||||
staticData: '<?',
|
||||
data: '=?',
|
||||
limit: '<?',
|
||||
onDataChange: '&?'
|
||||
}
|
||||
});
|
|
@ -1,34 +0,0 @@
|
|||
import './rest-model.js';
|
||||
|
||||
describe('Component vnRestModel', () => {
|
||||
let $componentController;
|
||||
let $httpBackend;
|
||||
let controller;
|
||||
|
||||
beforeEach(() => {
|
||||
angular.mock.module('client');
|
||||
});
|
||||
|
||||
beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => {
|
||||
$componentController = _$componentController_;
|
||||
controller = $componentController('vnRestModel', {$httpBackend});
|
||||
}));
|
||||
|
||||
describe('set url', () => {
|
||||
it(`should call clear function when the controller _url is undefined`, () => {
|
||||
spyOn(controller, 'clear');
|
||||
controller.url = 'localhost';
|
||||
|
||||
expect(controller._url).toEqual('localhost');
|
||||
expect(controller.clear).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it(`should do nothing when the url is matching`, () => {
|
||||
controller._url = 'localhost';
|
||||
spyOn(controller, 'clear');
|
||||
controller.url = 'localhost';
|
||||
|
||||
expect(controller.clear).not.toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,7 +1,22 @@
|
|||
import EventEmitter from './event-emitter';
|
||||
|
||||
/**
|
||||
* Base class for component controllers.
|
||||
*/
|
||||
export default class Component {
|
||||
export default class Component extends EventEmitter {
|
||||
/**
|
||||
* Contructor.
|
||||
*
|
||||
* @param {HTMLElement} $element The main component element
|
||||
* @param {$rootScope.Scope} $scope The element scope
|
||||
*/
|
||||
constructor($element, $scope) {
|
||||
super($element, $scope);
|
||||
this.element = $element[0];
|
||||
this.element.$ctrl = this;
|
||||
this.$element = $element;
|
||||
this.$ = $scope;
|
||||
}
|
||||
/**
|
||||
* The component owner window.
|
||||
*/
|
||||
|
@ -14,17 +29,5 @@ export default class Component {
|
|||
get document() {
|
||||
return this.element.ownerDocument;
|
||||
}
|
||||
/**
|
||||
* Contructor.
|
||||
*
|
||||
* @param {HTMLElement} $element The main component element
|
||||
* @param {$rootScope.Scope} $scope The element scope
|
||||
*/
|
||||
constructor($element, $scope) {
|
||||
this.element = $element[0];
|
||||
this.element.$ctrl = this;
|
||||
this.$element = $element;
|
||||
this.$ = $scope;
|
||||
}
|
||||
}
|
||||
Component.$inject = ['$element', '$scope'];
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import {kebabToCamel} from './string';
|
||||
|
||||
export default class EventEmitter {
|
||||
constructor($element, $scope) {
|
||||
if (!$element) return;
|
||||
let attrs = $element[0].attributes;
|
||||
for (let attr of attrs) {
|
||||
if (attr.name.substr(0, 2) !== 'on') continue;
|
||||
let eventName = kebabToCamel(attr.name.substr(3));
|
||||
let callback = locals => $scope.$parent.$eval(attr.nodeValue, locals);
|
||||
this.on(eventName, callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to an object event.
|
||||
*
|
||||
* @param {String} eventName The event name
|
||||
* @param {Function} callback The callback function
|
||||
* @param {Object} thisArg The scope for the callback or %null
|
||||
*/
|
||||
on(eventName, callback, thisArg) {
|
||||
if (!this.$events)
|
||||
this.$events = {};
|
||||
if (!this.$events[eventName])
|
||||
this.$events[eventName] = [];
|
||||
this.$events[eventName].push({callback, thisArg});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects all handlers for callback.
|
||||
*
|
||||
* @param {Function} callback The callback function
|
||||
*/
|
||||
off(callback) {
|
||||
if (!this.$events) return;
|
||||
for (let event in this.$events)
|
||||
for (let i = 0; i < event.length; i++)
|
||||
if (event[i].callback === callback)
|
||||
event.splice(i--, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects all instance callbacks.
|
||||
*
|
||||
* @param {Object} thisArg The callbacks instance
|
||||
*/
|
||||
disconnect(thisArg) {
|
||||
if (!this.$events) return;
|
||||
for (let event in this.$events)
|
||||
for (let i = 0; i < event.length; i++)
|
||||
if (event[i].thisArg === thisArg)
|
||||
event.splice(i--, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event.
|
||||
*
|
||||
* @param {String} eventName The event name
|
||||
* @param {...*} args Arguments to pass to the callbacks
|
||||
*/
|
||||
emit(eventName) {
|
||||
if (!this.$events || !this.$events[eventName])
|
||||
return;
|
||||
|
||||
let args = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
let callbacks = this.$events[eventName];
|
||||
for (let callback of callbacks)
|
||||
callback.callback.apply(callback.thisArg, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Links the object with another object events.
|
||||
*
|
||||
* @param {Object} propValue The property and the new value
|
||||
* @param {Object} handlers The event handlers
|
||||
*/
|
||||
linkEvents(propValue, handlers) {
|
||||
for (let prop in propValue) {
|
||||
let value = propValue[prop];
|
||||
if (this[prop])
|
||||
this[prop].disconnect(this);
|
||||
this[prop] = value;
|
||||
if (value)
|
||||
for (let event in handlers)
|
||||
value.on(event, handlers[event], this);
|
||||
}
|
||||
}
|
||||
}
|
||||
EventEmitter.$inject = ['$element', '$scope'];
|
|
@ -16,10 +16,10 @@
|
|||
<vn-autocomplete vn-one
|
||||
url="/item/api/ItemTypes"
|
||||
label="Type"
|
||||
select-fields=["code","name"]
|
||||
fields="['code', 'name']"
|
||||
value-field="id"
|
||||
field="$ctrl.item.typeFk"
|
||||
where="{or: [{code: {regexp: 'search'}}, {name: {regexp: 'search'}}]}">
|
||||
search-function="{or: [{code: {regexp: $search}}, {name: {regexp: $search}}]}">
|
||||
<tpl-item style="display: flex;">
|
||||
<div style="width: 3em; padding-right: 1em;">{{::code}}</div>
|
||||
<div>{{::name}}</div>
|
||||
|
@ -31,7 +31,7 @@
|
|||
show-field="description"
|
||||
value-field="id"
|
||||
field="$ctrl.item.intrastatFk"
|
||||
where="{or: [{id: {regexp: 'search'}}, {description: {regexp: 'search'}}]}">
|
||||
search-function="{or: [{id: {regexp: $search}}, {description: {regexp: $search}}]}">
|
||||
<tpl-item style="display: flex;">
|
||||
<div style="width: 6em; text-align: right; padding-right: 1em;">{{::id}}</div>
|
||||
<div>{{::description}}</div>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
show-field="description"
|
||||
value-field="id"
|
||||
field="$ctrl.item.intrastatFk"
|
||||
where="{or: [{id: {regexp: 'search'}}, {description: {regexp: 'search'}}]}"
|
||||
search-function="{or: [{id: {regexp: $search}}, {description: {regexp: $search}}]}"
|
||||
initial-data="$ctrl.item.intrastat">
|
||||
<tpl-item style="display: flex;">
|
||||
<div style="width: 6em; text-align: right; padding-right: 1em;">{{::id}}</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
url="/item/api/ItemTags"
|
||||
fields="['id', 'itemFk', 'tagFk', 'value', 'priority']"
|
||||
link="{itemFk: $ctrl.$stateParams.id}"
|
||||
filter="{include: {relation: 'tag'}}"
|
||||
include="$ctrl.include"
|
||||
order="priority ASC"
|
||||
data="itemTags">
|
||||
</vn-crud-model>
|
||||
|
@ -17,17 +17,16 @@
|
|||
<vn-title>Tags</vn-title>
|
||||
<vn-horizontal ng-repeat="itemTag in itemTags">
|
||||
<vn-autocomplete
|
||||
vn-id="tag"
|
||||
vn-one
|
||||
field="itemTag.tagFk"
|
||||
url="/item/api/Tags"
|
||||
select-fields="['id','name','isFree']"
|
||||
show-field="name"
|
||||
vn-id="tag"
|
||||
label="Tag"
|
||||
on-change="itemTag.value = null"
|
||||
initial-data="itemTag.tag"
|
||||
field="itemTag.tagFk"
|
||||
show-field="name"
|
||||
url="/item/api/Tags"
|
||||
fields="$ctrl.include.scope.fields"
|
||||
vn-acl="buyer"
|
||||
vn-focus
|
||||
disabled="itemTag.id != null">
|
||||
vn-focus>
|
||||
</vn-autocomplete>
|
||||
<vn-textfield
|
||||
ng-show="tag.selection.isFree || tag.selection.isFree == undefined"
|
||||
|
|
|
@ -4,6 +4,12 @@ class Controller {
|
|||
constructor($stateParams, $scope) {
|
||||
this.$stateParams = $stateParams;
|
||||
this.$scope = $scope;
|
||||
this.include = {
|
||||
relation: 'tag',
|
||||
scope: {
|
||||
fields: ['id', 'name', 'isFree']
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
add() {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<vn-autocomplete
|
||||
disabled="!$ctrl.clientFk"
|
||||
url="{{ $ctrl.clientFk ? '/api/Clients/'+ $ctrl.clientFk +'/addresses' : null }}"
|
||||
select-fields=["nickname","street","city"]
|
||||
fields="['nickname', 'street', 'city']"
|
||||
field="$ctrl.addressFk"
|
||||
show-field="nickname"
|
||||
value-field="id"
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
label="Package"
|
||||
show-field="name"
|
||||
value-field="packagingFk"
|
||||
where="{or: [{'itemFk': {like: '%search%'}}, {'name': {like: '%search%'}}]}"
|
||||
search-function="{or: [{itemFk: {like: '%'+ $search +'%'}}, {'name': {like: '%'+ $search +'%'}}]}"
|
||||
field="package.packagingFk">
|
||||
<tpl-item>{{itemFk}} : {{name}}</tpl-item>
|
||||
</vn-autocomplete>
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
|
||||
/**
|
||||
* Passes a loopback fields filter to an object.
|
||||
*
|
||||
* @param {Object} fields The fields object or array
|
||||
* @return {Object} The fields as object
|
||||
*/
|
||||
function fieldsToObject(fields) {
|
||||
let fieldsObj = {};
|
||||
|
||||
if (Array.isArray(fields))
|
||||
for (let field of fields)
|
||||
fieldsObj[field] = true;
|
||||
else if (typeof fields == 'object')
|
||||
for (let field in fields)
|
||||
if (fields[field])
|
||||
fieldsObj[field] = true;
|
||||
|
||||
return fieldsObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two loopback fields filters.
|
||||
*
|
||||
* @param {Object|Array} src The source fields
|
||||
* @param {Object|Array} dst The destination fields
|
||||
* @return {Array} The merged fields as an array
|
||||
*/
|
||||
function mergeFields(src, dst) {
|
||||
let fields = {};
|
||||
Object.assign(fields,
|
||||
fieldsToObject(src),
|
||||
fieldsToObject(dst)
|
||||
);
|
||||
return Object.keys(fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two loopback where filters.
|
||||
*
|
||||
* @param {Object|Array} src The source where
|
||||
* @param {Object|Array} dst The destination where
|
||||
* @return {Array} The merged wheres
|
||||
*/
|
||||
function mergeWhere(src, dst) {
|
||||
let and = [];
|
||||
if (src) and.push(src);
|
||||
if (dst) and.push(dst);
|
||||
|
||||
switch (and.length) {
|
||||
case 0:
|
||||
return undefined;
|
||||
case 1:
|
||||
return and[0];
|
||||
default:
|
||||
return {and};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two loopback filters returning the merged filter.
|
||||
*
|
||||
* @param {Object} src The source filter
|
||||
* @param {Object} dst The destination filter
|
||||
* @return {Object} The result filter
|
||||
*/
|
||||
function mergeFilters(src, dst) {
|
||||
let res = Object.assign({}, dst);
|
||||
|
||||
if (!src)
|
||||
return res;
|
||||
|
||||
if (src.fields)
|
||||
res.fields = mergeFields(src.fields, res.fields);
|
||||
if (src.where)
|
||||
res.where = mergeWhere(res.where, src.where);
|
||||
if (src.include)
|
||||
res.include = src.include;
|
||||
if (src.order)
|
||||
res.order = src.order;
|
||||
if (src.limit)
|
||||
res.limit = src.limit;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fieldsToObject: fieldsToObject,
|
||||
mergeFields: mergeFields,
|
||||
mergeWhere: mergeWhere,
|
||||
mergeFilters: mergeFilters
|
||||
};
|
Loading…
Reference in New Issue