-
+ popover="vn-client-search-panel"
+ ignore-keys = "['page', 'size', 'search']"
+ >
diff --git a/client/client/src/index/index.js b/client/client/src/index/index.js
index 38d9996ca..d07b12549 100644
--- a/client/client/src/index/index.js
+++ b/client/client/src/index/index.js
@@ -7,7 +7,6 @@ export default class Controller {
this.model = {};
}
search(index) {
- index.filter.search = this.model.search;
index.accept();
}
}
diff --git a/client/client/src/index/index.spec.js b/client/client/src/index/index.spec.js
index 821cb4685..d1dd2532b 100644
--- a/client/client/src/index/index.spec.js
+++ b/client/client/src/index/index.spec.js
@@ -18,19 +18,19 @@ describe('Client', () => {
expect(controller.model).toEqual({});
});
- describe('search()', () => {
- it(`should set model's search to the search input`, () => {
- controller.model.search = 'batman';
- let index = {
- filter: {},
- accept: () => {
- return 'accepted';
- }
- };
- controller.search(index);
+ // describe('search()', () => {
+ // it(`should set model's search to the search input`, () => {
+ // controller.model.search = 'batman';
+ // let index = {
+ // filter: {},
+ // accept: () => {
+ // return 'accepted';
+ // }
+ // };
+ // controller.search(index);
- expect(index.filter.search).toBe('batman');
- });
- });
+ // expect(index.filter.search).toBe('batman');
+ // });
+ // });
});
});
diff --git a/client/client/src/index/item-client.html b/client/client/src/index/item-client.html
index df64195b8..05cf2f614 100644
--- a/client/client/src/index/item-client.html
+++ b/client/client/src/index/item-client.html
@@ -1,7 +1,7 @@
-
- {{$ctrl.client.name}}
- Client id: {{$ctrl.client.id}}
- Phone: {{$ctrl.client.phone | phone}}
- Town/City: {{$ctrl.client.city}}
- Email: {{$ctrl.client.email}}
+
+ {{::$ctrl.client.name}}
+ Client id: {{::$ctrl.client.id}}
+ Phone: {{::$ctrl.client.phone | phone}}
+ Town/City: {{::$ctrl.client.city}}
+ Email: {{::$ctrl.client.email}}
diff --git a/client/client/src/index/locale/es.json b/client/client/src/index/locale/es.json
deleted file mode 100644
index 8b063f658..000000000
--- a/client/client/src/index/locale/es.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "Client id": "Id cliente",
- "Phone": "Teléfono",
- "Town/City": "Ciudad",
- "Email": "Correo electrónico",
- "Create client": "Crear cliente"
-}
\ No newline at end of file
diff --git a/client/client/src/index/locale/es.yml b/client/client/src/index/locale/es.yml
new file mode 100644
index 000000000..c221de7f6
--- /dev/null
+++ b/client/client/src/index/locale/es.yml
@@ -0,0 +1,5 @@
+Client id: Id cliente
+Phone: Teléfono
+Town/City: Ciudad
+Email: Correo electrónico
+Create client: Crear cliente
\ No newline at end of file
diff --git a/client/client/src/index/style.css b/client/client/src/index/style.css
index b30351f09..123a0bf97 100644
--- a/client/client/src/index/style.css
+++ b/client/client/src/index/style.css
@@ -12,5 +12,5 @@ vn-item-client a:hover {
}
.vn-item-client-name {
- font-family: raleway-bold;
+ font-family: vn-font-bold;
}
diff --git a/client/client/src/locale/en.json b/client/client/src/locale/en.json
deleted file mode 100644
index 78e01b929..000000000
--- a/client/client/src/locale/en.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "Client": "Client",
- "Clients": "Clients"
-}
\ No newline at end of file
diff --git a/client/client/src/locale/en.yml b/client/client/src/locale/en.yml
new file mode 100644
index 000000000..3dec4a7ed
--- /dev/null
+++ b/client/client/src/locale/en.yml
@@ -0,0 +1,2 @@
+Client: Client
+Clients: Clients
\ No newline at end of file
diff --git a/client/client/src/locale/es.json b/client/client/src/locale/es.json
deleted file mode 100644
index 20089e85c..000000000
--- a/client/client/src/locale/es.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "Client": "Cliente",
- "Clients": "Clientes",
- "Fiscal data": "Datos Fiscales",
- "Has to invoice": "Factura",
- "Invoice by mail": "Factura impresa",
- "Country": "País",
- "Street": "Domicilio fiscal",
- "City": "Municipio",
- "Postcode": "Código postal",
- "Province": "Provincia",
- "Save": "Guardar",
- "Pay method" : "Forma de pago"
-}
diff --git a/client/client/src/locale/es.yml b/client/client/src/locale/es.yml
new file mode 100644
index 000000000..bb2984d9b
--- /dev/null
+++ b/client/client/src/locale/es.yml
@@ -0,0 +1,23 @@
+Active: Activo
+Client: Cliente
+Clients: Clientes
+Basic data: Datos básicos
+Fiscal data: Datos Fiscales
+Addresses: Consignatarios
+Web access: Acceso web
+Notes: Notas
+Has to invoice: Factura
+Invoice by mail: Factura impresa
+Country: País
+Street: Domicilio fiscal
+City: Municipio
+Postcode: Código postal
+Province: Provincia
+Save: Guardar
+Pay method : Forma de pago
+Address: Consignatario
+Credit : Crédito
+Secured credit: Crédito asegurado
+Verified data: Datos comprobados
+Mandate: Mandato
+Amount: Importe
\ No newline at end of file
diff --git a/client/client/src/mandate/locale/es.yml b/client/client/src/mandate/locale/es.yml
new file mode 100644
index 000000000..545707023
--- /dev/null
+++ b/client/client/src/mandate/locale/es.yml
@@ -0,0 +1,3 @@
+Company: Empresa
+Register date: Fecha alta
+End date: Fecha baja
\ No newline at end of file
diff --git a/client/client/src/mandate/mandate.html b/client/client/src/mandate/mandate.html
new file mode 100644
index 000000000..f129da64b
--- /dev/null
+++ b/client/client/src/mandate/mandate.html
@@ -0,0 +1,31 @@
+
+
+
+ Mandate
+
+
+
+
+
+
+
+
+
+
+ {{::mandate.id}}
+ {{::mandate.company.code}}
+ {{::mandate.mandateType.name}}
+ {{::mandate.created | date:'dd/MM/yyyy HH:mm' }}
+ {{::mandate.finished | date:'dd/MM/yyyy HH:mm' || '-'}}
+
+
+ No results
+
+
+
+
+
\ No newline at end of file
diff --git a/client/client/src/mandate/mandate.js b/client/client/src/mandate/mandate.js
new file mode 100644
index 000000000..e48222ce6
--- /dev/null
+++ b/client/client/src/mandate/mandate.js
@@ -0,0 +1,7 @@
+import ngModule from '../module';
+import FilterClientList from '../filterClientList';
+
+ngModule.component('vnClientMandate', {
+ template: require('./mandate.html'),
+ controller: FilterClientList
+});
diff --git a/client/client/src/note-create/locale/es.json b/client/client/src/note-create/locale/es.json
deleted file mode 100644
index ed49b6323..000000000
--- a/client/client/src/note-create/locale/es.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "New note": "Nueva nota",
- "Note": "Nota"
-}
\ No newline at end of file
diff --git a/client/client/src/note-create/locale/es.yml b/client/client/src/note-create/locale/es.yml
new file mode 100644
index 000000000..bfe773f48
--- /dev/null
+++ b/client/client/src/note-create/locale/es.yml
@@ -0,0 +1,2 @@
+New note: Nueva nota
+Note: Nota
\ No newline at end of file
diff --git a/client/client/src/notes/locale/es.json b/client/client/src/notes/locale/es.json
deleted file mode 100644
index 28b37f5a8..000000000
--- a/client/client/src/notes/locale/es.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "Notes": "Notas"
-}
\ No newline at end of file
diff --git a/client/client/src/notes/notes.html b/client/client/src/notes/notes.html
index ecea1d668..0b29a5d7f 100644
--- a/client/client/src/notes/notes.html
+++ b/client/client/src/notes/notes.html
@@ -1,13 +1,19 @@
Notes
-
-
- {{::n.created | date:'dd/MM/yyyy HH:mm'}}
- {{::n.employee.name}}
- {{::n.text}}
-
-
+
+
+ {{::n.worker.firstName}} {{::n.worker.name}}
+ {{::n.created | date:'dd/MM/yyyy HH:mm'}}
+
+
+ {{::n.text}}
+
+
{
$componentController = _$componentController_;
$state = _$state_;
$httpBackend = _$httpBackend_;
- controller = $componentController('vnClientNotes', {$httpBackend: $httpBackend, $state: $state});
+ $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
+ controller = $componentController('vnClientNotes', {$state: $state});
}));
describe('$onChanges()', () => {
diff --git a/client/client/src/notes/style.css b/client/client/src/notes/style.css
index 4d0f8eba7..e69de29bb 100644
--- a/client/client/src/notes/style.css
+++ b/client/client/src/notes/style.css
@@ -1,3 +0,0 @@
-.notes-date {
- font-family: raleway-bold;
-}
\ No newline at end of file
diff --git a/client/client/src/search-panel/locale/es.json b/client/client/src/search-panel/locale/es.json
deleted file mode 100644
index 1dc7b850f..000000000
--- a/client/client/src/search-panel/locale/es.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "Client id": "Id cliente",
- "Tax number": "NIF/CIF",
- "Name": "Nombre",
- "Social name": "Razon social",
- "Town/City": "Ciudad",
- "Postcode": "Código postal",
- "Email": "Correo electrónico",
- "Phone": "Teléfono"
-}
\ No newline at end of file
diff --git a/client/client/src/search-panel/locale/es.yml b/client/client/src/search-panel/locale/es.yml
new file mode 100644
index 000000000..590d0e6d8
--- /dev/null
+++ b/client/client/src/search-panel/locale/es.yml
@@ -0,0 +1,8 @@
+Client id: Id cliente
+Tax number: NIF/CIF
+Name: Nombre
+Social name: Razon social
+Town/City: Ciudad
+Postcode: Código postal
+Email: Correo electrónico
+Phone: Teléfono
\ No newline at end of file
diff --git a/client/client/src/search-panel/search-panel.js b/client/client/src/search-panel/search-panel.js
index 3f78dc809..74c4647ba 100644
--- a/client/client/src/search-panel/search-panel.js
+++ b/client/client/src/search-panel/search-panel.js
@@ -1,28 +1,16 @@
import ngModule from '../module';
export default class Controller {
- constructor(sessionStorage) {
- this.sessionStorage = sessionStorage;
+ constructor() {
// onSubmit() is defined by @vnSearchbar
this.onSubmit = () => {};
}
onSearch() {
- this.setStorageValue();
this.onSubmit(this.filter);
}
-
- $onChanges() {
- var value = this.sessionStorage.get('filter');
- if (value !== undefined)
- this.filter = value;
- }
-
- setStorageValue() {
- this.sessionStorage.set('filter', this.filter);
- }
}
-Controller.$inject = ['sessionStorage'];
+Controller.$inject = [];
ngModule.component('vnClientSearchPanel', {
template: require('./search-panel.html'),
diff --git a/client/client/src/search-panel/search-panel.spec.js b/client/client/src/search-panel/search-panel.spec.js
deleted file mode 100644
index e4b7b5634..000000000
--- a/client/client/src/search-panel/search-panel.spec.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import './search-panel.js';
-
-describe('Client', () => {
- describe('Component vnClientSearchPanel', () => {
- let $componentController;
- let sessionStorage;
- let controller;
-
- beforeEach(() => {
- angular.mock.module('client');
- });
-
- beforeEach(angular.mock.inject((_$componentController_, _sessionStorage_) => {
- $componentController = _$componentController_;
- sessionStorage = _sessionStorage_;
- controller = $componentController('vnClientSearchPanel', {sessionStorage: sessionStorage});
- }));
-
- describe('onSearch()', () => {
- it('should call setStorageValue() and onSubmit()', () => {
- spyOn(controller, 'setStorageValue');
- spyOn(controller, 'onSubmit');
- controller.setStorageValue();
- controller.onSubmit();
-
- expect(controller.setStorageValue).toHaveBeenCalledWith();
- expect(controller.onSubmit).toHaveBeenCalledWith();
- });
- });
-
- describe('$onChanges()', () => {
- it('should set filter properties using the search values', () => {
- expect(controller.filter).not.toBeDefined();
- spyOn(sessionStorage, 'get').and.returnValue({data: 'data'});
- controller.$onChanges();
-
- expect(controller.filter).toBe(sessionStorage.get({data: 'data'}));
- });
- });
- });
-});
diff --git a/client/client/src/web-access/locale/es.json b/client/client/src/web-access/locale/es.json
deleted file mode 100644
index 82652f89f..000000000
--- a/client/client/src/web-access/locale/es.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "User": "Usuario",
- "Enable web access": "Habilitar acceso web",
- "Web access": "Acceso web",
- "New password": "Nueva contraseña",
- "Repeat password": "Repetir contraseña",
- "Change password": "Cambiar contraseña"
-}
\ No newline at end of file
diff --git a/client/client/src/web-access/locale/es.yml b/client/client/src/web-access/locale/es.yml
new file mode 100644
index 000000000..2d1905c16
--- /dev/null
+++ b/client/client/src/web-access/locale/es.yml
@@ -0,0 +1,5 @@
+User: Usuario
+Enable web access: Habilitar acceso web
+New password: Nueva contraseña
+Repeat password: Repetir contraseña
+Change password: Cambiar contraseña
\ No newline at end of file
diff --git a/client/client/src/web-access/web-access.js b/client/client/src/web-access/web-access.js
index 696d8a0ad..41aefc0bf 100644
--- a/client/client/src/web-access/web-access.js
+++ b/client/client/src/web-access/web-access.js
@@ -16,7 +16,7 @@ export default class Controller {
isCustomer() {
if (this.client && this.client.id) {
- this.$http.get(`/client/api/Clients/${this.client.id}/getRoleCustomer`).then(res => {
+ this.$http.get(`/client/api/Clients/${this.client.id}/hasCustomerRole`).then(res => {
this.canChangePassword = (res.data) ? res.data.isCustomer : false;
});
} else {
diff --git a/client/client/src/web-access/web-access.spec.js b/client/client/src/web-access/web-access.spec.js
index 584ad58c1..a700f4c8b 100644
--- a/client/client/src/web-access/web-access.spec.js
+++ b/client/client/src/web-access/web-access.spec.js
@@ -15,6 +15,7 @@ describe('Component VnClientWebAccess', () => {
$componentController = _$componentController_;
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
+ $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
vnApp = _vnApp_;
spyOn(vnApp, 'showError');
controller = $componentController('vnClientWebAccess', {$scope: $scope});
@@ -37,8 +38,8 @@ describe('Component VnClientWebAccess', () => {
controller.client = {id: '1234'};
controller.isCustomer();
- $httpBackend.when('GET', `/client/api/Clients/${controller.client.id}/getRoleCustomer`).respond('ok');
- $httpBackend.expectGET(`/client/api/Clients/${controller.client.id}/getRoleCustomer`);
+ $httpBackend.when('GET', `/client/api/Clients/${controller.client.id}/hasCustomerRole`).respond('ok');
+ $httpBackend.expectGET(`/client/api/Clients/${controller.client.id}/hasCustomerRole`);
$httpBackend.flush();
});
});
diff --git a/client/core/package.json b/client/core/package.json
deleted file mode 100644
index e85f98a47..000000000
--- a/client/core/package.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "name": "@salix/core",
- "version": "0.0.0",
- "description": "",
- "main": "index.js",
- "repository": {
- "type": "git",
- "url": "https://git.verdnatura.es/salix"
- }
-}
diff --git a/client/core/src/autocomplete/autocomplete.html b/client/core/src/autocomplete/autocomplete.html
index 962798629..df78db6d9 100644
--- a/client/core/src/autocomplete/autocomplete.html
+++ b/client/core/src/autocomplete/autocomplete.html
@@ -1,6 +1,12 @@
-
-
-
+
+
+ {{$parent.item.name}}
+ parent = "$ctrl.element"
+ >{{$parent.item[$ctrl.showField]}}
\ No newline at end of file
diff --git a/client/core/src/autocomplete/autocomplete.js b/client/core/src/autocomplete/autocomplete.js
index 853267592..528c8fadf 100644
--- a/client/core/src/autocomplete/autocomplete.js
+++ b/client/core/src/autocomplete/autocomplete.js
@@ -19,14 +19,15 @@ class Autocomplete extends Component {
this._field = null;
this._preLoad = false;
this.maxRow = 10;
- this.showField = this.showField || 'name';
- this.valueField = this.valueField || 'id';
- this.order = this.order || 'name ASC';
+ this.showField = 'name';
+ this.valueField = 'id';
this.items = copyObject(this.data) || [];
this.displayValueMultiCheck = [];
this._multiField = [];
this.readonly = true;
this.removeLoadMore = false;
+ this.form = null;
+ this.findForm = false;
}
get showDropDown() {
@@ -87,6 +88,7 @@ class Autocomplete extends Component {
if (this.multiple) {
this.setMultiField(value[this.valueField]);
}
+ this.setDirtyForm();
} else {
this.setValue(value);
}
@@ -191,6 +193,10 @@ class Autocomplete extends Component {
return fields;
}
+ getOrder() {
+ return this.order ? this.order : `${this.showField} ASC`;
+ }
+
findItems(search) {
if (this.url && search && !this.finding) {
this.maxRow = false;
@@ -204,7 +210,7 @@ class Autocomplete extends Component {
Object.assign(filter.where, this.filter.where);
}
}
- filter.order = this.order;
+ filter.order = this.getOrder();
let json = JSON.stringify(filter);
this.finding = true;
this.$http.get(`${this.url}?filter=${json}`).then(
@@ -248,7 +254,7 @@ class Autocomplete extends Component {
filter.skip = this.items.length;
}
filter.limit = this.maxRow;
- filter.order = this.order;
+ filter.order = this.getOrder();
}
if (this.filter) {
Object.assign(filter, this.filter);
@@ -284,6 +290,22 @@ class Autocomplete extends Component {
}
}
}
+ _parentForm() {
+ this.findForm = true;
+ let formScope = this.$scope;
+ while (formScope && !formScope.form && formScope.$id > 1) {
+ formScope = formScope.$parent;
+ }
+ this.form = formScope ? formScope.form || null : null;
+ }
+ setDirtyForm() {
+ if (!this.form && !this.findForm) {
+ this._parentForm();
+ }
+ if (this.form) {
+ this.form.$setDirty();
+ }
+ }
$onInit() {
this.findMore = this.url && this.maxRow;
diff --git a/client/core/src/autocomplete/autocomplete.spec.js b/client/core/src/autocomplete/autocomplete.spec.js
index 7f8acb427..3425a84f2 100644
--- a/client/core/src/autocomplete/autocomplete.spec.js
+++ b/client/core/src/autocomplete/autocomplete.spec.js
@@ -16,6 +16,7 @@ describe('Component vnAutocomplete', () => {
$componentController = _$componentController_;
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
+ $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$timeout = _$timeout_;
$element = angular.element('');
controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
@@ -120,7 +121,6 @@ describe('Component vnAutocomplete', () => {
it(`should perform a query if the item id isn't present in the controller.items property`, () => {
controller.url = 'test.com';
- $httpBackend.whenGET(`${controller.url}?filter={"fields":{"id":true,"name":true},"where":{"id":3}}`).respond();
$httpBackend.expectGET(`${controller.url}?filter={"fields":{"id":true,"name":true},"where":{"id":3}}`);
controller.items = [{id: 1, name: 'test1'}, {id: 2, name: 'Bruce Wayne'}];
controller.field = 3;
@@ -136,7 +136,6 @@ describe('Component vnAutocomplete', () => {
it(`should set field performing a query as the item id isn't present in the controller.items property`, () => {
controller.url = 'test.com';
- $httpBackend.whenGET(`${controller.url}?filter={"fields":{"id":true,"name":true},"where":{"id":3}}`).respond();
$httpBackend.expectGET(`${controller.url}?filter={"fields":{"id":true,"name":true},"where":{"id":3}}`);
controller.items = [{id: 1, name: 'test1'}, {id: 2, name: 'Bruce Wayne'}];
controller.field = 3;
@@ -150,7 +149,7 @@ describe('Component vnAutocomplete', () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.url = 'test.com';
let search = 'The Joker';
- let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.order});
+ let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.getOrder()});
$httpBackend.whenGET(`${controller.url}?filter=${json}`).respond([{id: 3, name: 'The Joker'}]);
$httpBackend.expectGET(`${controller.url}?filter=${json}`);
controller.findItems(search);
@@ -163,8 +162,8 @@ describe('Component vnAutocomplete', () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.url = 'test.com';
let search = 'The Joker';
- controller.filterSearch = "{where: {surname: {regexp: 'search'}}}";
- let json = JSON.stringify({where: {surname: {regexp: search}}, order: controller.order});
+ controller.filterSearch = "{where: {name: {regexp: 'search'}}}";
+ let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.getOrder()});
$httpBackend.whenGET(`${controller.url}?filter=${json}`).respond([{id: 3, name: 'The Joker'}]);
$httpBackend.expectGET(`${controller.url}?filter=${json}`);
controller.findItems(search);
@@ -177,7 +176,7 @@ describe('Component vnAutocomplete', () => {
controller.url = 'test.com';
let search = 'Joker';
controller.multiple = true;
- let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.order});
+ let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.getOrder()});
$httpBackend.whenGET(`${controller.url}?filter=${json}`).respond([{id: 3, name: 'The Joker'}, {id: 4, name: 'Joker'}]);
$httpBackend.expectGET(`${controller.url}?filter=${json}`);
controller.findItems(search);
diff --git a/client/core/src/autocomplete/style.scss b/client/core/src/autocomplete/style.scss
index 1472dbcf3..15e59d4d3 100644
--- a/client/core/src/autocomplete/style.scss
+++ b/client/core/src/autocomplete/style.scss
@@ -18,18 +18,23 @@ ul.vn-autocomplete {
}
&.load-more {
color: #ffa410;
- font-weight: bold;
+ font-family: vn-font-bold;
padding: .4em .8em;
}
}
}
vn-autocomplete {
+ position: relative;
+ vn-vertical {
+ outline:none;
+ }
+
.mdl-chip__action {
position: absolute;
top: 0px;
right: -6px;
margin: 22px 0px;
- background-color: white;
+ background: transparent;
}
.material-icons {
font-size: 18px;
diff --git a/client/core/src/column-header/column-header.html b/client/core/src/column-header/column-header.html
index 435057a19..df0a4caee 100644
--- a/client/core/src/column-header/column-header.html
+++ b/client/core/src/column-header/column-header.html
@@ -1,11 +1,22 @@
-
-
- {{::$ctrl.text}}
+
+
+ {{::$ctrl.text}}
-
-
-
+
+
+
diff --git a/client/core/src/column-header/column-header.js b/client/core/src/column-header/column-header.js
index 2f5f4d3c3..51f990db6 100644
--- a/client/core/src/column-header/column-header.js
+++ b/client/core/src/column-header/column-header.js
@@ -1,19 +1,29 @@
import {module} from '../module';
export default class ColumnHeader {
- constructor() {
+ constructor($attrs) {
this.order = undefined;
this.mouseIsOver = false;
+ this.orderLocked = ($attrs.orderLocked !== undefined);
}
- onClick() {
- if (this.order === 'ASC') {
- this.order = 'DESC';
- } else {
- this.order = 'ASC';
+ onClick(event, order) {
+ if (!this.orderLocked) {
+ if (order) {
+ this.order = order;
+ } else if (this.order === 'ASC') {
+ this.order = 'DESC';
+ } else {
+ this.order = 'ASC';
+ }
+ this.gridHeader.selectColum(this);
}
- this.gridHeader.selectColum(this);
+ if (event)
+ event.preventDefault();
}
showArrow(type) {
+ if (this.orderLocked)
+ return false;
+
let showArrow = (this.gridHeader && this.gridHeader.currentColumn && this.gridHeader.currentColumn.field === this.field && this.order === type);
let showOther = (this.gridHeader && this.gridHeader.currentColumn && this.gridHeader.currentColumn.field === this.field && this.order !== type);
if (type === 'DESC' && this.mouseIsOver && !showOther) {
@@ -22,13 +32,13 @@ export default class ColumnHeader {
return showArrow;
}
$onInit() {
- if (this.defaultOrder) {
+ if (this.defaultOrder && !this.orderLocked) {
this.order = this.defaultOrder;
this.onClick();
}
}
}
-ColumnHeader.$inject = [];
+ColumnHeader.$inject = ['$attrs'];
module.component('vnColumnHeader', {
template: require('./column-header.html'),
diff --git a/client/core/src/column-header/column-header.spec.js b/client/core/src/column-header/column-header.spec.js
index 5c6d29d73..2291094b3 100644
--- a/client/core/src/column-header/column-header.spec.js
+++ b/client/core/src/column-header/column-header.spec.js
@@ -3,6 +3,8 @@ import './column-header.js';
describe('Component vnColumnHeader', () => {
let $componentController;
let controller;
+ let $event;
+ let $attrs;
beforeEach(() => {
angular.mock.module('client');
@@ -10,14 +12,18 @@ describe('Component vnColumnHeader', () => {
beforeEach(angular.mock.inject(_$componentController_ => {
$componentController = _$componentController_;
- controller = $componentController('vnColumnHeader', {});
+ $event = {
+ preventDefault: () => {}
+ };
+ $attrs = {};
+ controller = $componentController('vnColumnHeader', {$attrs});
}));
describe('onClick()', () => {
it(`should change the ordenation to DESC (descendant) if it was ASC (ascendant)`, () => {
controller.gridHeader = {selectColum: () => {}};
controller.order = 'ASC';
- controller.onClick();
+ controller.onClick($event);
expect(controller.order).toEqual('DESC');
});
@@ -25,7 +31,7 @@ describe('Component vnColumnHeader', () => {
it(`should change the ordenation to ASC (ascendant) if it wasnt ASC`, () => {
controller.gridHeader = {selectColum: () => {}};
controller.order = 'DESC or any other value that might occur';
- controller.onClick();
+ controller.onClick($event);
expect(controller.order).toEqual('ASC');
});
@@ -34,7 +40,7 @@ describe('Component vnColumnHeader', () => {
controller.gridHeader = {selectColum: () => {}};
controller.order = 'Change me!';
spyOn(controller.gridHeader, 'selectColum');
- controller.onClick();
+ controller.onClick($event);
expect(controller.gridHeader.selectColum).toHaveBeenCalledWith(controller);
});
diff --git a/client/core/src/confirm/style.css b/client/core/src/confirm/style.css
index 7177560fd..b19288ecb 100644
--- a/client/core/src/confirm/style.css
+++ b/client/core/src/confirm/style.css
@@ -1,5 +1,5 @@
-
-vn-confirm .dialog-title {
- color:#424242;
- font-family: raleway-bold;
-}
+
+vn-confirm .dialog-title {
+ color:#424242;
+ font-family: vn-font-bold;
+}
diff --git a/client/core/src/datePicker/datePicker.html b/client/core/src/datePicker/datePicker.html
index d91f0a53f..21839aa16 100644
--- a/client/core/src/datePicker/datePicker.html
+++ b/client/core/src/datePicker/datePicker.html
@@ -4,7 +4,8 @@
ng-focus="$ctrl.hasFocus = true"
ng-blur="$ctrl.hasFocus = false"
ng-mouseenter="$ctrl.hasMouseIn = true"
- ng-mouseleave="$ctrl.hasMouseIn = false"
+ ng-mouseleave="$ctrl.hasMouseIn = false"
+ ng-click="$ctrl.onClick()"
>
{
+ this.model = value;
+ }, 250);
+ } else {
+ this.model = null;
+ this.modelView = '';
+ this._waitingInit = 0;
}
}
get modelView() {
@@ -52,17 +77,23 @@ class DatePicker extends Component {
this._modelView = value;
this.input.value = value;
this._setModel(value);
- this.$timeout(() => {
- this.mdlUpdate();
- }, 500);
+ this.mdlUpdate();
}
+
onClear() {
this.modelView = null;
}
+ onClick() {
+ if (this.vp) {
+ this.vp.open();
+ }
+ }
mdlUpdate() {
- let mdlField = this.element.firstChild.MaterialTextfield;
- if (mdlField)
- mdlField.updateClasses_();
+ this.$timeout(() => {
+ let mdlField = this.element.firstChild.MaterialTextfield;
+ if (mdlField)
+ mdlField.updateClasses_();
+ }, 500);
}
_formatFlat2Angular(string) { // change string Flatpickr format to angular format (d-m-Y -> dd-MM-yyyy)
@@ -83,54 +114,59 @@ class DatePicker extends Component {
return parts.join('-');
}
+ _string2BackFormat(value) {
+ let formats = this.iniOptions.dateFormat.split(/[ZT.,/ :-]/);
+ let aux = value.split(/[ZT.,/ :-]/);
+ let date = {};
+ formats.forEach(
+ (k, i) => {
+ if (k.toLowerCase() === 'y') {
+ date.year = aux[i];
+ } else if (k === 'm' || k === 'n') {
+ date.month = aux[i];
+ } else if (k === 'd' || k === 'j') {
+ date.day = aux[i];
+ } else if (k.toLowerCase() === 'h') {
+ date.hour = aux[i];
+ } else if (k === 'i') {
+ date.minutes = aux[i];
+ } else if (k === 's') {
+ date.seccons = aux[i];
+ }
+ }
+ );
+
+ let dateStr = '';
+ let hourStr = '';
+
+ if (date.year && date.month && date.day) {
+ dateStr = `${date.year}-${date.month}-${date.day}`;
+ }
+ if (date.hour) {
+ hourStr = date.hour;
+ if (date.minutes) {
+ hourStr += ':' + date.minutes;
+ } else {
+ hourStr += ':00';
+ }
+ if (date.seccons) {
+ hourStr += ':' + date.seccons;
+ } else {
+ hourStr += ':00';
+ }
+ }
+ return `${dateStr} ${hourStr}`.trim();
+ }
+
_setModel(value) {
let model;
+ let options = this._getOptions();
if (!value) {
model = undefined;
- } else if (!this.iniOptions || (this.iniOptions.dateFormat && this.iniOptions.dateFormat.startsWith('Y-m-d'))) {
+ } else if (!options || (options.dateFormat && options.dateFormat.startsWith('Y-m-d'))) {
model = value;
} else {
- let formats = this.iniOptions.dateFormat.split(/[ZT.,/ :-]/);
- let aux = value.split(/[ZT.,/ :-]/);
- let date = {};
- formats.forEach(
- (k, i) => {
- if (k.toLowerCase() === 'y') {
- date.year = aux[i];
- } else if (k === 'm' || k === 'n') {
- date.month = aux[i];
- } else if (k === 'd' || k === 'j') {
- date.day = aux[i];
- } else if (k.toLowerCase() === 'h') {
- date.hour = aux[i];
- } else if (k === 'i') {
- date.minutes = aux[i];
- } else if (k === 's') {
- date.seccons = aux[i];
- }
- }
- );
-
- let dateStr = '';
- let hourStr = '';
-
- if (date.year && date.month && date.day) {
- dateStr = `${date.year}-${date.month}-${date.day}`;
- }
- if (date.hour) {
- hourStr = date.hour;
- if (date.minutes) {
- hourStr += ':' + date.minutes;
- } else {
- hourStr += ':00';
- }
- if (date.seccons) {
- hourStr += ':' + date.seccons;
- } else {
- hourStr += ':00';
- }
- }
- model = `${dateStr} ${hourStr}`.trim();
+ model = this._string2BackFormat(value);
}
if (this.model !== model) {
@@ -148,8 +184,8 @@ class DatePicker extends Component {
if (!this.iniOptions.locale)
this.iniOptions.locale = this.$translate.use();
- if (!this.iniOptions.dateFormat && this.iniOptions.locale === 'es')
- this.iniOptions.dateFormat = 'd-m-Y';
+ if (!this.iniOptions.dateFormat)
+ this.iniOptions.dateFormat = this.iniOptions.locale === 'es' ? 'd-m-Y' : 'Y-m-d';
else if (this.iniOptions.dateFormat) {
let format = this.iniOptions.dateFormat.split(/[ZT.,/ :-]/);
if (format.length <= 1) {
@@ -163,21 +199,44 @@ class DatePicker extends Component {
}
);
}
+
+ if (this.$attrs.hasOwnProperty('today')) {
+ this.iniOptions.defaultDate = new Date();
+ }
+
this._optionsChecked = true;
return this.iniOptions;
}
- $onInit() {
+ initPicker() {
this.iniOptions = this._getOptions();
this.isTimePicker = (this.iniOptions && this.iniOptions.enableTime && this.iniOptions.noCalendar);
this.vp = new Flatpickr(this.input, this.iniOptions);
+ if (this.iniOptions.defaultDate) {
+ this.modelView = this.vp.formatDate(this.iniOptions.defaultDate, this.iniOptions.dateFormat);
+ }
}
- $onDestroy() {
+ destroyPicker() {
if (this.vp)
this.vp.destroy();
+ this.vp = undefined;
+ }
+
+ $onChanges(objChange) {
+ if (objChange.iniOptions && objChange.iniOptions.currentValue) {
+ this.iniOptions = Object.assign(this.iniOptions, objChange.iniOptions.currentValue);
+ }
+ }
+
+ $onInit() {
+ this.initPicker();
+ }
+
+ $onDestroy() {
+ this.destroyPicker();
}
}
-DatePicker.$inject = ['$element', '$translate', '$filter', '$timeout'];
+DatePicker.$inject = ['$element', '$translate', '$filter', '$timeout', '$attrs'];
module.component('vnDatePicker', {
template: require('./datePicker.html'),
diff --git a/client/core/src/datePicker/datePicker.spec.js b/client/core/src/datePicker/datePicker.spec.js
index bd0807dd7..05ff49d4f 100644
--- a/client/core/src/datePicker/datePicker.spec.js
+++ b/client/core/src/datePicker/datePicker.spec.js
@@ -2,9 +2,10 @@ import './datePicker.js';
describe('Component vnDatePicker', () => {
let $componentController;
- let $scope;
+ let $filter;
let $timeout;
let $element;
+ let $attrs;
let $translate;
let controller;
@@ -12,13 +13,14 @@ describe('Component vnDatePicker', () => {
angular.mock.module('client');
});
- beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$timeout_, _$translate_) => {
+ beforeEach(angular.mock.inject((_$componentController_, _$filter_, _$timeout_, _$translate_) => {
$componentController = _$componentController_;
- $scope = $rootScope.$new();
+ $filter = _$filter_;
$timeout = _$timeout_;
$element = angular.element(``);
$translate = _$translate_;
- controller = $componentController('vnDatePicker', {$scope, $element, $translate, $timeout});
+ $attrs = {};
+ controller = $componentController('vnDatePicker', {$element, $translate, $filter, $timeout, $attrs});
}));
describe('_formatFlat2Angular()', () => {
@@ -36,6 +38,7 @@ describe('Component vnDatePicker', () => {
it(`should split the given string into parts`, () => {
controller.iniOptions = {dateFormat: 'd/m/Y'};
+ controller._optionsChecked = true;
controller.model = '2017-12-23';
expect(controller.modelView).toBe('23-12-2017');
diff --git a/client/core/src/dialog/style.scss b/client/core/src/dialog/style.scss
index 1b30f5e98..73bd5ba19 100644
--- a/client/core/src/dialog/style.scss
+++ b/client/core/src/dialog/style.scss
@@ -1,68 +1,68 @@
-.vn-dialog {
- display: none;
- z-index: 100;
- position: fixed;
- left: 0;
- top: 0;
- height: 100%;
- width: 100%;
- background-color: rgba(1,1,1,.4);
-
- button.close {
- position: absolute;
- top: 0;
- right: 0;
- border-style: none;
- background-color: transparent;
- padding: .3em;
- cursor: pointer;
-
- vn-icon {
- display: block;
-
- i {
- display: block;
- }
- }
-
- &:hover {
- background-color: rgba(0, 0, 0, .1);
- }
- }
- & > div {
- position: relative;
- box-shadow: 0 0 .4em rgba(1,1,1,.4);
- background-color: white;
- border-radius: .2em;
- overflow: auto;
- top: 50%;
- left: 50%;
- padding: 2em;
- box-sizing: border-box;
-
- width: 28em;
- margin-top: -10em;
- margin-left: -14em;
- }
- .button-bar {
- margin-top: 1.5em;
- text-align: right;
-
- button {
- background: none;
- border: none;
- text-transform: uppercase;
- font-size: 1.1em;
- color: #ffa410;
- font-weight: bold;
- cursor: pointer;
- padding: .5em;
- margin: -0.5em;
- margin-left: .5em;
-
- &:hover {
- background-color: rgba(1,1,1,.1);
- }
- }
- }
-}
+.vn-dialog {
+ display: none;
+ z-index: 100;
+ position: fixed;
+ left: 0;
+ top: 0;
+ height: 100%;
+ width: 100%;
+ background-color: rgba(1,1,1,.4);
+
+ button.close {
+ position: absolute;
+ top: 0;
+ right: 0;
+ border-style: none;
+ background-color: transparent;
+ padding: .3em;
+ cursor: pointer;
+
+ vn-icon {
+ display: block;
+
+ i {
+ display: block;
+ }
+ }
+
+ &:hover {
+ background-color: rgba(0, 0, 0, .1);
+ }
+ }
+ & > div {
+ position: relative;
+ box-shadow: 0 0 .4em rgba(1,1,1,.4);
+ background-color: white;
+ border-radius: .2em;
+ overflow: auto;
+ top: 50%;
+ left: 50%;
+ padding: 2em;
+ box-sizing: border-box;
+
+ width: 28em;
+ margin-top: -10em;
+ margin-left: -14em;
+ }
+ .button-bar {
+ margin-top: 1.5em;
+ text-align: right;
+
+ button {
+ background: none;
+ border: none;
+ text-transform: uppercase;
+ font-size: 1.1em;
+ color: #ffa410;
+ font-family: vn-font-bold;
+ cursor: pointer;
+ padding: .5em;
+ margin: -0.5em;
+ margin-left: .5em;
+
+ &:hover {
+ background-color: rgba(1,1,1,.1);
+ }
+ }
+ }
+}
diff --git a/client/core/src/directives/acl.js b/client/core/src/directives/acl.js
index 362dad65d..f29142e1f 100644
--- a/client/core/src/directives/acl.js
+++ b/client/core/src/directives/acl.js
@@ -1,6 +1,25 @@
import {module} from '../module';
function vnAcl(aclService, $timeout) {
+ function getMaterialType(className) {
+ let type = '';
+ if (className) {
+ type = className.replace('mdl-', '').replace('__input', '');
+ type = type.charAt(0).toUpperCase() + type.slice(1);
+ }
+ return type;
+ }
+ function udateMaterial(input) {
+ if (input && input.className) {
+ let find = input.className.match(/mdl-[\w]+input/g);
+ if (find && find.length && find[0]) {
+ let type = getMaterialType(find[0]);
+ if (type && input.parentNode[`Material${type}`] && input.parentNode[`Material${type}`].updateClasses_) {
+ input.parentNode[`Material${type}`].updateClasses_();
+ }
+ }
+ }
+ }
return {
restrict: 'A',
priority: -1,
@@ -18,6 +37,7 @@ function vnAcl(aclService, $timeout) {
if (input) {
$timeout(() => {
input.setAttribute("disabled", "true");
+ udateMaterial(input);
});
$element[0].querySelectorAll('i, vn-drop-down').forEach(element => {
element.parentNode.removeChild(element);
diff --git a/client/core/src/directives/index.js b/client/core/src/directives/index.js
index 4bd512eb0..67093faa8 100644
--- a/client/core/src/directives/index.js
+++ b/client/core/src/directives/index.js
@@ -3,3 +3,4 @@ import './focus';
import './dialog';
import './validation';
import './acl';
+import './onErrorSrc';
diff --git a/client/core/src/directives/onErrorSrc.js b/client/core/src/directives/onErrorSrc.js
new file mode 100644
index 000000000..fd3cf2b82
--- /dev/null
+++ b/client/core/src/directives/onErrorSrc.js
@@ -0,0 +1,17 @@
+import {module} from '../module';
+
+function onErrorSrc() {
+ return {
+ restrict: 'A',
+ link: (scope, element, attrs) => {
+ let imgError = '/static/images/no-image200x200.png';
+ element.bind('error', function() {
+ if (attrs.src != imgError) {
+ attrs.$set('src', imgError);
+ }
+ });
+ }
+ };
+}
+
+module.directive('onErrorSrc', onErrorSrc);
diff --git a/client/core/src/directives/specs/acl.spec.js b/client/core/src/directives/specs/acl.spec.js
index 615de5146..a8c1afc42 100644
--- a/client/core/src/directives/specs/acl.spec.js
+++ b/client/core/src/directives/specs/acl.spec.js
@@ -9,7 +9,8 @@ describe('Directive acl', () => {
});
compile = (hasPermissions, _element) => {
- inject(($compile, $rootScope, aclService, _$timeout_) => {
+ inject(($compile, $rootScope, aclService, _$timeout_, _$httpBackend_) => {
+ _$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
spyOn(aclService, 'aclPermission').and.returnValue(hasPermissions);
scope = $rootScope.$new();
$timeout = _$timeout_;
diff --git a/client/core/src/directives/specs/dialog.spec.js b/client/core/src/directives/specs/dialog.spec.js
index 38d4ed9e2..f2d20ae0e 100644
--- a/client/core/src/directives/specs/dialog.spec.js
+++ b/client/core/src/directives/specs/dialog.spec.js
@@ -20,8 +20,9 @@ describe('Directive dialog', () => {
});
};
- beforeEach(angular.mock.inject(_$componentController_ => {
+ beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => {
$componentController = _$componentController_;
+ _$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$element = angular.element('');
controller = $componentController('vnDialog', {$element});
}));
diff --git a/client/core/src/directives/specs/focus.spec.js b/client/core/src/directives/specs/focus.spec.js
index f610e9ffb..6b13c98a3 100644
--- a/client/core/src/directives/specs/focus.spec.js
+++ b/client/core/src/directives/specs/focus.spec.js
@@ -8,12 +8,11 @@ describe('Directive focus', () => {
});
compile = (_element, _childElement) => {
- inject(($compile, $rootScope) => {
+ inject(($compile, $rootScope, _$httpBackend_) => {
$scope = $rootScope.$new();
+ _$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$element = angular.element(_element);
if (_childElement) {
- let childElement = angular.element(_childElement);
- $element[0] < childElement;
$element[0].firstChild.focus = jasmine.createSpy(focus);
}
$element[0].focus = jasmine.createSpy('focus');
diff --git a/client/core/src/directives/specs/id.spec.js b/client/core/src/directives/specs/id.spec.js
index 5755d1ea9..5c6fb0baa 100644
--- a/client/core/src/directives/specs/id.spec.js
+++ b/client/core/src/directives/specs/id.spec.js
@@ -8,8 +8,9 @@ describe('Directive vnId', () => {
});
compile = _element => {
- inject(($compile, $rootScope) => {
+ inject(($compile, $rootScope, _$httpBackend_) => {
$scope = $rootScope.$new();
+ _$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$element = angular.element(_element);
$compile($element)($scope);
$scope.$digest();
diff --git a/client/core/src/directives/specs/validation.spec.js b/client/core/src/directives/specs/validation.spec.js
index e69de29bb..26208e34b 100644
--- a/client/core/src/directives/specs/validation.spec.js
+++ b/client/core/src/directives/specs/validation.spec.js
@@ -0,0 +1,187 @@
+describe('Directive validation', () => {
+ let scope;
+ let element;
+ let compile;
+
+ beforeEach(() => {
+ angular.mock.module('client');
+ });
+
+ compile = (_element, validations, value) => {
+ inject(($compile, $rootScope, aclService, _$timeout_, $window, _$httpBackend_) => {
+ $window.validations = validations;
+ _$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
+ scope = $rootScope.$new();
+ scope.user = {name: value};
+ element = angular.element(_element);
+ $compile(element)(scope);
+ scope.$digest();
+ });
+ };
+
+ it(`should throw an error if the vnValidation doesn't have the right syntax`, () => {
+ let html = ``;
+
+ expect(() => {
+ compile(html, {});
+ }).toThrow(new Error(`vnValidation: Attribute must have this syntax: [entity].[field]`));
+ });
+
+ it('should throw an error if the window.validations aint defined', () => {
+ let html = ``;
+
+ expect(() => {
+ compile(html, {});
+ }).toThrow(new Error(`vnValidation: Entity 'User' doesn't exist`));
+ });
+
+ describe('Validator presence()', () => {
+ it('should not validate the user name as it is an empty string', () => {
+ let html = ``;
+ let validations = {User: {validations: {name: [{validation: 'presence'}]}}};
+ compile(html, validations, 'Spiderman');
+ scope.user.name = '';
+ scope.$digest();
+
+ expect(element[0].classList).toContain('ng-invalid');
+ expect(element[0].classList).not.toContain('ng-valid');
+ });
+ });
+
+ describe('Validator absence()', () => {
+ it('should not validate the entity as it should be an empty string', () => {
+ let html = ``;
+ let validations = {User: {validations: {name: [{validation: 'absence'}]}}};
+ compile(html, validations, 'Spiderman');
+ scope.$digest();
+
+ expect(element[0].classList).toContain('ng-invalid');
+ expect(element[0].classList).not.toContain('ng-valid');
+ });
+
+ it('should validate the entity as it is an empty string', () => {
+ let html = ``;
+ let validations = {User: {validations: {name: [{validation: 'absence'}]}}};
+ compile(html, validations, '');
+ scope.$digest();
+
+ expect(element[0].classList).toContain('ng-valid');
+ expect(element[0].classList).not.toContain('ng-invalid');
+ });
+ });
+
+ describe('Validator length()', () => {
+ it('should not validate the user name as it should have min length of 15', () => {
+ let html = ``;
+ let validations = {User: {validations: {name: [{validation: 'length', min: 10, max: 50, is: 15}]}}};
+ compile(html, validations, 'fifteen!');
+ scope.$digest();
+
+ expect(element[0].classList).toContain('ng-invalid');
+ expect(element[0].classList).not.toContain('ng-valid');
+ });
+
+ it('should validate the user name as it has length of 15', () => {
+ let html = ``;
+ let validations = {User: {validations: {name: [{validation: 'length', min: 10, max: 50, is: 15}]}}};
+ compile(html, validations, 'fifteen length!');
+ scope.$digest();
+
+ expect(element[0].classList).toContain('ng-valid');
+ expect(element[0].classList).not.toContain('ng-invalid');
+ });
+
+ it('should not validate the user name as it should have min length of 10', () => {
+ let html = ``;
+ let validations = {User: {validations: {name: [{validation: 'length', min: 10}]}}};
+ compile(html, validations, 'shortname');
+ scope.$digest();
+
+ expect(element[0].classList).toContain('ng-invalid');
+ expect(element[0].classList).not.toContain('ng-valid');
+ });
+
+ it('should validate the user name as its length is greater then the minimum', () => {
+ let html = ``;
+ let validations = {User: {validations: {name: [{validation: 'length', min: 10}]}}};
+ compile(html, validations, 'verylongname');
+ scope.$digest();
+
+ expect(element[0].classList).toContain('ng-valid');
+ expect(element[0].classList).not.toContain('ng-invalid');
+ });
+
+ it('should not validate the user name as its length is greater then the maximum', () => {
+ let html = ``;
+ let validations = {User: {validations: {name: [{validation: 'length', max: 10}]}}};
+ compile(html, validations, 'toolongname');
+ scope.$digest();
+
+ expect(element[0].classList).toContain('ng-invalid');
+ expect(element[0].classList).not.toContain('ng-valid');
+ });
+ });
+
+ describe('Validator numericality()', () => {
+ it('should not validate the phone number as it should a integer', () => {
+ let html = ``;
+ let validations = {User: {validations: {phone: [{validation: 'numericality', is: 'what is this?'}]}}};
+ compile(html, validations, 'spiderman');
+ scope.user.phone = 'this is not a phone number!';
+ scope.$digest();
+
+ expect(element[0].classList).toContain('ng-invalid');
+ expect(element[0].classList).not.toContain('ng-valid');
+ });
+
+ it('should validate the phone number as it an integer', () => {
+ let html = ``;
+ let validations = {User: {validations: {phone: [{validation: 'numericality', is: 'what is this?'}]}}};
+ compile(html, validations, 'spiderman');
+ scope.user.phone = '555555555';
+ scope.$digest();
+
+ expect(element[0].classList).toContain('ng-valid');
+ expect(element[0].classList).not.toContain('ng-invalid');
+ });
+ });
+
+ describe('Validator inclusion()', () => {
+ it('should not validate the phone number as it is not an integer', () => {
+ let html = ``;
+ let validations = {User: {validations: {phone: [{validation: 'inclusion'}]}}};
+ compile(html, validations, 'spiderman');
+ scope.user.phone = 'this is not a phone number!';
+ scope.$digest();
+
+ expect(element[0].classList).toContain('ng-invalid');
+ expect(element[0].classList).not.toContain('ng-valid');
+ });
+ });
+
+ describe('Validator exclusion()', () => {
+ it('should validate the phone number as it is an integer', () => {
+ let html = ``;
+ let validations = {User: {validations: {phone: [{validation: 'exclusion'}]}}};
+ compile(html, validations, 'spiderman');
+ scope.user.phone = '555555555';
+ scope.$digest();
+
+ expect(element[0].classList).toContain('ng-valid');
+ expect(element[0].classList).not.toContain('ng-invalid');
+ });
+ });
+
+ describe('Validator format()', () => {
+ it('should not validate the email number as it doesnt contain @', () => {
+ let html = ``;
+ let validations = {User: {validations: {email: [{validation: 'format', with: '@'}]}}};
+ compile(html, validations, 'spiderman');
+ scope.user.email = 'userverdnatura.es';
+ scope.$digest();
+
+ expect(element[0].classList).toContain('ng-invalid');
+ expect(element[0].classList).not.toContain('ng-valid');
+ });
+ });
+});
diff --git a/client/core/src/directives/validation.js b/client/core/src/directives/validation.js
index cd3878d9e..09c4a9f8e 100644
--- a/client/core/src/directives/validation.js
+++ b/client/core/src/directives/validation.js
@@ -29,7 +29,6 @@ export function directive(interpolate, compile, $window) {
throw new Error(`vnValidation: Entity '${entityName}' doesn't exist`);
let validations = entity.validations[fieldName];
-
if (!validations || validations.length == 0)
return;
@@ -40,7 +39,7 @@ export function directive(interpolate, compile, $window) {
let errorShown = false;
input.$options.$$options.allowInvalid = true;
- input.$validators.entity = function(value) {
+ input.$validators.entity = value => {
try {
validateAll(value, validations);
return true;
@@ -51,9 +50,9 @@ export function directive(interpolate, compile, $window) {
}
};
- scope.$watch(function() {
+ scope.$watch(() => {
return (form.$submitted || input.$dirty) && input.$invalid;
- }, function(value) {
+ }, value => {
let parent = element.parent();
if (value) {
diff --git a/client/core/src/drop-down/drop-down.html b/client/core/src/drop-down/drop-down.html
index 9bab06069..e155c67af 100644
--- a/client/core/src/drop-down/drop-down.html
+++ b/client/core/src/drop-down/drop-down.html
@@ -1,13 +1,13 @@
-
-
+
+
-
-
+
+
-
+
\ No newline at end of file
diff --git a/client/core/src/drop-down/drop-down.js b/client/core/src/drop-down/drop-down.js
index 90a02e857..5a3666651 100644
--- a/client/core/src/drop-down/drop-down.js
+++ b/client/core/src/drop-down/drop-down.js
@@ -1,5 +1,6 @@
import {module} from '../module';
import './style.scss';
+import validKey from '../lib/keyCodes';
export default class DropDown {
constructor($element, $filter, $timeout) {
@@ -7,26 +8,36 @@ export default class DropDown {
this.$filter = $filter;
this.$timeout = $timeout;
- this.parent = this.parent || $element[0].parentNode;
this._search = null;
this.itemsFiltered = [];
this._activeOption = -1;
this._focusingFilter = false;
+ this._tryToShow = 0;
}
+
+ get container() {
+ return this.$element[0].querySelector('ul.dropdown');
+ }
+
get show() {
return this._show;
}
set show(value) {
let oldValue = this.show;
- this._show = value;
- if (value && !this._focusingFilter && oldValue !== value && this.filter) {
- let inputFilterSearch = this.$element[0].querySelector('input');
- this._focusingFilter = true;
+ // It wait up to 1 second if the dropdown opens but there is no data to show
+ if (value && !oldValue && !this.itemsFiltered.length && this._tryToShow < 4) {
this.$timeout(() => {
- inputFilterSearch.focus();
- this._focusingFilter = false;
+ this._tryToShow++;
+ this.show = true;
+ if (this.activeOption === -1) {
+ this.activeOption = 0;
+ }
}, 250);
+ } else {
+ this._tryToShow = 0;
+ this._show = value;
+ this._toggleDropDown(value, oldValue);
}
}
@@ -51,18 +62,92 @@ export default class DropDown {
set activeOption(value) {
if (value < 0) {
value = 0;
- } else if (value >= this.items.length) {
- value = this.showLoadMore ? this.items.length : this.items.length - 1;
+ } else if (value >= this.itemsFiltered.length) {
+ value = this.showLoadMore ? this.itemsFiltered.length : this.itemsFiltered.length - 1;
}
this.$timeout(() => {
this._activeOption = value;
- // AutoLoad items with "scroll" (1st version):
- if (value && value >= this.items.length - 3 && !this.removeLoadMore) {
+ if (value && value >= this.itemsFiltered.length - 3 && !this.removeLoadMore) {
this.loadItems();
}
});
}
+ _toggleDropDown(value, oldValue) {
+ this.$timeout(() => {
+ this._eventScroll(value);
+ this._calculatePosition(value, oldValue);
+ });
+ }
+
+ _eventScroll(add, num) {
+ let count = num || 0;
+ if (add) {
+ if (this.container) {
+ this.container.addEventListener('scroll', e => this.loadFromScroll(e));
+ // this.$timeout(() => { // falla al entrar por primera vez xq pierde el foco y cierra el dropdown
+ // this._setFocusInFilterInput();
+ // });
+ } else if (count < 4) {
+ count++;
+ this.$timeout(() => { // wait angular ngIf
+ this._eventScroll(add, count);
+ }, 250);
+ }
+ } else if (this.container) {
+ this.container.removeEventListener('scroll', e => this.loadFromScroll(e));
+ }
+ }
+
+ _setFocusInFilterInput() {
+ let inputFilterSearch = this.$element[0].querySelector('input');
+ this._focusingFilter = true;
+ if (inputFilterSearch)
+ this.$timeout(() => {
+ inputFilterSearch.focus();
+ this._focusingFilter = false;
+ }, 250);
+ }
+
+ _background(create) {
+ let el = document.getElementById('ddBack');
+ if (el) {
+ el.parentNode.removeChild(el);
+ }
+
+ if (create) {
+ el = document.createElement('div');
+ el.id = 'ddBack';
+ document.body.appendChild(el);
+ }
+ }
+ _calculatePosition(value, oldValue) {
+ if (value && !oldValue) {
+ if (this.parent === undefined) {
+ this.parent = this.$element.parent().parent();
+ }
+
+ let parentRect = this.parent.getBoundingClientRect();
+ let elemetRect = this.$element[0].getBoundingClientRect();
+ let instOffset = parentRect.bottom + elemetRect.height;
+
+ if (instOffset >= window.innerHeight) {
+ this._background(true);
+ this.$element.addClass('fixed-dropDown');
+ this.$element.css('top', `${(parentRect.top - elemetRect.height)}px`);
+ this.$element.css('left', `${(parentRect.x)}px`);
+ this.$element.css('height', `${elemetRect.height}px`);
+ }
+ } else if (!value && oldValue) {
+ this.$element.removeAttr('style');
+ if (this.itemWidth) {
+ this.$element.css('width', this.itemWidth + 'px');
+ }
+ this.$element.removeClass('fixed-dropDown');
+ this._background();
+ }
+ }
+
filterItems() {
this.itemsFiltered = this.search ? this.$filter('filter')(this.items, this.search) : this.items;
}
@@ -73,18 +158,6 @@ export default class DropDown {
}
}
- $onChanges(changesObj) {
- if (changesObj.show && changesObj.top && changesObj.top.currentValue) {
- this.$element.css('top', changesObj.top.currentValue + 'px');
- }
- if (changesObj.show && changesObj.itemWidth && changesObj.itemWidth.currentValue) {
- this.$element.css('width', changesObj.itemWidth.currentValue + 'px');
- }
- if (changesObj.items) {
- this.filterItems();
- }
- }
-
clearSearch() {
this.search = null;
}
@@ -101,42 +174,55 @@ export default class DropDown {
onKeydown(event) {
if (this.show) {
- switch (event.keyCode) {
- case 13: // Enter
+ if (event.keyCode === 13) { // Enter
this.$timeout(() => {
this.selectOption();
});
event.preventDefault();
- break;
- case 27: // Escape
+ } else if (event.keyCode === 27) { // Escape
this.clearSearch();
- break;
- case 38: // Arrow up
+ } else if (event.keyCode === 38) { // Arrow up
this.activeOption--;
this.$timeout(() => {
this.setScrollPosition();
}, 100);
- break;
- case 40: // Arrow down
+ } else if (event.keyCode === 40) { // Arrow down
this.activeOption++;
this.$timeout(() => {
this.setScrollPosition();
}, 100);
- break;
- default:
- return;
+ } else if (event.keyCode === 35) { // End
+ this.activeOption = this.itemsFiltered.length - 1;
+ this.$timeout(() => {
+ this.setScrollPosition();
+ }, 100);
+ } else if (event.keyCode === 36) { // Start
+ this.activeOption = 0;
+ this.$timeout(() => {
+ this.setScrollPosition();
+ }, 100);
+ } else if (this.filter) {
+ let oldValue = this.search || '';
+ if (validKey(event)) {
+ this.search = oldValue + String.fromCharCode(event.keyCode);
+ } else if (event.keyCode === 8) { // backSpace
+ this.search = oldValue.slice(0, -1);
+ }
+ } /* else {
+ console.error(`Error: keyCode ${event.keyCode} not supported`);
+ } */
+ }
+ }
+ setScrollPosition() {
+ let child = this.$element[0].querySelector('ul.dropdown li.active');
+ if (child) {
+ let childRect = child.getBoundingClientRect();
+ let containerRect = this.container.getBoundingClientRect();
+ if (typeof child.scrollIntoView === 'function' && (childRect.top > containerRect.top + containerRect.height || childRect.top < containerRect.top)) {
+ child.scrollIntoView();
}
}
}
-
- setScrollPosition() {
- let dropdown = this.$element[0].querySelector('ul.dropdown');
- let child = dropdown ? dropdown.childNodes[this.activeOption] : null;
- if (child && typeof child.scrollIntoView === 'function') {
- child.scrollIntoView();
- }
- }
-
selectItem(item) {
this.selected = item;
if (this.multiple) {
@@ -146,14 +232,26 @@ export default class DropDown {
this.show = false;
}
}
-
loadItems() {
if (this.showLoadMore && this.loadMore) {
this.loadMore();
}
this.show = true;
}
-
+ loadFromScroll(e) {
+ let containerRect = e.target.getBoundingClientRect();
+ if (e.target.scrollHeight - e.target.scrollTop - containerRect.height <= 50) {
+ this.loadItems();
+ }
+ }
+ $onChanges(changesObj) {
+ if (changesObj.show && changesObj.itemWidth && changesObj.itemWidth.currentValue) {
+ this.$element.css('width', changesObj.itemWidth.currentValue + 'px');
+ }
+ if (changesObj.items) {
+ this.filterItems();
+ }
+ }
$onInit() {
if (this.parent)
this.parent.addEventListener('keydown', e => this.onKeydown(e));
@@ -179,7 +277,6 @@ module.component('vnDropDown', {
removeLoadMore: '',
filterAction: '&?',
showLoadMore: '',
- top: '',
itemWidth: '',
parent: '',
multiple: ''
diff --git a/client/core/src/drop-down/drop-down.spec.js b/client/core/src/drop-down/drop-down.spec.js
index 23fab3b08..13facff6b 100644
--- a/client/core/src/drop-down/drop-down.spec.js
+++ b/client/core/src/drop-down/drop-down.spec.js
@@ -11,12 +11,14 @@ describe('Component vnDropDown', () => {
angular.mock.module('client');
});
- beforeEach(angular.mock.inject((_$componentController_, _$timeout_, _$filter_) => {
+ beforeEach(angular.mock.inject((_$componentController_, _$timeout_, _$filter_, _$httpBackend_) => {
+ _$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$componentController = _$componentController_;
$element = angular.element('');
$timeout = _$timeout_;
$filter = _$filter_;
controller = $componentController('vnDropDown', {$element, $timeout, $filter});
+ controller.parent = angular.element('')[0];
}));
describe('show() setter', () => {
@@ -62,43 +64,43 @@ describe('Component vnDropDown', () => {
it(`should set _activeOption as items.length if showLoadMore is defined if activeOption is bigger than items.length then call loadItems()`, () => {
spyOn(controller, 'loadItems');
controller.showLoadMore = true;
- controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
+ controller.itemsFiltered = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
controller.activeOption = 10;
$timeout.flush();
- expect(controller._activeOption).toEqual(4);
+ expect(controller.activeOption).toEqual(4);
expect(controller.loadItems).toHaveBeenCalledWith();
});
it(`should set _activeOption as activeOption if showLoadMore is defined if activeOption is smaller than items.length then call loadItems()`, () => {
spyOn(controller, 'loadItems');
controller.showLoadMore = true;
- controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
+ controller.itemsFiltered = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
controller.activeOption = 2;
$timeout.flush();
- expect(controller._activeOption).toEqual(2);
+ expect(controller.activeOption).toEqual(2);
expect(controller.loadItems).toHaveBeenCalledWith();
});
it(`should set _activeOption as items.length -1 if showLoadMore is not defined then call loadItems()`, () => {
spyOn(controller, 'loadItems');
controller.showLoadMore = undefined;
- controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
+ controller.itemsFiltered = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
controller.activeOption = 10;
$timeout.flush();
- expect(controller._activeOption).toEqual(3);
+ expect(controller.activeOption).toEqual(3);
expect(controller.loadItems).toHaveBeenCalledWith();
});
it(`should define _activeOption as activeOption and never call loadItems()`, () => {
spyOn(controller, 'loadItems');
- controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}, {id: 5, name: 'Doctor X'}];
+ controller.itemsFiltered = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}, {id: 5, name: 'Doctor X'}];
controller.activeOption = 1;
$timeout.flush();
- expect(controller._activeOption).toEqual(1);
+ expect(controller.activeOption).toEqual(1);
expect(controller.loadItems).not.toHaveBeenCalledWith();
});
});
@@ -139,14 +141,6 @@ describe('Component vnDropDown', () => {
});
describe('$onChanges()', () => {
- it(`should set the top css of the $element`, () => {
- let argumentObject = {show: true, top: {currentValue: 100}};
- spyOn(controller.$element, 'css');
- controller.$onChanges(argumentObject);
-
- expect(controller.$element.css).toHaveBeenCalledWith('top', '100px');
- });
-
it(`should set the width css of the $element`, () => {
let argumentObject = {show: true, itemWidth: {currentValue: 100}};
spyOn(controller.$element, 'css');
@@ -235,7 +229,7 @@ describe('Component vnDropDown', () => {
});
it(`should call clearSearch() Esc key is pressed and take off 1 from _activeOption`, () => {
- controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
+ controller.itemsFiltered = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
spyOn(controller, 'setScrollPosition');
controller._show = true;
controller.element = document.createElement('div');
@@ -250,7 +244,7 @@ describe('Component vnDropDown', () => {
});
it(`should call clearSearch() Esc key is pressed and add up 1 to _activeOption`, () => {
- controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
+ controller.itemsFiltered = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
spyOn(controller, 'setScrollPosition');
controller._show = true;
controller.element = document.createElement('div');
@@ -268,7 +262,11 @@ describe('Component vnDropDown', () => {
describe('setScrollPosition()', () => {
it(`should call child.scrollIntoView if defined `, () => {
$element[0].firstChild.setAttribute('class', 'dropdown');
+ $element[0].firstChild.firstChild.setAttribute('class', 'active');
let child = $element[0].firstChild.firstChild;
+ spyOn(child, 'getBoundingClientRect').and.returnValue({top: 100});
+ let container = $element[0].firstChild;
+ spyOn(container, 'getBoundingClientRect').and.returnValue({top: 10, height: 70});
child.scrollIntoView = () => {};
spyOn(child, 'scrollIntoView');
controller._activeOption = 0;
@@ -293,26 +291,40 @@ describe('Component vnDropDown', () => {
controller.selectItem(item);
expect(controller.selected).toEqual(item);
- expect(controller._show).toEqual(true);
+ expect(controller._show).not.toBeDefined();
});
});
describe('loadItems()', () => {
- it(`should set controller._show to true`, () => {
+ it(`should set controller.show to true`, () => {
+ controller.show = false;
+ controller.itemsFiltered = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
controller.loadItems();
- expect(controller._show).toEqual(true);
+ expect(controller.show).toEqual(true);
});
- it(`should call loadMore() and then set controller._show to true`, () => {
+ it(`should call loadMore() and then set controller._show to true if there are items`, () => {
controller.showLoadMore = () => {};
controller.loadMore = () => {};
spyOn(controller, 'loadMore');
+ controller.itemsFiltered = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
controller.loadItems();
expect(controller._show).toEqual(true);
expect(controller.loadMore).toHaveBeenCalledWith();
});
+
+ it(`should call loadMore() and then set controller._show to undefined if there are not items`, () => {
+ controller.showLoadMore = () => {};
+ controller.loadMore = () => {};
+ spyOn(controller, 'loadMore');
+ controller.itemsFiltered = [];
+ controller.loadItems();
+
+ expect(controller._show).not.toBeDefined();
+ expect(controller.loadMore).toHaveBeenCalledWith();
+ });
});
describe('$onInit()', () => {
diff --git a/client/core/src/drop-down/style.scss b/client/core/src/drop-down/style.scss
index e65fd4e6e..d4c28f734 100644
--- a/client/core/src/drop-down/style.scss
+++ b/client/core/src/drop-down/style.scss
@@ -4,6 +4,8 @@ vn-drop-down {
padding: 0 15px;
margin-left: -15px;
background: transparent;
+ max-height: 446px;
+ overflow: hidden;
.dropdown-body{
background: white;
border: 1px solid #A7A7A7;
@@ -27,7 +29,7 @@ vn-drop-down {
padding: 0;
margin: 0;
background: white;
- max-height: 400px;
+ max-height: 378px;
overflow-y: auto;
li {
outline: none;
@@ -55,4 +57,24 @@ vn-drop-down {
}
}
}
+}
+#ddBack {
+ position: fixed;
+ z-index: 9998;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: transparent;
+}
+vn-drop-down.fixed-dropDown {
+ position: fixed;
+ margin: 0;
+ padding: 0;
+ .dropdown-body{
+ height: 100%;
+ ul{
+ border-bottom: 1px solid #A7A7A7;
+ }
+ }
}
\ No newline at end of file
diff --git a/client/core/src/grid-header/style.scss b/client/core/src/grid-header/style.scss
index cc983dcf1..8f3475b35 100644
--- a/client/core/src/grid-header/style.scss
+++ b/client/core/src/grid-header/style.scss
@@ -2,7 +2,6 @@ vn-grid-header {
border-bottom: 3px solid #9D9D9D;
font-weight: bold;
.orderly{
- cursor: pointer;
text-align: center;
white-space: nowrap;
justify-content: center;
@@ -11,13 +10,18 @@ vn-grid-header {
}
}
vn-icon{
- line-height: 17px;
- font-size: 17px;
margin: 0;
padding: 0;
- display: inline;
i {
- padding-top: 3px;
+ padding: 0;
+ margin: -2px -17px 0 0;
+ opacity: 0.2;
+ cursor: pointer;
+ }
+ &.active {
+ i {
+ opacity: 1;
+ }
}
}
[min-none]{
diff --git a/client/core/src/icon-menu/icon-menu.html b/client/core/src/icon-menu/icon-menu.html
index 6f49a6f7e..c2d5ffeff 100644
--- a/client/core/src/icon-menu/icon-menu.html
+++ b/client/core/src/icon-menu/icon-menu.html
@@ -1,7 +1,7 @@
-