#135 Refactor vnAutocomplete

This commit is contained in:
Juan 2018-03-09 14:15:30 +01:00
parent 4497b00bab
commit bbcd39f30d
10 changed files with 975 additions and 1231 deletions

View File

@ -1,22 +1,23 @@
<vn-vertical ng-click="$ctrl.showDropDown = true" tabindex="0">
<vn-textfield vn-auto
label="{{$ctrl.label}}"
model="$ctrl.displayValue"
readonly="$ctrl.readonly"
tab-index="-1">
</vn-textfield>
<vn-drop-down vn-auto
items="$ctrl.items"
show="$ctrl.showDropDown"
selected="$ctrl.field"
filter="true"
load-more="$ctrl.getItems()"
show-load-more="$ctrl.maxRow"
remove-load-more="$ctrl.removeLoadMore"
filter-action="$ctrl.findItems(search)"
item-width="$ctrl.width"
multiple="$ctrl.multiple"
parent = "$ctrl.element">
<vn-item ng-transclude="tplItem">{{$parent.item[$ctrl.showField]}}</vn-item>
<div>
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input
type="button"
class="mdl-textfield__input"
ng-click="$ctrl.onMouseDown($event)"
ng-keydown="$ctrl.onKeyDown($event)">
</input>
<div class="icons">
<vn-icon
icon="clear"
class="clear"
ng-click="$ctrl.onClearClick($event)"
translate-attr="{title: 'Clear'}">
</vn-icon>
</div>
<label class="mdl-textfield__label" translate>{{::$ctrl.label}}</label>
</div>
<vn-drop-down
vn-id="drop-down"
on-select="$ctrl.onDropDownSelect(value)">
</vn-drop-down>
</vn-vertical>
</div>

482
client/core/src/components/autocomplete/autocomplete.js Normal file → Executable file
View File

