This commit is contained in:
Vicente Falco 2017-05-31 12:57:18 +02:00
parent cadbb2b6c2
commit fae56408bb
55 changed files with 4 additions and 1865 deletions

View File

@ -1,3 +1,3 @@
import './ngModule';
import './config';
import './login/index';
import './login/login';

View File

View File

@ -76,6 +76,6 @@ export default class Controller {
Controller.$inject = ['$element', '$scope', '$window', '$http'];
ngModule.component('vnLogin', {
template: require('./index.html'),
template: require('./login.html'),
controller: Controller
});

View File

@ -1,46 +0,0 @@
<vn-watcher
vn-id="watcher"
url="/client/api/Addresses"
id-field="id"
data="$ctrl.address"
form="form">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" pad-medium>
<vn-card >
<vn-vertical pad-large>
<vn-title>Consignatario</vn-title>
<vn-horizontal>
<vn-check vn-one label="Predeterminado" field="$ctrl.address.default"></vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Consignatario" field="$ctrl.address.consignee" vn-focus></vn-textfield>
<vn-textfield vn-one label="Domicilio" field="$ctrl.address.street"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Código Postal" field="$ctrl.address.postcode"></vn-textfield>
<vn-textfield vn-one label="Municipio" field="$ctrl.address.city"></vn-textfield>
<vn-autocomplete vn-one
field="$ctrl.address.provinceFk"
url="/client/api/Provinces"
show-field="name"
value-field="id"
label="Provincia">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
field="$ctrl.address.agencyFk"
url="/client/api/AgencyServices"
show-field="name"
value-field="id"
label="Agencia">
</vn-autocomplete>
<vn-textfield vn-one label="Teléfono" field="$ctrl.address.phone"></vn-textfield>
<vn-textfield vn-one label="Móvil" field="$ctrl.address.mobile"></vn-textfield>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Guardar"></vn-submit>
</vn-button-bar>
</form>

View File

@ -1,25 +0,0 @@
import {module} from '../module';
class Controller {
constructor($scope, $state) {
this.$ = $scope;
this.$state = $state;
this.address = {
clientFk: parseInt($state.params.id),
enabled: true
};
}
onSubmit() {
this.$.watcher.submit().then(
() => this.$state.go('clientCard.addresses')
);
}
}
Controller.$inject = ['$scope', '$state'];
export const NAME = 'vnAddressCreate';
export const COMPONENT = {
template: require('./index.html'),
controller: Controller
};
module.component(NAME, COMPONENT);

View File

@ -1,48 +0,0 @@
<vn-watcher
vn-id="watcher"
get="true"
url="/client/api/Addresses"
id-field="id"
data="addressData.address"
form="form">
</vn-watcher>
<form name="form" ng-submit="watcher.submitBack()" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Consignatario</vn-title>
<vn-horizontal>
<vn-check vn-one label="Activo" field="addressData.address.enabled"></vn-check>
<vn-check vn-one label="Predeterminado" field="addressData.address.default"></vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Consignatario" field="addressData.address.consignee" vn-focus></vn-textfield>
<vn-textfield vn-one label="Domicilio" field="addressData.address.street"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Código Postal" field="addressData.address.postcode"></vn-textfield>
<vn-textfield vn-one label="Municipio" field="addressData.address.city"></vn-textfield>
<vn-autocomplete vn-one
field="addressData.address.provinceFk"
url="/client/api/Provinces"
show-field="name"
value-field="id"
label="Provincia">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
field="addressData.address.defaultAgencyFk"
url="/client/api/AgencyServices"
show-field="name"
value-field="id"
label="Agencia">
</vn-autocomplete>
<vn-textfield vn-one label="Teléfono" field="addressData.address.phone"></vn-textfield>
<vn-textfield vn-one label="Móvil" field="addressData.address.mobile"></vn-textfield>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Guardar"></vn-submit>
</vn-button-bar>
</form>

View File

@ -1,18 +0,0 @@
import {module} from '../module';
class Controller {
constructor($stateParams) {
this.address = {
id: $stateParams.addressId
};
}
}
Controller.$inject = ['$stateParams'];
export const NAME = 'vnAddressEdit';
export const COMPONENT = {
template: require('./index.html'),
controllerAs: 'addressData',
controller: Controller
};
module.component(NAME, COMPONENT);

View File

@ -1,32 +0,0 @@
<mg-ajax path="/client/api/Clients/{{index.params.id}}/addressesList" options="vnIndex"></mg-ajax>
<vn-vertical pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-horizontal>
<vn-title vn-one>Consignatario</vn-title>
</vn-horizontal>
<vn-horizontal ng-repeat="i in index.model" class="pad-medium-top" style="align-items: center;">
<vn-auto style="border-radius: .5em;" class="pad-small border-solid"
ng-class="{'bg-dark-item': i.default,'bg-opacity-item': !i.enabled && !i.default}">
<vn-horizontal style="align-items: center;">
<vn-auto>
<div><b>{{i.consignee}}</b></div>
<div>{{i.street}}</div>
<div>{{i.city}}, {{i.province}}</div>
<div>{{i.phone}}, {{i.mobile}}</div>
</vn-auto>
<a vn-empty ui-sref="clientCard.addresses.edit({addressId: {{i.id}}})">
<vn-icon-button icon="edit"></vn-icon-button>
</a>
</vn-horizontal>
</vn-auto>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-paging index="index"></vn-paging>
<vn-float-button
fixed-bottom-right
ui-sref="clientCard.addresses.create"
icon="add">
</vn-float-button>
</vn-vertical>

View File

@ -1,6 +0,0 @@
import {module} from '../module';
export const component = {
template: require('./index.html')
};
module.component('vnClientAddresses', component);

View File

@ -1,48 +0,0 @@
<mg-ajax path="/client/api/Clients/{{put.params.id}}" options="vnPut"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.client"
form="form"
save="put">
</vn-watcher>
<form name="form" ng-submit="watcher.submit()" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Datos básicos</vn-title>
<vn-horizontal>
<vn-textfield vn-one label="Nombre" field="$ctrl.client.name" vn-focus></vn-textfield>
<vn-textfield vn-one label="NIF/CIF" field="$ctrl.client.fi"></vn-textfield>
<vn-textfield autofocus vn-one label="Razón social" field="$ctrl.client.socialName"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Teléfono" field="$ctrl.client.phone"></vn-textfield>
<vn-textfield vn-one label="Móvil" field="$ctrl.client.mobile"></vn-textfield>
<vn-textfield vn-one label="Fax" field="$ctrl.client.fax"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Email" field="$ctrl.client.email"></vn-textfield>
<vn-autocomplete vn-one
initial-value="$ctrl.client.salesPerson"
field="$ctrl.client.salesPersonFk"
url="/client/api/Employees"
show-field="name"
value-field="id"
select-fields="surname"
label="Comercial">
<tpl-item>
{{::i.name}} {{::i.surname}}
</tpl-item>
</vn-autocomplete>
<vn-autocomplete vn-one
initial-value="$ctrl.client.contactChannel"
field="$ctrl.client.contactChannelFk"
url="/client/api/ContactChannels/scope"
label="Canal">
</vn-autocomplete>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Guardar"></vn-submit>
</vn-button-bar>
</form>

View File

@ -1,9 +0,0 @@
import {module} from '../module';
export const component = {
template: require('./index.html'),
bindings: {
client: '<'
}
};
module.component('vnClientBasicData', component);

View File

@ -1,43 +0,0 @@
<mg-ajax path="/client/api/Clients/{{put.params.id}}" options="vnPut"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="bill.client"
form="form"
save="put">
</vn-watcher>
<form name="form" ng-submit="watcher.submit()" pad-medium>
<vn-card margin-small-bottom>
<vn-vertical pad-large>
<vn-title>Información de facturación</vn-title>
<vn-horizontal>
<vn-autocomplete vn-two
field="bill.client.payMethodFk"
url="/client/api/PayMethods"
select-fields="ibanRequired"
initial-data="bill.client.payMethod"
label="Forma de pago">
</vn-autocomplete>
<vn-textfield vn-two label="IBAN" field="bill.client.iban"></vn-textfield>
<vn-textfield vn-one label="Vencimiento" field="bill.client.dueDay"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Crédito" field="bill.client.credit"></vn-textfield>
<vn-textfield vn-one label="Crédito asegurado" field="bill.client.creditInsurance"></vn-textfield>
<vn-check vn-three label="Recargo de equivalencia" field="bill.client.surcharge"></vn-check>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-card margin-small-bottom>
<vn-vertical pad-large>
<vn-title>Documentación</vn-title>
<vn-horizontal>
<vn-check vn-one label="Recibido core VNH" field="bill.client.coreVnh"></vn-check>
<vn-check vn-one label="Recibido core VNL" field="bill.client.coreVnl"></vn-check>
<vn-check vn-one label="Recibido B2B VNL" field="bill.client.sepaVnl"></vn-check>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Guardar"></vn-submit>
</vn-button-bar>
</form>

View File

@ -1,11 +0,0 @@
import {module} from '../module';
export const NAME = 'vnClientBillingData';
export const COMPONENT = {
template: require('./index.html'),
controllerAs: 'bill',
bindings: {
client: '<'
}
};
module.component(NAME, COMPONENT);

View File

@ -1,10 +0,0 @@
<vn-horizontal>
<mg-ajax path="/client/api/Clients/{{edit.params.id}}/card" options="mgEdit" actions="card.client=edit.model;"></mg-ajax>
<vn-empty style="min-width: 18em; padding-left: 1em; padding-bottom: 1em;">
<vn-descriptor client="card.client" active="card.client.active" class="display-block" ></vn-descriptor>
<vn-left-menu></vn-left-menu>
</vn-empty>
<vn-auto>
<vn-vertical style="max-width: 70em; margin: 0 auto;" ui-view></vn-vertical>
</vn-auto>
</vn-horizontal>

View File

@ -1,17 +0,0 @@
import {module} from '../module';
import './style.css';
export const NAME = 'vnClientCard';
export default class vnClientCard {
constructor() {
this.client = null;
}
}
module.component(NAME, {
template: require('./index.html'),
controllerAs: 'card',
controller: vnClientCard
});

View File

@ -1,28 +0,0 @@
<mg-ajax path="/client/api/Clients" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.client"
form="form"
save="post">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" margin-medium>
<div style="max-width: 70em; margin: 0 auto;">
<vn-card>
<vn-vertical pad-large>
<vn-title>Crear Cliente</vn-title>
<vn-horizontal>
<vn-textfield vn-one label="Nombre" field="$ctrl.client.name" vn-focus></vn-textfield>
<vn-textfield vn-one label="NIF/CIF" field="$ctrl.client.fi"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Razón social" field="$ctrl.client.socialName"></vn-textfield>
<vn-one></vn-one>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Crear y continuar"></vn-submit>
<vn-button label="Crear" ng-click="watcher.submitBack()"></vn-button>
</vn-button-bar>
</div>
</form>

View File

@ -1,23 +0,0 @@
import {module} from '../module';
class Controller {
constructor($scope, $state) {
this.$scope = $scope;
this.$state = $state;
this.client = {
active: true
};
}
onSubmit() {
this.$scope.watcher.submit().then(
json => this.$state.go('clientCard.basicData', {id: json.data.id})
);
}
}
Controller.$inject = ['$scope', '$state'];
export const component = {
template: require('./index.html'),
controller: Controller
};
module.component('vnClientCreate', component);

View File

@ -1,15 +0,0 @@
<vn-card>
<vn-vertical class="margin-medium" pad-medium-top pad-medium-bottom>
<vn-horizontal>
<vn-one>
<i class="material-icons descriptor-icon">person</i>
</vn-one>
<vn-vertical vn-two>
<div class="margin-none">{{descriptor.client.id}}</div>
<div class="margin-none">{{descriptor.client.name}}</div>
<div class="margin-none">{{descriptor.client.phone}}</div>
<vn-switch label="Activo" model="descriptor.active"></vn-switch>
</vn-vertical>
</vn-horizontal>
</vn-vertical>
</vn-card>

View File

@ -1,21 +0,0 @@
import {module} from '../module';
import './style.css';
export const NAME = 'vnDescriptor';
export const COMPONENT = {
template: require('./index.html'),
controllerAs: 'descriptor',
bindings: {
client: '<',
active: '<'
},
controller: function($http, $scope) {
var self = this;
$scope.$watch('descriptor.active', function(newValue, oldValue) {
if (oldValue !== undefined)
$http.put(`/client/api/Clients/${self.client.id}/activate`);
});
}
};
COMPONENT.controller.$inject = ['$http', '$scope'];
module.component(NAME, COMPONENT);

View File

@ -1,45 +0,0 @@
<mg-ajax path="/client/api/Clients/{{put.params.id}}" options="vnPut"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="fiscal.client"
form="form"
save="put">
</vn-watcher>
<form name="form" ng-submit="watcher.submit()" pad-medium>
<vn-card margin-small-bottom>
<vn-vertical pad-large>
<vn-title>Datos fiscales y de facturación</vn-title>
<vn-horizontal>
<vn-check vn-one label="Facturar" field="fiscal.client.hasToInvoice"></vn-check>
<vn-check vn-one label="Factura impresa" field="fiscal.client.invoiceByEmail"></vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-two label="Domicilio fiscal" field="fiscal.client.street" vn-focus></vn-textfield>
<vn-textfield vn-one label="Municipio" field="fiscal.client.city"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Código postal" field="fiscal.client.postcode"></vn-textfield>
<vn-autocomplete vn-one
initial-value="fiscal.client.province"
field="fiscal.client.provinceFk"
url="/client/api/Provinces"
show-field="name"
value-field="id"
label="Provincia">
</vn-autocomplete>
<vn-autocomplete vn-one
initial-value="fiscal.client.country"
field="fiscal.client.countryFk"
url="/client/api/Countries"
show-field="name"
value-field="id"
label="País">
</vn-autocomplete>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Guardar"></vn-submit>
</vn-button-bar>
</form>

View File

@ -1,11 +0,0 @@
import {module} from '../module';
export const NAME = 'vnClientFiscalData';
export const COMPONENT = {
template: require('./index.html'),
controllerAs: 'fiscal',
bindings: {
client: '<'
}
};
module.component(NAME, COMPONENT);

View File

@ -1,18 +0,0 @@
<vn-watcher
vn-id="watcher"
url="/client/api/ClientObservations"
id-field="id"
data="newNote.note"
form="form">
</vn-watcher>
<form name="form" ng-submit="newNote.onSubmit()" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Nueva nota</vn-title>
<vn-textarea label="Nueva nota" model="newNote.note.text" vn-focus padd-medium-top></vn-textarea>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Guardar"></vn-submit>
</vn-button-bar>
</form>

View File

@ -1,27 +0,0 @@
import template from './index.html';
import {module} from '../module';
class Controller {
constructor($element, $state) {
this.element = $element[0];
this.$state = $state;
this.note = {
clientFk: $state.params.id,
text: null
};
}
onSubmit() {
this.element.querySelector('vn-watcher').$ctrl.submit().then(
() => this.$state.go('clientCard.notes')
);
}
}
Controller.$inject = ['$element', '$state'];
export const NAME = 'vnNewNote';
export const COMPONENT = {
template: template,
controllerAs: 'newNote',
controller: Controller
};
module.component(NAME, COMPONENT);

View File

@ -1,17 +0,0 @@
<vn-card ng-show="observation.observations.length" pad-medium>
<vn-vertical pad-large>
<vn-title>Notas</vn-title>
<vn-horizontal ng-repeat="n in observation.observations" margin-small-bottom style="align-items: center;">
<vn-auto style="border-radius: .3em;" class="pad-small border-solid">
<div class="notes-date">{{n.created | date:'dd/MM/yyyy HH:mm'}}</div>
<div class="notes-date">{{n.employee.name}}</div>
<div>{{n.text}}</div>
</vn-auto>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-float-button
fixed-bottom-right
ng-click="observation.newObservation()"
icon="add">
</vn-float-button>

View File

@ -1,34 +0,0 @@
import './style.css';
import template from './index.html';
import {module} from '../module';
export const NAME = 'vnClientNotes';
export const COMPONENT = {
template: template,
controllerAs: 'observation',
bindings: {
client: '<'
},
controller: function($http, $state) {
this.$onChanges = function(changes) {
if (this.client) {
this.getObservation(this.client.id);
}
};
this.getObservation = function(clientId) {
let json = JSON.stringify({where: {clientFk: this.client.id}, order: 'created DESC'});
$http.get(`/client/api/clientObservations?filter=${json}`).then(
json => {
this.observations = json.data;
}
);
};
this.newObservation = () => {
$state.go("clientCard.notes.create", {id: this.client.id});
};
}
};
COMPONENT.controller.$inject = ['$http', '$state'];
module.component(NAME, COMPONENT);

View File

@ -1,26 +0,0 @@
<div pad-large style="min-width: 30em;" ng-show="$ctrl.formVisibility">
<form name="form" ng-submit="form.$valid && $ctrl.onSearch()" ng-keyup="$ctrl.getKeyPressed($event)">
<vn-horizontal>
<vn-textfield vn-one label="Id Cliente" model="$ctrl.filter.id" vn-focus></vn-textfield>
<vn-textfield vn-one label="NIF/CIF" model="$ctrl.filter.fi"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Nombre" model="$ctrl.filter.name"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Razon Social" model="$ctrl.filter.socialName"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Población" model="$ctrl.filter.city"></vn-textfield>
<vn-textfield vn-one label="Código Postal" model="$ctrl.filter.postcode"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Email" model="$ctrl.filter.email"></vn-textfield>
<vn-textfield vn-one label="Teléfono" model="$ctrl.filter.phone"></vn-textfield>
</vn-horizontal>
<vn-horizontal margin-large-top>
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -1,28 +0,0 @@
import {module} from '../module';
export const NAME = 'vnClientSearchPanel';
export const COMPONENT = {
template: require('./index.html'),
controller: function($scope, $window) {
this.filter = {id: null, fi: null, name: null, socialName: null, city: null, postcode: null, email: null, phone: null};
this.formVisibility = true;
this.onSearch = () => {
this.setStorageValue();
this.onSubmit(this.filter);
};
this.getKeyPressed = function(event) {
if (event.which === 27)
this.formVisibility = false;
};
this.$onChanges = () => {
var value = JSON.parse($window.sessionStorage.getItem('filter'));
if (value !== undefined)
this.filter = value;
};
this.setStorageValue = () => {
$window.sessionStorage.setItem('filter', JSON.stringify(this.filter));
};
}
};
module.component(NAME, COMPONENT);

View File

@ -1,42 +0,0 @@
<vn-watcher
vn-id="watcher"
url="/client/api/Accounts"
id-field="id"
data="$ctrl.client.account"
to="$ctrl.account"
form="form">
</vn-watcher>
<form name="form" ng-submit="watcher.submit()" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Web access</vn-title>
<vn-check label="Acceso Web" field="$ctrl.account.active"></vn-check>
<vn-textfield label="Usuario" class="margin-medium-top" field="$ctrl.account.name" vn-focus></vn-textfield>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Guardar"></vn-submit>
<vn-button label="Cambiar contraseña" vn-dialog="change-pass"></vn-button>
</vn-button-bar>
</form>
<vn-dialog
vn-id="change-pass"
on-open="$ctrl.onPassOpen()"
on-response="$ctrl.onPassChange(response)">
<tpl-body>
<vn-textfield
type="password"
label="New password"
model="$ctrl.newPassword">
</vn-textfield>
<vn-textfield
type="password"
label="Repeat password"
model="$ctrl.repeatPassword">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<button response="CANCEL" translate>Cancel</button>
<button response="ACCEPT" translate>Change password</button>
</tpl-buttons>
</vn-dialog>

View File

@ -1,48 +0,0 @@
import {module} from '../module';
class Controller {
constructor($scope, $http, vnAppLogger) {
this.$scope = $scope;
this.$http = $http;
this.vnAppLogger = vnAppLogger;
}
$onChanges() {
if(this.client)
this.account = this.client.account;
}
onPassOpen() {
this.newPassword = '';
this.repeatPassword = '';
this.$scope.$apply();
}
onPassChange(response) {
if(response == 'ACCEPT')
try {
if(!this.newPassword)
throw new Error(`Passwords can't be empty`);
if(this.newPassword != this.repeatPassword)
throw new Error(`Passwords don't match`);
let account = {
password: this.newPassword
};
this.$http.put(`/client/api/Accounts/${this.client.id}`, account);
}
catch(e) {
this.vnAppLogger.showError(e.message);
return false;
}
return true;
}
}
Controller.$inject = ['$scope', '$http', 'vnAppLogger'];
module.component('vnClientWebAccess', {
template: require('./index.html'),
bindings: {
client: '<'
},
controller: Controller
});

View File

@ -1,10 +0,0 @@
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input type="text"
class="mdl-textfield__input"
ng-keydown="$ctrl.onKeydown($event)"
ng-click="$ctrl.onClick($event)"
ng-keyup="$ctrl.onKeyup($event)"
ng-focus="$ctrl.onFocus($event)"
ng-blur="$ctrl.onBlur($event)"/>
<label class="mdl-textfield__label">{{$ctrl.label | translate}}</label>
</div>

View File

@ -1,418 +0,0 @@
import {module} from '../module';
import Component from '../lib/component';
import './style.scss';
/**
* Combobox like component with search and partial data loading features.
*/
export default class Autocomplete extends Component {
constructor($element, $scope, $http, vnPopover, $transclude) {
super($element);
this.input = $element[0].querySelector('input');
this.item = null;
this.data = null;
this.popover = null;
this.displayData = null;
this.timeoutId = null;
this.lastSearch = null;
this.lastRequest = null;
this.currentRequest = null;
this.moreData = false;
this.activeOption = -1;
this.locked = false;
this.$http = $http;
this.$scope = $scope;
this.vnPopover = vnPopover;
this.$transclude = $transclude;
this.scopes = null;
Object.assign(this, {
maxRows: 10,
requestDelay: 350,
showField: 'name',
valueField: 'id',
itemAs: 'i'
});
componentHandler.upgradeElement($element[0].firstChild);
}
set field(value) {
this.locked = true;
this.setValue(value);
this.locked = false;
}
get field() {
return this.value;
}
set initialData(value) {
if (value) {
if (!this.data)
this.data = [];
this.data.push(value);
}
}
set selectFields(value) {
this._selectFields = [];
if (!value)
return;
let res = value.split(',');
for (let i of res)
this._selectFields.push(i.trim());
}
mdlUpdate() {
let mdlField = this.element.firstChild.MaterialTextfield;
if (mdlField)
mdlField.updateClasses_();
}
loadData(textFilter) {
textFilter = textFilter ? textFilter : '';
if (this.lastSearch === textFilter) {
this.showPopoverIfFocus();
return;
}
this.lastSearch = textFilter;
let lastRequest = this.lastRequest;
let requestWillSame = lastRequest !== null
&& !this.moreData
&& textFilter.substr(0, lastRequest.length) === lastRequest;
if (requestWillSame)
this.localFilter(textFilter);
else
this.requestData(textFilter, false);
}
getRequestFields() {
let fields = {};
fields[this.valueField] = true;
fields[this.showField] = true;
if (this._selectFields)
for (let field of this._selectFields)
fields[field] = true;
return fields;
}
requestData(textFilter, append) {
let where = {};
let skip = 0;
if (textFilter)
where[this.showField] = {regexp: textFilter};
if (append && this.data)
skip = this.data.length;
let filter = {
fields: this.getRequestFields(),
where: where,
order: `${this.showField} ASC`,
skip: skip,
limit: this.maxRows
};
this.lastRequest = textFilter ? textFilter : '';
let json = JSON.stringify(filter);
if (this.currentRequest)
this.currentRequest.resolve();
this.currentRequest = this.$http.get(`${this.url}?filter=${json}`);
this.currentRequest.then(
json => this.onRequest(json.data, append),
json => this.onRequest([])
);
}
onRequest(data, append) {
this.currentRequest = null;
this.moreData = data.length >= this.maxRows;
if (!append || !this.data)
this.data = data;
else
this.data = this.data.concat(data);
this.setDisplayData(this.data);
}
localFilter(textFilter) {
let regex = new RegExp(textFilter, 'i');
let data = this.data.filter(item => {
return regex.test(item[this.showField]);
});
this.setDisplayData(data);
}
setDisplayData(data) {
this.displayData = data;
this.showPopoverIfFocus();
}
showPopoverIfFocus() {
if (this.hasFocus)
this.showPopover();
}
destroyScopes() {
if (this.scopes)
for (let scope of this.scopes)
scope.$destroy();
}
showPopover() {
let data = this.displayData;
if (!data)
return;
let fragment = this.document.createDocumentFragment();
this.destroyScopes();
this.scopes = [];
let hasTemplate = this.$transclude.isSlotFilled('tplItem');
for (let i = 0; i < data.length; i++) {
let li = this.document.createElement('li');
fragment.appendChild(li);
if (hasTemplate) {
this.$transclude((clone, scope) => {
scope[this.itemAs] = data[i];
li.appendChild(clone[0]);
this.scopes[i] = scope;
}, null, 'tplItem');
} else {
let text = this.document.createTextNode(data[i][this.showField]);
li.appendChild(text);
}
}
if (this.moreData) {
let li = this.document.createElement('li');
li.appendChild(this.document.createTextNode('Load more'));
li.className = 'load-more';
fragment.appendChild(li);
}
if (this.popover) {
this.popover.innerHTML = '';
this.popover.appendChild(fragment);
} else {
let popover = this.document.createElement('ul');
popover.addEventListener('click',
e => this.onPopoverClick(e));
popover.addEventListener('mousedown',
e => this.onPopoverMousedown(e));
popover.className = 'vn-autocomplete';
popover.appendChild(fragment);
this.vnPopover.show(popover, this.input);
this.popover = popover;
}
}
hidePopover() {
if (!this.popover) return;
this.activeOption = -1;
this.vnPopover.hide();
this.destroyScopes();
this.popover = null;
}
selectPopoverOption(index) {
if (!this.popover || index === -1) return;
if (index < this.displayData.length) {
this.selectOptionByDataIndex(this.displayData, index);
this.hidePopover();
} else
this.requestData(this.lastRequest, true);
}
onPopoverClick(event) {
let target = event.target;
let childs = this.popover.childNodes;
if (target === this.popover)
return;
while (target.parentNode !== this.popover)
target = target.parentNode;
for (let i = 0; i < childs.length; i++)
if (childs[i] === target) {
this.selectPopoverOption(i);
break;
}
}
onPopoverMousedown(event) {
// Prevents input from loosing focus
event.preventDefault();
}
onClick(event) {
if (!this.popover)
this.showPopover();
}
onFocus() {
this.hasFocus = true;
this.input.select();
this.loadData();
}
onBlur() {
this.hasFocus = false;
this.restoreShowValue();
this.hidePopover();
}
onKeydown(event) {
switch (event.keyCode) {
case 13: // Enter
this.selectPopoverOption(this.activeOption);
break;
case 27: // Escape
this.restoreShowValue();
this.input.select();
break;
case 38: // Arrow up
this.activateOption(this.activeOption - 1);
break;
case 40: // Arrow down
this.activateOption(this.activeOption + 1);
break;
default:
return;
}
event.preventDefault();
}
onKeyup(event) {
if (!this.isKeycodePrintable(event.keyCode)) return;
if (this.timeoutId) clearTimeout(this.timeoutId);
this.timeoutId = setTimeout(() => this.onTimeout(), this.requestDelay);
}
onTimeout() {
this.loadData(this.input.value);
this.timeoutId = null;
}
isKeycodePrintable(keyCode) {
return keyCode === 32 // Spacebar
|| keyCode === 8 // Backspace
|| (keyCode > 47 && keyCode < 58) // Numbers
|| (keyCode > 64 && keyCode < 91) // Letters
|| (keyCode > 95 && keyCode < 112) // Numpad
|| (keyCode > 185 && keyCode < 193) // ;=,-./`
|| (keyCode > 218 && keyCode < 223); // [\]'
}
restoreShowValue() {
this.putItem(this.item);
}
requestItem() {
if (!this.value) return;
let where = {};
where[this.valueField] = this.value;
let filter = {
fields: this.getRequestFields(),
where: where
};
let json = JSON.stringify(filter);
this.$http.get(`${this.url}?filter=${json}`).then(
json => this.onItemRequest(json.data),
json => this.onItemRequest(null)
);
}
onItemRequest(data) {
if (data && data.length > 0)
this.showItem(data[0]);
else
this.showItem(null);
}
activateOption(index) {
if (!this.popover)
this.showPopover();
let popover = this.popover;
let childs = popover.childNodes;
let len = this.displayData.length;
if (this.activeOption >= 0)
childs[this.activeOption].className = '';
if (index >= len)
index = 0;
else if (index < 0)
index = len - 1;
if (index >= 0) {
let opt = childs[index];
let top = popover.scrollTop;
let height = popover.clientHeight;
if (opt.offsetTop + opt.offsetHeight > top + height)
top = opt.offsetTop + opt.offsetHeight - height;
else if (opt.offsetTop < top)
top = opt.offsetTop;
opt.className = 'active';
popover.scrollTop = top;
}
this.activeOption = index;
}
setValue(value) {
this.value = value;
if (value) {
let data = this.data;
if (data)
for (let i = 0; i < data.length; i++)
if (data[i][this.valueField] == value) {
this.putItem(data[i]);
return;
}
this.requestItem();
} else
this.putItem(null);
}
selectOptionByIndex(index) {
this.selectOptionByDataIndex(this.data, index);
}
selectOptionByDataIndex(data, index) {
if (data && index >= 0 && index < data.length)
this.putItem(data[index]);
else
this.putItem(null);
}
putItem(item) {
this.showItem(item);
let value = item ? item[this.valueField] : undefined;
if (!this.locked)
this.value = value;
}
showItem(item) {
this.input.value = item ? item[this.showField] : '';
this.item = item;
this.mdlUpdate();
}
$onDestroy() {
this.destroyScopes();
}
}
Autocomplete.$inject = ['$element', '$scope', '$http', 'vnPopover', '$transclude'];
module.component('vnAutocomplete', {
template: require('./index.html'),
bindings: {
url: '@',
showField: '@?',
valueField: '@?',
selectFields: '@?',
initialData: '<?',
itemAs: '@?',
field: '=',
label: '@'
},
transclude: {
tplItem: '?tplItem'
},
controller: Autocomplete
});

View File

@ -1,23 +0,0 @@
<div ng-mousedown="$ctrl.onDialogMouseDown($event)">
<button ng-click="$ctrl.hide()" class="close" translate-attr="{title: 'Close'}">
<vn-icon vn-one icon="clear" style="color:black"></vn-icon>
</button>
<form>
<div>
<tpl-body>
<h6 class="dialog-title" translate>
{{$ctrl.question}}
</h6>
<h6 translate>
{{$ctrl.message}}
</h6>
</tpl-body>
</div>
<div class="button-bar" ng-click="$ctrl.onButtonClick($event)">
<tpl-buttons>
<button response="CANCEL" translate>Cancel</button>
<button response="ACCEPT" translate>Accept</button>
</tpl-buttons>
</div>
</form>
</div>

View File

@ -1,16 +0,0 @@
import {module} from '../module';
import Dialog from '../dialog/index';
import './style.css';
export default class Confirm extends Dialog {}
Dialog.$inject = ['$element'];
module.component('vnConfirm', {
template: require('./index.html'),
bindings: {
onResponse: '&',
question: '@',
message: '@'
},
controller: Confirm
});

View File

@ -1,13 +0,0 @@
<div ng-mousedown="$ctrl.onDialogMouseDown($event)">
<button ng-click="$ctrl.hide()" class="close" translate-attr="{title: 'Close'}">
<vn-icon vn-one icon="clear" style="color:black"></vn-icon>
</button>
<form>
<div ng-transclude="tplBody"></div>
<div
ng-transclude="tplButtons"
class="button-bar"
ng-click="$ctrl.onButtonClick($event)">
</div>
</form>
</div>

View File

@ -1,111 +0,0 @@
import {module} from '../module';
import Component from '../lib/component';
import './style.scss';
/**
* Dialog component.
*/
export default class Dialog extends Component {
/**
* Contructor.
*/
constructor($element) {
super($element);
$element.addClass('vn-dialog');
this.dialog = $element[0].firstChild;
this.element.addEventListener('mousedown',
event => this.onBackgroundMouseDown(event));
}
/**
* Displays the dialog to the user.
*/
show() {
let style = this.dialog.style;
let screenMargin = 20;
let window = this.window;
let innerWidth = window.innerWidth;
let innerHeight = window.innerHeight;
let width = this.dialog.offsetWidth;
let height = this.dialog.offsetHeight;
if (width + screenMargin > innerWidth) {
width = innerWidth - dblMargin;
style.width = width + 'px';
}
if (height + screenMargin > innerHeight) {
height = innerHeight - dblMargin;
style.height = height + 'px';
}
this.keypressHandler =
event => this.onKeypress(event);
this.document.addEventListener('keypress',
this.keypressHandler);
this.element.style.display = 'block';
if (this.onOpen)
this.onOpen();
}
/**
* Hides the dialog calling the response handler.
*/
hide() {
this.fireResponse();
this.realHide();
}
/**
* Calls the response handler.
*/
fireResponse(response) {
let cancel = false;
if (this.onResponse)
cancel = this.onResponse({response: response});
return cancel;
}
realHide() {
this.element.style.display = 'none';
this.document.removeEventListener('keypress',
this.keypressHandler);
this.lastEvent = null;
}
onButtonClick(event) {
let buttonBar = this.element.querySelector('.button-bar');
let buttons = buttonBar.querySelector('tpl-buttons');
let node = event.target;
while (node.parentNode != buttons) {
if (node == buttonBar) return;
node = node.parentNode;
}
let response = node.getAttribute('response');
let cancel = this.fireResponse(response);
if (cancel !== false) this.realHide();
}
onDialogMouseDown(event) {
this.lastEvent = event;
}
onBackgroundMouseDown(event) {
if (event != this.lastEvent)
this.hide();
}
onKeypress(event) {
if (event.keyCode == 27) // Esc
this.hide();
}
}
Dialog.$inject = ['$element'];
module.component('vnDialog', {
template: require('./index.html'),
transclude: {
tplBody: 'tplBody',
tplButtons: 'tplButtons'
},
bindings: {
onOpen: '&',
onResponse: '&'
},
controller: Dialog
});

View File

@ -1,19 +0,0 @@
import {module} from '../module';
import './index.mdl';
import './style.css';
import * as resolveFactory from '../lib/resolveDefaultComponents';
const _NAME = 'icon';
export const NAME = 'vnIcon';
export function directive(resolver) {
return {
restrict: 'E',
template: function(_, attrs) {
return resolver.getTemplate(_NAME, attrs);
}
};
}
directive.$inject = [resolveFactory.NAME];
module.directive(NAME, directive);

View File

@ -1 +0,0 @@
<i class="material-icons">*[icon]*</i>

View File

@ -1,12 +0,0 @@
import {module} from '../module';
import template from './index.mdl.html';
export const NAME = 'vnIconMdlFactory';
export function factory() {
return {
template: template,
default: {}
}
}
module.factory(NAME, factory);

View File

@ -1,10 +0,0 @@
<paging
page="$ctrl.currentPage"
page-size="$ctrl.numPerPage"
total="$ctrl.numItems"
show-prev-next="true"
show-first-last="false"
active-class="active"
ng-click="$ctrl.figureOutToDisplay()"
paging-action="$ctrl.onPageChange(page)">
</paging>

View File

@ -1,46 +0,0 @@
import {module} from '../module';
import './style.scss';
export default class Paging {
get numPages() {
return Math.ceil(this.numItems / this.numPerPage);
}
constructor($http, $scope) {
this.$http = $http;
this.$scope = $scope;
this.where = this.filter;
this.numPerPage = null;
this.numItems = 0;
$scope.$watch('$ctrl.index.model.length', () => this.onModelUpdated());
}
$onChanges(changes) {
if (!this.index) return;
this.numPerPage = this.index.filter.size;
if(changes.total)
this.numItems = changes.total.currentValue;
}
onModelUpdated() {
let index = this.index;
let filter = index.filter;
if (filter.page >= this.numPages
&& index.model.length >= this.numPerPage)
this.numItems = filter.page * filter.size + 1;
}
onPageChange(page) {
this.index.filter.page = page;
this.index.accept();
}
}
Paging.$inject = ['$http', '$scope'];
export const NAME = 'vnPaging';
export const COMPONENT = {
template: require('./index.html'),
bindings: {
index: '<',
total: '<'
},
controller: Paging
};
module.component(NAME, COMPONENT);

View File

@ -1,98 +0,0 @@
import {module} from '../module';
import './style.css';
directive.$inject = ['vnPopover'];
export function directive(popover) {
return {
restrict: 'A',
link: function($scope, $element, $attrs) {
$element.on('click', function(event) {
popover.showComponent($attrs.vnDialog, $scope, $element);
event.preventDefault();
});
}
};
}
module.directive('vnPopover', directive);
export class Popover {
constructor($document, $compile) {
this.document = $document[0];
this.$compile = $compile;
}
show(childElement, parent) {
let popover = this.document.createElement('div');
popover.className = 'vn-popover';
popover.addEventListener('mousedown',
event => this.onPopoverMouseDown(event));
popover.appendChild(childElement);
this.popover = popover;
let style = popover.style;
let spacing = 0;
let screenMargin = 20;
let dblMargin = screenMargin * 2;
let width = popover.offsetWidth;
let height = popover.offsetHeight;
let innerWidth = window.innerWidth;
let innerHeight = window.innerHeight;
if (width + dblMargin > innerWidth) {
width = innerWidth - dblMargin;
style.width = width + 'px';
}
if (height + dblMargin > innerHeight) {
height = innerHeight - dblMargin;
style.height = height + 'px';
}
if (parent) {
let parentNode = parent;
let rect = parentNode.getBoundingClientRect();
let left = rect.left;
let top = rect.top + spacing + parentNode.offsetHeight;
if (left + width > innerWidth)
left -= (left + width) - innerWidth + margin;
if (top + height > innerHeight)
top -= height + parentNode.offsetHeight + spacing * 2;
if (left < 0)
left = screenMargin;
if (top < 0)
top = screenMargin;
style.top = (top) + 'px';
style.left = (left) + 'px';
style.minWidth = (rect.width) + 'px';
}
this.document.body.appendChild(popover);
this.docMouseDownHandler = event => this.onDocMouseDown(event);
this.document.addEventListener('mousedown', this.docMouseDownHandler);
}
showComponent(childComponent, $scope, parent) {
let childElement = this.document.createElement(childComponent);
this.$compile(childElement)($scope);
this.show(childElement, parent);
}
hide() {
if (!this.popover) return;
this.document.removeEventListener('mousedown', this.docMouseDownHandler);
this.document.body.removeChild(this.popover);
this.popover = null;
this.lastEvent = null;
this.docMouseDownHandler = null;
}
onDocMouseDown(event) {
if (event != this.lastEvent)
this.hide();
}
onPopoverMouseDown(event) {
this.lastEvent = event;
}
}
Popover.$inject = ['$document', '$compile'];
module.service('vnPopover', Popover);

View File

@ -1,4 +0,0 @@
<div class="mdl-js-snackbar mdl-snackbar" style="z-index: 200;">
<div class="mdl-snackbar__text"></div>
<button class="mdl-snackbar__action" type="button"></button>
</div>

View File

@ -1,25 +0,0 @@
import {module} from '../module';
/**
* A simple component to show non-obstructive notifications to the user.
*/
export default class Controller {
constructor($element) {
this.snackbar = $element[0].firstChild;
componentHandler.upgradeElement(this.snackbar);
}
/**
* Shows a notification.
*
* @param {Object} data The message data
*/
show(data) {
this.snackbar.MaterialSnackbar.showSnackbar(data);
}
}
Controller.$inject = ['$element'];
module.component('vnSnackbar', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,2 +0,0 @@
<div class="mdl-spinner mdl-spinner--single-color mdl-js-spinner">
</div>

View File

@ -1,58 +0,0 @@
import {module} from '../module';
import Component from '../lib/component';
import './style.css';
/**
* A spinner to inform the user about loading process.
*/
export default class Spinner extends Component {
constructor($element, $scope) {
super($element);
this._enable = false;
this.spinner = $element[0].firstChild;
componentHandler.upgradeElement(this.spinner);
}
/**
* Enables/disables the spinner.
*
* @param {Boolean} value %true to enable, %false to disable
*/
set enable(value) {
if (value)
this.start();
else
this.stop();
}
/**
* Returns the current spinner state.
*
* @return {Boolean} %true if it's enabled, %false otherwise
*/
get enable() {
return this._enable;
}
/**
* Activates the spinner.
*/
start() {
this.spinner.MaterialSpinner.start();
this._enable = true;
}
/**
* Deactivates the spinner.
*/
stop() {
this.spinner.MaterialSpinner.stop();
this._enable = false;
}
}
Spinner.$inject = ['$element', '$scope'];
export const component = {
template: require('./index.html'),
bindings: {
enable: '='
},
controller: Spinner
};
module.component('vnSpinner', component);

View File

@ -1,2 +0,0 @@
<h5 style="margin-top: 0;" class="margin-medium-bottom" ng-transclude>
</h5>

View File

@ -1,6 +0,0 @@
import {module} from '../module';
module.component('vnSubtitle', {
template: require('./index.html'),
transclude: true
});

View File

@ -1,82 +0,0 @@
import {module} from '../module';
import Component from '../lib/component';
import * as resolveFactory from '../lib/resolveDefaultComponents';
import * as normalizerFactory from '../lib/inputAttrsNormalizer';
import './style.scss';
import './index.mdl';
export default class Textfield extends Component {
constructor($element, $scope, $attrs) {
super($element);
let input = this.input = this.element.querySelector('input');
input.addEventListener('input',
() => this.checkValue());
input.addEventListener('focus',
() => this.checkValue());
input.addEventListener('blur',
() => this.showClear(false));
let clearButton = this.element.querySelector('button');
clearButton.addEventListener('click',
() => this.onClearClick());
clearButton.addEventListener('mousedown',
event => event.preventDefault());
let div = this.element.firstChild;
componentHandler.upgradeElement(div);
}
link($scope, $attrs) {
let mdlTextField = this.element.firstChild.MaterialTextfield;
mdlTextField.updateClasses_();
$scope.$watch($attrs.model,
() => mdlTextField.updateClasses_());
}
onClearClick() {
this.input.value = '';
this.checkValue();
let event = this.document.createEvent('HTMLEvents');
event.initEvent('change', false, true);
this.input.dispatchEvent(event);
}
checkValue() {
this.showClear(this.input.value);
}
showClear(show) {
show = show && document.activeElement === this.input;
let clearButton = this.element.querySelector('button');
clearButton.style.visibility = show ? 'visible' : 'hidden';
}
/**
* Selects the textfield.
*/
select() {
this.input.select();
}
/**
* Puts the focus on the textfield.
*/
focus() {
this.input.focus();
}
}
Textfield.$inject = ['$element', '$scope', '$attrs'];
directive.$inject = [resolveFactory.NAME, normalizerFactory.NAME];
export function directive(resolve, normalizer) {
return {
restrict: 'E',
template: function(_, attrs) {
normalizer.normalize(attrs);
return resolve.getTemplate('textfield', attrs);
},
link: function($scope, $element, $attrs, $ctrl) {
$ctrl.link($scope, $attrs);
},
controller: Textfield
};
}
module.directive('vnTextfield', directive);

View File

@ -1,18 +0,0 @@
<div class="mdl-textfield mdl-js-textfield *[className]*">
<input
class="mdl-textfield__input"
type="*[type]*"
name="*[name]*"
ng-model="*[model]*"
vn-validation="*[rule]*"
ng-disabled="*[disable]*"/>
<button
type="button"
class="mdl-chip__action"
tabindex="-1"
translate-attr="{title: 'Clear'}"
style="visibility: hidden">
<i class="material-icons">clear</i>
</button>
<label class="mdl-textfield__label" translate>*[label]*</label>
</div>

View File

@ -1,16 +0,0 @@
import {module} from '../module';
export const NAME = 'vnTextfieldMdlFactory';
export function factory() {
return {
template: require('./index.mdl.html'),
default: {
label: 'text',
className: 'mdl-textfield--floating-label',
type: 'text'
}
};
}
module.factory(NAME, factory);

View File

@ -1,2 +0,0 @@
<h3 style="margin-top: 0;" class="margin-medium-bottom" ng-transclude>
</h3>

View File

@ -1,6 +0,0 @@
import {module} from '../module';
module.component('vnTitle', {
template: require('./index.html'),
transclude: true
});

View File

@ -1,6 +0,0 @@
<vn-confirm
vn-id="confirm"
question="Are you sure exit without saving?"
message="Unsaved changes will be lost"
on-response="$ctrl.onConfirmResponse(response)">
</vn-confirm>

View File

@ -1,162 +0,0 @@
import {module} from '../module';
import Component from '../lib/component';
import getModifiedData from '../lib/modified';
import copyObject from '../lib/copy';
import isEqual from '../lib/equals';
/**
* Component that checks for changes on a specific model property and
* asks the user to save or discard it when the state changes.
* Also it can save the data to the server when the @url and @idField
* properties are provided.
*/
export default class Watcher extends Component {
constructor($element, $scope, $state, $transitions, $http, vnAppLogger, $translate) {
super($element);
this.$scope = $scope;
this.$state = $state;
this.$http = $http;
this.$translate = $translate;
this.vnAppLogger = vnAppLogger;
this.state = null;
this.deregisterCallback = $transitions.onStart({},
transition => this.callback(transition));
this.copyData();
}
$onInit() {
if (this.get) {
this.fetchData();
}
}
$onChanges(changes) {
if (this.data) {
this.copyData();
}
}
$onDestroy() {
this.deregisterCallback();
}
fetchData() {
let id = this.data[this.idField];
return new Promise((resolve, reject) => {
this.$http.get(`${this.url}/${id}`).then(
json => this.writeData(json, resolve),
json => reject(json)
);
});
}
/**
* Submits the data and goes back in the history.
*/
submitBack() {
this.submit().then(
() => this.window.history.back()
);
}
/**
* Submits the data to the server.
*
* @return {Promise} The http request promise
*/
submit() {
if (this.form) {
this.form.$setSubmitted();
if (!this.form.$valid)
return new Promise(
(resolve, reject) => this.invalidForm(reject)
);
}
if (!this.dataChanged()) {
return new Promise(
(resolve, reject) => this.noChanges(reject)
);
}
let changedData = getModifiedData(this.data, this.orgData);
if (this.save) {
this.save.model = changedData;
return new Promise((resolve, reject) => {
this.save.accept().then(
json => this.writeData({data: json}, resolve),
json => reject(json)
);
});
}
// XXX: Alternative when mgCrud is not used
let id = this.orgData[this.idField];
if (id) {
return new Promise((resolve, reject) => {
this.$http.put(`${this.url}/${id}`, changedData).then(
json => this.writeData(json, resolve),
json => reject(json)
);
});
}
return new Promise((resolve, reject) => {
this.$http.post(this.url, this.data).then(
json => this.writeData(json, resolve),
json => reject(json)
);
});
}
writeData(json, resolve) {
copyObject(json.data, this.data);
this.copyData();
resolve(json);
}
noChanges(resolve) {
this.vnAppLogger.showMessage(
this.$translate.instant('No changes to save')
);
resolve();
}
invalidForm(resolve) {
this.vnAppLogger.showMessage(
this.$translate.instant('Some fields are invalid')
);
resolve();
}
copyData() {
this.orgData = copyObject(this.data);
}
callback(transition) {
if (!this.state && this.dataChanged()) {
this.state = transition.to().name;
this.$scope.confirm.show();
return false;
}
return true;
}
dataChanged() {
return !isEqual(this.data, this.orgData);
}
onConfirmResponse(response) {
if (response === 'ACCEPT') {
copyObject(this.orgData, this.data);
this.$state.go(this.state);
} else {
this.state = null;
}
}
}
Watcher.$inject = ['$element', '$scope', '$state', '$transitions', '$http', 'vnAppLogger', '$translate'];
module.component('vnWatcher', {
template: require('./index.html'),
bindings: {
url: '@?',
idField: '@?',
data: '<',
form: '<',
save: '<',
get: '=?'
},
controller: Watcher
});

View File

@ -1 +1,2 @@
node_modules
node_modules
config.json