Merge branch 'dev' of http://git.verdnatura.es/salix into dev

This commit is contained in:
Carlos Jimenez 2018-03-12 13:31:05 +01:00
commit 23e3e4bf36
26 changed files with 460 additions and 471 deletions

View File

@ -1,7 +1,7 @@
{ {
"module": "client", "module": "client",
"name": "Clients", "name": "Clients",
"icon": "/static/images/icon_client.png", "icon": "person",
"validations" : true, "validations" : true,
"routes": [ "routes": [
{ {

View File

@ -30,7 +30,7 @@
<span>{{::observation.description}}</span> <span>{{::observation.description}}</span>
</vn-one> </vn-one>
</vn-vertical> </vn-vertical>
<a vn-auto ui-sref="clientCard.addresses.edit({addressId: {{address.id}}})"> <a vn-auto ui-sref="clientCard.addresses.edit({addressId: {{::address.id}}})">
<vn-icon-button icon="edit"></vn-icon-button> <vn-icon-button icon="edit"></vn-icon-button>
</a> </a>
</vn-horizontal> </vn-horizontal>

View File

@ -9,13 +9,6 @@ vn-autocomplete > div > .mdl-textfield {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
&:focus {
outline: none;
}
&::-moz-focus-inner {
border: 0;
}
} }
& > .icons { & > .icons {
display: none; display: none;

View File

@ -1,7 +1,4 @@
vn-date-picker { vn-date-picker {
div {
outline: none; //remove chrome outline
}
.mdl-chip__action { .mdl-chip__action {
position: absolute; position: absolute;
width: auto; width: auto;

View File

@ -1,35 +1,38 @@
<div <vn-model
class="body" vn-id="model"
ng-mousedown="$ctrl.onMouseDown($event)"> on-data-change="$ctrl.onModelDataChange()">
<div ng-show="$ctrl.showFilter" class="filter"> </vn-model>
<input <vn-popover
type="text" vn-id="popover"
ng-model="$ctrl.search" on-open="$ctrl.onOpen()"
tabindex="-1" on-close="$ctrl.onClose()">
class="search" <div class="dropdown">
ng-blur="$ctrl.onFocusOut()" <div ng-show="$ctrl.showFilter" class="filter">
translate-attr="{placeholder: 'Search'}"/> <input
<vn-icon type="text"
icon="clear" ng-model="$ctrl.search"
ng-click="$ctrl.onClearClick()" tabindex="-1"
translate-attr="{title: 'Clear'}"> class="search"
</vn-icon> ng-blur="$ctrl.onFocusOut()"
</div> translate-attr="{placeholder: 'Search'}"/>
<vn-model <vn-icon
vn-id="model" icon="clear"
on-data-change="$ctrl.onModelDataChange()"> ng-click="$ctrl.onClearClick()"
</vn-model> translate-attr="{title: 'Clear'}">
<div class="list" tabindex="-1"> </vn-icon>
<ul </div>
class="dropdown" <div class="list" tabindex="-1">
ng-click="$ctrl.onContainerClick($event)"> <ul
</ul> class="dropdown"
<div ng-click="$ctrl.onContainerClick($event)">
ng-if="$ctrl.statusText" </ul>
ng-click="$ctrl.onLoadMoreClick($event)" <div
class="status" ng-if="$ctrl.statusText"
translate> ng-click="$ctrl.onLoadMoreClick($event)"
{{$ctrl.statusText}} class="status"
translate>
{{$ctrl.statusText}}
</div>
</div> </div>
</div> </div>
</div> </vn-popover>

View File

@ -4,45 +4,34 @@ import './style.scss';
import './model'; import './model';
export default class DropDown extends Component { export default class DropDown extends Component {
constructor($element, $scope, $transclude, $timeout, $http, $translate) { constructor($element, $scope, $transclude, $timeout, $http) {
super($element, $scope); super($element, $scope);
this.$transclude = $transclude; this.$transclude = $transclude;
this.$timeout = $timeout; this.$timeout = $timeout;
this.$translate = $translate;
this.valueField = 'id'; this.valueField = 'id';
this.showField = 'name'; this.showField = 'name';
this._search = undefined; this._search = undefined;
this._shown = false;
this._activeOption = -1; this._activeOption = -1;
this.showLoadMore = true; this.showLoadMore = true;
this.showFilter = 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.docKeyDownHandler = e => this.onDocKeyDown(e);
this.docFocusInHandler = e => this.onDocFocusIn(e); }
this.element.addEventListener('mousedown', $postLink() {
e => this.onBackgroundMouseDown(e)); this.input = this.element.querySelector('.search');
this.element.addEventListener('focusin', this.ul = this.element.querySelector('ul');
e => this.onFocusIn(e)); this.list = this.element.querySelector('.list');
this.list.addEventListener('scroll', this.list.addEventListener('scroll', e => this.onScroll(e));
e => this.onScroll(e));
} }
get shown() { get shown() {
return this._shown; return this.$.popover.shown;
} }
set shown(value) { set shown(value) {
if (value) this.$.popover.shown = value;
this.show();
else
this.hide();
} }
get search() { get search() {
@ -97,62 +86,18 @@ export default class DropDown extends Component {
* @param {String} search The initial search term or %null * @param {String} search The initial search term or %null
*/ */
show(search) { show(search) {
if (this._shown) return;
this._shown = true;
this._activeOption = -1;
this.search = search; this.search = search;
this._activeOption = -1;
this.buildList(); this.buildList();
this.element.style.display = 'block'; this.$.popover.parent = this.parent;
this.list.scrollTop = 0; this.$.popover.show();
this.$timeout(() => this.$element.addClass('shown'), 40);
this.document.addEventListener('keydown', this.docKeyDownHandler);
this.document.addEventListener('focusin', this.docFocusInHandler);
this.relocate();
this.input.focus();
} }
/** /**
* Hides the drop-down. * Hides the drop-down.
*/ */
hide() { hide() {
if (!this._shown) return; this.$.popover.hide();
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();
}
/**
* 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`;
} }
/** /**
@ -190,7 +135,7 @@ export default class DropDown extends Component {
let data = this.$.model.data; let data = this.$.model.data;
if (option >= 0 && data && option < data.length) { if (option >= 0 && data && option < data.length) {
this.activeLi = this.container.children[option]; this.activeLi = this.ul.children[option];
this.activeLi.className = 'active'; this.activeLi.className = 'active';
} }
} }
@ -224,7 +169,7 @@ export default class DropDown extends Component {
} }
if (!this.multiple) if (!this.multiple)
this.hide(); this.$.popover.hide();
} }
refreshModel() { refreshModel() {
@ -260,22 +205,14 @@ export default class DropDown extends Component {
return undefined; return undefined;
} }
onMouseDown(event) { onOpen() {
this.lastMouseEvent = event; this.document.addEventListener('keydown', this.docKeyDownHandler);
this.list.scrollTop = 0;
this.input.focus();
} }
onBackgroundMouseDown(event) { onClose() {
if (event != this.lastMouseEvent) this.document.removeEventListener('keydown', this.docKeyDownHandler);
this.hide();
}
onFocusIn(event) {
this.lastFocusEvent = event;
}
onDocFocusIn(event) {
if (event !== this.lastFocusEvent)
this.hide();
} }
onClearClick() { onClearClick() {
@ -299,7 +236,7 @@ export default class DropDown extends Component {
onContainerClick(event) { onContainerClick(event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
let index = getPosition(this.container, event); let index = getPosition(this.ul, event);
if (index != -1) this.selectOption(index); if (index != -1) this.selectOption(index);
} }
@ -318,9 +255,6 @@ export default class DropDown extends Component {
case 13: // Enter case 13: // Enter
this.selectOption(option); this.selectOption(option);
break; break;
case 27: // Escape
this.hide();
break;
case 38: // Up case 38: // Up
this.moveToOption(option <= 0 ? nOpts : option - 1); this.moveToOption(option <= 0 ? nOpts : option - 1);
break; break;
@ -342,8 +276,7 @@ export default class DropDown extends Component {
} }
buildList() { buildList() {
this.destroyScopes(); this.destroyList();
this.scopes = [];
let hasTemplate = this.$transclude && let hasTemplate = this.$transclude &&
this.$transclude.isSlotFilled('tplItem'); this.$transclude.isSlotFilled('tplItem');
@ -377,23 +310,26 @@ export default class DropDown extends Component {
} }
} }
this.container.innerHTML = ''; this.ul.appendChild(fragment);
this.container.appendChild(fragment);
this.activateOption(this._activeOption); this.activateOption(this._activeOption);
this.relocate(); this.$.popover.relocate();
} }
destroyScopes() { destroyList() {
this.ul.innerHTML = '';
if (this.scopes) if (this.scopes)
for (let scope of this.scopes) for (let scope of this.scopes)
scope.$destroy(); scope.$destroy();
this.scopes = [];
} }
$onDestroy() { $onDestroy() {
this.destroyScopes(); this.destroyList();
} }
} }
DropDown.$inject = ['$element', '$scope', '$transclude', '$timeout', '$http', '$translate']; DropDown.$inject = ['$element', '$scope', '$transclude', '$timeout', '$http'];
/** /**
* Gets the position of an event element relative to a parent. * Gets the position of an event element relative to a parent.

View File

@ -7,6 +7,7 @@ describe('Component vnDropDown', () => {
let $element; let $element;
let $scope; let $scope;
let $httpBackend; let $httpBackend;
let $transitions;
let $q; let $q;
let $filter; let $filter;
let controller; let controller;
@ -15,18 +16,23 @@ describe('Component vnDropDown', () => {
angular.mock.module('client'); angular.mock.module('client');
}); });
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$timeout_, _$httpBackend_, _$q_, _$filter_) => { beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$timeout_, _$httpBackend_, _$q_, _$filter_, _$transitions_) => {
$componentController = _$componentController_; $componentController = _$componentController_;
$element = angular.element(`<div>${template}</div>`); $element = angular.element(`<div>${template}</div>`);
$timeout = _$timeout_; $timeout = _$timeout_;
$transitions = _$transitions_;
$q = _$q_; $q = _$q_;
$filter = _$filter_; $filter = _$filter_;
$scope = $rootScope.$new(); $scope = $rootScope.$new();
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
let popoverTemplate = require('../popover/popover.html');
let $popover = angular.element(`<div>${popoverTemplate}</div>`);
$scope.popover = $componentController('vnPopover', {$element: $popover, $scope, $timeout, $transitions});
$scope.model = $componentController('vnModel', {$httpBackend, $q, $filter}); $scope.model = $componentController('vnModel', {$httpBackend, $q, $filter});
controller = $componentController('vnDropDown', {$element, $scope, $transclude: null, $timeout, $httpBackend, $translate: null}); controller = $componentController('vnDropDown', {$element, $scope, $transclude: null, $timeout, $httpBackend, $translate: null});
controller.$postLink();
controller.parent = angular.element('<vn-parent></vn-parent>')[0]; controller.parent = angular.element('<vn-parent></vn-parent>')[0];
})); }));

View File

@ -1,30 +1,7 @@
vn-drop-down { vn-drop-down {
z-index: 10; .dropdown {
position: fixed;
display: none;
top: 0;
left: 0;
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; display: flex;
flex-direction: column; flex-direction: column;
transform: translateY(-.4em);
opacity: 0;
transition-property: opacity, transform;
transition-duration: 250ms;
transition-timing-function: ease-in-out;
& > .filter { & > .filter {
position: relative; position: relative;
@ -35,6 +12,7 @@ vn-drop-down {
box-sizing: border-box; box-sizing: border-box;
border: none; border: none;
border-bottom: 1px solid #ccc; border-bottom: 1px solid #ccc;
font-size: inherit;
padding: .6em; padding: .6em;
} }
& > vn-icon[icon=clear] { & > vn-icon[icon=clear] {
@ -64,10 +42,9 @@ vn-drop-down {
ul { ul {
padding: 0; padding: 0;
margin: 0; margin: 0;
list-style-type: none;
} }
li, .status { li, .status {
outline: none;
list-style-type: none;
padding: .6em; padding: .6em;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;

View File

@ -2,10 +2,6 @@
vn-icon{ vn-icon{
cursor: pointer; cursor: pointer;
} }
&:focus, &:active, &:hover{
outline: none;
border: none;
}
.primaryCheckbox { .primaryCheckbox {
vn-icon{ vn-icon{
font-size: 22px; font-size: 22px;

View File

@ -0,0 +1,4 @@
<div class="popover">
<div class="arrow"></div>
<div class="content" ng-transclude></div>
</div>

View File

@ -1,205 +1,220 @@
import ngModule from '../../module'; import ngModule from '../../module';
import './style.css'; import Component from '../../lib/component';
import './style.scss';
directive.$inject = ['vnPopover']; /**
export function directive(vnPopover) { * A simple popover.
return { */
restrict: 'A', export default class Popover extends Component {
link: function($scope, $element, $attrs) { constructor($element, $scope, $timeout, $transitions) {
$element.on('click', function(event) { super($element, $scope);
vnPopover.showComponent($attrs.vnDialog, $scope, $element); this.$timeout = $timeout;
event.preventDefault();
});
}
};
}
ngModule.directive('vnPopover', directive);
export class Popover {
constructor($document, $compile, $transitions) {
this.document = $document[0];
this.$compile = $compile;
this.$transitions = $transitions; this.$transitions = $transitions;
this.removeScope = false; this._shown = false;
this.popOpens = 0;
}
_init() {
this.docMouseDownHandler = e => this.onDocMouseDown(e);
this.document.addEventListener('mousedown', this.docMouseDownHandler);
this.docKeyDownHandler = e => this.onDocKeyDown(e); 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.popover = this.element.querySelector('.popover');
this.popover.addEventListener('mousedown',
e => this.onMouseDown(e));
this.arrow = this.element.querySelector('.arrow');
this.content = this.element.querySelector('.content');
}
set child(value) {
this.content.appendChild(value);
}
get child() {
return this.content.firstChild;
}
get shown() {
return this._shown;
}
set shown(value) {
if (value)
this.show();
else
this.hide();
}
/**
* Shows the popover. If a parent is specified it is shown in a visible
* relative position to it.
*/
show() {
if (this._shown) return;
this._shown = true;
this.element.style.display = 'block';
this.$timeout.cancel(this.showTimeout);
this.showTimeout = this.$timeout(() => {
this.$element.addClass('shown');
this.showTimeout = null;
}, 30);
this.document.addEventListener('keydown', this.docKeyDownHandler); this.document.addEventListener('keydown', this.docKeyDownHandler);
this.deregisterCallback = this.$transitions.onStart({}, this.document.addEventListener('focusin', this.docFocusInHandler);
() => this.hideAll());
this.deregisterCallback = this.$transitions.onStart({}, () => this.hide());
this.relocate();
if (this.onOpen)
this.onOpen();
} }
_destroy() {
this.document.removeEventListener('mousedown', this.docMouseDownHandler); /**
* Hides the popover.
*/
hide() {
if (!this._shown) return;
this._shown = false;
this.$element.removeClass('shown');
this.$timeout.cancel(this.showTimeout);
this.showTimeout = this.$timeout(() => {
this.element.style.display = 'none';
this.showTimeout = null;
}, 250);
this.document.removeEventListener('keydown', this.docKeyDownHandler); this.document.removeEventListener('keydown', this.docKeyDownHandler);
this.docMouseDownHandler = null; this.document.removeEventListener('focusin', this.docFocusInHandler);
this.docKeyDownHandler = null;
this.deregisterCallback();
}
show(childElement, parent, popoverId) {
this.childElement = childElement;
let popover = this.document.createElement('div');
this.popOpens++;
if (!popoverId) { if (this.deregisterCallback)
popoverId = 'popover-' + this.popOpens; this.deregisterCallback();
popover.id = popoverId;
}
popover.className = 'vn-popover'; if (this.parent)
popover.addEventListener('mousedown', this.parent.focus();
e => this.onPopoverMouseDown(e));
popover.appendChild(childElement);
this.popover = popover;
let style = popover.style; if (this.onClose)
this.onClose();
let spacing = 0;
let screenMargin = 20;
let dblMargin = screenMargin * 2;
let width = popover.offsetWidth;
let height = popover.offsetHeight;
let innerWidth = window.innerWidth;
let innerHeight = window.innerHeight;
if (width + dblMargin > innerWidth) {
width = innerWidth - dblMargin;
style.width = width + 'px';
}
if (height + dblMargin > innerHeight) {
height = innerHeight - dblMargin;
style.height = height + 'px';
}
if (parent) {
let parentNode = parent;
let rect = parentNode.getBoundingClientRect();
let left = rect.left;
let top = rect.top + spacing + parentNode.offsetHeight;
if (left + width > innerWidth)
left -= (left + width) - innerWidth + margin;
if (top + height > innerHeight)
top -= height + parentNode.offsetHeight + spacing * 2;
if (left < 0)
left = screenMargin;
if (top < 0)
top = screenMargin;
style.top = (top) + 'px';
style.left = (left) + 'px';
style.minWidth = (rect.width) + 'px';
}
this.document.body.appendChild(popover);
if (this.popOpens === 1) {
this._init();
}
return popoverId;
} }
showComponent(childComponent, $scope, parent) { /**
let childElement = this.document.createElement(childComponent); * Repositions the popover to a correct location relative to the parent.
let id = 'popover-' + this.popOpens; */
childElement.id = id; relocate() {
this.removeScope = true; if (!(this.parent && this._shown)) return;
this.$compile(childElement)($scope.$new());
this.show(childElement, parent, id);
return childElement;
}
_checkOpens() { let style = this.popover.style;
this.popOpens = this.document.querySelectorAll('*[id^="popover-"]').length; style.width = '';
if (this.popOpens === 0) { style.height = '';
this._destroy();
}
}
_removeElement(val) { let arrowStyle = this.arrow.style;
if (!val) return; arrowStyle.top = '';
let element = angular.element(val); arrowStyle.bottom = '';
let parent = val.parentNode;
if (element.scope() && element.scope().$id > 1) {
element.scope().$destroy();
}
element.remove();
if (parent.className.indexOf('vn-popover') !== -1)
this._removeElement(parent);
}
hide(id) { let parentRect = this.parent.getBoundingClientRect();
let popover = this.document.querySelector(`#${id}`); let popoverRect = this.popover.getBoundingClientRect();
if (popover) { let arrowRect = this.arrow.getBoundingClientRect();
this._removeElement(popover);
}
this._checkOpens();
}
hideChilds(id) { let arrowHeight = Math.sqrt(Math.pow(arrowRect.height, 2) * 2) / 2;
let popovers = this.document.querySelectorAll('*[id^="popover-"]');
let idNumber = parseInt(id.split('-')[1], 10);
popovers.forEach(
val => {
if (parseInt(val.id.split('-')[1], 10) > idNumber)
this._removeElement(val);
}
);
this._checkOpens();
}
hideAll() { let top = parentRect.top + parentRect.height + arrowHeight;
let popovers = this.document.querySelectorAll('*[id^="popover-"]'); let left = parentRect.left;
popovers.forEach( let height = popoverRect.height;
val => { let width = Math.max(popoverRect.width, parentRect.width);
this._removeElement(val);
}
);
this._checkOpens();
}
_findPopOver(node) { let margin = 10;
while (node != null) { let showTop = top + height + margin > window.innerHeight;
if (node.id && node.id.startsWith('popover-')) {
return node.id;
}
node = node.parentNode;
}
return null;
}
onDocMouseDown(event) { if (showTop)
let targetId = this._findPopOver(event.target); top = Math.max(parentRect.top - height - arrowHeight, margin);
if (targetId) { if (left + width + margin > window.innerWidth)
this.hideChilds(targetId); left = window.innerWidth - width - margin;
} else {
this.hideAll(); if (showTop)
} arrowStyle.bottom = `0`;
else
arrowStyle.top = `0`;
arrowStyle.left = `${(parentRect.left - left) + parentRect.width / 2}px`;
style.top = `${top}px`;
style.left = `${left}px`;
style.width = `${width}px`;
if (height + margin * 2 + arrowHeight > window.innerHeight)
style.height = `${window.innerHeight - margin * 2 - arrowHeight}px`;
} }
onDocKeyDown(event) { onDocKeyDown(event) {
if (event.keyCode === 27) { if (event.defaultPrevented) return;
let targetId = this._findPopOver(this.lastTarget);
if (targetId) { if (event.keyCode == 27) { // Esc
this.hideChilds(targetId); event.preventDefault();
} else { this.hide();
this.hideAll(); this.$.$applyAsync();
}
this.lastTarget = null;
} }
} }
onPopoverMouseDown(event) { onMouseDown(event) {
this.lastTarget = event.target; this.lastMouseEvent = event;
}
onBackgroundMouseDown(event) {
if (event != this.lastMouseEvent)
this.hide();
}
onFocusIn(event) {
this.lastFocusEvent = event;
}
onDocFocusIn(event) {
if (event !== this.lastFocusEvent)
this.hide();
} }
} }
Popover.$inject = ['$document', '$compile', '$transitions']; Popover.$inject = ['$element', '$scope', '$timeout', '$transitions'];
ngModule.service('vnPopover', Popover); ngModule.component('vnPopover', {
template: require('./popover.html'),
controller: Popover,
transclude: true,
bindings: {
onOpen: '&?',
onClose: '&?'
}
});
class PopoverService {
constructor($document, $compile, $transitions, $rootScope) {
this.$compile = $compile;
this.$rootScope = $rootScope;
this.$document = $document;
this.stack = [];
}
show(child, parent, $scope) {
let element = this.$compile('<vn-popover/>')($scope || this.$rootScope)[0];
let popover = element.$ctrl;
popover.parent = parent;
popover.child = child;
popover.show();
popover.onClose = () => {
this.$document[0].body.removeChild(element);
if ($scope) $scope.$destroy();
};
this.$document[0].body.appendChild(element);
return popover;
}
showComponent(componentTag, $scope, parent) {
let $newScope = $scope.$new();
let childElement = this.$compile(`<${componentTag}/>`)($newScope)[0];
this.show(childElement, parent, $newScope);
return childElement;
}
}
PopoverService.$inject = ['$document', '$compile', '$transitions', '$rootScope'];
ngModule.service('vnPopover', PopoverService);

View File

@ -1,9 +0,0 @@
.vn-popover {
position: fixed;
box-shadow: 0 0 .4em rgba(1,1,1,.4);
background-color: white;
z-index: 100;
border-radius: .1em;
top: 0;
left: 0;
}

View File

@ -0,0 +1,43 @@
vn-popover {
display: none;
z-index: 10;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0;
transform: translateY(-.6em);
transition-property: opacity, transform;
transition-duration: 200ms;
transition-timing-function: ease-in-out;
&.shown {
transform: translateY(0);
opacity: 1;
}
& > .popover {
position: absolute;
display: flex;
box-shadow: 0 .1em .4em rgba(1, 1, 1, .4);
& > .arrow {
width: 1em;
height: 1em;
margin: -.5em;
background-color: white;
box-shadow: 0 .1em .4em rgba(1, 1, 1, .4);
position: absolute;
transform: rotate(45deg);
z-index: 0;
}
& > .content {
width: 100%;
border-radius: .1em;
overflow: auto;
background-color: white;
z-index: 1;
}
}
}

View File

@ -1,7 +1,4 @@
vn-textfield { vn-textfield {
div {
outline: none; //remove chrome outline
}
.mdl-chip__action { .mdl-chip__action {
position: absolute; position: absolute;
width: auto; width: auto;

View File

@ -1,6 +1,7 @@
import './id'; import './id';
import './focus'; import './focus';
import './dialog'; import './dialog';
import './popover';
import './validation'; import './validation';
import './acl'; import './acl';
import './on-error-src'; import './on-error-src';

View File

@ -0,0 +1,26 @@
import ngModule from '../module';
import Popover from '../components/popover/popover';
import {kebabToCamel} from '../lib/string';
/**
* Directive used to open a popover.
*
* @return {Object} The directive
*/
export function directive() {
return {
restrict: 'A',
link: function($scope, $element, $attrs) {
$element.on('click', function(event) {
let popoverKey = kebabToCamel($attrs.vnPopover);
let popover = $scope[popoverKey];
if (popover instanceof Popover) {
popover.parent = $element[0];
popover.show();
}
event.preventDefault();
});
}
};
}
ngModule.directive('vnPopover', directive);

View File

@ -1,7 +1,7 @@
{ {
"module": "item", "module": "item",
"name": "Items", "name": "Items",
"icon": "/static/images/icon_item.png", "icon": "inbox",
"validations" : true, "validations" : true,
"routes": [ "routes": [
{ {

View File

@ -1,7 +1,7 @@
{ {
"module": "production", "module": "production",
"name": "Production", "name": "Production",
"icon": "/static/images/icon_production.png", "icon": "local_florist",
"validations" : false, "validations" : false,
"routes": [ "routes": [
{ {

View File

@ -6,29 +6,33 @@
<vn-icon <vn-icon
id="apps" id="apps"
icon="apps" icon="apps"
vn-popover="apps-menu"
translate-attr="{title: 'Applications'}"> translate-attr="{title: 'Applications'}">
</vn-icon> </vn-icon>
<ul id="apps-menu" for="apps" class="mdl-menu mdl-js-menu mdl-menu--bottom-right" pad-small> <vn-popover vn-id="apps-menu" ng-click="appsMenu.hide()">
<li ng-repeat="mod in ::$ctrl.modules" class="mdl-menu__item" ui-sref="{{::mod.route.state}}"> <ul id="apps-menu" pad-small>
<vn-icon ng-if="::mod.icon && !mod.icon.startsWith('/')" icon="{{::mod.icon}}"></vn-icon> <li ng-repeat="mod in ::$ctrl.modules" ui-sref="{{::mod.route.state}}">
<img ng-if="::mod.icon && mod.icon.startsWith('/')" ng-src="{{::mod.icon}}" /> <vn-icon icon="{{::mod.icon}}"></vn-icon>
<span translate="{{::mod.name}}"></span> <span translate>{{::mod.name}}</span>
</li> </li>
</ul> </ul>
</vn-popover>
<vn-icon <vn-icon
id="lang" id="lang"
icon="language" icon="language"
vn-popover="langs-menu"
translate-attr="{title: 'Change language'}"> translate-attr="{title: 'Change language'}">
</vn-icon> </vn-icon>
<ul id="langs-menu" for="lang" class="mdl-menu mdl-js-menu mdl-menu--bottom-right" pad-small> <vn-popover vn-id="langs-menu">
<li <ul id="langs-menu" pad-small>
ng-repeat="lang in ::$ctrl.langs" <li
name="{{::lang}}" ng-repeat="lang in ::$ctrl.langs"
class="mdl-menu__item" name="{{::lang}}"
ng-click="$ctrl.onChangeLangClick(lang)"> ng-click="$ctrl.onChangeLangClick(lang)">
<span>{{::lang}}</span> <span>{{::lang}}</span>
</li> </li>
</ul> </ul>
</vn-popover>
<vn-icon <vn-icon
id="logout" id="logout"
icon="exit_to_app" icon="exit_to_app"

View File

@ -14,26 +14,30 @@ vn-main-menu {
color: #FF9300; color: #FF9300;
} }
} }
li.mdl-menu__item { vn-popover ul {
background-color: #FF9300; list-style-type: none;
margin-bottom: 8px; margin: 0;
color: white; color: white;
img {
max-width: 18px; li {
vertical-align: middle; background-color: #FF9300;
margin-top: -3px; margin-bottom: .6em;
cursor: pointer;
padding: .8em;
border-radius: .1em;
min-width: 4em;
& > vn-icon {
padding-right: .3em;
vertical-align: middle;
}
&:hover {
background-color: #FF9300;
opacity: 0.7 !important;
}
&:last-child {
margin-bottom: 0;
}
} }
i {
float: left;
padding-top: 13px;
margin-right: 3px;
}
}
li.mdl-menu__item:hover {
background-color: #FF9300;
opacity: 0.7 !important;
}
li.mdl-menu__item:last-child {
margin-bottom: 0;
} }
} }

View File

@ -3,6 +3,21 @@
@import "colors"; @import "colors";
@import "border"; @import "border";
a:focus,
input:focus,
button:focus
{
outline: none;
}
button::-moz-focus-inner,
input[type=submit]::-moz-focus-inner,
input[type=button]::-moz-focus-inner,
input[type=reset]::-moz-focus-inner
{
border: none;
}
.form { .form {
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;

View File

@ -8,7 +8,6 @@ export default {
vnSubmit: 'vn-submit > input', vnSubmit: 'vn-submit > input',
vnTopbar: 'vn-topbar > header', vnTopbar: 'vn-topbar > header',
vnIcon: 'vn-icon', vnIcon: 'vn-icon',
vnMainMenu: 'vn-main-menu > div',
vnModuleContainer: 'vn-module-container > a', vnModuleContainer: 'vn-module-container > a',
vnSearchBar: 'vn-searchbar > form > vn-horizontal', vnSearchBar: 'vn-searchbar > form > vn-horizontal',
vnFloatButton: 'vn-float-button > button', vnFloatButton: 'vn-float-button > button',

View File

@ -6,8 +6,8 @@ export default {
globalItems: { globalItems: {
logOutButton: `#logout`, logOutButton: `#logout`,
applicationsMenuButton: `#apps`, applicationsMenuButton: `#apps`,
applicationsMenuVisible: `${components.vnMainMenu} .is-visible > div`, applicationsMenuVisible: `vn-main-menu [vn-id="apps-menu"] ul`,
clientsButton: `${components.vnMainMenu} > div > ul > li:nth-child(1)` clientsButton: `vn-main-menu [vn-id="apps-menu"] ul > li:nth-child(1)`
}, },
moduleAccessView: { moduleAccessView: {
clientsSectionButton: `${components.vnModuleContainer}[ui-sref="clients"]`, clientsSectionButton: `${components.vnModuleContainer}[ui-sref="clients"]`,

View File

@ -1,5 +1,3 @@
let md5 = require('md5');
module.exports = function(Self) { module.exports = function(Self) {
Self.remoteMethod('createWithUser', { Self.remoteMethod('createWithUser', {
description: 'Creates both client and its web account', description: 'Creates both client and its web account',
@ -18,43 +16,33 @@ module.exports = function(Self) {
} }
}); });
Self.createWithUser = (data, callback) => { Self.createWithUser = async data => {
let firstEmail = data.email ? data.email.split(',')[0] : null; let firstEmail = data.email ? data.email.split(',')[0] : null;
let user = { let user = {
name: data.userName, name: data.userName,
email: firstEmail, email: firstEmail,
password: md5(parseInt(Math.random() * 100000000000000)) password: parseInt(Math.random() * 100000000000000)
}; };
let Account = Self.app.models.Account; let Account = Self.app.models.Account;
Account.beginTransaction({}, (error, transaction) => { let transaction = await Account.beginTransaction({});
if (error) return callback(error);
Account.create(user, {transaction}, (error, account) => { try {
if (error) { let account = await Account.create(user, {transaction});
transaction.rollback(); let client = {
return callback(error); id: account.id,
} name: data.name,
fi: data.fi,
let client = { socialName: data.socialName,
name: data.name, email: data.email,
fi: data.fi, salesPersonFk: data.salesPersonFk
socialName: data.socialName, };
id: account.id, newClient = await Self.create(client, {transaction});
email: data.email, await transaction.commit();
salesPersonFk: data.salesPersonFk return newClient;
}; } catch (e) {
transaction.rollback();
Self.create(client, {transaction}, (error, newClient) => { throw e;
if (error) { }
transaction.rollback();
return callback(error);
}
transaction.commit();
callback(null, newClient);
});
});
});
}; };
}; };

View File

@ -48,45 +48,39 @@ describe('Client Create', () => {
.catch(catchErrors(done)); .catch(catchErrors(done));
}); });
it('should not be able to create a user if exists', done => { it('should not be able to create a user if exists', async() => {
app.models.Client.findOne({where: {name: 'Charles Xavier'}}) let client = await app.models.Client.findOne({where: {name: 'Charles Xavier'}});
.then(client => { let account = await app.models.Account.findOne({where: {id: client.id}});
app.models.Account.findOne({where: {id: client.id}})
.then(account => {
let formerAccountData = {
name: client.name,
userName: account.name,
email: client.email,
fi: client.fi,
socialName: client.socialName
};
app.models.Client.createWithUser(formerAccountData, (err, client) => { let formerAccountData = {
expect(err.details.codes.name[0]).toEqual('uniqueness'); name: client.name,
done(); userName: account.name,
}); email: client.email,
}); fi: client.fi,
}) socialName: client.socialName
.catch(catchErrors(done)); };
try {
let client = await app.models.Client.createWithUser(formerAccountData);
expect(client).toBeNull();
} catch (err) {
expect(err.details.codes.name[0]).toEqual('uniqueness');
}
}); });
it('should create a new account', done => { it('should create a new account', async() => {
app.models.Client.createWithUser(newAccountData, (error, client) => { let client = await app.models.Client.createWithUser(newAccountData);
if (error) return catchErrors(done)(error); let account = await app.models.Account.findOne({where: {name: newAccountData.userName}});
app.models.Account.findOne({where: {name: newAccountData.userName}})
.then(account => { expect(account.name).toEqual(newAccountData.userName);
expect(account.name).toEqual(newAccountData.userName);
app.models.Client.findOne({where: {name: newAccountData.name}}) client = await app.models.Client.findOne({where: {name: newAccountData.name}});
.then(client => {
expect(client.id).toEqual(account.id); expect(client.id).toEqual(account.id);
expect(client.name).toEqual(newAccountData.name); expect(client.name).toEqual(newAccountData.name);
expect(client.email).toEqual(newAccountData.email); expect(client.email).toEqual(newAccountData.email);
expect(client.fi).toEqual(newAccountData.fi); expect(client.fi).toEqual(newAccountData.fi);
expect(client.socialName).toEqual(newAccountData.socialName); expect(client.socialName).toEqual(newAccountData.socialName);
done();
});
})
.catch(catchErrors(done));
});
}); });
}); });

View File

@ -35,7 +35,7 @@ module.exports = function(Self) {
message: 'Correo electrónico inválido', message: 'Correo electrónico inválido',
allowNull: true, allowNull: true,
allowBlank: true, allowBlank: true,
with: /^[\w|-|.]+@[\w|-]+(\.[\w|-]+)*(,[\w|-|.]+@[\w|-]+(\.[\w|-]+)*)*$/ with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/
}); });
Self.validatesLengthOf('postcode', { Self.validatesLengthOf('postcode', {
allowNull: true, allowNull: true,