@ -1,381 +1,255 @@
import ngModule from '../../module';
import Component from '../../lib/component';
import copyObject from '../../lib/copy';
import './style.scss';
class Autocomplete extends Component {
constructor($element, $scope, $http, $timeout, $filter) {
super($element);
this.$element = $element;
this.$scope = $scope;
/**
* Input with option selector.
*
* @property {String} valueField The data field name that should be shown
* @property {String} showFiled The data field name that should be used as value
* @property {Array} data Static data for the autocomplete
* @property {Object} intialData A initial data to avoid the server request used to get the selection
* @property {Boolean} multiple Wether to allow multiple selection
*/
export default class Autocomplete extends Component {
constructor($element, $scope, $http, $transclude) {
super($element, $scope);
this.$http = $http;
this.$timeout = $timeout;
this.$filter = $filter;
this.$transclude = $transclude;
this._showDropDown = false;
this.finding = false;
this.findMore = false;
this._value = null;
this._field = null;
this._preLoad = false;
this.maxRow = 10;
this.showField = 'name';
this._field = undefined;
this._selection = null;
this.valueField = 'id';
this.items = copyObject(this.data) || [];
this.displayValueMultiCheck = [];
this.showField = 'name';
this._multiField = [];
this.readonly = true;
this.removeLoadMore = false;
this.form = null;
this.findForm = false;
}
get showDropDown() {
return this._showDropDown;
}
set showDropDown(value) {
if (value && this.url && !this._preLoad) {
this._preLoad = true;
this.getItems();
}
if (value && !this.width) {
let rectangle = this.$element[0].getBoundingClientRect();
this.width = Math.round(rectangle.width) - 10;
}
this._showDropDown = value;
}
get displayValue() {
return this._value;
}
set displayValue(value) {
let val = (value === undefined || value === '') ? null : value;
if (this.multiple && val) {
let index = this.displayValueMultiCheck.indexOf(val);
if (index === -1)
this.displayValueMultiCheck.push(val);
else
this.displayValueMultiCheck.splice(index, 1);
this._value = this.displayValueMultiCheck.join(', ');
} else {
this._value = val;
}
if (value === null) {
this.field = null;
if (this.multiple && this.items.length) {
this.displayValueMultiCheck = [];
this.items.map(item => {
item.checked = false;
return item;
});
}
}
this.input = this.element.querySelector('.mdl-textfield__input');
componentHandler.upgradeElement(
this.element.querySelector('.mdl-textfield'));
}
/**
* @type {any} The autocomplete value.
*/
get field() {
return this.multiple ? this._multiField : this._field;
return this._field;
}
set field(value) {
if (!angular.equals(value, this.field)) {
this.finding = true;
if (value && value.hasOwnProperty(this.valueField)) {
this._field = value[this.valueField];
if (this.multiple) {
this.setMultiField(value[this.valueField]);
}
this.setDirtyForm();
} else {
this.setValue(value);
}
if (angular.equals(value, this._field))
return;
if (value && value.hasOwnProperty(this.showField))
this.displayValue = value[this.showField];
this._field = value;
this.refreshSelection();
this.finding = false;
if (this.onChange)
this.onChange({item: this._field});
}
if (this.onChange)
this.onChange(value);
}
set initialData(value) {
if (value && value.hasOwnProperty(this.valueField)) {
this._field = value[this.valueField];
if (this.multiple) {
this._multiField = [value[this.valueField]];
}
if (value.hasOwnProperty(this.showField)) {
this.displayValue = value[this.showField];
}
}
/**
* @type {Object} The selected data object, you can use this property
* to prevent requests to display the initial value.
*/
get selection() {
return this._selection;
}
setMultiField(val) {
if (val && typeof val === 'object' && val[this.valueField]) {
val = val[this.valueField];
}
if (val === null) {
this._multiField = [];
} else {
let index = this._multiField.indexOf(val);
if (index === -1) {
this._multiField.push(val);
} else {
this._multiField.splice(index, 1);
}
}
set selection(value) {
this._selection = value;
this.refreshDisplayed();
}
setValue(value) {
if (value) {
let data = this.items;
selectionIsValid(selection) {
return selection
&& selection[this.valueField] == this._field
&& selection[this.showField] != null;
}
if (data && data.length)
refreshSelection() {
if (this.selectionIsValid(this._selection))
return;
let value = this._field;
if (value && this.valueField && this.showField) {
if (this.selectionIsValid(this.initialData)) {
this.selection = this.initialData;
return;
}
let data = this.data;
if (!data && this.$.dropDown)
data = this.$.dropDown.$.model.data;
if (data)
for (let i = 0; i < data.length; i++)
if (data[i][this.valueField] === value) {
this.showItem(data[i]);
this.selection = data[i];
return;
}
this.requestItem(value);
} else {
this._field = null;
this.setMultiField(null);
this.displayValue = '';
}
if (this.url) {
this.requestSelection(value);
return;
}
} else
this.selection = null;
}
requestItem(value) {
if (!value) return;
requestSelection(value) {
let where = {};
where[this.valueField] = value;
if (this.multiple)
where[this.valueField] = {inq: this.field};
else
where[this.valueField] = value;
let filter = {
fields: this.getRequestFields(),
fields: this.getFields(),
where: where
};
let json = JSON.stringify(filter);
let json = encodeURIComponent(JSON.stringify(filter));
this.$http.get(`${this.url}?filter=${json}`).then(
json => this.onItemRequest(json.data),
json => this.onItemRequest(null)
json => this.onSelectionRequest(json.data),
() => this.onSelectionRequest(null)
);
}
onItemRequest(data) {
if (data && data.length > 0)
this.showItem(data[0]);
else
this.showItem(null);
onSelectionRequest(data) {
if (data && data.length > 0) {
if (this.multiple)
this.selection = data;
else
this.selection = data[0];
} else
this.selection = null;
}
showItem(item) {
this.displayValue = item ? item[this.showField] : '';
this.field = item;
refreshDisplayed() {
let display = '';
if (this._selection && this.showField) {
if (this.multiple && Array.isArray(this._selection)) {
for (var item of this._selection) {
if (display.length > 0) display += ', ';
display += item[this.showField];
}
} else {
display = this._selection[this.showField];
}
}
this.input.value = display;
this.mdlUpdate();
}
getRequestFields() {
let fields = {};
fields[this.valueField] = true;
fields[this.showField] = true;
getFields() {
let fields = [];
fields.push(this.valueField);
fields.push(this.showField);
if (this._selectFields)
for (let field of this._selectFields)
fields[field] = true;
if (this.selectFields)
for (let field of this.selectFields)
fields.push(field);
return fields;
}
getOrder() {
return this.order ? this.order : `${this.showField} ASC`;
mdlUpdate() {
let field = this.element.querySelector('.mdl-textfield');
let mdlField = field.MaterialTextfield;
if (mdlField) mdlField.updateClasses_();
}
findItems(search) {
if (this.url && search && !this.finding) {
this.maxRow = false;
let filter = {};
if (this.filterSearch) {
let toSearch = this.filterSearch.replace(/search/g, search);
filter = this.$scope.$eval(toSearch);
} else {
filter = {where: {name: {regexp: search}}};
if (this.filter && this.filter.where) {
Object.assign(filter.where, this.filter.where);
}
}
filter.order = this.getOrder();
let json = JSON.stringify(filter);
this.finding = true;
this.$http.get(`${this.url}?filter=${json}`).then(
json => {
this.items = [];
json.data.forEach(
el => {
if (this.multiple) {
el.checked = this.field.indexOf(el[this.valueField]) !== -1;
}
this.items.push(el);
}
);
this.finding = false;
},
() => {
this.finding = false;
}
);
} else if (search && !this.url && this.data) {
this.items = this.$filter('filter')(this.data, search);
} else if (!search && !this.finding) {
this.maxRow = 10;
this.items = [];
this.getItems();
}
setValue(value) {
this.field = value;
if (this.form) this.form.$setDirty();
}
getItems() {
if (this.url === undefined) {
this.items = copyObject(this.data);
this.maxRow = false;
this.removeLoadMore = true;
} else {
let filter = {};
if (!this.finding) {
this.finding = true;
if (this.maxRow) {
if (this.items) {
filter.skip = this.items.length;
}
filter.limit = this.maxRow;
filter.order = this.getOrder();
}
if (this.filter) {
Object.assign(filter, this.filter);
}
let json = JSON.stringify(filter);
this.removeLoadMore = false;
this.$http.get(`${this.url}?filter=${json}`).then(
json => {
if (json.data.length) {
json.data.forEach(
el => {
if (this.multiple) {
el.checked = this.field.indexOf(el[this.valueField]) !== -1;
}
this.items.push(el);
}
);
if (filter.skip === 0 && this.maxRow && json.data.length < this.maxRow) {
this.removeLoadMore = true;
}
} else {
this.maxRow = false;
}
this.finding = false;
},
() => {
this.finding = false;
}
);
}
}
}
_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();
}
onDropDownSelect(value) {
this.setValue(value);
this.field = value;
}
$onInit() {
this.findMore = this.url && this.maxRow;
this.mouseFocus = false;
this.focused = false;
onClearClick(event) {
event.preventDefault();
this.setValue(null);
}
this.$element.bind('mouseover', e => {
this.$timeout(() => {
this.mouseFocus = true;
this.showDropDown = this.focused;
});
onKeyDown(event) {
if (event.defaultPrevented) return;
switch (event.keyCode) {
case 38: // Up
case 40: // Down
case 13: // Enter
this.showDropDown();
break;
default:
if (event.key.length == 1)
this.showDropDown(event.key);
else
return;
}
event.preventDefault();
}
onMouseDown(event) {
event.preventDefault();
this.showDropDown();
}
showDropDown(search) {
Object.assign(this.$.dropDown.$.model, {
url: this.url,
staticData: this.data
});
this.$element.bind('mouseout', () => {
this.$timeout(() => {
this.mouseFocus = false;
this.showDropDown = this.focused;
});
});
this.$element.bind('focusin', e => {
this.$timeout(() => {
this.focused = true;
this.showDropDown = true;
});
});
this.$element.bind('focusout', e => {
this.$timeout(() => {
this.focused = false;
this.showDropDown = this.mouseFocus;
});
Object.assign(this.$.dropDown, {
valueField: this.valueField,
showField: this.showField,
selectFields: this.getFields(),
where: this.where,
order: this.order,
parent: this.input,
multiple: this.multiple,
limit: this.limit,
$transclude: this.$transclude
});
this.$.dropDown.show(search);
}
$onDestroy() {
this.$element.unbind('mouseover');
this.$element.unbind('mouseout');
this.$element.unbind('focusin');
this.$element.unbind('focusout');
}
$onChanges(objectChange) {
if (objectChange.data && objectChange.data.currentValue && objectChange.data.currentValue.length) {
this.items = copyObject(objectChange.data.currentValue);
this.maxRow = false;
this.removeLoadMore = true;
}
}
}
Autocomplete.$inject = ['$element', '$scope', '$http', '$timeout', '$filter'];
Autocomplete.$inject = ['$element', '$scope', '$http', '$transclude'];
ngModule.component('vnAutocomplete', {
template: require('./autocomplete.html'),
controller: Autocomplete,
bindings: {
url: '@?',
data: '<?',
showField: '@?',
valueField: '@?',
selectFields: '@?',
initialData: '<?',
onChange: '&?',
data: '<?',
field: '=',
label: '@',
multiple: '@?',
selectFields: '<?',
where: '@?',
order: '@?',
filter: '<?',
filterSearch: '@?'
label: '@',
initialData: '<?',
field: '=?',
limit: '<?',
showFilter: '<?',
selection: '<?',
multiple: '<?',
onChange: '&?'
},
transclude: {
tplItem: '?tplItem'
},
require: {
form: '?^form'
}
});

View File

@ -1,208 +1,74 @@
import './autocomplete.js';
import template from './autocomplete.html';
describe('Component vnAutocomplete', () => {
let $componentController;
let $element;
let $scope;
let $httpBackend;
let $timeout;
let $element;
let controller;
let data = {id: 1, name: 'Bruce Wayne'};
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_, _$timeout_) => {
$componentController = _$componentController_;
$scope = $rootScope.$new();
$element = angular.element(`<div>${template}</div>`);
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$timeout = _$timeout_;
$element = angular.element('<div></div>');
controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller = _$componentController_('vnAutocomplete', {$element, $scope, $httpBackend, $transclude: null});
}));
describe('showDropDown() setter', () => {
it(`should set _showDropDown value`, () => {
controller._showDropDown = '';
controller.showDropDown = 'some value';
describe('field() setter/getter', () => {
it(`should set field controllers property`, () => {
controller.field = data.id;
expect(controller._showDropDown).toEqual('some value');
expect(controller.field).toEqual(data.id);
});
it(`should set _showDropDown value`, () => {
controller._showDropDown = '';
controller.showDropDown = 'some value';
it(`should set selection finding an existing item in the initialData property`, () => {
controller.valueField = 'id';
controller.showField = 'name';
controller.initialData = data;
controller.field = data.id;
expect(controller._showDropDown).toEqual('some value');
});
});
describe('displayValue() setter', () => {
it(`should display value in a formated way`, () => {
let value = 'some value';
controller.displayValue = value;
expect(controller._value).toEqual(value);
expect(controller.selection).toEqual(data);
});
describe('when the autocomeplete is multiple', () => {
it(`should display values separated with commas`, () => {
controller.multiple = true;
controller.displayValue = 'some value';
controller.displayValue = 'another value';
it(`should set selection finding an existing item in the data property`, () => {
controller.valueField = 'id';
controller.showField = 'name';
controller.data = [data];
controller.field = data.id;
expect(controller._value).toEqual('some value, another value');
});
});
});
describe('field() setter', () => {
describe('when value is an object', () => {
it(`should set _field controllers property`, () => {
controller.field = {id: 1, name: 'Bruce Wayne'};
expect(controller._field).toEqual(1);
});
it(`should set _multifield controllers property `, () => {
controller.multiple = true;
controller.field = {id: 1, name: 'Bruce Wayne'};
expect(controller._field).toEqual(1);
expect(controller._multiField[0]).toEqual(1);
controller.field = {id: 1, name: 'Bruce Wayne'};
expect(controller._multiField).toEqual([]);
expect(controller._field).toEqual(1);
});
it(`should set _multifield value and remove it if called a second type with same value`, () => {
controller.multiple = true;
controller.field = {id: 1, name: 'Bruce Wayne'};
expect(controller._field).toEqual(1);
expect(controller._multiField[0]).toEqual(1);
controller.field = {id: 1, name: 'Bruce Wayne'};
expect(controller._multiField).toEqual([]);
expect(controller._field).toEqual(1);
});
it(`should set displayValue finding an existing item in the controller.items property`, () => {
controller.items = [{id: 1, name: 'test1'}, {id: 2, name: 'Bruce Wayne'}];
controller.field = {id: 2, name: 'Bruce Wayne'};
expect(controller.displayValue).toEqual('Bruce Wayne');
});
expect(controller.selection).toEqual(data);
});
describe('when value is a number', () => {
it(`should set _field controller property finding an existing item in the controller.items property`, () => {
controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce Wayne'}];
controller.field = 2;
it(`should set selection to null when can't find an existing item in the data property`, () => {
controller.valueField = 'id';
controller.showField = 'name';
controller.field = data.id;
expect(controller._field).toEqual(2);
});
it(`should set _multifield value and remove it if called a second type with same value finding an existing item in the controller.items property`, () => {
controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce Wayne'}];
controller.multiple = true;
controller.field = 2;
expect(controller._multiField[0]).toEqual(2);
controller.field = 2;
expect(controller._multiField).toEqual([]);
});
it(`should perform a query if the item id isn't present in the controller.items property`, () => {
controller.url = 'test.com';
$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;
$httpBackend.flush();
});
it(`should set displayValue finding an existing item in the controller.items property`, () => {
controller.items = [{id: 1, name: 'test1'}, {id: 2, name: 'Bruce Wayne'}];
controller.field = 2;
expect(controller.displayValue).toEqual('Bruce Wayne');
});
it(`should set field performing a query as the item id isn't present in the controller.items property`, () => {
controller.url = 'test.com';
$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;
$httpBackend.flush();
});
expect(controller.selection).toEqual(null);
});
});
describe('findItems()', () => {
it(`should perform a search and store the result in controller items`, () => {
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.getOrder()});
$httpBackend.whenGET(`${controller.url}?filter=${json}`).respond([{id: 3, name: 'The Joker'}]);
$httpBackend.expectGET(`${controller.url}?filter=${json}`);
controller.findItems(search);
it(`should perform a query if the item id isn't present in the data property`, () => {
controller.valueField = 'id';
controller.showField = 'name';
controller.url = 'localhost';
controller.field = data.id;
let filter = {
fields: ['id', 'name'],
where: {id: data.id}
};
let json = encodeURIComponent(JSON.stringify(filter));
$httpBackend.expectGET(`localhost?filter=${json}`);
controller.field = data.id;
$httpBackend.flush();
expect(controller.items[0]).toEqual({id: 3, name: 'The Joker'});
});
it(`should perform a search and store the result in controller items with filterSearch`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.url = 'test.com';
let search = 'The Joker';
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);
$httpBackend.flush();
expect(controller.items[0]).toEqual({id: 3, name: 'The Joker'});
});
it(`should perform a search with multiple true and store the result in controller items with the checked property defined`, () => {
controller.url = 'test.com';
let search = 'Joker';
controller.multiple = true;
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);
$httpBackend.flush();
expect(controller.items).toEqual([{id: 3, name: 'The Joker', checked: false}, {id: 4, name: 'Joker', checked: false}]);
});
it(`should call getItems function if there's no search value`, () => {
controller.url = 'test.com';
spyOn(controller, 'getItems');
controller.findItems();
expect(controller.getItems).toHaveBeenCalledWith();
});
});
describe('getItems()', () => {
it(`should perfom a query to fill the items without filter`, () => {
controller.url = 'test.com';
$httpBackend.whenGET(`${controller.url}?filter={"skip":0,"limit":10,"order":"name ASC"}`).respond([{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce Wayne'}]);
$httpBackend.expectGET(`${controller.url}?filter={"skip":0,"limit":10,"order":"name ASC"}`);
controller.getItems();
$httpBackend.flush();
expect(controller.items).toEqual([{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce Wayne'}]);
});
});
});

69
client/core/src/components/autocomplete/style.scss Normal file → Executable file
View File

@ -1,3 +1,47 @@
vn-autocomplete > div > .mdl-textfield {
position: relative;
width: 100%;
& > input {
cursor: pointer;
height: 26px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
&:focus {
outline: none;
}
&::-moz-focus-inner {
border: 0;
}
}
& > .icons {
display: none;
position: absolute;
right: 0;
top: 1.3em;
height: 1em;
color: #888;
border-radius: .2em;
background-color: rgba(255, 255, 255, .8);
& > vn-icon {
cursor: pointer;
font-size: 18px;
&:hover {
color: #333;
}
}
}
&:hover > .icons,
& > input:focus + .icons {
display: block;
}
}
ul.vn-autocomplete {
list-style-type: none;
padding: 1em;
@ -14,7 +58,7 @@ ul.vn-autocomplete {
&.active,
&:hover {
background-color: rgba(1,1,1,.1);
background-color: rgba(1, 1, 1, .1);
}
&.load-more {
color: #ffa410;
@ -23,26 +67,3 @@ ul.vn-autocomplete {
}
}
}
vn-autocomplete {
position: relative;
vn-vertical {
outline:none;
}
.mdl-chip__action {
position: absolute;
top: 0px;
right: -6px;
margin: 22px 0px;
background: transparent;
}
.material-icons {
font-size: 18px;
}
vn-drop-down{
margin-top: 47px;
}
vn-drop-down .dropdown-body .filter vn-icon {
margin-left: -26px;
}
}

62
client/core/src/components/drop-down/drop-down.html Normal file → Executable file
View File

@ -1,28 +1,36 @@
<vn-vertical class="dropdown-body" ng-if="$ctrl.show">
<vn-auto ng-show="$ctrl.filter" class="filter">
<vn-horizontal>
<input vn-one placeholder="{{'Search' | translate}}" type="text" ng-model="$ctrl.search"/>
<vn-icon vn-none icon="clear" ng-click="$ctrl.clearSearch()"></vn-icon>
</vn-horizontal>
</vn-auto>
<vn-auto>
<ul class="dropdown">
<li
ng-repeat="item in $ctrl.itemsFiltered track by $index"
ng-click="$ctrl.selectItem(item)"
ng-class="{'active': $index === $ctrl.activeOption, 'checked': item.checked}"
ng-mouseover="$ctrl.activeOption = $index"
>
<input type="checkbox" ng-checked="item.checked" ng-if="$ctrl.multiple">
<div ng-transclude="vnItem">{{item.name}}</div>
</li>
<li
ng-if="$ctrl.loadMore&&!$ctrl.removeLoadMore"
class="dropdown__loadMore"
ng-class="{'active': $ctrl.itemsFiltered.length === $ctrl.activeOption, 'noMore' : !$ctrl.showLoadMore}"
ng-click="$ctrl.loadItems()"
translate="{{$ctrl.showLoadMore ? 'Show More' : 'No more results'}}"
></li>
<div
class="body"
ng-mousedown="$ctrl.onMouseDown($event)">
<div ng-show="$ctrl.showFilter" class="filter">
<input
type="text"
ng-model="$ctrl.search"
tabindex="-1"
class="search"
ng-blur="$ctrl.onFocusOut()"
translate-attr="{placeholder: 'Search'}"/>
<vn-icon
icon="clear"
ng-click="$ctrl.onClearClick()"
translate-attr="{title: 'Clear'}">
</vn-icon>
</div>
<vn-model
vn-id="model"
on-data-change="$ctrl.onModelDataChange()">
</vn-model>
<div class="list">
<ul
class="dropdown"
tabindex="-1"
ng-click="$ctrl.onContainerClick($event)">
</ul>
</vn-auto>
</vn-vertical>
<div
ng-if="$ctrl.statusText"
ng-click="$ctrl.onLoadMoreClick($event)"
class="status"
translate>
{{$ctrl.statusText}}
</div>
</div>
</div>

592
client/core/src/components/drop-down/drop-down.js Normal file → Executable file
View File

@ -1,44 +1,48 @@
import ngModule from '../../module';
import validKey from '../../lib/key-codes';
import Component from '../../lib/component';
import './style.scss';
import './model';
export default class DropDown {
constructor($element, $filter, $timeout) {
this.$element = $element;
this.$filter = $filter;
export default class DropDown extends Component {
constructor($element, $scope, $transclude, $timeout, $http, $translate) {
super($element, $scope);
this.$transclude = $transclude;
this.$timeout = $timeout;
this.$translate = $translate;
this._search = null;
this.itemsFiltered = [];
this.valueField = 'id';
this.showField = 'name';
this._search = undefined;
this._shown = false;
this._activeOption = -1;
this._focusingFilter = false;
this._tryToShow = 0;
this.showLoadMore = true;
this.showFilter = true;
this.input = this.element.querySelector('.search');
this.body = this.element.querySelector('.body');
this.container = this.element.querySelector('ul');
this.list = this.element.querySelector('.list');
this.docKeyDownHandler = e => this.onDocKeyDown(e);
this.docFocusInHandler = e => this.onDocFocusIn(e);
this.element.addEventListener('mousedown',
e => this.onBackgroundMouseDown(e));
this.element.addEventListener('focusin',
e => this.onFocusIn(e));
this.list.addEventListener('scroll',
e => this.onScroll(e));
}
get container() {
return this.$element[0].querySelector('ul.dropdown');
get shown() {
return this._shown;
}
get show() {
return this._show;
}
set show(value) {
let oldValue = this.show;
// 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(() => {
this._tryToShow++;
this.show = true;
if (this.activeOption === -1) {
this.activeOption = 0;
}
}, 250);
} else {
this._tryToShow = 0;
this._show = value;
this._toggleDropDown(value, oldValue);
}
set shown(value) {
if (value)
this.show();
else
this.hide();
}
get search() {
@ -46,242 +50,390 @@ export default class DropDown {
}
set search(value) {
let val = (value === undefined || value === '') ? null : value;
this._search = val;
value = value == '' || value == null ? null : value;
if (value === this._search) return;
if (this.filterAction)
this.onFilterRest();
else
this.filterItems();
this._search = value;
this.$.model.clear();
this.$timeout.cancel(this.searchTimeout);
this.searchTimeout = this.$timeout(() => {
this.refreshModel();
this.searchTimeout = null;
}, 350);
this.buildList();
}
get statusText() {
let model = this.$.model;
let data = model.data;
let statusText = null;
if (model.isLoading || this.searchTimeout)
statusText = 'Loading...';
else if (data == null)
statusText = 'No data';
else if (model.moreRows)
statusText = 'Load More';
else if (data.length === 0)
statusText = 'No results found';
return statusText;
}
get model() {
return this.$.model;
}
get activeOption() {
return this._activeOption;
}
set activeOption(value) {
if (value < 0) {
value = 0;
} else if (value >= this.itemsFiltered.length) {
value = this.showLoadMore ? this.itemsFiltered.length : this.itemsFiltered.length - 1;
}
this.$timeout(() => {
this._activeOption = value;
if (value && value >= this.itemsFiltered.length - 3 && !this.removeLoadMore) {
this.loadItems();
}
});
/**
* Shows the drop-down. If a parent is specified it is shown in a visible
* relative position to it.
*
* @param {String} search The initial search term or %null
*/
show(search) {
if (this._shown) return;
this._shown = true;
this._activeOption = -1;
this.search = search;
this.buildList();
this.element.style.display = 'block';
this.list.scrollTop = 0;
this.$timeout(() => this.$element.addClass('shown'), 40);
this.document.addEventListener('keydown', this.docKeyDownHandler);
this.document.addEventListener('focusin', this.docFocusInHandler);
this.relocate();
this.input.focus();
}
_toggleDropDown(value, oldValue) {
this.$timeout(() => {
this._eventScroll(value);
this._calculatePosition(value, oldValue);
});
/**
* Hides the drop-down.
*/
hide() {
if (!this._shown) return;
this._shown = false;
this.element.style.display = '';
this.$element.removeClass('shown');
this.document.removeEventListener('keydown', this.docKeyDownHandler);
this.document.removeEventListener('focusin', this.docFocusInHandler);
if (this.parent)
this.parent.focus();
}
_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));
/**
* Repositions the drop-down to a correct location relative to the parent.
*/
relocate() {
if (!this.parent) return;
let style = this.body.style;
style.width = '';
style.height = '';
let parentRect = this.parent.getBoundingClientRect();
let bodyRect = this.body.getBoundingClientRect();
let top = parentRect.top + parentRect.height;
let height = bodyRect.height;
let width = Math.max(bodyRect.width, parentRect.width);
let margin = 10;
if (top + height + margin > window.innerHeight)
top = Math.max(parentRect.top - height, margin);
style.top = `${top}px`;
style.left = `${parentRect.left}px`;
style.width = `${width}px`;
if (height + margin * 2 > window.innerHeight)
style.height = `${window.innerHeight - margin * 2}px`;
}
/**
* Activates a option and scrolls the drop-down to that option.
*
* @param {Number} option The option index
*/
moveToOption(option) {
this.activateOption(option);
let list = this.list;
let li = this.activeLi;
if (!li) return;
let liRect = li.getBoundingClientRect();
let listRect = list.getBoundingClientRect();
if (liRect.bottom > listRect.bottom)
list.scrollTop += liRect.bottom - listRect.bottom;
else if (liRect.top < listRect.top)
list.scrollTop -= listRect.top - liRect.top;
}
/**
* Activates a option.
*
* @param {Number} option The option index
*/
activateOption(option) {
this._activeOption = option;
if (this.activeLi)
this.activeLi.className = '';
let data = this.$.model.data;
if (option >= 0 && data && option < data.length) {
this.activeLi = this.container.children[option];
this.activeLi.className = 'active';
}
}
_setFocusInFilterInput() {
let inputFilterSearch = this.$element[0].querySelector('input');
this._focusingFilter = true;
if (inputFilterSearch)
this.$timeout(() => {
inputFilterSearch.focus();
this._focusingFilter = false;
}, 250);
}
/**
* Selects an option.
*
* @param {Number} option The option index
*/
selectOption(option) {
if (option != -1) {
let data = this.$.model.data;
let item = data ? data[option] : null;
let value = item ? item[this.valueField] : null;
_background(create) {
let el = document.getElementById('ddBack');
if (el) {
el.parentNode.removeChild(el);
}
if (this.multiple) {
if (!Array.isArray(this.selection)) {
this.selection = [];
this.field = [];
}
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();
this.selection.push(item);
this.field.push(value);
} else {
this.selection = item;
this.field = value;
}
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();
if (this.onSelect)
this.onSelect({value: value});
}
if (!this.multiple)
this.hide();
}
filterItems() {
this.itemsFiltered = this.search ? this.$filter('filter')(this.items, this.search) : this.items;
refreshModel() {
this.$.model.filter = {
fields: this.selectFields,
where: this.getWhere(this._search),
order: this.getOrder(),
limit: this.limit || 8
};
this.$.model.refresh(this._search);
}
onFilterRest() {
if (this.filterAction) {
this.filterAction({search: this.search});
getWhere(search) {
if (search == '' || search == null)
return undefined;
if (this.where) {
let jsonFilter = this.where.replace(/search/g, search);
return this.$.$eval(jsonFilter);
}
let where = {};
where[this.showField] = {regexp: search};
return where;
}
clearSearch() {
getOrder() {
if (this.order)
return this.order;
else if (this.showField)
return `${this.showField} ASC`;
return undefined;
}
onMouseDown(event) {
this.lastMouseEvent = event;
}
onBackgroundMouseDown(event) {
if (event != this.lastMouseEvent)
this.hide();
}
onFocusIn(event) {
this.lastFocusEvent = event;
}
onDocFocusIn(event) {
if (event !== this.lastFocusEvent && event.originalTarget !== this.parent)
this.hide();
}
onClearClick() {
this.search = null;
}
selectOption() {
if (this.activeOption >= 0 && this.activeOption < this.items.length && this.items[this.activeOption]) {
this.selected = this.items[this.activeOption];
this.show = false;
this.clearSearch();
} else if (this.showLoadMore && this.activeOption === this.items.length) {
this.loadMore();
}
onScroll() {
let list = this.list;
let shouldLoad =
list.scrollTop + list.clientHeight >= (list.scrollHeight - 40)
&& !this.$.model.isLoading;
if (shouldLoad)
this.$.model.loadMore();
}
onKeydown(event) {
if (this.show) {
if (event.keyCode === 13) { // Enter
this.$timeout(() => {
this.selectOption();
});
event.preventDefault();
} else if (event.keyCode === 27) { // Escape
this.clearSearch();
} else if (event.keyCode === 38) { // Arrow up
this.activeOption--;
this.$timeout(() => {
this.setScrollPosition();
}, 100);
} else if (event.keyCode === 40) { // Arrow down
this.activeOption++;
this.$timeout(() => {
this.setScrollPosition();
}, 100);
} 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`);
} */
}
onLoadMoreClick(event) {
event.preventDefault();
this.$.model.loadMore();
}
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();
onContainerClick(event) {
if (event.defaultPrevented) return;
let index = getPosition(this.container, event);
if (index != -1) this.selectOption(index);
}
onModelDataChange() {
this.buildList();
}
onDocKeyDown(event) {
if (event.defaultPrevented) return;
let data = this.$.model.data;
let option = this.activeOption;
let nOpts = data ? data.length - 1 : 0;
switch (event.keyCode) {
case 13: // Enter
this.selectOption(option);
break;
case 27: // Escape
this.hide();
break;
case 38: // Up
this.moveToOption(option <= 0 ? nOpts : option - 1);
break;
case 40: // Down
this.moveToOption(option >= nOpts ? 0 : option + 1);
break;
case 35: // End
this.moveToOption(nOpts);
break;
case 36: // Start
this.moveToOption(0);
break;
default:
return;
}
event.preventDefault();
this.$.$applyAsync();
}
buildList() {
this.destroyScopes();
this.scopes = [];
let hasTemplate = this.$transclude &&
this.$transclude.isSlotFilled('tplItem');
let fragment = this.document.createDocumentFragment();
let data = this.$.model.data;
if (data) {
for (let i = 0; i < data.length; i++) {
let li = this.document.createElement('li');
fragment.appendChild(li);
if (this.multiple) {
let check = this.document.createElement('input');
check.type = 'checkbox';
li.appendChild(check);
if (this.field && this.field.indexOf(data[i][this.valueField]) != -1)
check.checked = true;
}
if (hasTemplate) {
this.$transclude((clone, scope) => {
Object.assign(scope, 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);
}
}
}
this.container.innerHTML = '';
this.container.appendChild(fragment);
this.activateOption(this._activeOption);
this.relocate();
}
selectItem(item) {
this.selected = item;
if (this.multiple) {
item.checked = !item.checked;
this.show = true;
} else {
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));
destroyScopes() {
if (this.scopes)
for (let scope of this.scopes)
scope.$destroy();
}
$onDestroy() {
if (this.parent)
this.parent.removeEventListener('keydown', e => this.onKeydown(e));
this.destroyScopes();
}
}
DropDown.$inject = ['$element', '$scope', '$transclude', '$timeout', '$http', '$translate'];
DropDown.$inject = ['$element', '$filter', '$timeout'];
/**
* Gets the position of an event element relative to a parent.
*
* @param {HTMLElement} parent The parent element
* @param {Event} event The event object
* @return {Number} The element index
*/
function getPosition(parent, event) {
let target = event.target;
let children = parent.children;
if (target === parent)
return -1;
while (target.parentNode !== parent)
target = target.parentNode;
for (let i = 0; i < children.length; i++)
if (children[i] === target)
return i;
return -1;
}
ngModule.component('vnDropDown', {
template: require('./drop-down.html'),
controller: DropDown,
bindings: {
items: '<',
show: '<',
filter: '@?',
selected: '=',
search: '=?',
loadMore: '&?',
removeLoadMore: '<?',
filterAction: '&?',
showLoadMore: '<?',
itemWidth: '<?',
field: '=?',
data: '<?',
selection: '=?',
search: '<?',
limit: '<?',
showFilter: '<?',
parent: '<?',
multiple: '<?'
multiple: '<?',
onSelect: '&?'
},
transclude: {
vnItem: '?vnItem'
tplItem: '?tplItem'
}
});

View File

@ -1,9 +1,13 @@
import './drop-down.js';
import template from './drop-down.html';
describe('Component vnDropDown', () => {
let $componentController;
let $timeout;
let $element;
let $scope;
let $httpBackend;
let $q;
let $filter;
let controller;
@ -11,337 +15,34 @@ describe('Component vnDropDown', () => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, _$timeout_, _$filter_, _$httpBackend_) => {
_$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$timeout_, _$httpBackend_, _$q_, _$filter_) => {
$componentController = _$componentController_;
$element = angular.element('<div><ul><li></li></ul></div>');
$element = angular.element(`<div>${template}</div>`);
$timeout = _$timeout_;
$q = _$q_;
$filter = _$filter_;
controller = $componentController('vnDropDown', {$element, $timeout, $filter});
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$scope.model = $componentController('vnModel', {$httpBackend, $q, $filter});
controller = $componentController('vnDropDown', {$element, $scope, $transclude: null, $timeout, $httpBackend, $translate: null});
controller.parent = angular.element('<vn-parent></vn-parent>')[0];
}));
describe('show() setter', () => {
describe('show() method', () => {
it(`should define controllers _show using the value received as argument`, () => {
controller._show = 'old value';
controller.show = 'new value';
controller.show();
expect(controller._show).toEqual('new value');
expect(controller.shown).toEqual(true);
});
});
describe('search()', () => {
it(`should set controllers _search property with the value received`, () => {
controller.search = 'some filter valiue';
describe('hide() method', () => {
it(`should define controllers _show using the value received as argument`, () => {
controller.hide();
expect(controller._search).toEqual('some filter valiue');
});
it(`should set controllers _search property to null as the value received is an empty string`, () => {
controller.search = '';
expect(controller._search).toEqual(null);
});
it(`should call onFilterRest() if controllers filterAction is defined`, () => {
controller.filterAction = true;
spyOn(controller, 'onFilterRest');
controller.search = 'some filter valiue';
expect(controller.onFilterRest).toHaveBeenCalledWith();
});
it(`should call filterItems() if controllers filterAction is undefined`, () => {
controller.filterAction = undefined;
spyOn(controller, 'filterItems');
controller.search = 'some filter valiue';
expect(controller.filterItems).toHaveBeenCalledWith();
});
});
describe('activeOption() setter', () => {
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.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.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.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.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.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.loadItems).toHaveBeenCalledWith();
});
it(`should define _activeOption as activeOption and never call loadItems()`, () => {
spyOn(controller, 'loadItems');
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.loadItems).not.toHaveBeenCalledWith();
});
});
describe('filterItems() setter', () => {
it(`should set _itemsFiltered using the value of items`, () => {
controller.items = [{id: 1, name: 'Batman'}];
controller.filterItems();
expect(controller.itemsFiltered).toEqual(controller.items);
});
it(`should set _itemsFiltered with the filtered value of items`, () => {
controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
controller.search = 'Batman';
controller.filterItems();
expect(controller.itemsFiltered).toEqual([Object({id: 1, name: 'Batman'})]);
});
it(`should set _itemsFiltered an empty array`, () => {
controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
controller.search = 'the Joker';
expect(controller.itemsFiltered.length).toEqual(0);
});
});
describe('onFilterRest()', () => {
it(`should call the filterAction() with a constructed object as argument`, () => {
controller.filterAction = () => {};
controller.search = 'Batman';
spyOn(controller, 'filterAction');
controller.onFilterRest();
expect(controller.filterAction).toHaveBeenCalledWith({search: controller.search});
});
});
describe('$onChanges()', () => {
it(`should set the width css of the $element`, () => {
let argumentObject = {show: true, itemWidth: {currentValue: 100}};
spyOn(controller.$element, 'css');
controller.$onChanges(argumentObject);
expect(controller.$element.css).toHaveBeenCalledWith('width', '100px');
});
it(`should set the width css of the $element`, () => {
let argumentObject = {items: {id: 1, name: 'Batman'}};
spyOn(controller, 'filterItems');
controller.$onChanges(argumentObject);
expect(controller.filterItems).toHaveBeenCalledWith();
});
});
describe('clearSearch()', () => {
it(`should set the controllers search property to null`, () => {
controller.search = true;
controller.clearSearch();
expect(controller.search).toEqual(null);
});
});
describe('selectOption()', () => {
it(`should set controllers selected and show properties then call clearSearch() as _activeOption is smaller than items.length`, () => {
spyOn(controller, 'clearSearch');
controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
controller._activeOption = 0;
controller.selectOption();
expect(controller.selected).toEqual(controller.items[controller._activeOption]);
expect(controller._show).toEqual(false);
expect(controller.clearSearch).toHaveBeenCalledWith();
});
it(`should not set controllers selected, show and never call clearSearch() as _activeOption is bigger than items.length`, () => {
spyOn(controller, 'clearSearch');
controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
controller._activeOption = 100;
controller.selectOption();
expect(controller.selected).not.toBeDefined();
expect(controller._show).not.toBeDefined();
expect(controller.clearSearch).not.toHaveBeenCalledWith();
});
it(`should call loadMore() if the activeValue equals items.length`, () => {
controller.loadMore = () => {};
spyOn(controller, 'loadMore');
controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce'}, {id: 3, name: 'Logan'}, {id: 4, name: 'Wolverine'}];
controller._activeOption = 4;
controller.showLoadMore = 4;
controller.selectOption();
expect(controller.loadMore).toHaveBeenCalledWith();
});
});
describe('onKeydown()', () => {
it(`should call selectOption() and preventDefault() if Enter key is pressed`, () => {
spyOn(controller, 'selectOption');
controller._show = true;
controller.element = document.createElement('div');
let event = {target: controller.element, preventDefault: () => {}};
event.keyCode = 13;
spyOn(event, 'preventDefault');
controller.onKeydown(event);
$timeout.flush();
expect(controller.selectOption).toHaveBeenCalledWith();
expect(event.preventDefault).toHaveBeenCalledWith();
});
it(`should call clearSearch() Esc key is pressed`, () => {
spyOn(controller, 'clearSearch');
controller._show = true;
controller.element = document.createElement('div');
let event = {target: controller.element};
event.keyCode = 27;
controller.onKeydown(event);
expect(controller.clearSearch).toHaveBeenCalledWith();
});
it(`should call clearSearch() Esc key is pressed and take off 1 from _activeOption`, () => {
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');
let event = {target: controller.element};
event.keyCode = 38;
controller._activeOption = 1;
controller.onKeydown(event);
$timeout.flush();
expect(controller.setScrollPosition).toHaveBeenCalledWith();
expect(controller._activeOption).toEqual(0);
});
it(`should call clearSearch() Esc key is pressed and add up 1 to _activeOption`, () => {
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');
let event = {target: controller.element};
event.keyCode = 40;
controller._activeOption = 1;
controller.onKeydown(event);
$timeout.flush();
expect(controller.setScrollPosition).toHaveBeenCalledWith();
expect(controller._activeOption).toEqual(2);
});
});
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;
controller.setScrollPosition();
expect(child.scrollIntoView).toHaveBeenCalledWith();
});
});
describe('selectItem()', () => {
it(`should pass item to selected and set controller._show to false`, () => {
let item = {id: 1, name: 'Batman'};
controller.selectItem(item);
expect(controller.selected).toEqual(item);
expect(controller._show).toEqual(false);
});
it(`should pass item to selected and set controller._show to true if the controller.multiple is defined`, () => {
let item = {id: 1, name: 'Batman'};
controller.multiple = true;
controller.selectItem(item);
expect(controller.selected).toEqual(item);
expect(controller._show).not.toBeDefined();
});
});
describe('loadItems()', () => {
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);
});
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()', () => {
it(`should add an event listener to the parent element`, () => {
spyOn(controller.parent, 'addEventListener');
controller.$onInit();
expect(controller.parent.addEventListener).toHaveBeenCalledWith('keydown', jasmine.any(Function));
});
});
describe('$onDestroy()', () => {
it(`should remove an event listener from the parent element`, () => {
spyOn(controller.parent, 'removeEventListener');
controller.$onDestroy();
expect(controller.parent.removeEventListener).toHaveBeenCalledWith('keydown', jasmine.any(Function));
expect(controller.shown).toEqual(false);
});
});
});

View File

@ -0,0 +1,107 @@
import ngModule from '../../module';
export default class Model {
constructor($http, $q, $filter) {
this.$http = $http;
this.$q = $q;
this.$filter = $filter;
this.filter = null;
this.clear();
}
get isLoading() {
return this.canceler != null;
}
loadMore() {
if (this.moreRows) {
let filter = Object.assign({}, this.myFilter);
filter.skip += filter.limit;
this.sendRequest(filter, true);
}
}
clear() {
this.cancelRequest();
this.data = null;
this.moreRows = false;
this.dataChanged();
}
refresh(search) {
if (this.url) {
let filter = Object.assign({}, this.filter);
if (filter.limit)
filter.skip = 0;
this.clear();
this.sendRequest(filter);
} else if (this.staticData) {
if (search)
this.data = this.$filter('filter')(this.staticData, search);
else
this.data = this.staticData;
this.dataChanged();
}
}
cancelRequest() {
if (this.canceler) {
this.canceler.resolve();
this.canceler = null;
this.request = null;
}
}
sendRequest(filter, append) {
this.cancelRequest();
this.canceler = this.$q.defer();
let options = {timeout: this.canceler.promise};
let json = encodeURIComponent(JSON.stringify(filter));
this.request = this.$http.get(`${this.url}?filter=${json}`, options).then(
json => this.onRemoteDone(json, filter, append),
json => this.onRemoteError(json)
);
}
onRemoteDone(json, filter, append) {
let data = json.data;
if (append)
this.data = this.data.concat(data);
else
this.data = data;
this.myFilter = filter;
this.moreRows = filter.limit && data.length == filter.limit;
this.onRequestEnd();
this.dataChanged();
}
onRemoteError(json) {
this.onRequestEnd();
}
onRequestEnd() {
this.request = null;
this.canceler = null;
}
dataChanged() {
if (this.onDataChange)
this.onDataChange();
}
}
Model.$inject = ['$http', '$q', '$filter'];
ngModule.component('vnModel', {
controller: Model,
bindings: {
url: '@?',
staticData: '<?',
data: '=?',
limit: '<?',
onDataChange: '&?'
}
});

158
client/core/src/components/drop-down/style.scss Normal file → Executable file
View File

@ -1,80 +1,94 @@
vn-drop-down {
position: absolute;
z-index: 10;
padding: 0 15px;
margin-left: -15px;
background: transparent;
max-height: 446px;
overflow: hidden;
.dropdown-body{
background: white;
border: 1px solid #A7A7A7;
.filter{
padding: 5px 9px 5px 5px;
input{
height: 25px;
padding-left: 5px;
}
vn-icon{
font-size: 16px;
margin-left: -20px;
margin-top: 7px;
cursor: pointer;
&:hover{
color: red;
}
}
}
ul{
padding: 0;
margin: 0;
background: white;
max-height: 378px;
overflow-y: auto;
li {
outline: none;
list-style-type: none;
padding: 5px 10px;
cursor: pointer;
white-space: nowrap;
&.dropdown__loadMore{
color: rgb(255,171,64);
font-weight: 700;
}
&.dropdown__loadMore.noMore{
color:#424242;
font-weight: 700;
opacity: 0.7;
}
input[type=checkbox]{
float: left;
margin: 5px 5px 0 0;
}
}
li.active{
background-color: #3D3A3B;
color: white;
}
}
}
}
#ddBack {
position: fixed;
z-index: 9998;
display: none;
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;
right: 0;
bottom: 0;
&.shown {
& > .body {
transform: translateY(0);
opacity: 1;
}
}
& > .body {
position: fixed;
box-shadow: 0 .1em .4em rgba(1, 1, 1, .4);
border-radius: .1em;
background-color: white;
display: flex;
flex-direction: column;
transform: translateY(-.4em);
opacity: 0;
transition-property: opacity, transform;
transition-duration: 250ms;
transition-timing-function: ease-in-out;
& > .filter {
position: relative;
padding: .4em;
& > .search {
height: 26px;
display: block;
width: 100%;
box-sizing: border-box;
}
& > vn-icon[icon=clear] {
display: none;
cursor: pointer;
position: absolute;
right: .6em;
top: .8em;
height: 1em;
color: #888;
border-radius: .2em;
background-color: rgba(255, 255, 255, .8);
font-size: 18px;
&:hover {
color: #333;
}
}
&:hover > vn-icon[icon=clear] {
display: block;
}
}
& > .list {
max-height: 12.2em;
overflow: auto;
ul {
padding: 0;
margin: 0;
}
li, .status {
outline: none;
list-style-type: none;
padding: .6em;
cursor: pointer;
white-space: nowrap;
transition: background-color 250ms ease-out;
display: flex;
& > input[type=checkbox] {
margin: 0;
margin-right: .6em;
}
&:hover {
background-color: rgba(0, 0, 0, .1);
}
&.active {
background-color: #3D3A3B;
color: white;
}
}
.status {
color: rgb(255,171,64);
font-weight: bold;
}
}
}
}

View File

@ -25,8 +25,8 @@ export default {
socialName: `${components.vnTextfield}[name="socialName"]`,
userName: `${components.vnTextfield}[name="userName"]`,
email: `${components.vnTextfield}[name="email"]`,
salesPersonInput: `${components.vnAutocomplete}[field="$ctrl.client.salesPersonFk"] > vn-vertical > ${components.vnTextfield}`,
salesBruceBannerOption: `${components.vnAutocomplete}[field="$ctrl.client.salesPersonFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(1)`,
salesPersonInput: `vn-autocomplete[field="$ctrl.client.salesPersonFk"] input`,
salesBruceBannerOption: `vn-autocomplete[field="$ctrl.client.salesPersonFk"] vn-drop-down ul > li:nth-child(1)`,
createButton: `${components.vnSubmit}`
},
clientBasicData: {
@ -37,10 +37,10 @@ export default {
mobileInput: `${components.vnTextfield}[name="mobile"]`,
faxInput: `${components.vnTextfield}[name="fax"]`,
emailInput: `${components.vnTextfield}[name="email"]`,
salesPersonInput: `${components.vnAutocomplete}[field="$ctrl.client.salesPersonFk"] > vn-vertical > ${components.vnTextfield}`,
salesPersonOptionOne: `${components.vnAutocomplete}[field="$ctrl.client.salesPersonFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(1)`,
channelInput: `${components.vnAutocomplete}[field="$ctrl.client.contactChannelFk"] > vn-vertical > ${components.vnTextfield}`,
channelMetropolisOption: `${components.vnAutocomplete}[field="$ctrl.client.contactChannelFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(3)`,
salesPersonInput: `${components.vnAutocomplete}[field="$ctrl.client.salesPersonFk"] input`,
salesPersonOptionOne: `${components.vnAutocomplete}[field="$ctrl.client.salesPersonFk"] vn-drop-down ul > li:nth-child(1)`,
channelInput: `${components.vnAutocomplete}[field="$ctrl.client.contactChannelFk"] input`,
channelMetropolisOption: `${components.vnAutocomplete}[field="$ctrl.client.contactChannelFk"] vn-drop-down ul > li:nth-child(3)`,
saveButton: `${components.vnSubmit}`
},
clientFiscalData: {
@ -52,10 +52,10 @@ export default {
addressInput: `${components.vnTextfield}[name="street"]`,
cityInput: `${components.vnTextfield}[name="city"]`,
postcodeInput: `${components.vnTextfield}[name="postcode"]`,
provinceInput: `${components.vnAutocomplete}[field="$ctrl.client.provinceFk"] > vn-vertical > ${components.vnTextfield}`,
provinceFifthOption: `${components.vnAutocomplete}[field="$ctrl.client.provinceFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(5)`,
countryInput: `${components.vnAutocomplete}[field="$ctrl.client.countryFk"] > vn-vertical > ${components.vnTextfield}`,
countryThirdOption: `${components.vnAutocomplete}[field="$ctrl.client.countryFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(3)`,
provinceInput: `${components.vnAutocomplete}[field="$ctrl.client.provinceFk"] input`,
provinceFifthOption: `${components.vnAutocomplete}[field="$ctrl.client.provinceFk"] vn-drop-down ul > li:nth-child(5)`,
countryInput: `${components.vnAutocomplete}[field="$ctrl.client.countryFk"] input`,
countryThirdOption: `${components.vnAutocomplete}[field="$ctrl.client.countryFk"] vn-drop-down ul > li:nth-child(3)`,
activeCheckboxLabel: `${components.vnCheck}[label='Active'] > label`,
invoiceByAddressCheckboxInput: `${components.vnCheck}[label='Invoice by address'] > label > input`,
verifiedDataCheckboxInput: `${components.vnCheck}[label='Verified data'] > label > input`,
@ -66,9 +66,9 @@ export default {
},
clientPayMethod: {
payMethodButton: `${components.vnMenuItem}[ui-sref="clientCard.billingData"]`,
payMethodInput: `${components.vnAutocomplete}[field="$ctrl.client.payMethodFk"] > vn-vertical > ${components.vnTextfield}`,
payMethodIBANOption: `${components.vnAutocomplete}[field="$ctrl.client.payMethodFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(5)`,
payMethodOptionOne: `${components.vnAutocomplete}[field="$ctrl.client.payMethodFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(2)`,
payMethodInput: `${components.vnAutocomplete}[field="$ctrl.client.payMethodFk"] input`,
payMethodIBANOption: `${components.vnAutocomplete}[field="$ctrl.client.payMethodFk"] vn-drop-down ul > li:nth-child(5)`,
payMethodOptionOne: `${components.vnAutocomplete}[field="$ctrl.client.payMethodFk"] vn-drop-down ul > li:nth-child(2)`,
IBANInput: `${components.vnTextfield}[name="iban"]`,
dueDayInput: `${components.vnTextfield}[name="dueDay"]`,
cancelNotificationButton: 'vn-client-billing-data > vn-confirm button[response=CANCEL]',
@ -85,10 +85,10 @@ export default {
streetAddressInput: `${components.vnTextfield}[name="street"]`,
postcodeInput: `${components.vnTextfield}[name="postalCode"]`,
cityInput: `${components.vnTextfield}[name="city"]`,
provinceInput: `${components.vnAutocomplete}[field="$ctrl.address.provinceFk"] > vn-vertical > ${components.vnTextfield}`,
provinceSecondOption: `${components.vnAutocomplete}[field="$ctrl.address.provinceFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(2)`,
agencyInput: `${components.vnAutocomplete}[field="$ctrl.address.agencyModeFk"] > vn-vertical > ${components.vnTextfield}`,
agenctySecondOption: `${components.vnAutocomplete}[field="$ctrl.address.agencyModeFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(2)`,
provinceInput: `${components.vnAutocomplete}[field="$ctrl.address.provinceFk"] input`,
provinceSecondOption: `${components.vnAutocomplete}[field="$ctrl.address.provinceFk"] vn-drop-down ul > li:nth-child(2)`,
agencyInput: `${components.vnAutocomplete}[field="$ctrl.address.agencyModeFk"] input`,
agenctySecondOption: `${components.vnAutocomplete}[field="$ctrl.address.agencyModeFk"] vn-drop-down ul > li:nth-child(2)`,
phoneInput: `${components.vnTextfield}[name="phone"]`,
mobileInput: `${components.vnTextfield}[name="mobile"]`,
defaultAddress: 'vn-client-addresses > vn-vertical > vn-card > div > vn-horizontal:nth-child(2) > vn-one > vn-horizontal > vn-one > div:nth-child(2)',
@ -98,14 +98,14 @@ export default {
activeCheckbox: `${components.vnCheck}[label='Enabled'] > label > input`,
equalizationTaxCheckboxLabel: `${components.vnCheck}[label='Is equalizated'] > label > input`,
addAddressNoteButton: `${components.vnIcon}[icon="add_circle"]`,
firstObservationTypeSelect: `${components.vnAutocomplete}[field="observation.observationTypeFk"]:nth-child(1) > vn-vertical > ${components.vnTextfield}`,
firstObservationTypeSelectOptionOne: `${components.vnAutocomplete}[field="observation.observationTypeFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(1)`,
firstObservationTypeSelect: `${components.vnAutocomplete}[field="observation.observationTypeFk"]:nth-child(1) input`,
firstObservationTypeSelectOptionOne: `${components.vnAutocomplete}[field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(1)`,
firstObservationDescriptionInput: `vn-horizontal:nth-child(3) > vn-textfield[label="Description"] > div > input`,
secondObservationTypeSelect: `${components.vnAutocomplete}[field="observation.observationTypeFk"]:nth-child(2) > vn-vertical > ${components.vnTextfield}`,
secondObservationTypeSelectOptionTwo: `${components.vnAutocomplete}[field="observation.observationTypeFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(2)`,
secondObservationTypeSelect: `${components.vnAutocomplete}[field="observation.observationTypeFk"]:nth-child(2) input`,
secondObservationTypeSelectOptionTwo: `${components.vnAutocomplete}[field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(2)`,
secondObservationDescriptionInput: `vn-horizontal:nth-child(4) > vn-textfield[label="Description"] > div > input`,
thirdObservationTypeSelect: `${components.vnAutocomplete}[field="observation.observationTypeFk"]:nth-child(3) > vn-vertical > ${components.vnTextfield}`,
thirdObservationTypeSelectOptionThree: `${components.vnAutocomplete}[field="observation.observationTypeFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(3)`,
thirdObservationTypeSelect: `${components.vnAutocomplete}[field="observation.observationTypeFk"]:nth-child(3) input`,
thirdObservationTypeSelectOptionThree: `${components.vnAutocomplete}[field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(3)`,
thirdObservationDescriptionInput: `vn-horizontal:nth-child(5) > vn-textfield[label="Description"] > div > input`,
saveButton: `${components.vnSubmit}`
},
@ -134,8 +134,8 @@ export default {
addGreugeFloatButton: `${components.vnFloatButton}`,
amountInput: `${components.vnTextfield}[name="amount"]`,
descriptionInput: `${components.vnTextfield}[name="description"]`,
typeInput: `${components.vnAutocomplete}[field="$ctrl.greuge.greugeTypeFk"] > vn-vertical > ${components.vnTextfield}`,
typeSecondOption: `${components.vnAutocomplete}[field="$ctrl.greuge.greugeTypeFk"] > vn-vertical > vn-drop-down > vn-vertical > vn-auto:nth-child(2) > ul > li`,
typeInput: `${components.vnAutocomplete}[field="$ctrl.greuge.greugeTypeFk"] input`,
typeSecondOption: `${components.vnAutocomplete}[field="$ctrl.greuge.greugeTypeFk"] vn-drop-down ul > li`,
saveButton: `${components.vnSubmit}`,
firstGreugeText: 'vn-client-greuge-list .list-element'
},
@ -159,54 +159,54 @@ export default {
},
itemCreateView: {
name: `${components.vnTextfield}[name="name"]`,
typeSelect: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] > vn-vertical > ${components.vnTextfield}`,
typeSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] > vn-vertical > vn-drop-down > vn-vertical > vn-auto > ul > li:nth-child(2)`,
intrastatSelect: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] > vn-vertical > ${components.vnTextfield}`,
intrastatSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] > vn-vertical > vn-drop-down > vn-vertical > vn-auto > ul > li:nth-child(2)`,
originSelect: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] > vn-vertical > ${components.vnTextfield}`,
originSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] > vn-vertical > vn-drop-down > vn-vertical > vn-auto > ul > li:nth-child(2)`,
expenceSelect: `${components.vnAutocomplete}[field="$ctrl.item.expenceFk"] > vn-vertical > ${components.vnTextfield}`,
expenceSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.expenceFk"] > vn-vertical > vn-drop-down > vn-vertical > vn-auto > ul > li:nth-child(2)`,
typeSelect: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] input`,
typeSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] vn-drop-down ul > li:nth-child(2)`,
intrastatSelect: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] input`,
intrastatSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] vn-drop-down ul > li:nth-child(2)`,
originSelect: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] input`,
originSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] vn-drop-down ul > li:nth-child(2)`,
expenceSelect: `${components.vnAutocomplete}[field="$ctrl.item.expenceFk"] input`,
expenceSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.expenceFk"] vn-drop-down ul > li:nth-child(2)`,
createButton: `${components.vnSubmit}`
},
itemBasicData: {
goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]',
basicDataButton: `${components.vnMenuItem}[ui-sref="item.card.data"]`,
typeSelect: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] > vn-vertical > ${components.vnTextfield}`,
typeSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(2)`,
intrastatSelect: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] > vn-vertical > ${components.vnTextfield}`,
intrastatSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(1)`,
typeSelect: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] input`,
typeSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] vn-drop-down ul > li:nth-child(2)`,
intrastatSelect: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] input`,
intrastatSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] vn-drop-down ul > li:nth-child(1)`,
nameInput: `vn-horizontal:nth-child(2) > ${components.vnTextfield}`,
relevancyInput: `vn-horizontal:nth-child(3) > ${components.vnTextfield}`,
originSelect: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] > vn-vertical > ${components.vnTextfield}`,
originSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(2)`,
expenceSelect: `${components.vnAutocomplete}[field="$ctrl.item.expenceFk"] > vn-vertical > ${components.vnTextfield}`,
expenceSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.item.expenceFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(2)`,
originSelect: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] input`,
originSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] vn-drop-down ul > li:nth-child(2)`,
expenceSelect: `${components.vnAutocomplete}[field="$ctrl.item.expenceFk"] input`,
expenceSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.item.expenceFk"] vn-drop-down ul > li:nth-child(2)`,
submitBasicDataButton: `${components.vnSubmit}`
},
itemTags: {
goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]',
tagsButton: `${components.vnMenuItem}[ui-sref="item.card.tags"]`,
firstTagSelect: `vn-item-tags vn-horizontal:nth-child(2) > ${components.vnAutocomplete}[field="itemTag.tagFk"] > vn-vertical > ${components.vnTextfield}`,
firstTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(2) > ${components.vnAutocomplete}[field="itemTag.tagFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(1)`,
firstTagSelect: `vn-item-tags vn-horizontal:nth-child(2) > ${components.vnAutocomplete}[field="itemTag.tagFk"] input`,
firstTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(2) > ${components.vnAutocomplete}[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(1)`,
firstValueInput: `vn-item-tags vn-horizontal:nth-child(2) > vn-textfield[label="Value"] > div > input`,
firstRelevancyInput: `vn-horizontal:nth-child(2) > vn-textfield[label="Relevancy"] > div > input`,
secondTagSelect: `vn-item-tags vn-horizontal:nth-child(3) > ${components.vnAutocomplete}[field="itemTag.tagFk"] > vn-vertical > ${components.vnTextfield}`,
secondTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(3) > ${components.vnAutocomplete}[field="itemTag.tagFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(1)`,
secondTagSelect: `vn-item-tags vn-horizontal:nth-child(3) > ${components.vnAutocomplete}[field="itemTag.tagFk"] input`,
secondTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(3) > ${components.vnAutocomplete}[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(1)`,
secondValueInput: `vn-item-tags vn-horizontal:nth-child(3) > vn-textfield[label="Value"] > div > input`,
secondRelevancyInput: `vn-horizontal:nth-child(3) > vn-textfield[label="Relevancy"] > div > input`,
thirdTagSelect: `vn-item-tags vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="itemTag.tagFk"] > vn-vertical > ${components.vnTextfield}`,
thirdTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="itemTag.tagFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(1)`,
thirdTagSelect: `vn-item-tags vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="itemTag.tagFk"] input`,
thirdTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(1)`,
thirdValueInput: `vn-item-tags vn-horizontal:nth-child(4) > vn-textfield[label="Value"] > div > input`,
thirdRelevancyInput: `vn-horizontal:nth-child(4) > vn-textfield[label="Relevancy"] > div > input`,
fourthTagSelect: `vn-item-tags vn-horizontal:nth-child(5) > ${components.vnAutocomplete}[field="itemTag.tagFk"] > vn-vertical > ${components.vnTextfield}`,
fourthTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(5) > ${components.vnAutocomplete}[field="itemTag.tagFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(1)`,
fourthTagSelect: `vn-item-tags vn-horizontal:nth-child(5) > ${components.vnAutocomplete}[field="itemTag.tagFk"] input`,
fourthTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(5) > ${components.vnAutocomplete}[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(1)`,
fourthValueInput: `vn-item-tags vn-horizontal:nth-child(5) > vn-textfield[label="Value"] > div > input`,
fourthRelevancyInput: `vn-horizontal:nth-child(5) > vn-textfield[label="Relevancy"] > div > input`,
fifthRemoveTagButton: `vn-item-tags vn-horizontal:nth-child(6) > ${components.vnIcon}[icon="remove_circle_outline"]`,
fifthTagSelect: `vn-item-tags vn-horizontal:nth-child(6) > ${components.vnAutocomplete}[field="itemTag.tagFk"] > vn-vertical > ${components.vnTextfield}`,
fifthTagSelectOptionFive: `vn-item-tags vn-horizontal:nth-child(6) > ${components.vnAutocomplete}[field="itemTag.tagFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(5)`,
fifthTagSelect: `vn-item-tags vn-horizontal:nth-child(6) > ${components.vnAutocomplete}[field="itemTag.tagFk"] input`,
fifthTagSelectOptionFive: `vn-item-tags vn-horizontal:nth-child(6) > ${components.vnAutocomplete}[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(5)`,
fifthValueInput: `vn-item-tags vn-horizontal:nth-child(6) > vn-textfield[label="Value"] > div > input`,
fifthRelevancyInput: `vn-horizontal:nth-child(6) > vn-textfield[label="Relevancy"] > div > input`,
addItemTagButton: `${components.vnIcon}[icon="add_circle"]`,
@ -214,12 +214,12 @@ export default {
},
itemTax: {
taxButton: `${components.vnMenuItem}[ui-sref="item.card.tax"]`,
firstClassSelect: `vn-horizontal:nth-child(2) > ${components.vnAutocomplete}[field="tax.taxClassFk"] > vn-vertical > ${components.vnTextfield}`,
firstClassSelectOptionTwo: `vn-horizontal:nth-child(2) > ${components.vnAutocomplete} > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(2)`,
secondClassSelect: `vn-horizontal:nth-child(3) > ${components.vnAutocomplete}[field="tax.taxClassFk"] > vn-vertical > ${components.vnTextfield}`,
secondClassSelectOptionOne: `vn-horizontal:nth-child(3) > ${components.vnAutocomplete} > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(1)`,
thirdClassSelect: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="tax.taxClassFk"] > vn-vertical > ${components.vnTextfield}`,
thirdClassSelectOptionTwo: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete} > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(2)`,
firstClassSelect: `vn-horizontal:nth-child(2) > ${components.vnAutocomplete}[field="tax.taxClassFk"] input`,
firstClassSelectOptionTwo: `vn-horizontal:nth-child(2) > ${components.vnAutocomplete} vn-drop-down ul > li:nth-child(2)`,
secondClassSelect: `vn-horizontal:nth-child(3) > ${components.vnAutocomplete}[field="tax.taxClassFk"] input`,
secondClassSelectOptionOne: `vn-horizontal:nth-child(3) > ${components.vnAutocomplete} vn-drop-down ul > li:nth-child(1)`,
thirdClassSelect: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="tax.taxClassFk"] input`,
thirdClassSelectOptionTwo: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete} vn-drop-down ul > li:nth-child(2)`,
submitTaxButton: `${components.vnSubmit}`
},
itemBarcodes: {
@ -232,11 +232,11 @@ export default {
itemNiches: {
nicheButton: `${components.vnMenuItem}[ui-sref="item.card.niche"]`,
addNicheButton: `${components.vnIcon}[icon="add_circle"]`,
firstWarehouseSelect: `${components.vnAutocomplete}[field="itemNiche.warehouseFk"] > vn-vertical > ${components.vnTextfield}`,
firstWarehouseSelectSecondOption: `${components.vnAutocomplete}[field="itemNiche.warehouseFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(2)`,
secondWarehouseSelect: `vn-horizontal:nth-child(3) > ${components.vnAutocomplete}[field="itemNiche.warehouseFk"] > vn-vertical > ${components.vnTextfield}`,
thirdWarehouseSelect: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="itemNiche.warehouseFk"] > vn-vertical > ${components.vnTextfield}`,
thirdWarehouseSelectFourthOption: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="itemNiche.warehouseFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(4)`,
firstWarehouseSelect: `${components.vnAutocomplete}[field="itemNiche.warehouseFk"] input`,
firstWarehouseSelectSecondOption: `${components.vnAutocomplete}[field="itemNiche.warehouseFk"] vn-drop-down ul > li:nth-child(2)`,
secondWarehouseSelect: `vn-horizontal:nth-child(3) > ${components.vnAutocomplete}[field="itemNiche.warehouseFk"] input`,
thirdWarehouseSelect: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="itemNiche.warehouseFk"] input`,
thirdWarehouseSelectFourthOption: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="itemNiche.warehouseFk"] vn-drop-down ul > li:nth-child(4)`,
secondNicheRemoveButton: `vn-horizontal:nth-child(3) > vn-one > ${components.vnIcon}[icon="remove_circle_outline"]`,
firstCodeInput: `vn-horizontal:nth-child(2) > ${components.vnTextfield}`,
secondCodeInput: `vn-horizontal:nth-child(3) > ${components.vnTextfield}`,
@ -246,12 +246,12 @@ export default {
itemBotanical: {
botanicalButton: `${components.vnMenuItem}[ui-sref="item.card.botanical"]`,
botanicalInput: `vn-horizontal:nth-child(2) > ${components.vnTextfield}`,
genusSelect: `${components.vnAutocomplete}[field="$ctrl.botanical.genusFk"] > vn-vertical > ${components.vnTextfield}`,
genusSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.botanical.genusFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(1)`,
genusSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.botanical.genusFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(2)`,
speciesSelect: `${components.vnAutocomplete}[field="$ctrl.botanical.specieFk"] > vn-vertical > ${components.vnTextfield}`,
speciesSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.botanical.specieFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(1)`,
speciesSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.botanical.specieFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(2)`,
genusSelect: `${components.vnAutocomplete}[field="$ctrl.botanical.genusFk"] input`,
genusSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.botanical.genusFk"] vn-drop-down ul > li:nth-child(1)`,
genusSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.botanical.genusFk"] vn-drop-down ul > li:nth-child(2)`,
speciesSelect: `${components.vnAutocomplete}[field="$ctrl.botanical.specieFk"] input`,
speciesSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.botanical.specieFk"] vn-drop-down ul > li:nth-child(1)`,
speciesSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.botanical.specieFk"] vn-drop-down ul > li:nth-child(2)`,
submitBotanicalButton: `${components.vnSubmit}`
},
itemSummary: {