+
-
-
+ rule="{{::$ctrl.rule}}"/>
+
+
+ query_builder
+ clear
+
+
\ No newline at end of file
diff --git a/client/core/src/datePicker/datePicker.js b/client/core/src/datePicker/datePicker.js
index 70a6344b8..2ebed10d6 100644
--- a/client/core/src/datePicker/datePicker.js
+++ b/client/core/src/datePicker/datePicker.js
@@ -27,7 +27,9 @@ class DatePicker extends Component {
this.enabled = true;
this._modelView = null;
this._model = undefined;
-
+ this._optionsChecked = false;
+ this.hasFocus = false;
+ this.hasMouseIn = false;
componentHandler.upgradeElement($element[0].firstChild);
}
@@ -37,7 +39,9 @@ class DatePicker extends Component {
set model(value) {
this._model = value;
if (value && !this.modelView) {
- let format = this._formatFlat2Angular(this.iniOptions.dateFormat || 'Y-m-d');
+ let options = this._getOptions();
+ let initialDateFormat = (options && options.dateFormat) ? options.dateFormat : 'Y-m-d';
+ let format = this._formatFlat2Angular(initialDateFormat);
this.modelView = this.$filter('date')(value, format);
}
}
@@ -48,9 +52,8 @@ class DatePicker extends Component {
this._modelView = value;
this.input.value = value;
this._setModel(value);
- this.$timeout(
- () => {
- this.mdlUpdate();
+ this.$timeout(() => {
+ this.mdlUpdate();
}, 500);
}
onClear() {
@@ -76,16 +79,15 @@ class DatePicker extends Component {
return `${dates} ${hours}`.trim();
} else if (string.indexOf(':') !== -1) { // only time format
return parts.join(':');
- } else { // only date format
- return parts.join('-');
- }
+ } // only date format
+ return parts.join('-');
}
_setModel(value) {
let model;
if (!value) {
model = undefined;
- } else if (!this.iniOptions.dateFormat || (this.iniOptions.dateFormat && this.iniOptions.dateFormat.startsWith('Y-m-d'))) {
+ } else if (!this.iniOptions || (this.iniOptions.dateFormat && this.iniOptions.dateFormat.startsWith('Y-m-d'))) {
model = value;
} else {
let formats = this.iniOptions.dateFormat.split(/[ZT.,/ :-]/);
@@ -136,9 +138,12 @@ class DatePicker extends Component {
}
}
- $onInit() {
- if (!this.iniOptions)
+ _getOptions() {
+ if (this.iniOptions && this._optionsChecked) {
+ return this.iniOptions;
+ } else if (!this.iniOptions) {
this.iniOptions = {};
+ }
if (!this.iniOptions.locale)
this.iniOptions.locale = this.$translate.use();
@@ -158,9 +163,14 @@ class DatePicker extends Component {
}
);
}
+ this._optionsChecked = true;
+ return this.iniOptions;
+ }
- if (this.input)
- this.vp = new Flatpickr(this.input, this.iniOptions);
+ $onInit() {
+ this.iniOptions = this._getOptions();
+ this.isTimePicker = (this.iniOptions && this.iniOptions.enableTime && this.iniOptions.noCalendar);
+ this.vp = new Flatpickr(this.input, this.iniOptions);
}
$onDestroy() {
if (this.vp)
diff --git a/client/core/src/datePicker/datePicker.spec.js b/client/core/src/datePicker/datePicker.spec.js
new file mode 100644
index 000000000..bd0807dd7
--- /dev/null
+++ b/client/core/src/datePicker/datePicker.spec.js
@@ -0,0 +1,44 @@
+import './datePicker.js';
+
+describe('Component vnDatePicker', () => {
+ let $componentController;
+ let $scope;
+ let $timeout;
+ let $element;
+ let $translate;
+ let controller;
+
+ beforeEach(() => {
+ angular.mock.module('client');
+ });
+
+ beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$timeout_, _$translate_) => {
+ $componentController = _$componentController_;
+ $scope = $rootScope.$new();
+ $timeout = _$timeout_;
+ $element = angular.element(`
`);
+ $translate = _$translate_;
+ controller = $componentController('vnDatePicker', {$scope, $element, $translate, $timeout});
+ }));
+
+ describe('_formatFlat2Angular()', () => {
+ it(`should format date from Y-m-d to yyyy-MM-dd`, () => {
+ let formatedDate = controller._formatFlat2Angular(`Y-m-d`);
+
+ expect(formatedDate).toBe('yyyy-MM-dd');
+ });
+
+ it(`should format date from d-m-Y to dd-MM-yyyy`, () => {
+ let formatedDate = controller._formatFlat2Angular(`d-m-Y`);
+
+ expect(formatedDate).toBe('dd-MM-yyyy');
+ });
+
+ it(`should split the given string into parts`, () => {
+ controller.iniOptions = {dateFormat: 'd/m/Y'};
+ controller.model = '2017-12-23';
+
+ expect(controller.modelView).toBe('23-12-2017');
+ });
+ });
+});
diff --git a/client/core/src/datePicker/style.scss b/client/core/src/datePicker/style.scss
index f1bd30dab..d729ba947 100644
--- a/client/core/src/datePicker/style.scss
+++ b/client/core/src/datePicker/style.scss
@@ -1,12 +1,18 @@
vn-date-picker {
+ div {
+ outline: none; //remove chrome outline
+ }
.mdl-chip__action {
- position: absolute;
+ position: absolute;
+ width: auto;
top: 0px;
right: -6px;
margin: 22px 0px;
background-color: white;
}
.material-icons {
- font-size: 18px;
+ font-size: 18px;
+ float: right;
+ margin-right: 5px;
}
}
\ No newline at end of file
diff --git a/client/core/src/dialog/dialog.js b/client/core/src/dialog/dialog.js
index 6f2c7d3c7..a8d33b4ca 100644
--- a/client/core/src/dialog/dialog.js
+++ b/client/core/src/dialog/dialog.js
@@ -13,21 +13,20 @@ export default class Dialog extends Component {
super($element);
$element.addClass('vn-dialog');
this.dialog = $element[0].firstChild;
- this.element.addEventListener('mousedown',
- event => this.onBackgroundMouseDown(event));
+ this.element.addEventListener('mousedown', event => this.onBackgroundMouseDown(event));
}
/**
* Displays the dialog to the user.
*/
show() {
let style = this.dialog.style;
- let screenMargin = 20;
-
let window = this.window;
let innerWidth = window.innerWidth;
let innerHeight = window.innerHeight;
let width = this.dialog.offsetWidth;
let height = this.dialog.offsetHeight;
+ let screenMargin = 20;
+ let dblMargin = screenMargin * 2;
if (width + screenMargin > innerWidth) {
width = innerWidth - dblMargin;
@@ -38,10 +37,8 @@ export default class Dialog extends Component {
style.height = height + 'px';
}
- this.keypressHandler =
- event => this.onKeypress(event);
- this.document.addEventListener('keypress',
- this.keypressHandler);
+ this.keypressHandler = event => this.onKeypress(event);
+ this.document.addEventListener('keypress', this.keypressHandler);
this.element.style.display = 'block';
if (this.onOpen)
@@ -66,17 +63,17 @@ export default class Dialog extends Component {
cancel = this.onResponse({response: response});
return cancel;
}
+
realHide() {
this.element.style.display = 'none';
- this.document.removeEventListener('keypress',
- this.keypressHandler);
+ this.document.removeEventListener('keypress', this.keypressHandler);
this.lastEvent = null;
}
+
onButtonClick(event) {
let buttonBar = this.element.querySelector('.button-bar');
let buttons = buttonBar.querySelector('tpl-buttons');
let node = event.target;
-
while (node.parentNode != buttons) {
if (node == buttonBar) return;
node = node.parentNode;
@@ -86,13 +83,16 @@ export default class Dialog extends Component {
let cancel = this.fireResponse(response);
if (cancel !== false) this.realHide();
}
+
onDialogMouseDown(event) {
this.lastEvent = event;
}
+
onBackgroundMouseDown(event) {
if (event != this.lastEvent)
this.hide();
}
+
onKeypress(event) {
if (event.keyCode == 27) // Esc
this.hide();
@@ -107,8 +107,8 @@ module.component('vnDialog', {
tplButtons: 'tplButtons'
},
bindings: {
- onOpen: '&',
- onResponse: '&'
+ onOpen: '&?',
+ onResponse: '&?'
},
controller: Dialog
});
diff --git a/client/core/src/dialog/dialog.spec.js b/client/core/src/dialog/dialog.spec.js
new file mode 100644
index 000000000..e13242d6b
--- /dev/null
+++ b/client/core/src/dialog/dialog.spec.js
@@ -0,0 +1,176 @@
+describe('Component vnDialog', () => {
+ let $componentController;
+ let $element;
+ let controller;
+
+ beforeEach(() => {
+ angular.mock.module('client');
+ });
+
+ beforeEach(angular.mock.inject(_$componentController_ => {
+ $componentController = _$componentController_;
+ $element = angular.element('
');
+ controller = $componentController('vnDialog', {$element});
+ }));
+
+ describe('show()', () => {
+ it(`should define keypressHandler function, call addEventListener function and define element.style.display to block then call onOpen function`, () => {
+ window.innerHeight = 600;
+ window.innerWidth = 800;
+ controller.onOpen = () => {};
+ controller.dialog = {style: {}, offsetWidth: 780, offsetHeight: 581};
+ spyOn(controller.document, 'addEventListener');
+ spyOn(controller, 'onOpen');
+ controller.show();
+
+ expect(controller.keypressHandler).toBeDefined();
+ expect(controller.document.addEventListener).toHaveBeenCalledWith('keypress', controller.keypressHandler);
+ expect(controller.element.style.display).toEqual('block');
+ expect(controller.onOpen).toHaveBeenCalledWith();
+ });
+
+ it(`should define keypressHandler function, call addEventListener function and define element.style.display to block and never call onOpen function`, () => {
+ window.innerHeight = 600;
+ window.innerWidth = 800;
+ controller.dialog = {style: {}, offsetWidth: 781, offsetHeight: 581};
+ spyOn(controller.document, 'addEventListener');
+ controller.show();
+
+ expect(controller.keypressHandler).toBeDefined();
+ expect(controller.document.addEventListener).toHaveBeenCalledWith('keypress', controller.keypressHandler);
+ expect(controller.element.style.display).toEqual('block');
+ expect(controller.onOpen).not.toBeDefined();
+ });
+ });
+
+ describe('hide()', () => {
+ it(`should call fireResponse() and realHide()`, () => {
+ spyOn(controller, 'fireResponse');
+ spyOn(controller, 'realHide');
+ controller.hide();
+
+ expect(controller.fireResponse).toHaveBeenCalledWith();
+ expect(controller.realHide).toHaveBeenCalledWith();
+ });
+ });
+
+ describe('fireResponse()', () => {
+ it(`should return cancel as false`, () => {
+ let result = controller.fireResponse('I am the answer!');
+
+ expect(controller.onResponse).not.toBeDefined();
+ expect(result).toEqual(false);
+ });
+
+ it(`should return onResponse()`, () => {
+ let text = 'I am the answer!';
+ controller.onResponse = () => {
+ return {response: text};
+ };
+ let result = controller.fireResponse(text);
+
+ expect(result.response).toEqual(text);
+ });
+ });
+
+ describe('realHide()', () => {
+ it(`should set element.style.display and lastEvent properties and call removeEvenListener()`, () => {
+ spyOn(controller.document, 'removeEventListener');
+
+ expect(controller.element.style.display).not.toEqual('none');
+ expect(controller.lastEvent).not.toBeDefined();
+ controller.realHide();
+
+ expect(controller.element.style.display).toEqual('none');
+ expect(controller.document.removeEventListener).toHaveBeenCalledWith('keypress', controller.keypressHandler);
+ expect(controller.lastEvent).toEqual(null);
+ });
+ });
+
+ describe('onButtonClick()', () => {
+ it(`should call realHide if cancel isn't false`, () => {
+ controller.element = document.createElement('div');
+ controller.element.className = 'tpl-buttons';
+ let childElement = document.createElement('div');
+ childElement.className = 'button-bar';
+ controller.element.appendChild(childElement);
+ let event = {target: controller.element, attribute: true};
+ spyOn(controller, 'realHide');
+ spyOn(controller, 'fireResponse').and.returnValue(true);
+ controller.onButtonClick(event);
+
+ expect(controller.realHide).toHaveBeenCalledWith();
+ });
+
+ it(`should call fireResponse with the value of response`, () => {
+ controller.element = document.createElement('div');
+ controller.element.className = 'tpl-buttons';
+ let childElement = document.createElement('div');
+ childElement.className = 'button-bar';
+ controller.element.appendChild(childElement);
+ let attribute = document.createAttribute('response');
+ attribute.value = 'I am the response!';
+ controller.element.setAttributeNode(attribute);
+ spyOn(controller, 'fireResponse');
+ let event = {target: controller.element};
+ controller.onButtonClick(event);
+
+ expect(controller.fireResponse).toHaveBeenCalledWith('I am the response!');
+ });
+ });
+
+ describe('onDialogMouseDown()', () => {
+ it(`should set controller's lastEvent property`, () => {
+ controller.element = document.createElement('div');
+ let event = {target: controller.element};
+ controller.onDialogMouseDown(event);
+
+ expect(controller.lastEvent).toEqual(event);
+ });
+ });
+
+ describe('onBackgroundMouseDown()', () => {
+ it(`shouldn't call hide() function as event equals lastEvent`, () => {
+ controller.element = document.createElement('div');
+ let event = {target: controller.element};
+ controller.lastEvent = event;
+ spyOn(controller, 'hide');
+ controller.onBackgroundMouseDown(event);
+
+ expect(controller.hide).not.toHaveBeenCalledWith();
+ });
+
+ it(`should call hide() function as event doesn't equal lastEvent`, () => {
+ controller.element = document.createElement('div');
+ let event = {target: controller.element};
+ controller.lastEvent = event;
+ controller.lastEvent = 'the singularity event!';
+ spyOn(controller, 'hide');
+ controller.onBackgroundMouseDown(event);
+
+ expect(controller.hide).toHaveBeenCalledWith();
+ });
+ });
+
+ describe('onKeypress()', () => {
+ it(`should call hide() if the key pressed equal the code 27`, () => {
+ controller.element = document.createElement('div');
+ let event = {target: controller.element};
+ event.keyCode = 27;
+ spyOn(controller, 'hide');
+ controller.onKeypress(event);
+
+ expect(controller.hide).toHaveBeenCalledWith();
+ });
+
+ it(`should't call hide() as the key pressed equal the code 999`, () => {
+ controller.element = document.createElement('div');
+ let event = {target: controller.element};
+ event.keyCode = 999;
+ spyOn(controller, 'hide');
+ controller.onKeypress(event);
+
+ expect(controller.hide).not.toHaveBeenCalledWith();
+ });
+ });
+});
diff --git a/client/core/src/directives/acl.js b/client/core/src/directives/acl.js
index 713c61e27..362dad65d 100644
--- a/client/core/src/directives/acl.js
+++ b/client/core/src/directives/acl.js
@@ -19,8 +19,8 @@ function vnAcl(aclService, $timeout) {
$timeout(() => {
input.setAttribute("disabled", "true");
});
- $element[0].querySelectorAll('i, vn-drop-down').forEach(i => {
- i.parentNode.removeChild(i);
+ $element[0].querySelectorAll('i, vn-drop-down').forEach(element => {
+ element.parentNode.removeChild(element);
});
}
} else {
diff --git a/client/core/src/directives/dialog.js b/client/core/src/directives/dialog.js
index d4ac65b45..5ee10258e 100644
--- a/client/core/src/directives/dialog.js
+++ b/client/core/src/directives/dialog.js
@@ -11,7 +11,8 @@ export function directive() {
restrict: 'A',
link: function($scope, $element, $attrs) {
$element.on('click', function(event) {
- let dialog = $scope[kebabToCamel($attrs.vnDialog)];
+ let dialogKey = kebabToCamel($attrs.vnDialog);
+ let dialog = $scope[dialogKey];
if (dialog instanceof Dialog)
dialog.show();
event.preventDefault();
diff --git a/client/core/src/directives/focus.js b/client/core/src/directives/focus.js
index 39ae0f196..bf4d86122 100644
--- a/client/core/src/directives/focus.js
+++ b/client/core/src/directives/focus.js
@@ -2,6 +2,8 @@ import {module} from '../module';
/**
* Sets the focus and selects the text on the input.
+ *
+ * @return {Object} The directive
*/
export function directive() {
return {
diff --git a/client/core/src/directives/repeat.js b/client/core/src/directives/repeat.js
deleted file mode 100644
index 643b86a4a..000000000
--- a/client/core/src/directives/repeat.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import {module} from '../module';
-
-directive.$inject = ['$compile'];
-function directive($compile) {
- return {
- restrict: 'A',
- priority: 9999,
- link: function(scope, element, attrs) {
- element.removeAttr('vn-repeat');
- element.attr('ng-repeat', attrs.vnRepeat);
- $compile(element)(scope);
- }
- };
-}
-
-module.directive('vnRepeat', directive);
diff --git a/client/core/src/directives/specs/acl.spec.js b/client/core/src/directives/specs/acl.spec.js
new file mode 100644
index 000000000..615de5146
--- /dev/null
+++ b/client/core/src/directives/specs/acl.spec.js
@@ -0,0 +1,54 @@
+describe('Directive acl', () => {
+ let scope;
+ let element;
+ let compile;
+ let $timeout;
+
+ beforeEach(() => {
+ angular.mock.module('client');
+ });
+
+ compile = (hasPermissions, _element) => {
+ inject(($compile, $rootScope, aclService, _$timeout_) => {
+ spyOn(aclService, 'aclPermission').and.returnValue(hasPermissions);
+ scope = $rootScope.$new();
+ $timeout = _$timeout_;
+ element = angular.element(_element);
+ $compile(element)(scope);
+ scope.$digest();
+ });
+ };
+
+ it('should not disable the input element as the user has permision', () => {
+ let html = `
`;
+ compile(true, html);
+ let input = element.find('input');
+
+ expect(input).toBeDefined();
+ expect(input.attr('disabled')).toBeFalsy();
+ });
+
+ it('should delete the element as the user does not have permission and there is no action', () => {
+ let html = `
`;
+ compile(false, html);
+
+ expect(element.children().length).toEqual(0);
+ });
+
+ it('should disable the element as the action is to disable it but the user has no permission but present', () => {
+ let html = `
`;
+ compile(false, html);
+ let input = element.find('input');
+ $timeout.flush();
+
+ expect(input).toBeDefined();
+ expect(input.attr('disabled')).toBeTruthy();
+ });
+
+ it('should delete any element with the tag i and vn-drop-down', () => {
+ let html = `
`;
+ compile(false, html);
+
+ expect(element.find('i').length).toBe(0);
+ });
+});
diff --git a/client/core/src/directives/specs/dialog.spec.js b/client/core/src/directives/specs/dialog.spec.js
new file mode 100644
index 000000000..38d4ed9e2
--- /dev/null
+++ b/client/core/src/directives/specs/dialog.spec.js
@@ -0,0 +1,37 @@
+describe('Directive dialog', () => {
+ let $scope;
+ let $element;
+ let element;
+ let compile;
+ let $componentController;
+ let controller;
+
+ beforeEach(() => {
+ angular.mock.module('client');
+ });
+
+ compile = _element => {
+ inject(($compile, $rootScope) => {
+ $scope = $rootScope.$new();
+ $scope.myDialog = controller;
+ element = angular.element(_element);
+ $compile(element)($scope);
+ $scope.$digest();
+ });
+ };
+
+ beforeEach(angular.mock.inject(_$componentController_ => {
+ $componentController = _$componentController_;
+ $element = angular.element('
');
+ controller = $componentController('vnDialog', {$element});
+ }));
+
+ it('should call show() function if dialog is a instance of vnDialog', () => {
+ let html = `
`;
+ spyOn(controller, 'show');
+ compile(html);
+ element[0].click();
+
+ expect(controller.show).toHaveBeenCalledWith();
+ });
+});
diff --git a/client/core/src/directives/specs/focus.spec.js b/client/core/src/directives/specs/focus.spec.js
new file mode 100644
index 000000000..f610e9ffb
--- /dev/null
+++ b/client/core/src/directives/specs/focus.spec.js
@@ -0,0 +1,55 @@
+describe('Directive focus', () => {
+ let $scope;
+ let $element;
+ let compile;
+
+ beforeEach(() => {
+ angular.mock.module('client');
+ });
+
+ compile = (_element, _childElement) => {
+ inject(($compile, $rootScope) => {
+ $scope = $rootScope.$new();
+ $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');
+ $element[0].select = jasmine.createSpy('select');
+ $compile($element)($scope);
+ $scope.$digest();
+ });
+ };
+
+ it('should call the querySelector function upon the input to redefine it with the expected selector then call focus', () => {
+ let html = `
`;
+ let childHtml = '
';
+ compile(html, childHtml);
+
+ expect($element[0].firstChild.focus).toHaveBeenCalled();
+ });
+
+ it('should print a warning message on console', () => {
+ let html = `
`;
+ console.warn = jasmine.createSpy('warn');
+ compile(html);
+
+ expect(console.warn).toHaveBeenCalledWith(`vnFocus: Can't find a focusable element`);
+ });
+
+ it('should call focus function on the element', () => {
+ let html = `
`;
+ compile(html);
+
+ expect($element[0].focus).toHaveBeenCalledWith();
+ });
+
+ it('should call select function on the element', () => {
+ let html = `
`;
+ compile(html);
+
+ expect($element[0].select).toHaveBeenCalledWith();
+ });
+});
diff --git a/client/core/src/directives/specs/id.spec.js b/client/core/src/directives/specs/id.spec.js
new file mode 100644
index 000000000..5755d1ea9
--- /dev/null
+++ b/client/core/src/directives/specs/id.spec.js
@@ -0,0 +1,43 @@
+describe('Directive vnId', () => {
+ let $scope;
+ let $element;
+ let compile;
+
+ beforeEach(() => {
+ angular.mock.module('client');
+ });
+
+ compile = _element => {
+ inject(($compile, $rootScope) => {
+ $scope = $rootScope.$new();
+ $element = angular.element(_element);
+ $compile($element)($scope);
+ $scope.$digest();
+ });
+ };
+
+ it(`should throw an error when there's no id defined`, () => {
+ let html = `
`;
+
+ expect(() => {
+ compile(html);
+ }).toThrow(new Error(`vnId: Attribute can't be null`));
+ });
+
+ it(`should throw an error when these's no controller defined in $element[0]`, () => {
+ let html = `
`;
+
+ expect(() => {
+ compile(html);
+ }).toThrow(new Error(`vnId: Can't find controller for element '1'`));
+ });
+
+ it(`should set the controller into the $scope as there are no errors being thrown`, () => {
+ let html = `
`;
+
+ expect($scope['1']).not.toBeDefined();
+ compile(html);
+
+ expect($scope['1']).toBeDefined();
+ });
+});
diff --git a/client/core/src/directives/specs/validation.spec.js b/client/core/src/directives/specs/validation.spec.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/client/core/src/drop-down/drop-down.html b/client/core/src/drop-down/drop-down.html
index c65feac28..9bab06069 100644
--- a/client/core/src/drop-down/drop-down.html
+++ b/client/core/src/drop-down/drop-down.html
@@ -16,7 +16,14 @@
{{item.name}}
-
+
\ 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 465137070..90a02e857 100644
--- a/client/core/src/drop-down/drop-down.js
+++ b/client/core/src/drop-down/drop-down.js
@@ -16,6 +16,7 @@ export default class DropDown {
get show() {
return this._show;
}
+
set show(value) {
let oldValue = this.show;
this._show = value;
@@ -27,12 +28,14 @@ export default class DropDown {
this._focusingFilter = false;
}, 250);
}
- }
+ }
+
get search() {
return this._search;
}
+
set search(value) {
- let val = (value === undefined && value === '') ? null : value;
+ let val = (value === undefined || value === '') ? null : value;
this._search = val;
if (this.filterAction)
@@ -40,9 +43,11 @@ export default class DropDown {
else
this.filterItems();
}
+
get activeOption() {
return this._activeOption;
}
+
set activeOption(value) {
if (value < 0) {
value = 0;
@@ -51,6 +56,10 @@ export default class DropDown {
}
this.$timeout(() => {
this._activeOption = value;
+ // AutoLoad items with "scroll" (1st version):
+ if (value && value >= this.items.length - 3 && !this.removeLoadMore) {
+ this.loadItems();
+ }
});
}
@@ -59,7 +68,9 @@ export default class DropDown {
}
onFilterRest() {
- this.filterAction({search: this.search});
+ if (this.filterAction) {
+ this.filterAction({search: this.search});
+ }
}
$onChanges(changesObj) {
@@ -117,6 +128,7 @@ export default class DropDown {
}
}
}
+
setScrollPosition() {
let dropdown = this.$element[0].querySelector('ul.dropdown');
let child = dropdown ? dropdown.childNodes[this.activeOption] : null;
@@ -135,6 +147,13 @@ export default class DropDown {
}
}
+ loadItems() {
+ if (this.showLoadMore && this.loadMore) {
+ this.loadMore();
+ }
+ this.show = true;
+ }
+
$onInit() {
if (this.parent)
this.parent.addEventListener('keydown', e => this.onKeydown(e));
@@ -144,6 +163,7 @@ export default class DropDown {
this.parent.removeEventListener('keydown', e => this.onKeydown(e));
}
}
+
DropDown.$inject = ['$element', '$filter', '$timeout'];
module.component('vnDropDown', {
@@ -156,8 +176,9 @@ module.component('vnDropDown', {
selected: '=',
search: '=?',
loadMore: '&?',
+ removeLoadMore: '',
filterAction: '&?',
- showLoadMore: '=?',
+ showLoadMore: '',
top: '',
itemWidth: '',
parent: '',
diff --git a/client/core/src/drop-down/drop-down.spec.js b/client/core/src/drop-down/drop-down.spec.js
new file mode 100644
index 000000000..23fab3b08
--- /dev/null
+++ b/client/core/src/drop-down/drop-down.spec.js
@@ -0,0 +1,335 @@
+import './drop-down.js';
+
+describe('Component vnDropDown', () => {
+ let $componentController;
+ let $timeout;
+ let $element;
+ let $filter;
+ let controller;
+
+ beforeEach(() => {
+ angular.mock.module('client');
+ });
+
+ beforeEach(angular.mock.inject((_$componentController_, _$timeout_, _$filter_) => {
+ $componentController = _$componentController_;
+ $element = angular.element('
');
+ $timeout = _$timeout_;
+ $filter = _$filter_;
+ controller = $componentController('vnDropDown', {$element, $timeout, $filter});
+ }));
+
+ describe('show() setter', () => {
+ it(`should define controllers _show using the value received as argument`, () => {
+ controller._show = 'old value';
+ controller.show = 'new value';
+
+ expect(controller._show).toEqual('new value');
+ });
+ });
+
+ describe('search()', () => {
+ it(`should set controllers _search property with the value received`, () => {
+ controller.search = 'some filter valiue';
+
+ 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.items = [{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.items = [{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.items = [{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.items = [{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 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');
+ 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.items = [{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.items = [{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');
+ let child = $element[0].firstChild.firstChild;
+ 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).toEqual(true);
+ });
+ });
+
+ describe('loadItems()', () => {
+ it(`should set controller._show to true`, () => {
+ controller.loadItems();
+
+ expect(controller._show).toEqual(true);
+ });
+
+ it(`should call loadMore() and then set controller._show to true`, () => {
+ controller.showLoadMore = () => {};
+ controller.loadMore = () => {};
+ spyOn(controller, 'loadMore');
+ controller.loadItems();
+
+ expect(controller._show).toEqual(true);
+ 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));
+ });
+ });
+});
diff --git a/client/core/src/drop-down/style.scss b/client/core/src/drop-down/style.scss
index f66d23ea6..e65fd4e6e 100644
--- a/client/core/src/drop-down/style.scss
+++ b/client/core/src/drop-down/style.scss
@@ -39,6 +39,11 @@ vn-drop-down {
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;
diff --git a/client/core/src/grid-header/grid-header.spec.js b/client/core/src/grid-header/grid-header.spec.js
new file mode 100644
index 000000000..03294a348
--- /dev/null
+++ b/client/core/src/grid-header/grid-header.spec.js
@@ -0,0 +1,43 @@
+import './grid-header.js';
+
+describe('Component vnGridHeader', () => {
+ let $componentController;
+ let controller;
+
+ beforeEach(() => {
+ angular.mock.module('client');
+ });
+
+ beforeEach(angular.mock.inject(_$componentController_ => {
+ $componentController = _$componentController_;
+ controller = $componentController('vnGridHeader', {});
+ }));
+
+ describe('selectColum()', () => {
+ it(`should set controller currentColumn to equal the argument received`, () => {
+ let col = {columnStuff: 'some stuff'};
+ controller.selectColum(col);
+
+ expect(controller.currentColumn).toEqual(col);
+ });
+
+ it(`should set controller currentColumn.order to undefined then set currentColumn to equal the argument received`, () => {
+ controller.currentColumn = {field: 'some field', order: 'ordered'};
+ let col = {columnStuff: 'some stuff'};
+ controller.selectColum(col);
+
+ expect(controller.currentColumn.order).not.toBeDefined();
+ expect(controller.currentColumn).toEqual(col);
+ });
+
+ it(`should set controller currentColumn.order to undefined then call onOrder passing currentColumn as argument`, () => {
+ controller.onOrder = () => {};
+ spyOn(controller, 'onOrder');
+ let col = {columnStuff: 'some stuff'};
+ controller.selectColum(col);
+
+ expect(controller.currentColumn).toEqual(col);
+ expect(controller.onOrder).toHaveBeenCalledWith(col);
+ });
+ });
+});
diff --git a/client/core/src/icon-menu/icon-menu.html b/client/core/src/icon-menu/icon-menu.html
index 9cf978b55..6f49a6f7e 100644
--- a/client/core/src/icon-menu/icon-menu.html
+++ b/client/core/src/icon-menu/icon-menu.html
@@ -1,5 +1,8 @@