diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index db03f7a49..000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "google", - "installedESLint": true, - "rules": { - "indent": ["error", 4], - "require-jsdoc": 0, - "max-len": 0 - } -} diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 000000000..d92eef6f2 --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,8 @@ +extends: google +installedESLint: true +rules: + indent: [error, 4] + no-undef: 0 + require-jsdoc: 0 + max-len: 0 + eqeqeq: 0 diff --git a/client/client/src/addresses/index.js b/client/client/src/addresses/index.js index f66295d37..8afad6631 100644 --- a/client/client/src/addresses/index.js +++ b/client/client/src/addresses/index.js @@ -5,7 +5,7 @@ class Controller { this.$state = $state; } onNewAddressClick() { - this.$state.go('clientCard.addressCreate', {id: this.client.id}); + this.$state.go('clientCard.addressCreate', {id: this.$state.params.id}); } } Controller.$inject = ['$state']; diff --git a/client/client/src/create/index.js b/client/client/src/create/index.js index a554df979..6cd940bd9 100644 --- a/client/client/src/create/index.js +++ b/client/client/src/create/index.js @@ -10,12 +10,12 @@ class Controller { }; } onSubmit() { - this.$scope.watcher.submit().then ( - json => this.$state.go(state, {id: json.data.id}) + this.$scope.watcher.submit().then( + json => this.$state.go('clientCard.basicData', {id: json.data.id}) ); } onCreate() { - this.$scope.watcher.submit().then ( + this.$scope.watcher.submit().then( () => this.$window.history.back() ); } diff --git a/client/core/src/autocomplete/index.js b/client/core/src/autocomplete/index.js index 80d6b5468..09c9c3535 100644 --- a/client/core/src/autocomplete/index.js +++ b/client/core/src/autocomplete/index.js @@ -46,7 +46,7 @@ export default class Autocomplete extends Component { loadData(textFilter) { textFilter = textFilter ? textFilter : ''; - if(this.lastSearch === textFilter) { + if (this.lastSearch === textFilter) { this.popoverDataReady(); return; } @@ -58,7 +58,7 @@ export default class Autocomplete extends Component { && !this.moreData && textFilter.substr(0, lastRequest.length) === lastRequest; - if(requestWillSame) + if (requestWillSame) this.localFilter(textFilter); else this.requestData(textFilter, false); @@ -73,9 +73,9 @@ export default class Autocomplete extends Component { let where = {}; let skip = 0; - if(textFilter) + if (textFilter) where[this.showField] = {ilike: textFilter}; - if(append && this.data) + if (append && this.data) skip = this.data.length; let filter = { @@ -89,7 +89,7 @@ export default class Autocomplete extends Component { this.lastRequest = textFilter ? textFilter : ''; let json = JSON.stringify(filter); - if(this.currentRequest) + if (this.currentRequest) this.currentRequest.resolve(); this.currentRequest = this.$http.get(`${this.url}?filter=${json}`); @@ -102,7 +102,7 @@ export default class Autocomplete extends Component { this.currentRequest = null; this.moreData = data.length >= this.maxRows; - if(!append || !this.data) + if (!append || !this.data) this.data = data; else this.data = this.data.concat(data); @@ -111,7 +111,7 @@ export default class Autocomplete extends Component { } localFilter(textFilter) { let regex = new RegExp(textFilter, 'i'); - let data = this.data.filter((item) => { + let data = this.data.filter(item => { return regex.test(item[this.showField]); }); this.setPopoverData(data); @@ -121,80 +121,78 @@ export default class Autocomplete extends Component { this.popoverDataReady(); } popoverDataReady() { - if(this.hasFocus) + if (this.hasFocus) this.showPopover(); } showPopover() { - if(!this.data) return; + if (!this.data) return; let fragment = this.document.createDocumentFragment(); let data = this.popoverData; - for(let i = 0; i < data.length; i++) { + for (let i = 0; i < data.length; i++) { let li = this.document.createElement('li'); li.appendChild(this.document.createTextNode(data[i][this.showField])); fragment.appendChild(li); } - if(this.moreData) { + if (this.moreData) { let li = this.document.createElement('li'); li.appendChild(this.document.createTextNode('Load more')); li.className = 'load-more'; fragment.appendChild(li); } - if (!this.popover) { + if (this.popover) { + this.popover.innerHTML = ''; + this.popover.appendChild(fragment); + } else { let popover = this.document.createElement('ul'); popover.addEventListener('click', - (e) => this.onPopoverClick(e)); + e => this.onPopoverClick(e)); popover.addEventListener('mousedown', - (e) => this.onPopoverMousedown(e)); + e => this.onPopoverMousedown(e)); popover.className = 'vn-autocomplete'; popover.appendChild(fragment); this.vnPopover.show(popover, this.input); this.popover = popover; } - else { - this.popover.innerHTML = ''; - this.popover.appendChild(fragment); - } } hidePopover() { - if(!this.popover) return; + if (!this.popover) return; this.activeOption = -1; this.vnPopover.hide(); this.popover = null; } selectPopoverOption(index) { - if(!this.popover || index == -1) return; - if(index < this.popoverData.length) { + if (!this.popover || index === -1) return; + if (index < this.popoverData.length) { this.selectOptionByDataIndex(this.popoverData, index); this.hidePopover(); - } - else + } else this.requestData(this.lastRequest, true); } onPopoverClick(event) { let childs = this.popover.childNodes; - for(let i = 0; i < childs.length; i++) - if(childs[i] === event.target) { - this.selectPopoverOption(i); - break; - } + for (let i = 0; i < childs.length; i++) + if (childs[i] === event.target) { + this.selectPopoverOption(i); + break; + } } onPopoverMousedown(event) { // Prevents input from loosing focus event.preventDefault(); } onClick(event) { - if(!this.popover) + if (!this.popover) this.showPopover(); } onFocus() { this.hasFocus = true; this.input.select(); - if(this.data) + if (this.data) this.showPopover(); else this.loadData(); @@ -205,29 +203,29 @@ export default class Autocomplete extends Component { this.hidePopover(); } onKeydown(event) { - switch(event.keyCode) { - case 13: // Enter - this.selectPopoverOption(this.activeOption); - break; - case 27: // Escape - this.restoreShowValue(); - this.input.select(); - break; - case 38: // Arrow up - this.activateOption(this.activeOption-1); - break; - case 40: // Arrow down - this.activateOption(this.activeOption+1); - break; - default: - return; + switch (event.keyCode) { + case 13: // Enter + this.selectPopoverOption(this.activeOption); + break; + case 27: // Escape + this.restoreShowValue(); + this.input.select(); + break; + case 38: // Arrow up + this.activateOption(this.activeOption - 1); + break; + case 40: // Arrow down + this.activateOption(this.activeOption + 1); + break; + default: + return; } event.preventDefault(); } onKeyup(event) { - if(!this.isKeycodePrintable(event.keyCode)) return; - if(this.timeoutId) clearTimeout(this.timeoutId); + if (!this.isKeycodePrintable(event.keyCode)) return; + if (this.timeoutId) clearTimeout(this.timeoutId); this.timeoutId = setTimeout(() => this.onTimeout(), this.requestDelay); } onTimeout() { @@ -235,8 +233,8 @@ export default class Autocomplete extends Component { this.timeoutId = null; } isKeycodePrintable(keyCode) { - return keyCode == 32 // Spacebar - || keyCode == 8 // Backspace + return keyCode === 32 // Spacebar + || keyCode === 8 // Backspace || (keyCode > 47 && keyCode < 58) // Numbers || (keyCode > 64 && keyCode < 91) // Letters || (keyCode > 95 && keyCode < 112) // Numpad @@ -247,14 +245,14 @@ export default class Autocomplete extends Component { this.putItem(this.item); } requestItem() { - if(!this.value) return; + if (!this.value) return; let where = {}; where[this.valueField] = this.value; let filter = { fields: this.getRequestFields(), - where: where, + where: where }; let json = JSON.stringify(filter); @@ -265,25 +263,25 @@ export default class Autocomplete extends Component { ); } onItemRequest(data) { - if(data && data.length > 0) + if (data && data.length > 0) this.showItem(data[0]); else this.showItem(null); } activateOption(index) { - if(!this.popover) + if (!this.popover) this.showPopover(); let popover = this.popover; let childs = popover.childNodes; let len = this.popoverData.length; - if(this.activeOption >= 0) + if (this.activeOption >= 0) childs[this.activeOption].className = ''; - if(index >= len) + if (index >= len) index = 0; - else if(index < 0) + else if (index < 0) index = len - 1; if (index >= 0) { @@ -291,9 +289,9 @@ export default class Autocomplete extends Component { let top = popover.scrollTop; let height = popover.clientHeight; - if(opt.offsetTop + opt.offsetHeight > top + height) + if (opt.offsetTop + opt.offsetHeight > top + height) top = opt.offsetTop + opt.offsetHeight - height; - else if(opt.offsetTop < top) + else if (opt.offsetTop < top) top = opt.offsetTop; opt.className = 'active'; @@ -305,26 +303,25 @@ export default class Autocomplete extends Component { setValue(value) { this.value = value; - if(value) { + if (value) { let data = this.data; - - if(data) - for(let i = 0; i < data.length; i++) - if(data[i][this.valueField] == value) { - this.putItem(data[i]); - return; - } + + if (data) + for (let i = 0; i < data.length; i++) + if (data[i][this.valueField] == value) { + this.putItem(data[i]); + return; + } this.requestItem(); - } - else + } else this.putItem(null); } selectOptionByIndex(index) { this.selectOptionByDataIndex(this.data, index); } selectOptionByDataIndex(data, index) { - if(data && index >= 0 && index < data.length) + if (data && index >= 0 && index < data.length) this.putItem(data[index]); else this.putItem(null); @@ -333,9 +330,9 @@ export default class Autocomplete extends Component { this.showItem(item); let value = item ? item[this.valueField] : undefined; - if(!this.locked) { + if (!this.locked) { this.value = value; - setTimeout ( + setTimeout( () => this.$scope.$apply()); } } diff --git a/client/core/src/directives/validation.js b/client/core/src/directives/validation.js index b03b27b5d..24fe52efb 100644 --- a/client/core/src/directives/validation.js +++ b/client/core/src/directives/validation.js @@ -8,59 +8,64 @@ export function directive(interpolate, compile, $window) { restrict: 'A', require: ['ngModel', '^^form'], link: link - } + }; function link(scope, element, attrs, ctrl) { let vnValidations = $window.validations; - if(!attrs['vnValidation'] || ! vnValidations) + if (!attrs.vnValidation || !vnValidations) return; - let split = attrs['vnValidation'].split('.'); + let split = attrs.vnValidation.split('.'); - if(split.length != 2) + if (split.length !== 2) throw new Error(`vnValidation: Attribute must have this syntax: [entity].[field]`); let entityName = firstUpper(split[0]); let fieldName = split[1]; let entity = vnValidations[entityName]; - if(!entity) + if (!entity) throw new Error(`vnValidation: Entity '${entityName}' doesn't exist`); let validations = entity.validations[fieldName]; - if(!validations || validations.length == 0) + if (!validations || validations.length == 0) return; - let input = ctrl[0], - form = ctrl[1]; + let errorMsg = angular.element(''); + element.after(errorMsg); - let template = ''; - let messageNode = interpolate(template)({form: form.$name, input: input.$name}); - messageNode = angular.element(messageNode); + let input = ctrl[0]; + let form = ctrl[1]; - input.$validators['entity'] = function(value) { - return isValid(value, validations, messageNode, element); - } + input.$validators.entity = function(value) { + let parent = element.parent(); - element.after(compile(messageNode)(scope)); - } - function isValid(value, validations, messageNode, element) { - let parent = element.parent(); + try { + validateAll(value, validations); + } catch (e) { + errorMsg.text(e.message); + parent.attr('title', e.message); + return false; + } - try { - validateAll(value, validations) - } - catch(e) { - messageNode.text(e.message); - parent.attr('title', e.message); - parent.addClass('invalid'); - return false; - } + return true; + }; - parent.removeClass('invalid'); - return true; + scope.$watch(function() { + return (form.$submitted || input.$dirty) && input.$invalid; + }, function(value) { + let parent = element.parent(); + + if (value) { + parent.addClass('invalid'); + errorMsg[0].style.display = 'block'; + } else { + parent.removeClass('invalid'); + errorMsg[0].style.display = 'none'; + } + }); } } module.directive('vnValidation', directive); diff --git a/client/core/src/lib/validator.js b/client/core/src/lib/validator.js index 11c582f47..fa32bf38a 100644 --- a/client/core/src/lib/validator.js +++ b/client/core/src/lib/validator.js @@ -1,36 +1,12 @@ import {validator} from 'vendor'; -export function validateAll(value, validations) { - for(let conf of validations) - validate(value, conf); -} -export function validate(value, conf) { - let validator = validators[conf.validation]; - try { - checkNull(value, conf); - validator && validator(value, conf); - } - catch(e) { - let message = conf.message ? conf.message : e.message; - throw new Error(message); - } -} -export function checkNull(value, conf) { - if (typeof value === 'undefined') { - if (!conf.allowBlank) - throw new Error(`Value can't be blank`); - } else { - if (value === null && !conf.allowNull) - throw new Error(`Value can't be null`); - } -} export const validators = { presence: function(value, conf) { - if(validator.isEmpty(value)) + if (validator.isEmpty(value)) throw new Error(`Value can't be empty`); }, - absence: function() { - if(!validator.isEmpty(value)) + absence: function(value, conf) { + if (!validator.isEmpty(value)) throw new Error(`Value should be empty`); }, length: function(value, conf) { @@ -38,34 +14,31 @@ export const validators = { min: conf.min || conf.is, max: conf.max || conf.is }; - - if(!validator.isLength(value, options)) { - if(conf.is) + + if (!validator.isLength(value, options)) { + if (conf.is) throw new Error(`Value should be ${conf.is} characters long`); else throw new Error(`Value should have a length between ${conf.min} and ${conf.max}`); } }, numericality: function(value, conf) { - if(conf.int) { - if(!validator.isInt(value)) + if (conf.int) { + if (!validator.isInt(value)) throw new Error(`Value should be integer`); - } - else { - if(!validator.isNumeric(vlaue)) - throw new Error(`Value should be a number`); - } + } else if (!validator.isNumeric(value)) + throw new Error(`Value should be a number`); }, inclusion: function(value, conf) { - if(!validator.isIn(value, conf.in)) + if (!validator.isIn(value, conf.in)) throw new Error(`Invalid value`); }, exclusion: function(value, conf) { - if(validator.isIn(value, conf.in)) + if (validator.isIn(value, conf.in)) throw new Error(`Invalid value`); }, format: function(value, conf) { - if(!validator.matches(value, conf.with)) + if (!validator.matches(value, conf.with)) throw new Error(`Invalid value`); }, custom: function(value, conf) { @@ -75,12 +48,42 @@ export const validators = { } let inst = {attr: value}; - conf.customValidator.call(inst, err) + conf.customValidator.call(inst, err); - if(!valid) + if (!valid) throw new Error(`Invalid value`); }, uniqueness: function() { // TODO: Implement me - }, + } }; + +export function validateAll(value, validations) { + for (let conf of validations) + validate(value, conf); +} + +export function validate(value, conf) { + let validator = validators[conf.validation]; + try { + checkNull(value, conf); + if (validator) validator(value, conf); + } catch (e) { + let message = conf.message ? conf.message : e.message; + throw new Error(message); + } +} + +/** + * Checks if value is null. + * + * @param {value} value The value + * @param {Object} conf The configuration for the field + */ +export function checkNull(value, conf) { + if (typeof value === 'undefined') { + if (!conf.allowBlank) + throw new Error(`Value can't be blank`); + } else if (value === null && !conf.allowNull) + throw new Error(`Value can't be null`); +} diff --git a/client/core/src/watcher/index.js b/client/core/src/watcher/index.js index 0d161b6da..9a629a6c4 100644 --- a/client/core/src/watcher/index.js +++ b/client/core/src/watcher/index.js @@ -21,16 +21,16 @@ export default class Watcher extends Component { this.state = null; this.deregisterCallback = $transitions.onStart({}, - (transition) => this.callback(transition)); + transition => this.callback(transition)); this.copyData(); } $onInit() { - if(this.get) { + if (this.get) { this.fetchData(); } } $onChanges(changes) { - if(this.data) { + if (this.data) { this.copyData(); } } @@ -47,19 +47,19 @@ export default class Watcher extends Component { }); } submit() { - if(this.form && !this.form.$valid) { - return new Promise ( + if (this.form && !this.form.$valid) { + return new Promise( (resolve, reject) => this.invalidForm(reject) ); } if (!this.dataChanged()) { - return new Promise ( + return new Promise( (resolve, reject) => this.noChanges(reject) ); } let changedData = getModifiedData(this.data, this.orgData); - if(this.save) { + if (this.save) { this.save.model = changedData; return new Promise((resolve, reject) => { this.save.accept().then( @@ -73,7 +73,7 @@ export default class Watcher extends Component { let id = this.orgData[this.idField]; - if(id) { + if (id) { return new Promise((resolve, reject) => { this.$http.put(`${this.url}/${id}`, changedData).then( json => this.writeData(json, resolve), @@ -81,14 +81,13 @@ export default class Watcher extends Component { ); }); } - else { - return new Promise((resolve, reject) => { - this.$http.post(this.url, this.data).then( - json => this.writeData(json, resolve), - json => reject(json) - ); - }); - } + + return new Promise((resolve, reject) => { + this.$http.post(this.url, this.data).then( + json => this.writeData(json, resolve), + json => reject(json) + ); + }); } writeData(json, resolve) { copyObject(json.data, this.data); @@ -116,18 +115,17 @@ export default class Watcher extends Component { this.$scope.confirm.show(); return false; } - + return true; } dataChanged() { return !isEqual(this.data, this.orgData); } onConfirmResponse(response) { - if(response == 'ACCEPT') { + if (response === 'ACCEPT') { copyObject(this.orgData, this.data); this.$state.go(this.state); - } - else { + } else { this.state = null; } }