pull from dev

This commit is contained in:
Joan Sanchez 2019-10-16 09:52:32 +02:00
commit dadfc92ebd
277 changed files with 14523 additions and 10273 deletions

View File

@ -94,7 +94,7 @@ module.exports = Self => {
const models = Self.app.models; const models = Self.app.models;
const storageConnector = Self.app.dataSources.storage.connector; const storageConnector = Self.app.dataSources.storage.connector;
const myUserId = ctx.req.accessToken.userId; const myUserId = ctx.req.accessToken.userId;
const myWorker = await models.Worker.findOne({where: {userFk: myUserId}}); const myWorker = await models.Worker.findOne({where: {userFk: myUserId}}, myOptions);
const args = ctx.args; const args = ctx.args;
const newDms = await Self.create({ const newDms = await Self.create({

View File

@ -29,8 +29,8 @@ module.exports = Self => {
const accessToken = ctx.options && ctx.options.accessToken || ctx.req && ctx.req.accessToken; const accessToken = ctx.options && ctx.options.accessToken || ctx.req && ctx.req.accessToken;
const userId = accessToken.userId; const userId = accessToken.userId;
const models = Self.app.models; const models = Self.app.models;
const sender = await models.Account.findById(userId, options); const sender = await models.Account.findById(userId, null, options);
const recipient = await models.Account.findById(data.recipientFk, options); const recipient = await models.Account.findById(data.recipientFk, null, options);
await Self.create({ await Self.create({
sender: sender.name, sender: sender.name,

View File

@ -0,0 +1,10 @@
ALTER TABLE `vn`.`route`
ADD COLUMN `zoneFk` INT NULL AFTER `description`,
ADD INDEX `fk_route_1_idx` (`zoneFk` ASC);
;
ALTER TABLE `vn`.`route`
ADD CONSTRAINT `fk_route_1`
FOREIGN KEY (`zoneFk`)
REFERENCES `vn`.`zone` (`id`)
ON DELETE RESTRICT
ON UPDATE CASCADE;

View File

@ -0,0 +1 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('UserPhone', '*', 'WRITE', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,18 @@
CREATE TABLE `vn`.`userLog` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`originFk` int(11) NOT NULL,
`userFk` int(10) unsigned DEFAULT NULL,
`action` set('insert','update','delete') COLLATE utf8_unicode_ci NOT NULL,
`creationDate` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`description` text CHARACTER SET utf8,
`changedModel` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
`oldInstance` text COLLATE utf8_unicode_ci,
`newInstance` text COLLATE utf8_unicode_ci,
`changedModelId` int(11) DEFAULT NULL,
`changedModelValue` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `originFk` (`originFk`),
KEY `userFk` (`userFk`),
CONSTRAINT `userLog_ibfk_1` FOREIGN KEY (`originFk`) REFERENCES `client` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `userLog_ibfk_2` FOREIGN KEY (`userFk`) REFERENCES `account`.`user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

View File

@ -0,0 +1,9 @@
CREATE TABLE `vn`.`userPhoneType` (
`code` VARCHAR(45) NOT NULL,
`description` VARCHAR(255) NULL,
PRIMARY KEY (`code`));
INSERT INTO `vn`.`userPhoneType` (`code`, `description`) VALUES ('businessPhone', 'Telefono de empresa del usuario');
INSERT INTO `vn`.`userPhoneType` (`code`, `description`) VALUES ('personalPhone', 'Telefono personal del usuario');

View File

@ -0,0 +1,63 @@
CREATE TABLE `vn`.`userPhone` (
`id` INT NOT NULL AUTO_INCREMENT,
`userFk` INT(10) UNSIGNED NOT NULL,
`typeFk` VARCHAR(45) NOT NULL,
`phone` INT(15) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `UserFK_Phone` (`userFk` ASC, `phone` ASC));
ALTER TABLE `vn`.`userPhone`
ADD CONSTRAINT `fgnTypeFk`
FOREIGN KEY (typeFk)
REFERENCES `vn`.`userPhoneType` (code)
ON DELETE RESTRICT
ON UPDATE CASCADE;
ALTER TABLE `vn`.`userPhone`
ADD CONSTRAINT `fgnUserFk`
FOREIGN KEY (userFk)
REFERENCES `account`.`user` (id)
ON DELETE CASCADE
ON UPDATE CASCADE;
insert into vn.userPhone(userFk,typeFk,phone)
select id,'PersonalPhone', phone
from vn.client
where phone is not null;
insert into vn.userPhone(userFk,typeFk,phone)
select id,'businessPhone', phone
from vn.worker
where phone is not null AND phone > '';
insert into vn.userPhone(userFk,typeFk,phone)
SELECT
`w`.`userFk`,
'businessPhone',
`m`.`value` AS `mediaValue`
FROM
(((((`postgresql`.`person` `p`
JOIN `postgresql`.`profile` `po` ON ((`po`.`person_id` = `p`.`person_id`)))
JOIN `postgresql`.`profile_media` `pom` ON ((`pom`.`profile_id` = `po`.`profile_id`)))
JOIN `postgresql`.`media` `m` ON ((`m`.`media_id` = `pom`.`media_id`)))
JOIN `postgresql`.`media_type` `mt` ON ((`mt`.`media_type_id` = `m`.`media_type_id`)))
JOIN `vn`.`worker` `w` ON ((`w`.`id` = `p`.`id_trabajador`)))
WHERE
(`mt`.`name` = 'movil empresa');
insert into vn.userPhone(userFk,typeFk,phone)
SELECT
`w`.`userFk`,
'personalPhone',
`m`.`value` AS `mediaValue`
FROM
(((((`postgresql`.`person` `p`
JOIN `postgresql`.`profile` `po` ON ((`po`.`person_id` = `p`.`person_id`)))
JOIN `postgresql`.`profile_media` `pom` ON ((`pom`.`profile_id` = `po`.`profile_id`)))
JOIN `postgresql`.`media` `m` ON ((`m`.`media_id` = `pom`.`media_id`)))
JOIN `postgresql`.`media_type` `mt` ON ((`mt`.`media_type_id` = `m`.`media_type_id`)))
JOIN `vn`.`worker` `w` ON ((`w`.`id` = `p`.`id_trabajador`)))
WHERE
(`mt`.`name` = 'movil personal')

File diff suppressed because one or more lines are too long

View File

@ -65,6 +65,11 @@ INSERT INTO `vn`.`warehouse`(`id`, `name`, `isComparative`, `isInventory`, `hasA
(4, 'Warehouse Four', 1, 1, 1, 1, 0), (4, 'Warehouse Four', 1, 1, 1, 1, 0),
(5, 'Warehouse Five', 1, 1, 1, 1, 0); (5, 'Warehouse Five', 1, 1, 1, 1, 0);
INSERT INTO `vn`.`sector`(`id`, `description`, `warehouseFk`, `isPreviousPreparedByPacking`, `code`, `pickingPlacement`, `path`)
VALUES
(1, 'First sector', 1, 1, 'FIRST', 999, 999),
(2, 'Second sector', 2, 0, 'SECOND', 100, 150);
INSERT INTO `vn`.`warehouseAlias`(`id`, `name`) INSERT INTO `vn`.`warehouseAlias`(`id`, `name`)
VALUES VALUES
(1, 'Main Warehouse'); (1, 'Main Warehouse');
@ -194,8 +199,8 @@ INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city
(108, 'Charles Xavier', '22641921P', 'Professor X', 'Beast', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1), (108, 'Charles Xavier', '22641921P', 'Professor X', 'Beast', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1),
(109, 'Bruce Banner', '16104829E', 'Hulk', 'Black widow', 'Somewhere in New York', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1), (109, 'Bruce Banner', '16104829E', 'Hulk', 'Black widow', 'Somewhere in New York', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1),
(110, 'Jessica Jones', '58282869H', 'Jessica Jones', 'Luke Cage', 'NYCC 2015 Poster', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1), (110, 'Jessica Jones', '58282869H', 'Jessica Jones', 'Luke Cage', 'NYCC 2015 Poster', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1),
(200, 'Missing', NULL, 'Missing man', 'Anton', 'The space', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, NULL, 1, 0, 1, 0, NULL, 1, 0, NULL, 0, 1), (111, 'Missing', NULL, 'Missing man', 'Anton', 'The space', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, NULL, 1, 0, 1, 0, NULL, 1, 0, NULL, 0, 1),
(400, 'Trash', NULL, 'Garbage man', 'Unknown name', 'New York city', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, NULL, 1, 0, 1, 0, NULL, 1, 0, NULL, 0, 1); (112, 'Trash', NULL, 'Garbage man', 'Unknown name', 'New York city', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, NULL, 1, 0, 1, 0, NULL, 1, 0, NULL, 0, 1);
INSERT INTO `vn`.`client`(`id`, `name`, `fi`, `socialName`, `contact`, `street`, `city`, `postcode`, `phone`, `isRelevant`, `email`, `iban`,`dueDay`,`accountingAccount`, `isEqualizated`, `provinceFk`, `hasToInvoice`, `credit`, `countryFk`, `isActive`, `gestdocFk`, `quality`, `payMethodFk`,`created`, `isTaxDataChecked`) INSERT INTO `vn`.`client`(`id`, `name`, `fi`, `socialName`, `contact`, `street`, `city`, `postcode`, `phone`, `isRelevant`, `email`, `iban`,`dueDay`,`accountingAccount`, `isEqualizated`, `provinceFk`, `hasToInvoice`, `credit`, `countryFk`, `isActive`, `gestdocFk`, `quality`, `payMethodFk`,`created`, `isTaxDataChecked`)
SELECT id, name, CONCAT(RPAD(CONCAT(id,9),8,id),'A'), CONCAT(name, 'Social'), CONCAT(name, 'Contact'), CONCAT(name, 'Street'), 'SILLA', 46460, 623111111, 1, CONCAT(name,'@mydomain.com'), NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1,NULL, 10, 5, CURDATE(), 1 SELECT id, name, CONCAT(RPAD(CONCAT(id,9),8,id),'A'), CONCAT(name, 'Social'), CONCAT(name, 'Contact'), CONCAT(name, 'Street'), 'SILLA', 46460, 623111111, 1, CONCAT(name,'@mydomain.com'), NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1,NULL, 10, 5, CURDATE(), 1
@ -221,8 +226,8 @@ INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `pr
(8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Silla', 46460, 1, 1111111111, 222222222, 1, 108, 2, NULL, NULL, 0, 1), (8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Silla', 46460, 1, 1111111111, 222222222, 1, 108, 2, NULL, NULL, 0, 1),
(9, 'Bruce Banner', 'Somewhere in New York', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 1), (9, 'Bruce Banner', 'Somewhere in New York', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 1),
(10, 'Jessica Jones', 'NYCC 2015 Poster', 'Silla', 46460, 1, 1111111111, 222222222, 1, 110, 2, NULL, NULL, 0, 1), (10, 'Jessica Jones', 'NYCC 2015 Poster', 'Silla', 46460, 1, 1111111111, 222222222, 1, 110, 2, NULL, NULL, 0, 1),
(11, 'Missing', 'The space', 'Silla', 46460, 1, 1111111111, 222222222, 1, 200, 10, NULL, NULL, 0, 1), (11, 'Missing', 'The space', 'Silla', 46460, 1, 1111111111, 222222222, 1, 111, 10, NULL, NULL, 0, 1),
(12, 'Trash', 'New York city', 'Silla', 46460, 1, 1111111111, 222222222, 1, 400, 10, NULL, NULL, 0, 1), (12, 'Trash', 'New York city', 'Silla', 46460, 1, 1111111111, 222222222, 1, 112, 10, NULL, NULL, 0, 1),
(101, 'address 01', 'Somewhere in Thailand', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0), (101, 'address 01', 'Somewhere in Thailand', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(102, 'address 02', 'Somewhere in Poland', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0), (102, 'address 02', 'Somewhere in Poland', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0),
(103, 'address 03', 'Somewhere in Japan', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0), (103, 'address 03', 'Somewhere in Japan', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0),
@ -252,9 +257,7 @@ INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `pr
(127, 'address 27', 'Your pocket', 'Silla', 46460, 1, 1111111111, 222222222, 1, 107, 2, NULL, NULL, 0, 0), (127, 'address 27', 'Your pocket', 'Silla', 46460, 1, 1111111111, 222222222, 1, 107, 2, NULL, NULL, 0, 0),
(128, 'address 28', 'Cerebro', 'Silla', 46460, 1, 1111111111, 222222222, 1, 108, 2, NULL, NULL, 0, 0), (128, 'address 28', 'Cerebro', 'Silla', 46460, 1, 1111111111, 222222222, 1, 108, 2, NULL, NULL, 0, 0),
(129, 'address 29', 'Luke Cages Bar', 'Silla', 46460, 1, 1111111111, 222222222, 1, 110, 2, NULL, NULL, 0, 0), (129, 'address 29', 'Luke Cages Bar', 'Silla', 46460, 1, 1111111111, 222222222, 1, 110, 2, NULL, NULL, 0, 0),
(130, 'address 30', 'Non valid address', 'Silla', 46460, 1, 1111111111, 222222222, 0, 101, 2, NULL, NULL, 0, 0), (130, 'address 30', 'Non valid address', 'Silla', 46460, 1, 1111111111, 222222222, 0, 101, 2, NULL, NULL, 0, 0);
(131, 'Missing', 'The space', 'Silla', 46460, 1, 1111111111, 222222222, 1, 200, 10, NULL, NULL, 0, 0),
(132, 'Trash', 'New York city', 'Silla', 46460, 1, 1111111111, 222222222, 1, 400, 10, NULL, NULL, 0, 0);
INSERT INTO `vn`.`address`( `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `isActive`, `clientFk`, `agencyModeFk`, `isDefaultAddress`) INSERT INTO `vn`.`address`( `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `isActive`, `clientFk`, `agencyModeFk`, `isDefaultAddress`)
SELECT name, CONCAT(name, 'Street'), 'SILLA', 46460, 1, 1, id, 2, 1 SELECT name, CONCAT(name, 'Street'), 'SILLA', 46460, 1, 1, id, 2, 1
@ -471,7 +474,7 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF
(14, 1, 2, 1, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 104, 'Malibu Point', 4, NULL, 0, 9, CURDATE()), (14, 1, 2, 1, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 104, 'Malibu Point', 4, NULL, 0, 9, CURDATE()),
(15, 1, 7, 1, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 105, 'Plastic Cell', 125, NULL, 0, 3, CURDATE()), (15, 1, 7, 1, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 105, 'Plastic Cell', 125, NULL, 0, 3, CURDATE()),
(16, 1, 7, 1, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()), (16, 1, 7, 1, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()),
(17, 1, 7, 1, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()), (17, 1, 7, 2, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()),
(18, 1, 4, 4, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 108, 'Cerebro', 128, NULL, 0, 12, CURDATE()), (18, 1, 4, 4, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 108, 'Cerebro', 128, NULL, 0, 12, CURDATE()),
(19, 1, 5, 5, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 1, 13, CURDATE()), (19, 1, 5, 5, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 1, 13, CURDATE()),
(20, 1, 5, 5, 3, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)), (20, 1, 5, 5, 3, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),

File diff suppressed because it is too large Load Diff

View File

@ -45,20 +45,20 @@ TABLES=(
claimReason claimReason
claimRedelivery claimRedelivery
claimResult claimResult
ticketUpdateAction
state
) )
dump_tables ${TABLES[@]} dump_tables ${TABLES[@]}
TABLES=( TABLES=(
vn2008 vn2008
accion_dits accion_dits
bionic_updating_options
businessReasonEnd businessReasonEnd
container container
department department
escritos escritos
Grupos Grupos
iva_group_codigo iva_group_codigo
state
tarifa_componentes tarifa_componentes
tarifa_componentes_series tarifa_componentes_series
) )

View File

@ -1,6 +1,6 @@
export default { export default {
vnTextfield: 'vn-textfield > div > div > div > input', vnTextfield: 'vn-textfield input',
vnInputNumber: 'vn-input-number > div > div > div > input', vnInputNumber: 'vn-input-number input',
vnSubmit: 'vn-submit > input', vnSubmit: 'vn-submit > input',
vnFloatButton: 'vn-float-button > button' vnFloatButton: 'vn-float-button > button'
}; };

View File

@ -18,8 +18,11 @@ let actions = {
clearInput: function(selector, done) { clearInput: function(selector, done) {
this.wait(selector) this.wait(selector)
.evaluate(inputSelector => { .evaluate(selector => {
return document.querySelector(inputSelector).closest('*[model], *[field], *[value]').$ctrl.value = ''; let $ctrl = document.querySelector(selector).closest('.vn-field').$ctrl;
$ctrl.field = null;
$ctrl.$.$apply();
$ctrl.input.dispatchEvent(new Event('change'));
}, selector) }, selector)
.then(done) .then(done)
.catch(done); .catch(done);
@ -31,6 +34,7 @@ let actions = {
let doLogin = () => { let doLogin = () => {
this.wait(`vn-login input[name=user]`) this.wait(`vn-login input[name=user]`)
.clearInput(`vn-login input[name=user]`)
.write(`vn-login input[name=user]`, userName) .write(`vn-login input[name=user]`, userName)
.write(`vn-login input[name=password]`, 'nightmare') .write(`vn-login input[name=password]`, 'nightmare')
.click(`vn-login input[type=submit]`) .click(`vn-login input[type=submit]`)
@ -75,7 +79,7 @@ let actions = {
}, },
changeLanguageToEnglish: function(done) { changeLanguageToEnglish: function(done) {
let langSelector = '.user-configuration vn-autocomplete[field="$ctrl.lang"]'; let langSelector = '.user-popover vn-autocomplete[ng-model="$ctrl.lang"]';
this.waitToClick('#user') this.waitToClick('#user')
.wait(langSelector) .wait(langSelector)
@ -167,8 +171,8 @@ let actions = {
focusElement: function(selector, done) { focusElement: function(selector, done) {
this.wait(selector) this.wait(selector)
.evaluate_now(elemenetSelector => { .evaluate_now(selector => {
let element = document.querySelector(elemenetSelector); let element = document.querySelector(selector);
element.focus(); element.focus();
}, done, selector) }, done, selector)
.then(done) .then(done)
@ -401,8 +405,7 @@ let actions = {
}, },
autocompleteSearch: function(autocompleteSelector, searchValue, done) { autocompleteSearch: function(autocompleteSelector, searchValue, done) {
this.wait(`${autocompleteSelector} input`) this.waitToClick(`${autocompleteSelector} input`)
.waitToClick(`${autocompleteSelector} input`)
.write(`.vn-popover.shown .vn-drop-down input`, searchValue) .write(`.vn-popover.shown .vn-drop-down input`, searchValue)
.waitToClick(`.vn-popover.shown .vn-drop-down li.active`) .waitToClick(`.vn-popover.shown .vn-drop-down li.active`)
.wait((autocompleteSelector, searchValue) => { .wait((autocompleteSelector, searchValue) => {
@ -412,7 +415,7 @@ let actions = {
}, autocompleteSelector, searchValue) }, autocompleteSelector, searchValue)
.then(done) .then(done)
.catch(() => { .catch(() => {
done(new Error(`.autocompleteSearch() for ${autocompleteSelector}, timed out`)); done(new Error(`.autocompleteSearch() for value ${searchValue} in ${autocompleteSelector} timed out`));
}); });
}, },
@ -427,25 +430,22 @@ let actions = {
.catch(done); .catch(done);
}, },
datePicker: function(datePickerSelector, changeMonth, day, done) { datePicker: function(selector, changeMonth, day, done) {
this.wait(datePickerSelector) this.wait(selector)
.mousedown(datePickerSelector) .mousedown(`${selector} input`)
.wait('div.flatpickr-calendar.open'); .wait('.flatpickr-calendar.open');
if (changeMonth > 0) if (changeMonth > 0)
this.mousedown('body > div.flatpickr-calendar.open > div.flatpickr-months > span.flatpickr-next-month > svg'); this.mousedown(`.flatpickr-calendar.open .flatpickr-next-month`);
if (changeMonth < 0) if (changeMonth < 0)
this.mousedown('body > div.flatpickr-calendar.open > div.flatpickr-months > span.flatpickr-prev-month > svg'); this.mousedown(`.flatpickr-calendar.open .flatpickr-prev-month`);
let daySelector; let daySelector;
if (!day) if (!day)
daySelector = 'div.flatpickr-calendar.open span.flatpickr-day:nth-child(16)'; daySelector = `.flatpickr-calendar.open .flatpickr-day:nth-child(16)`;
if (day) if (day)
daySelector = `span.flatpickr-day[aria-label~="${day},"]:not(.prevMonthDay):not(.nextMonthDay)`; daySelector = `.flatpickr-calendar.open .flatpickr-day[aria-label~="${day},"]:not(.prevMonthDay):not(.nextMonthDay)`;
this.wait(selector => { this.wait(selector => {
return document.querySelector(selector); return document.querySelector(selector);

View File

@ -10,7 +10,10 @@ module.exports = function createNightmare(width = 1280, height = 720) {
typeInterval: 10, typeInterval: 10,
x: 0, x: 0,
y: 0, y: 0,
waitTimeout: 2000 waitTimeout: 2000,
// openDevTools: {
// mode: 'detach'
// }
}).viewport(width, height); }).viewport(width, height);
nightmare.on('console', (type, message, ...args) => { nightmare.on('console', (type, message, ...args) => {

View File

@ -11,14 +11,14 @@ export default {
claimsButton: '.modules-menu > li[ui-sref="claim.index"]', claimsButton: '.modules-menu > li[ui-sref="claim.index"]',
returnToModuleIndexButton: 'a[ui-sref="order.index"]', returnToModuleIndexButton: 'a[ui-sref="order.index"]',
userMenuButton: 'vn-topbar #user', userMenuButton: 'vn-topbar #user',
userLocalWarehouse: '.user-configuration vn-autocomplete[field="$ctrl.localWarehouseFk"]', userLocalWarehouse: '.user-popover vn-autocomplete[ng-model="$ctrl.localWarehouseFk"]',
userLocalBank: '.user-configuration vn-autocomplete[field="$ctrl.localBankFk"]', userLocalBank: '.user-popover vn-autocomplete[ng-model="$ctrl.localBankFk"]',
userLocalCompany: '.user-configuration vn-autocomplete[field="$ctrl.localCompanyFk"]', userLocalCompany: '.user-popover vn-autocomplete[ng-model="$ctrl.localCompanyFk"]',
userWarehouse: '.user-configuration vn-autocomplete[field="$ctrl.warehouseFk"]', userWarehouse: '.user-popover vn-autocomplete[ng-model="$ctrl.warehouseFk"]',
userCompany: '.user-configuration vn-autocomplete[field="$ctrl.companyFk"]', userCompany: '.user-popover vn-autocomplete[ng-model="$ctrl.companyFk"]',
userConfigFirstAutocompleteClear: '#localWarehouse > div > div > div > vn-icon.clear', userConfigFirstAutocompleteClear: '#localWarehouse .icons > vn-icon[icon=clear]',
userConfigSecondAutocompleteClear: '#localBank > div > div > div > vn-icon.clear', userConfigSecondAutocompleteClear: '#localBank .icons > vn-icon[icon=clear]',
userConfigThirdAutocompleteClear: '#localCompany > div > div > div > vn-icon.clear', userConfigThirdAutocompleteClear: '#localCompany .icons > vn-icon[icon=clear]',
acceptButton: 'vn-confirm button[response=ACCEPT]' acceptButton: 'vn-confirm button[response=ACCEPT]'
}, },
clientsIndex: { clientsIndex: {
@ -35,11 +35,11 @@ export default {
street: `${components.vnTextfield}[name="street"]`, street: `${components.vnTextfield}[name="street"]`,
postcode: `${components.vnTextfield}[name="postcode"]`, postcode: `${components.vnTextfield}[name="postcode"]`,
city: `${components.vnTextfield}[name="city"]`, city: `${components.vnTextfield}[name="city"]`,
province: `vn-autocomplete[field="$ctrl.client.provinceFk"]`, province: `vn-autocomplete[ng-model="$ctrl.client.provinceFk"]`,
country: `vn-autocomplete[field="$ctrl.client.countryFk"]`, country: `vn-autocomplete[ng-model="$ctrl.client.countryFk"]`,
userName: `${components.vnTextfield}[name="userName"]`, userName: `${components.vnTextfield}[name="userName"]`,
email: `${components.vnTextfield}[name="email"]`, email: `${components.vnTextfield}[name="email"]`,
salesPersonAutocomplete: `vn-autocomplete[field="$ctrl.client.salesPersonFk"]`, salesPersonAutocomplete: `vn-autocomplete[ng-model="$ctrl.client.salesPersonFk"]`,
createButton: `${components.vnSubmit}`, createButton: `${components.vnSubmit}`,
cancelButton: 'vn-button[href="#!/client/index"]' cancelButton: 'vn-button[href="#!/client/index"]'
}, },
@ -49,13 +49,13 @@ export default {
}, },
clientBasicData: { clientBasicData: {
basicDataButton: 'vn-left-menu a[ui-sref="client.card.basicData"]', basicDataButton: 'vn-left-menu a[ui-sref="client.card.basicData"]',
nameInput: 'vn-textfield[field="$ctrl.client.name"] input', nameInput: 'vn-textfield[ng-model="$ctrl.client.name"] input',
contactInput: 'vn-textfield[field="$ctrl.client.contact"] input', contactInput: 'vn-textfield[ng-model="$ctrl.client.contact"] input',
phoneInput: 'vn-textfield[field="$ctrl.client.phone"] input', phoneInput: 'vn-textfield[ng-model="$ctrl.client.phone"] input',
mobileInput: 'vn-textfield[field="$ctrl.client.mobile"] input', mobileInput: 'vn-textfield[ng-model="$ctrl.client.mobile"] input',
emailInput: 'vn-textfield[field="$ctrl.client.email"] input', emailInput: 'vn-textfield[ng-model="$ctrl.client.email"] input',
salesPersonAutocomplete: 'vn-autocomplete[field="$ctrl.client.salesPersonFk"]', salesPersonAutocomplete: 'vn-autocomplete[ng-model="$ctrl.client.salesPersonFk"]',
channelAutocomplete: 'vn-autocomplete[field="$ctrl.client.contactChannelFk"]', channelAutocomplete: 'vn-autocomplete[ng-model="$ctrl.client.contactChannelFk"]',
saveButton: `${components.vnSubmit}` saveButton: `${components.vnSubmit}`
}, },
clientFiscalData: { clientFiscalData: {
@ -67,8 +67,8 @@ export default {
addressInput: `${components.vnTextfield}[name="street"]`, addressInput: `${components.vnTextfield}[name="street"]`,
postcodeInput: `${components.vnTextfield}[name="postcode"]`, postcodeInput: `${components.vnTextfield}[name="postcode"]`,
cityInput: `${components.vnTextfield}[name="city"]`, cityInput: `${components.vnTextfield}[name="city"]`,
provinceAutocomplete: 'vn-autocomplete[field="$ctrl.client.provinceFk"]', provinceAutocomplete: 'vn-autocomplete[ng-model="$ctrl.client.provinceFk"]',
countryAutocomplete: 'vn-autocomplete[field="$ctrl.client.countryFk"]', countryAutocomplete: 'vn-autocomplete[ng-model="$ctrl.client.countryFk"]',
activeCheckbox: 'vn-check[label="Active"]', activeCheckbox: 'vn-check[label="Active"]',
frozenCheckbox: 'vn-check[label="Frozen"]', frozenCheckbox: 'vn-check[label="Frozen"]',
invoiceByAddressCheckbox: 'vn-check[label="Invoice by address"]', invoiceByAddressCheckbox: 'vn-check[label="Invoice by address"]',
@ -79,14 +79,14 @@ export default {
saveButton: `${components.vnSubmit}` saveButton: `${components.vnSubmit}`
}, },
clientBillingData: { clientBillingData: {
payMethodAutocomplete: 'vn-autocomplete[field="$ctrl.client.payMethodFk"]', payMethodAutocomplete: 'vn-client-billing-data vn-autocomplete[ng-model="$ctrl.client.payMethodFk"]',
IBANInput: `${components.vnTextfield}[name="iban"]`, IBANInput: `vn-client-billing-data ${components.vnTextfield}[name="iban"]`,
dueDayInput: `${components.vnInputNumber}[name="dueDay"]`, dueDayInput: `vn-client-billing-data ${components.vnInputNumber}[name="dueDay"]`,
receivedCoreLCRCheckbox: 'vn-check[label="Received LCR"]', receivedCoreLCRCheckbox: 'vn-client-billing-data vn-check[label="Received LCR"]',
receivedCoreVNLCheckbox: 'vn-check[label="Received core VNL"]', receivedCoreVNLCheckbox: 'vn-client-billing-data vn-check[label="Received core VNL"]',
receivedB2BVNLCheckbox: 'vn-check[label="Received B2B VNL"]', receivedB2BVNLCheckbox: 'vn-client-billing-data vn-check[label="Received B2B VNL"]',
swiftBicAutocomplete: 'vn-client-billing-data vn-autocomplete[field="$ctrl.client.bankEntityFk"]', swiftBicAutocomplete: 'vn-client-billing-data vn-autocomplete[ng-model="$ctrl.client.bankEntityFk"]',
clearswiftBicButton: 'vn-client-billing-data vn-autocomplete[field="$ctrl.client.bankEntityFk"] > div > div > div > vn-icon > i', clearswiftBicButton: 'vn-client-billing-data vn-autocomplete[ng-model="$ctrl.client.bankEntityFk"] .icons > vn-icon[icon=clear]',
newBankEntityButton: 'vn-client-billing-data vn-icon-button[vn-tooltip="New bank entity"] > button', newBankEntityButton: 'vn-client-billing-data vn-icon-button[vn-tooltip="New bank entity"] > button',
newBankEntityName: 'vn-client-billing-data > vn-dialog vn-textfield[label="Name"] input', newBankEntityName: 'vn-client-billing-data > vn-dialog vn-textfield[label="Name"] input',
newBankEntityBIC: 'vn-client-billing-data > vn-dialog vn-textfield[label="Swift / BIC"] input', newBankEntityBIC: 'vn-client-billing-data > vn-dialog vn-textfield[label="Swift / BIC"] input',
@ -102,8 +102,8 @@ export default {
streetAddressInput: `${components.vnTextfield}[name="street"]`, streetAddressInput: `${components.vnTextfield}[name="street"]`,
postcodeInput: `${components.vnTextfield}[name="postalCode"]`, postcodeInput: `${components.vnTextfield}[name="postalCode"]`,
cityInput: `${components.vnTextfield}[name="city"]`, cityInput: `${components.vnTextfield}[name="city"]`,
provinceAutocomplete: 'vn-autocomplete[field="$ctrl.address.provinceFk"]', provinceAutocomplete: 'vn-autocomplete[ng-model="$ctrl.address.provinceFk"]',
agencyAutocomplete: 'vn-autocomplete[field="$ctrl.address.agencyModeFk"]', agencyAutocomplete: 'vn-autocomplete[ng-model="$ctrl.address.agencyModeFk"]',
phoneInput: `${components.vnTextfield}[name="phone"]`, phoneInput: `${components.vnTextfield}[name="phone"]`,
mobileInput: `${components.vnTextfield}[name="mobile"]`, mobileInput: `${components.vnTextfield}[name="mobile"]`,
defaultAddress: 'vn-client-address-index div:nth-child(1) div[name="street"]', defaultAddress: 'vn-client-address-index div:nth-child(1) div[name="street"]',
@ -112,10 +112,10 @@ export default {
secondEditAddress: 'vn-client-address-index div:nth-child(2) > a', secondEditAddress: 'vn-client-address-index div:nth-child(2) > a',
activeCheckbox: 'vn-check[label="Enabled"]', activeCheckbox: 'vn-check[label="Enabled"]',
equalizationTaxCheckbox: 'vn-client-address-edit vn-check[label="Is equalizated"]', equalizationTaxCheckbox: 'vn-client-address-edit vn-check[label="Is equalizated"]',
firstObservationTypeAutocomplete: 'vn-client-address-edit [name=observations] :nth-child(1) [field="observation.observationTypeFk"]', firstObservationTypeAutocomplete: 'vn-client-address-edit [name=observations] :nth-child(1) [ng-model="observation.observationTypeFk"]',
firstObservationDescriptionInput: 'vn-client-address-edit [name=observations] :nth-child(1) [model="observation.description"] input', firstObservationDescriptionInput: 'vn-client-address-edit [name=observations] :nth-child(1) [ng-model="observation.description"] input',
secondObservationTypeAutocomplete: 'vn-client-address-edit [name=observations] :nth-child(2) [field="observation.observationTypeFk"]', secondObservationTypeAutocomplete: 'vn-client-address-edit [name=observations] :nth-child(2) [ng-model="observation.observationTypeFk"]',
secondObservationDescriptionInput: 'vn-client-address-edit [name=observations] :nth-child(2) [model="observation.description"] input', secondObservationDescriptionInput: 'vn-client-address-edit [name=observations] :nth-child(2) [ng-model="observation.description"] input',
addObservationButton: 'vn-client-address-edit div[name="observations"] vn-icon-button[icon="add_circle"]', addObservationButton: 'vn-client-address-edit div[name="observations"] vn-icon-button[icon="add_circle"]',
saveButton: `${components.vnSubmit}`, saveButton: `${components.vnSubmit}`,
cancelCreateAddressButton: 'button[ui-sref="client.card.address.index"]', cancelCreateAddressButton: 'button[ui-sref="client.card.address.index"]',
@ -143,7 +143,7 @@ export default {
addGreugeFloatButton: `${components.vnFloatButton}`, addGreugeFloatButton: `${components.vnFloatButton}`,
amountInput: `${components.vnInputNumber}[name="amount"]`, amountInput: `${components.vnInputNumber}[name="amount"]`,
descriptionInput: `${components.vnTextfield}[name="description"]`, descriptionInput: `${components.vnTextfield}[name="description"]`,
typeAutocomplete: 'vn-autocomplete[field="$ctrl.greuge.greugeTypeFk"]', typeAutocomplete: 'vn-autocomplete[ng-model="$ctrl.greuge.greugeTypeFk"]',
saveButton: `${components.vnSubmit}`, saveButton: `${components.vnSubmit}`,
firstGreugeText: 'vn-client-greuge-index vn-card > div vn-table vn-tbody > vn-tr' firstGreugeText: 'vn-client-greuge-index vn-card > div vn-table vn-tbody > vn-tr'
}, },
@ -162,10 +162,10 @@ export default {
}, },
clientBalance: { clientBalance: {
balanceButton: 'vn-left-menu a[ui-sref="client.card.balance.index"]', balanceButton: 'vn-left-menu a[ui-sref="client.card.balance.index"]',
companyAutocomplete: 'vn-client-balance-index vn-autocomplete[field="$ctrl.companyFk"]', companyAutocomplete: 'vn-client-balance-index vn-autocomplete[ng-model="$ctrl.companyFk"]',
newPaymentButton: `${components.vnFloatButton}`, newPaymentButton: `${components.vnFloatButton}`,
newPaymentBank: 'vn-client-balance-create vn-autocomplete[field="$ctrl.receipt.bankFk"]', newPaymentBank: 'vn-client-balance-create vn-autocomplete[ng-model="$ctrl.receipt.bankFk"]',
newPaymentAmountInput: 'vn-client-balance-create vn-input-number[field="$ctrl.receipt.amountPaid"] input', newPaymentAmountInput: 'vn-client-balance-create vn-input-number[ng-model="$ctrl.receipt.amountPaid"] input',
saveButton: 'vn-client-balance-create vn-button[label="Save"]', saveButton: 'vn-client-balance-create vn-button[label="Save"]',
firstBalanceLine: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(8)' firstBalanceLine: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(8)'
@ -209,9 +209,9 @@ export default {
}, },
itemCreateView: { itemCreateView: {
temporalName: `${components.vnTextfield}[name="provisionalName"]`, temporalName: `${components.vnTextfield}[name="provisionalName"]`,
typeAutocomplete: 'vn-autocomplete[field="$ctrl.item.typeFk"]', typeAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.typeFk"]',
intrastatAutocomplete: 'vn-autocomplete[field="$ctrl.item.intrastatFk"]', intrastatAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.intrastatFk"]',
originAutocomplete: 'vn-autocomplete[field="$ctrl.item.originFk"]', originAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.originFk"]',
createButton: `${components.vnSubmit}`, createButton: `${components.vnSubmit}`,
cancelButton: 'button[ui-sref="item.index"]' cancelButton: 'button[ui-sref="item.index"]'
}, },
@ -219,8 +219,8 @@ export default {
goBackToModuleIndexButton: 'vn-item-descriptor a[href="#!/item/index"]', goBackToModuleIndexButton: 'vn-item-descriptor a[href="#!/item/index"]',
moreMenu: 'vn-item-descriptor vn-icon-menu > div > vn-icon', moreMenu: 'vn-item-descriptor vn-icon-menu > div > vn-icon',
moreMenuRegularizeButton: '.vn-popover.shown .vn-drop-down li[name="Regularize stock"]', moreMenuRegularizeButton: '.vn-popover.shown .vn-drop-down li[name="Regularize stock"]',
regularizeQuantityInput: 'vn-item-descriptor > vn-dialog > div > form > div.body > tpl-body > div > vn-textfield > div > div > div.infix > input', regularizeQuantityInput: 'vn-item-descriptor vn-dialog tpl-body > div > vn-textfield input',
regularizeWarehouseAutocomplete: 'vn-item-descriptor vn-dialog vn-autocomplete[field="$ctrl.warehouseFk"]', regularizeWarehouseAutocomplete: 'vn-item-descriptor vn-dialog vn-autocomplete[ng-model="$ctrl.warehouseFk"]',
editButton: 'vn-item-card vn-item-descriptor vn-float-button[icon="edit"]', editButton: 'vn-item-card vn-item-descriptor vn-float-button[icon="edit"]',
regularizeSaveButton: 'vn-item-descriptor > vn-dialog > div > form > div.buttons > tpl-buttons > button', regularizeSaveButton: 'vn-item-descriptor > vn-dialog > div > form > div.buttons > tpl-buttons > button',
inactiveIcon: 'vn-item-descriptor vn-icon[icon="icon-unavailable"]', inactiveIcon: 'vn-item-descriptor vn-icon[icon="icon-unavailable"]',
@ -229,13 +229,13 @@ export default {
itemBasicData: { itemBasicData: {
basicDataButton: 'vn-left-menu a[ui-sref="item.card.basicData"]', basicDataButton: 'vn-left-menu a[ui-sref="item.card.basicData"]',
goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]', goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]',
typeAutocomplete: 'vn-autocomplete[field="$ctrl.item.typeFk"]', typeAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.typeFk"]',
intrastatAutocomplete: 'vn-autocomplete[field="$ctrl.item.intrastatFk"]', intrastatAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.intrastatFk"]',
nameInput: 'vn-textfield[label="Name"] input', nameInput: 'vn-textfield[label="Name"] input',
relevancyInput: 'vn-input-number[label="Relevancy"] input', relevancyInput: 'vn-input-number[ng-model="$ctrl.item.relevancy"] input',
originAutocomplete: 'vn-autocomplete[field="$ctrl.item.originFk"]', originAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.originFk"]',
expenceAutocomplete: 'vn-autocomplete[field="$ctrl.item.expenceFk"]', expenceAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.expenceFk"]',
longNameInput: 'vn-textfield[field="$ctrl.item.longName"] input', longNameInput: 'vn-textfield[ng-model="$ctrl.item.longName"] input',
isActiveCheckbox: 'vn-check[label="Active"]', isActiveCheckbox: 'vn-check[label="Active"]',
priceInKgCheckbox: 'vn-check[label="Price in kg"]', priceInKgCheckbox: 'vn-check[label="Price in kg"]',
submitBasicDataButton: `${components.vnSubmit}` submitBasicDataButton: `${components.vnSubmit}`
@ -243,26 +243,27 @@ export default {
itemTags: { itemTags: {
goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]', goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]',
tagsButton: 'vn-left-menu a[ui-sref="item.card.tags"]', tagsButton: 'vn-left-menu a[ui-sref="item.card.tags"]',
fourthTagAutocomplete: 'vn-item-tags vn-horizontal:nth-child(4) > vn-autocomplete[field="itemTag.tagFk"]', fourthTagAutocomplete: 'vn-item-tags vn-horizontal:nth-child(4) > vn-autocomplete[ng-model="itemTag.tagFk"]',
fourthValueInput: 'vn-item-tags vn-horizontal:nth-child(4) > vn-textfield[label="Value"] input', fourthValueInput: 'vn-item-tags vn-horizontal:nth-child(4) > vn-textfield[label="Value"] input',
fourthRelevancyInput: 'vn-item-tags vn-horizontal:nth-child(4) > vn-textfield[label="Relevancy"] input', fourthRelevancyInput: 'vn-item-tags vn-horizontal:nth-child(4) > vn-textfield[label="Relevancy"] input',
fourthRemoveTagButton: 'vn-item-tags vn-horizontal:nth-child(4) vn-icon-button[icon="delete"]', fourthRemoveTagButton: 'vn-item-tags vn-horizontal:nth-child(4) vn-icon-button[icon="delete"]',
fifthTagAutocomplete: 'vn-item-tags vn-horizontal:nth-child(5) > vn-autocomplete[field="itemTag.tagFk"]', fifthTagAutocomplete: 'vn-item-tags vn-horizontal:nth-child(5) > vn-autocomplete[ng-model="itemTag.tagFk"]',
fifthValueInput: 'vn-item-tags vn-horizontal:nth-child(5) > vn-textfield[label="Value"] input', fifthValueInput: 'vn-item-tags vn-horizontal:nth-child(5) > vn-textfield[label="Value"] input',
fifthRelevancyInput: 'vn-item-tags vn-horizontal:nth-child(5) > vn-textfield[label="Relevancy"] input', fifthRelevancyInput: 'vn-item-tags vn-horizontal:nth-child(5) > vn-textfield[label="Relevancy"] input',
sixthTagAutocomplete: 'vn-item-tags vn-horizontal:nth-child(6) > vn-autocomplete[field="itemTag.tagFk"]', sixthTagAutocomplete: 'vn-item-tags vn-horizontal:nth-child(6) > vn-autocomplete[ng-model="itemTag.tagFk"]',
sixthValueInput: 'vn-item-tags vn-horizontal:nth-child(6) > vn-textfield[label="Value"] input', sixthValueInput: 'vn-item-tags vn-horizontal:nth-child(6) > vn-textfield[label="Value"] input',
sixthRelevancyInput: 'vn-item-tags vn-horizontal:nth-child(6) > vn-textfield[label="Relevancy"] input', sixthRelevancyInput: 'vn-item-tags vn-horizontal:nth-child(6) > vn-textfield[label="Relevancy"] input',
seventhTagAutocomplete: 'vn-item-tags vn-horizontal:nth-child(7) > vn-autocomplete[field="itemTag.tagFk"]', seventhTagAutocomplete: 'vn-item-tags vn-horizontal:nth-child(7) > vn-autocomplete[ng-model="itemTag.tagFk"]',
seventhValueInput: 'vn-item-tags vn-horizontal:nth-child(7) > vn-textfield[label="Value"] input', seventhValueInput: 'vn-item-tags vn-horizontal:nth-child(7) > vn-textfield[label="Value"] input',
seventhRelevancyInput: 'vn-item-tags vn-horizontal:nth-child(7) > vn-textfield[label="Relevancy"] input', seventhRelevancyInput: 'vn-item-tags vn-horizontal:nth-child(7) > vn-textfield[label="Relevancy"] input',
addItemTagButton: 'vn-item-tags vn-icon-button[icon="add_circle"]', addItemTagButton: 'vn-item-tags vn-icon-button[icon="add_circle"]',
submitItemTagsButton: `vn-item-tags ${components.vnSubmit}` submitItemTagsButton: `vn-item-tags ${components.vnSubmit}`
}, },
itemTax: { itemTax: {
firstClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(1) > vn-autocomplete[field="tax.taxClassFk"]', undoChangesButton: 'vn-item-tax vn-button-bar > vn-button[label="Undo changes"]',
secondClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(2) > vn-autocomplete[field="tax.taxClassFk"]', firstClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(1) > vn-autocomplete[ng-model="tax.taxClassFk"]',
thirdClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(3) > vn-autocomplete[field="tax.taxClassFk"]', secondClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(2) > vn-autocomplete[ng-model="tax.taxClassFk"]',
thirdClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(3) > vn-autocomplete[ng-model="tax.taxClassFk"]',
submitTaxButton: `vn-item-tax ${components.vnSubmit}` submitTaxButton: `vn-item-tax ${components.vnSubmit}`
}, },
itemBarcodes: { itemBarcodes: {
@ -273,19 +274,19 @@ export default {
}, },
itemNiches: { itemNiches: {
addNicheButton: 'vn-item-niche vn-icon[icon="add_circle"]', addNicheButton: 'vn-item-niche vn-icon[icon="add_circle"]',
firstWarehouseAutocomplete: 'vn-item-niche vn-autocomplete[field="niche.warehouseFk"]', firstWarehouseAutocomplete: 'vn-item-niche vn-autocomplete[ng-model="niche.warehouseFk"]',
firstCodeInput: 'vn-item-niche vn-horizontal:nth-child(1) > vn-textfield[label="Code"] input', firstCodeInput: 'vn-item-niche vn-horizontal:nth-child(1) > vn-textfield[label="Code"] input',
secondWarehouseAutocomplete: 'vn-item-niche vn-horizontal:nth-child(2) > vn-autocomplete[field="niche.warehouseFk"]', secondWarehouseAutocomplete: 'vn-item-niche vn-horizontal:nth-child(2) > vn-autocomplete[ng-model="niche.warehouseFk"]',
secondCodeInput: 'vn-item-niche vn-horizontal:nth-child(2) > vn-textfield[label="Code"] input', secondCodeInput: 'vn-item-niche vn-horizontal:nth-child(2) > vn-textfield[label="Code"] input',
secondNicheRemoveButton: 'vn-item-niche vn-horizontal:nth-child(2) > vn-none > vn-icon-button[icon="delete"]', secondNicheRemoveButton: 'vn-item-niche vn-horizontal:nth-child(2) > vn-none > vn-icon-button[icon="delete"]',
thirdWarehouseAutocomplete: 'vn-item-niche vn-horizontal:nth-child(3) > vn-autocomplete[field="niche.warehouseFk"]', thirdWarehouseAutocomplete: 'vn-item-niche vn-horizontal:nth-child(3) > vn-autocomplete[ng-model="niche.warehouseFk"]',
thirdCodeInput: 'vn-item-niche vn-horizontal:nth-child(3) > vn-textfield[label="Code"] input', thirdCodeInput: 'vn-item-niche vn-horizontal:nth-child(3) > vn-textfield[label="Code"] input',
submitNichesButton: `vn-item-niche ${components.vnSubmit}` submitNichesButton: `vn-item-niche ${components.vnSubmit}`
}, },
itemBotanical: { itemBotanical: {
botanicalInput: `vn-item-botanical vn-horizontal:nth-child(1) > ${components.vnTextfield}`, botanicalInput: `vn-item-botanical vn-horizontal:nth-child(1) > ${components.vnTextfield}`,
genusAutocomplete: 'vn-item-botanical vn-autocomplete[field="$ctrl.botanical.genusFk"]', genusAutocomplete: 'vn-item-botanical vn-autocomplete[ng-model="$ctrl.botanical.genusFk"]',
speciesAutocomplete: 'vn-item-botanical vn-autocomplete[field="$ctrl.botanical.specieFk"]', speciesAutocomplete: 'vn-item-botanical vn-autocomplete[ng-model="$ctrl.botanical.specieFk"]',
submitBotanicalButton: `vn-item-botanical ${components.vnSubmit}` submitBotanicalButton: `vn-item-botanical ${components.vnSubmit}`
}, },
itemSummary: { itemSummary: {
@ -300,7 +301,7 @@ export default {
secondTicketId: 'vn-item-diary vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(2) > span', secondTicketId: 'vn-item-diary vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(2) > span',
firstBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(1) > vn-td.balance', firstBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(1) > vn-td.balance',
fourthBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(4) > vn-td.balance', fourthBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(4) > vn-td.balance',
warehouseAutocomplete: 'vn-item-diary vn-autocomplete[field="$ctrl.warehouseFk"]', warehouseAutocomplete: 'vn-item-diary vn-autocomplete[ng-model="$ctrl.warehouseFk"]',
}, },
itemLog: { itemLog: {
anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr', anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr',
@ -323,31 +324,31 @@ export default {
setOk: 'vn-ticket-summary vn-button[label="SET OK"] > button' setOk: 'vn-ticket-summary vn-button[label="SET OK"] > button'
}, },
ticketsIndex: { ticketsIndex: {
openAdvancedSearchButton: 'vn-ticket-index vn-searchbar t-right-icons > vn-icon[icon="keyboard_arrow_down"]', openAdvancedSearchButton: 'vn-ticket-index vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
advancedSearchInvoiceOut: 'vn-ticket-search-panel vn-textfield[model="filter.refFk"] input', advancedSearchInvoiceOut: 'vn-ticket-search-panel vn-textfield[ng-model="filter.refFk"] input',
newTicketButton: 'vn-ticket-index > a', newTicketButton: 'vn-ticket-index > a',
searchResult: 'vn-ticket-index vn-card > div > vn-table > div > vn-tbody > a.vn-tr', searchResult: 'vn-ticket-index vn-card > div > vn-table > div > vn-tbody > a.vn-tr',
searchWeeklyResult: 'vn-ticket-weekly-index vn-table vn-tbody > vn-tr', searchWeeklyResult: 'vn-ticket-weekly-index vn-table vn-tbody > vn-tr',
searchResultDate: 'vn-ticket-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(5)', searchResultDate: 'vn-ticket-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(5)',
searchTicketInput: `vn-ticket-index ${components.vnTextfield}`, searchTicketInput: `vn-ticket-index ${components.vnTextfield}`,
searchWeeklyTicketInput: `vn-ticket-weekly-index ${components.vnTextfield}`, searchWeeklyTicketInput: `vn-ticket-weekly-index ${components.vnTextfield}`,
searchWeeklyClearInput: 'vn-ticket-weekly-index vn-searchbar i[class="material-icons clear"]', searchWeeklyClearInput: 'vn-ticket-weekly-index vn-searchbar vn-icon[icon=clear]',
advancedSearchButton: 'vn-ticket-search-panel vn-submit[label="Search"] input', advancedSearchButton: 'vn-ticket-search-panel vn-submit[label="Search"] input',
searchButton: 'vn-ticket-index vn-searchbar vn-icon[icon="search"]', searchButton: 'vn-ticket-index vn-searchbar vn-icon[icon="search"]',
searchWeeklyButton: 'vn-ticket-weekly-index vn-searchbar vn-icon[icon="search"]', searchWeeklyButton: 'vn-ticket-weekly-index vn-searchbar vn-icon[icon="search"]',
moreMenu: 'vn-ticket-index vn-icon-menu[vn-id="more-button"] > div > vn-icon', moreMenu: 'vn-ticket-index vn-icon-menu[vn-id="more-button"] > div > vn-icon',
moreMenuWeeklyTickets: '.vn-popover.shown .vn-drop-down li:nth-child(2)', moreMenuWeeklyTickets: '.vn-popover.shown .vn-drop-down li:nth-child(2)',
sixthWeeklyTicket: 'vn-ticket-weekly-index vn-table vn-tr:nth-child(6) vn-autocomplete[field="weekly.weekDay"] input', sixthWeeklyTicket: 'vn-ticket-weekly-index vn-table vn-tr:nth-child(6) vn-autocomplete[ng-model="weekly.weekDay"] input',
weeklyTicket: 'vn-ticket-weekly-index vn-table > div > vn-tbody > vn-tr', weeklyTicket: 'vn-ticket-weekly-index vn-table > div > vn-tbody > vn-tr',
firstWeeklyTicketDeleteIcon: 'vn-ticket-weekly-index vn-tr:nth-child(1) vn-icon-button[icon="delete"]', firstWeeklyTicketDeleteIcon: 'vn-ticket-weekly-index vn-tr:nth-child(1) vn-icon-button[icon="delete"]',
acceptDeleteTurn: 'vn-ticket-weekly-index > vn-confirm[vn-id="deleteWeekly"] button[response="ACCEPT"]' acceptDeleteTurn: 'vn-ticket-weekly-index > vn-confirm[vn-id="deleteWeekly"] button[response="ACCEPT"]'
}, },
createTicketView: { createTicketView: {
clientAutocomplete: 'vn-ticket-create vn-autocomplete[field="$ctrl.clientFk"]', clientAutocomplete: 'vn-ticket-create vn-autocomplete[ng-model="$ctrl.clientFk"]',
addressAutocomplete: 'vn-ticket-create vn-autocomplete[field="$ctrl.addressFk"]', addressAutocomplete: 'vn-ticket-create vn-autocomplete[ng-model="$ctrl.addressFk"]',
deliveryDateInput: 'vn-ticket-create > div > div > vn-card > div > vn-ticket-create-card > vn-date-picker > div > input', deliveryDateInput: 'vn-ticket-create vn-date-picker[ng-model="$ctrl.landed"]',
warehouseAutocomplete: 'vn-ticket-create vn-autocomplete[field="$ctrl.warehouseFk"]', warehouseAutocomplete: 'vn-ticket-create vn-autocomplete[ng-model="$ctrl.warehouseFk"]',
agencyAutocomplete: 'vn-ticket-create vn-autocomplete[field="$ctrl.ticket.agencyModeFk"]', agencyAutocomplete: 'vn-ticket-create vn-autocomplete[ng-model="$ctrl.ticket.agencyModeFk"]',
createButton: `${components.vnSubmit}` createButton: `${components.vnSubmit}`
}, },
ticketDescriptor: { ticketDescriptor: {
@ -361,7 +362,8 @@ export default {
moreMenuDeleteTicket: '.vn-popover.shown .vn-drop-down li[name="Delete ticket"]', moreMenuDeleteTicket: '.vn-popover.shown .vn-drop-down li[name="Delete ticket"]',
moreMenuMakeInvoice: '.vn-popover.shown .vn-drop-down li[name="Make invoice"]', moreMenuMakeInvoice: '.vn-popover.shown .vn-drop-down li[name="Make invoice"]',
moreMenuChangeShippedHour: '.vn-popover.shown .vn-drop-down li[name="Change shipped hour"]', moreMenuChangeShippedHour: '.vn-popover.shown .vn-drop-down li[name="Change shipped hour"]',
changeShippedHourInput: 'vn-ticket-descriptor .vn-dialog.shown vn-input-time input', changeShippedHourDialog: 'vn-ticket-descriptor vn-dialog[vn-id="changeShippedDialog"]',
changeShippedHourInput: 'vn-ticket-descriptor vn-dialog[vn-id="changeShippedDialog"] vn-input-time[vn-id="newShipped"]',
addStowawayDialogFirstTicket: 'vn-ticket-descriptor > vn-add-stowaway > vn-dialog vn-table vn-tbody vn-tr', addStowawayDialogFirstTicket: 'vn-ticket-descriptor > vn-add-stowaway > vn-dialog vn-table vn-tbody vn-tr',
shipButton: 'vn-ticket-descriptor > div > div.body > div.quicklinks vn-icon[icon="icon-stowaway"]', shipButton: 'vn-ticket-descriptor > div > div.body > div.quicklinks vn-icon[icon="icon-stowaway"]',
thursdayButton: 'vn-ticket-descriptor > vn-dialog > div > form > div.body > tpl-body > div > vn-tool-bar > vn-button:nth-child(4)', thursdayButton: 'vn-ticket-descriptor > vn-dialog > div > form > div.body > tpl-body > div > vn-tool-bar > vn-button:nth-child(4)',
@ -376,7 +378,7 @@ export default {
ticketNotes: { ticketNotes: {
firstNoteRemoveButton: 'vn-icon[icon="delete"]', firstNoteRemoveButton: 'vn-icon[icon="delete"]',
addNoteButton: 'vn-icon[icon="add_circle"]', addNoteButton: 'vn-icon[icon="add_circle"]',
firstNoteTypeAutocomplete: 'vn-autocomplete[field="observation.observationTypeFk"]', firstNoteTypeAutocomplete: 'vn-autocomplete[ng-model="observation.observationTypeFk"]',
firstDescriptionInput: 'vn-textfield[label="Description"] input', firstDescriptionInput: 'vn-textfield[label="Description"] input',
submitNotesButton: `${components.vnSubmit}` submitNotesButton: `${components.vnSubmit}`
}, },
@ -389,10 +391,10 @@ export default {
ticketPackages: { ticketPackages: {
packagesButton: 'vn-left-menu a[ui-sref="ticket.card.package"]', packagesButton: 'vn-left-menu a[ui-sref="ticket.card.package"]',
firstPackageAutocomplete: 'vn-autocomplete[label="Package"]', firstPackageAutocomplete: 'vn-autocomplete[label="Package"]',
firstQuantityInput: 'vn-input-number[label="Quantity"] input', firstQuantityInput: 'vn-input-number[ng-model="package.quantity"] input',
firstRemovePackageButton: 'vn-icon-button[vn-tooltip="Remove package"]', firstRemovePackageButton: 'vn-icon-button[vn-tooltip="Remove package"]',
addPackageButton: 'vn-icon-button[vn-tooltip="Add package"]', addPackageButton: 'vn-icon-button[vn-tooltip="Add package"]',
clearPackageAutocompleteButton: 'vn-autocomplete[label="Package"] > div > div > div > vn-icon > i', clearPackageAutocompleteButton: 'vn-autocomplete[label="Package"] .icons > vn-icon[icon=clear]',
savePackagesButton: `${components.vnSubmit}` savePackagesButton: `${components.vnSubmit}`
}, },
ticketSales: { ticketSales: {
@ -408,7 +410,7 @@ export default {
moreMenuReserve: '.vn-popover.shown .vn-drop-down li[name="Mark as reserved"]', moreMenuReserve: '.vn-popover.shown .vn-drop-down li[name="Mark as reserved"]',
moreMenuUnmarkReseved: '.vn-popover.shown .vn-drop-down li[name="Unmark as reserved"]', moreMenuUnmarkReseved: '.vn-popover.shown .vn-drop-down li[name="Unmark as reserved"]',
moreMenuUpdateDiscount: '.vn-popover.shown .vn-drop-down li[name="Update discount"]', moreMenuUpdateDiscount: '.vn-popover.shown .vn-drop-down li[name="Update discount"]',
moreMenuUpdateDiscountInput: 'vn-ticket-sale vn-dialog form vn-ticket-sale-edit-discount vn-input-number[model="$ctrl.newDiscount"] input', moreMenuUpdateDiscountInput: 'vn-ticket-sale vn-dialog form vn-ticket-sale-edit-discount vn-input-number[ng-model="$ctrl.newDiscount"] input',
transferQuantityInput: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable > span > text', transferQuantityInput: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable > span > text',
transferQuantityCell: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable', transferQuantityCell: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable',
firstSaleClaimIcon: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(1) vn-icon[icon="icon-claims"]', firstSaleClaimIcon: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(1) vn-icon[icon="icon-claims"]',
@ -416,11 +418,11 @@ export default {
firstSaleText: 'vn-table div > vn-tbody > vn-tr:nth-child(1)', firstSaleText: 'vn-table div > vn-tbody > vn-tr:nth-child(1)',
firstSaleThumbnailImage: 'vn-ticket-sale:nth-child(1) vn-tr:nth-child(1) vn-td:nth-child(3) > img', firstSaleThumbnailImage: 'vn-ticket-sale:nth-child(1) vn-tr:nth-child(1) vn-td:nth-child(3) > img',
firstSaleZoomedImage: 'body > div > div > img', firstSaleZoomedImage: 'body > div > div > img',
firstSaleQuantity: 'vn-input-number[model="sale.quantity"]:nth-child(1) input', firstSaleQuantity: 'vn-input-number[ng-model="sale.quantity"]:nth-child(1) input',
firstSaleQuantityCell: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td-editable:nth-child(5)', firstSaleQuantityCell: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td-editable:nth-child(5)',
firstSaleQuantityClearInput: 'vn-textfield[model="sale.quantity"] div.suffix > i', firstSaleQuantityClearInput: 'vn-textfield[ng-model="sale.quantity"] div.suffix > i',
firstSaleIdInput: 'body > vn-app > div > ui-view > vn-ticket-card > vn-main-block > div > vn-ticket-sale > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > vn-autocomplete > div > div > input', firstSaleIdInput: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > vn-autocomplete input',
firstSaleIdAutocomplete: 'vn-ticket-sale > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > vn-autocomplete', firstSaleIdAutocomplete: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > vn-autocomplete',
idAutocompleteFirstResult: '.vn-popover.shown .vn-drop-down li', idAutocompleteFirstResult: '.vn-popover.shown .vn-drop-down li',
firstSalePrice: 'vn-ticket-sale vn-table vn-tr:nth-child(1) > vn-td:nth-child(7) > span', firstSalePrice: 'vn-ticket-sale vn-table vn-tr:nth-child(1) > vn-td:nth-child(7) > span',
firstSalePriceInput: '.vn-popover.shown vn-input-number input', firstSalePriceInput: '.vn-popover.shown vn-input-number input',
@ -428,9 +430,9 @@ export default {
firstSaleDiscountInput: 'vn-ticket-sale:nth-child(1) vn-ticket-sale-edit-discount vn-input-number input', firstSaleDiscountInput: 'vn-ticket-sale:nth-child(1) vn-ticket-sale-edit-discount vn-input-number input',
firstSaleImport: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(9)', firstSaleImport: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(9)',
firstSaleReservedIcon: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td:nth-child(2) > vn-icon:nth-child(3)', firstSaleReservedIcon: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td:nth-child(2) > vn-icon:nth-child(3)',
firstSaleColour: 'vn-tr:nth-child(1) vn-fetched-tags section', firstSaleColour: 'vn-ticket-sale vn-tr:nth-child(1) vn-fetched-tags section',
firstSaleLength: 'vn-ticket-sale vn-tr:nth-child(1) vn-td-editable:nth-child(6) section:nth-child(3)', firstSaleLength: 'vn-ticket-sale vn-tr:nth-child(1) vn-td-editable:nth-child(6) section:nth-child(3)',
firstSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(1) vn-check[field="sale.checked"]', firstSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(1) vn-check[ng-model="sale.checked"]',
secondSaleColour: 'vn-tr:nth-child(2) vn-fetched-tags section', secondSaleColour: 'vn-tr:nth-child(2) vn-fetched-tags section',
secondSalePrice: 'vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(7) > span', secondSalePrice: 'vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(7) > span',
secondSaleDiscount: 'vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(8)', secondSaleDiscount: 'vn-ticket-sale vn-tr:nth-child(2) vn-td:nth-child(8)',
@ -438,18 +440,18 @@ export default {
secondSaleText: 'vn-table div > vn-tbody > vn-tr:nth-child(2)', secondSaleText: 'vn-table div > vn-tbody > vn-tr:nth-child(2)',
secondSaleId: 'vn-ticket-sale:nth-child(2) vn-td-editable:nth-child(4) text > span', secondSaleId: 'vn-ticket-sale:nth-child(2) vn-td-editable:nth-child(4) text > span',
secondSaleIdCell: 'vn-ticket-sale vn-tr:nth-child(2) > vn-td-editable:nth-child(4)', secondSaleIdCell: 'vn-ticket-sale vn-tr:nth-child(2) > vn-td-editable:nth-child(4)',
secondSaleIdInput: 'body > vn-app > div > ui-view > vn-ticket-card > vn-main-block > div > vn-ticket-sale > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(4) > vn-autocomplete > div > div > input', secondSaleIdInput: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(4) > vn-autocomplete input',
secondSaleIdAutocomplete: 'vn-ticket-sale > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(4) > vn-autocomplete', secondSaleIdAutocomplete: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(4) > vn-autocomplete',
secondSaleQuantity: 'vn-ticket-sale vn-table vn-tr:nth-child(2) vn-input-number input', secondSaleQuantity: 'vn-ticket-sale vn-table vn-tr:nth-child(2) vn-input-number input',
secondSaleConceptCell: 'vn-ticket-sale vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td-editable:nth-child(6)', secondSaleConceptCell: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(2) > vn-td-editable:nth-child(6)',
secondSaleConceptInput: 'vn-ticket-sale vn-table vn-tr:nth-child(2) > vn-td-editable.ng-isolate-scope.selected vn-textfield input', secondSaleConceptInput: 'vn-ticket-sale vn-table vn-tr:nth-child(2) > vn-td-editable.ng-isolate-scope.selected vn-textfield input',
totalImport: 'vn-ticket-sale > vn-vertical > vn-card > div > vn-vertical > vn-horizontal > vn-one > p:nth-child(3) > strong', totalImport: 'vn-ticket-sale > vn-vertical > vn-card > div > vn-vertical > vn-horizontal > vn-one > p:nth-child(3) > strong',
selectAllSalesCheckbox: 'vn-ticket-sale vn-thead vn-check', selectAllSalesCheckbox: 'vn-ticket-sale vn-thead vn-check',
secondSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(2) vn-check[field="sale.checked"]', secondSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(2) vn-check[ng-model="sale.checked"]',
thirdSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(3) vn-check[field="sale.checked"]', thirdSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(3) vn-check[ng-model="sale.checked"]',
deleteSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="delete"]', deleteSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="delete"]',
transferSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="call_split"]', transferSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="call_split"]',
moveToTicketInput: '.vn-popover.shown vn-textfield[model="$ctrl.transfer.ticketId"] input', moveToTicketInput: '.vn-popover.shown vn-textfield[ng-model="$ctrl.transfer.ticketId"] input',
moveToTicketInputClearButton: '.vn-popover.shown i[title="Clear"]', moveToTicketInputClearButton: '.vn-popover.shown i[title="Clear"]',
moveToTicketButton: '.vn-popover.shown vn-icon[icon="arrow_forward_ios"]', moveToTicketButton: '.vn-popover.shown vn-icon[icon="arrow_forward_ios"]',
moveToNewTicketButton: '.vn-popover.shown vn-button[label="New ticket"]', moveToNewTicketButton: '.vn-popover.shown vn-button[label="New ticket"]',
@ -460,36 +462,34 @@ export default {
ticketTracking: { ticketTracking: {
trackingButton: 'vn-left-menu a[ui-sref="ticket.card.tracking.index"]', trackingButton: 'vn-left-menu a[ui-sref="ticket.card.tracking.index"]',
createStateButton: `${components.vnFloatButton}`, createStateButton: `${components.vnFloatButton}`,
stateAutocomplete: 'vn-ticket-tracking-edit vn-autocomplete[field="$ctrl.stateFk"]', stateAutocomplete: 'vn-ticket-tracking-edit vn-autocomplete[ng-model="$ctrl.stateFk"]',
saveButton: `${components.vnSubmit}`, saveButton: `${components.vnSubmit}`,
cancelButton: 'vn-ticket-tracking-edit vn-button[ui-sref="ticket.card.tracking.index"]' cancelButton: 'vn-ticket-tracking-edit vn-button[ui-sref="ticket.card.tracking.index"]'
}, },
ticketBasicData: { ticketBasicData: {
basicDataButton: 'vn-left-menu a[ui-sref="ticket.card.basicData.stepOne"]', basicDataButton: 'vn-left-menu a[ui-sref="ticket.card.basicData.stepOne"]',
clientAutocomplete: 'vn-autocomplete[field="$ctrl.clientFk"]', clientAutocomplete: 'vn-autocomplete[ng-model="$ctrl.clientFk"]',
addressAutocomplete: 'vn-autocomplete[field="$ctrl.ticket.addressFk"]', addressAutocomplete: 'vn-autocomplete[ng-model="$ctrl.ticket.addressFk"]',
agencyAutocomplete: 'vn-autocomplete[field="$ctrl.agencyModeId"]', agencyAutocomplete: 'vn-autocomplete[ng-model="$ctrl.agencyModeId"]',
zoneAutocomplete: 'vn-autocomplete[field="$ctrl.zoneId"]', zoneAutocomplete: 'vn-autocomplete[ng-model="$ctrl.zoneId"]',
nextStepButton: 'vn-step-control > section > section.buttons > section:nth-child(2) > vn-button', nextStepButton: 'vn-step-control > section > section.buttons > section:nth-child(2) > vn-button',
finalizeButton: 'vn-step-control > section > section.buttons > section:nth-child(2) > vn-submit', finalizeButton: 'vn-step-control > section > section.buttons > section:nth-child(2) > vn-submit',
stepTwoTotalPriceDif: 'vn-ticket-basic-data-step-two > form > vn-card > div > vn-horizontal > table > tfoot > tr > td:nth-child(4)', stepTwoTotalPriceDif: 'vn-ticket-basic-data-step-two > form > vn-card > div > vn-horizontal > table > tfoot > tr > td:nth-child(4)',
chargesReasonAutocomplete: 'vn-autocomplete[field="$ctrl.ticket.option"]', chargesReasonAutocomplete: 'vn-autocomplete[ng-model="$ctrl.ticket.option"]',
}, },
ticketComponents: { ticketComponents: {
base: 'vn-ticket-components tfoot > tr:nth-child(1) > td', base: 'vn-ticket-components [name="base-sum"]'
margin: 'vn-ticket-components tfoot > tr:nth-child(2) > td',
total: 'vn-ticket-components tfoot > tr:nth-child(3) > td'
}, },
ticketRequests: { ticketRequests: {
addRequestButton: 'vn-ticket-request-index > a > vn-float-button > button', addRequestButton: 'vn-ticket-request-index > a > vn-float-button > button',
request: 'vn-ticket-request-index > form > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr', request: 'vn-ticket-request-index vn-table vn-tr',
descriptionInput: 'vn-ticket-request-create > form > div > vn-card > div > vn-horizontal:nth-child(1) > vn-textfield > div > div > div.infix > input', descriptionInput: 'vn-ticket-request-create > form > div > vn-card > div > vn-horizontal:nth-child(1) > vn-textfield input',
atenderAutocomplete: 'vn-ticket-request-create vn-autocomplete[field="$ctrl.ticketRequest.atenderFk"]', atenderAutocomplete: 'vn-ticket-request-create vn-autocomplete[ng-model="$ctrl.ticketRequest.atenderFk"]',
quantityInput: 'vn-ticket-request-create > form > div > vn-card > div > vn-horizontal:nth-child(2) > vn-input-number:nth-child(1) > div > div > div.infix > input', quantityInput: 'vn-ticket-request-create vn-input-number input[name=quantity]',
priceInput: 'vn-ticket-request-create > form > div > vn-card > div > vn-horizontal:nth-child(2) > vn-input-number:nth-child(2) > div > div > div.infix > input', priceInput: 'vn-ticket-request-create vn-input-number input[name=price]',
firstRemoveRequestButton: 'vn-ticket-request-index vn-icon[icon="delete"]:nth-child(1)', firstRemoveRequestButton: 'vn-ticket-request-index vn-icon[icon="delete"]:nth-child(1)',
saveButton: 'vn-ticket-request-create > form > div > vn-button-bar > vn-submit[label="Create"] input', saveButton: 'vn-ticket-request-create > form > div > vn-button-bar > vn-submit[label="Create"] input',
firstDescription: 'vn-ticket-request-index > form > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(2)', firstDescription: 'vn-ticket-request-index vn-table vn-tr:nth-child(1) > vn-td:nth-child(2)',
}, },
ticketLog: { ticketLog: {
@ -501,20 +501,20 @@ export default {
ticketService: { ticketService: {
addServiceButton: 'vn-ticket-service vn-icon-button[vn-tooltip="Add service"] > button', addServiceButton: 'vn-ticket-service vn-icon-button[vn-tooltip="Add service"] > button',
firstAddDescriptionButton: 'vn-ticket-service vn-icon-button[vn-tooltip="New service type"] > button', firstAddDescriptionButton: 'vn-ticket-service vn-icon-button[vn-tooltip="New service type"] > button',
firstDescriptionAutocomplete: 'vn-ticket-service vn-autocomplete[field="service.description"]', firstDescriptionAutocomplete: 'vn-ticket-service vn-autocomplete[ng-model="service.description"]',
firstQuantityInput: 'vn-ticket-service vn-input-number[label="Quantity"] input', firstQuantityInput: 'vn-ticket-service vn-input-number[label="Quantity"] input',
firstPriceInput: 'vn-ticket-service vn-input-number[label="Price"] input', firstPriceInput: 'vn-ticket-service vn-input-number[label="Price"] input',
firstVatTypeAutocomplete: 'vn-ticket-service vn-autocomplete[label="Tax class"]', firstVatTypeAutocomplete: 'vn-ticket-service vn-autocomplete[label="Tax class"]',
fistDeleteServiceButton: 'vn-ticket-card > vn-main-block > div.content-block.ng-scope > vn-ticket-service > form > vn-card > div > vn-one:nth-child(1) > vn-horizontal:nth-child(1) > vn-auto > vn-icon-button[icon="delete"]', fistDeleteServiceButton: 'vn-ticket-service form vn-horizontal:nth-child(1) vn-icon-button[icon="delete"]',
newDescriptionInput: 'vn-ticket-service > vn-dialog vn-textfield[model="$ctrl.newServiceType.name"] input', newDescriptionInput: 'vn-ticket-service > vn-dialog vn-textfield[ng-model="$ctrl.newServiceType.name"] input',
serviceLine: 'vn-ticket-service > form > vn-card > div > vn-one:nth-child(2) > vn-horizontal', serviceLine: 'vn-ticket-service > form > vn-card > div > vn-one:nth-child(2) > vn-horizontal',
saveServiceButton: `${components.vnSubmit}`, saveServiceButton: `${components.vnSubmit}`,
saveDescriptionButton: 'vn-ticket-service > vn-dialog[vn-id="createServiceTypeDialog"] > div > form > div.buttons > tpl-buttons > button' saveDescriptionButton: 'vn-ticket-service > vn-dialog[vn-id="createServiceTypeDialog"] > div > form > div.buttons > tpl-buttons > button'
}, },
createStateView: { createStateView: {
stateAutocomplete: 'vn-autocomplete[field="$ctrl.stateFk"]', stateAutocomplete: 'vn-autocomplete[ng-model="$ctrl.stateFk"]',
workerAutocomplete: 'vn-autocomplete[field="$ctrl.workerFk"]', workerAutocomplete: 'vn-autocomplete[ng-model="$ctrl.workerFk"]',
clearStateInputButton: 'vn-autocomplete[field="$ctrl.stateFk"] > div > div > div > vn-icon > i', clearStateInputButton: 'vn-autocomplete[ng-model="$ctrl.stateFk"] .icons > vn-icon[icon=clear]',
saveStateButton: `${components.vnSubmit}` saveStateButton: `${components.vnSubmit}`
}, },
claimsIndex: { claimsIndex: {
@ -530,7 +530,7 @@ export default {
claimSummary: { claimSummary: {
header: 'vn-claim-summary > vn-card > div > h5', header: 'vn-claim-summary > vn-card > div > h5',
state: 'vn-claim-summary vn-label-value[label="State"] > section > span', state: 'vn-claim-summary vn-label-value[label="State"] > section > span',
observation: 'vn-claim-summary vn-textarea[model="$ctrl.summary.claim.observation"] > div > textarea', observation: 'vn-claim-summary vn-textarea[ng-model="$ctrl.summary.claim.observation"] textarea',
firstSaleItemId: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(4) > vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(1) > span', firstSaleItemId: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(4) > vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(1) > span',
firstSaleDescriptorImage: '.vn-popover.shown vn-item-descriptor img', firstSaleDescriptorImage: '.vn-popover.shown vn-item-descriptor img',
itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor', itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor',
@ -541,45 +541,45 @@ export default {
firstActionTicketDescriptor: '.vn-popover.shown vn-ticket-descriptor' firstActionTicketDescriptor: '.vn-popover.shown vn-ticket-descriptor'
}, },
claimBasicData: { claimBasicData: {
claimStateAutocomplete: 'vn-claim-basic-data vn-autocomplete[field="$ctrl.claim.claimStateFk"]', claimStateAutocomplete: 'vn-claim-basic-data vn-autocomplete[ng-model="$ctrl.claim.claimStateFk"]',
responsabilityInputRange: 'vn-input-range', responsabilityInputRange: 'vn-input-range',
observationInput: 'vn-textarea[field="$ctrl.claim.observation"] textarea', observationInput: 'vn-textarea[ng-model="$ctrl.claim.observation"] textarea',
saveButton: `${components.vnSubmit}` saveButton: `${components.vnSubmit}`
}, },
claimDetail: { claimDetail: {
secondItemDiscount: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(6) > span', secondItemDiscount: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(6) > span',
discountInput: '.vn-popover.shown vn-input-number[model="$ctrl.newDiscount"] > div > div > div.infix > input', discountInput: '.vn-popover.shown vn-input-number[ng-model="$ctrl.newDiscount"] input',
discoutPopoverMana: '.vn-popover.shown .content > div > vn-horizontal > h5', discoutPopoverMana: '.vn-popover.shown .content > div > vn-horizontal > h5',
addItemButton: 'vn-claim-detail a vn-float-button', addItemButton: 'vn-claim-detail a vn-float-button',
firstClaimableSaleFromTicket: 'vn-claim-detail > vn-dialog vn-tbody > vn-tr', firstClaimableSaleFromTicket: 'vn-claim-detail > vn-dialog vn-tbody > vn-tr',
claimDetailLine: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr', claimDetailLine: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr',
firstItemQuantityInput: 'vn-claim-detail vn-tr:nth-child(1) vn-input-number[model="saleClaimed.quantity"] input', firstItemQuantityInput: 'vn-claim-detail vn-tr:nth-child(1) vn-input-number[ng-model="saleClaimed.quantity"] input',
totalClaimed: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-horizontal > div > vn-label-value:nth-child(2) > section > span', totalClaimed: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-horizontal > div > vn-label-value:nth-child(2) > section > span',
secondItemDeleteButton: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(8) > vn-icon-button > button > vn-icon > i' secondItemDeleteButton: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(8) > vn-icon-button > button > vn-icon > i'
}, },
claimDevelopment: { claimDevelopment: {
addDevelopmentButton: 'vn-claim-development > vn-vertical > vn-card > div > vn-vertical > vn-one > vn-icon-button > button > vn-icon', addDevelopmentButton: 'vn-claim-development > vn-vertical > vn-card > div > vn-vertical > vn-one > vn-icon-button > button > vn-icon',
firstDeleteDevelopmentButton: 'vn-claim-development > vn-vertical > vn-card > div > vn-vertical > form > vn-horizontal:nth-child(2) > vn-icon-button > button > vn-icon', firstDeleteDevelopmentButton: 'vn-claim-development > vn-vertical > vn-card > div > vn-vertical > form > vn-horizontal:nth-child(2) > vn-icon-button > button > vn-icon',
firstClaimReasonAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[field="claimDevelopment.claimReasonFk"]', firstClaimReasonAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimReasonFk"]',
firstClaimResultAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[field="claimDevelopment.claimResultFk"]', firstClaimResultAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimResultFk"]',
firstClaimResponsibleAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[field="claimDevelopment.claimResponsibleFk"]', firstClaimResponsibleAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimResponsibleFk"]',
firstClaimWorkerAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[field="claimDevelopment.workerFk"]', firstClaimWorkerAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.workerFk"]',
firstClaimRedeliveryAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[field="claimDevelopment.claimRedeliveryFk"]', firstClaimRedeliveryAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimRedeliveryFk"]',
secondClaimReasonAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[field="claimDevelopment.claimReasonFk"]', secondClaimReasonAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimReasonFk"]',
secondClaimResultAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[field="claimDevelopment.claimResultFk"]', secondClaimResultAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimResultFk"]',
secondClaimResponsibleAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[field="claimDevelopment.claimResponsibleFk"]', secondClaimResponsibleAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimResponsibleFk"]',
secondClaimWorkerAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[field="claimDevelopment.workerFk"]', secondClaimWorkerAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.workerFk"]',
secondClaimRedeliveryAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[field="claimDevelopment.claimRedeliveryFk"]', secondClaimRedeliveryAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimRedeliveryFk"]',
saveDevelopmentButton: `${components.vnSubmit}` saveDevelopmentButton: `${components.vnSubmit}`
}, },
claimAction: { claimAction: {
importClaimButton: 'vn-claim-action vn-button[label="Import claim"]', importClaimButton: 'vn-claim-action vn-button[label="Import claim"]',
importTicketButton: 'vn-claim-action vn-button[label="Import ticket"]', importTicketButton: 'vn-claim-action vn-button[label="Import ticket"]',
secondImportableTicket: '.vn-popover.shown .content > div > vn-table > div > vn-tbody > vn-tr:nth-child(2)', secondImportableTicket: '.vn-popover.shown .content > div > vn-table > div > vn-tbody > vn-tr:nth-child(2)',
firstLineDestination: 'vn-claim-action vn-tr:nth-child(1) vn-autocomplete[field="saleClaimed.claimDestinationFk"]', firstLineDestination: 'vn-claim-action vn-tr:nth-child(1) vn-autocomplete[ng-model="saleClaimed.claimDestinationFk"]',
secondLineDestination: 'vn-claim-action vn-tr:nth-child(2) vn-autocomplete[field="saleClaimed.claimDestinationFk"]', secondLineDestination: 'vn-claim-action vn-tr:nth-child(2) vn-autocomplete[ng-model="saleClaimed.claimDestinationFk"]',
firstDeleteLine: 'vn-claim-action vn-tr:nth-child(1) vn-icon-button[icon="delete"]', firstDeleteLine: 'vn-claim-action vn-tr:nth-child(1) vn-icon-button[icon="delete"]',
isPaidWithManaCheckbox: 'vn-check[field="$ctrl.claim.isChargedToMana"]' isPaidWithManaCheckbox: 'vn-check[ng-model="$ctrl.claim.isChargedToMana"]'
}, },
ordersIndex: { ordersIndex: {
searchResult: 'vn-order-index vn-card > div > vn-table > div > vn-tbody > a.vn-tr', searchResult: 'vn-order-index vn-card > div > vn-table > div > vn-tbody > a.vn-tr',
@ -597,7 +597,7 @@ export default {
clientAutocomplete: 'vn-autocomplete[label="Client"]', clientAutocomplete: 'vn-autocomplete[label="Client"]',
addressAutocomplete: 'vn-autocomplete[label="Address"]', addressAutocomplete: 'vn-autocomplete[label="Address"]',
agencyAutocomplete: 'vn-autocomplete[label="Agency"]', agencyAutocomplete: 'vn-autocomplete[label="Agency"]',
landedDatePicker: 'vn-date-picker[label="Landed"] input', landedDatePicker: 'vn-date-picker[label="Landed"]',
createButton: `${components.vnSubmit}`, createButton: `${components.vnSubmit}`,
cancelButton: 'vn-button[href="#!/client/index"]' cancelButton: 'vn-button[href="#!/client/index"]'
}, },
@ -605,14 +605,14 @@ export default {
orderByAutocomplete: 'vn-autocomplete[label="Order by"]', orderByAutocomplete: 'vn-autocomplete[label="Order by"]',
plantRealmButton: 'vn-order-catalog > vn-side-menu vn-icon[icon="icon-plant"]', plantRealmButton: 'vn-order-catalog > vn-side-menu vn-icon[icon="icon-plant"]',
typeAutocomplete: 'vn-autocomplete[data="$ctrl.itemTypes"]', typeAutocomplete: 'vn-autocomplete[data="$ctrl.itemTypes"]',
itemIdInput: 'vn-order-catalog > vn-side-menu vn-catalog-filter vn-textfield[model="$ctrl.itemFk"] input', itemIdInput: 'vn-catalog-filter vn-textfield[ng-model="$ctrl.itemFk"] input',
itemTagValueInput: 'vn-order-catalog > vn-side-menu vn-catalog-filter vn-textfield[model="$ctrl.value"] input', itemTagValueInput: 'vn-catalog-filter vn-textfield[ng-model="$ctrl.value"] input',
openTagSearch: 'vn-order-catalog > vn-side-menu > div > vn-catalog-filter > div > vn-vertical > vn-textfield[model="$ctrl.value"] > div > div > div.rightIcons > t-right-icons > i', openTagSearch: 'vn-catalog-filter > div > vn-vertical > vn-textfield[ng-model="$ctrl.value"] .append i',
tagAutocomplete: 'vn-order-catalog-search-panel vn-autocomplete[field="filter.tagFk"]', tagAutocomplete: 'vn-order-catalog-search-panel vn-autocomplete[ng-model="filter.tagFk"]',
tagValueInput: 'vn-order-catalog-search-panel vn-textfield[model="filter.value"] input', tagValueInput: 'vn-order-catalog-search-panel vn-textfield[ng-model="filter.value"] input',
searchTagButton: 'vn-order-catalog-search-panel > div > form > vn-horizontal:nth-child(3) > vn-submit > input', searchTagButton: 'vn-order-catalog-search-panel > div > form > vn-horizontal:nth-child(3) > vn-submit > input',
thirdFilterRemoveButton: 'vn-order-catalog > vn-side-menu vn-catalog-filter > div > vn-horizontal.chips > vn-chip:nth-child(3) button', thirdFilterRemoveButton: 'vn-catalog-filter > div > vn-horizontal.chips > vn-chip:nth-child(3) button',
fourthFilterRemoveButton: 'vn-order-catalog > vn-side-menu vn-catalog-filter > div > vn-horizontal.chips > vn-chip:nth-child(4) button', fourthFilterRemoveButton: 'vn-catalog-filter > div > vn-horizontal.chips > vn-chip:nth-child(4) button',
}, },
orderBasicData: { orderBasicData: {
clientAutocomplete: 'vn-autocomplete[label="Client"]', clientAutocomplete: 'vn-autocomplete[label="Client"]',
@ -632,11 +632,11 @@ export default {
addNewRouteButton: 'vn-route-index > a[ui-sref="route.create"]' addNewRouteButton: 'vn-route-index > a[ui-sref="route.create"]'
}, },
createRouteView: { createRouteView: {
workerAutocomplete: 'vn-route-create vn-autocomplete[field="$ctrl.route.workerFk"]', workerAutocomplete: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.workerFk"]',
createdDatePicker: 'vn-route-create vn-date-picker[model="$ctrl.route.created"] > div > input', createdDatePicker: 'vn-route-create vn-date-picker[ng-model="$ctrl.route.created"]',
vehicleAutoComplete: 'vn-route-create vn-autocomplete[field="$ctrl.route.vehicleFk"]', vehicleAutoComplete: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.vehicleFk"]',
agencyAutoComplete: 'vn-route-create vn-autocomplete[field="$ctrl.route.agencyModeFk"]', agencyAutoComplete: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.agencyModeFk"]',
descriptionInput: 'vn-route-create vn-textfield[field="$ctrl.route.description"] input', descriptionInput: 'vn-route-create vn-textfield[ng-model="$ctrl.route.description"] input',
submitButton: 'vn-route-create vn-submit > input[type="submit"]' submitButton: 'vn-route-create vn-submit > input[type="submit"]'
}, },
routeDescriptor: { routeDescriptor: {
@ -646,29 +646,29 @@ export default {
routeId: 'vn-route-summary > vn-card > div > vn-horizontal > vn-one:nth-child(1) > vn-label-value:nth-child(1) > section > span' routeId: 'vn-route-summary > vn-card > div > vn-horizontal > vn-one:nth-child(1) > vn-label-value:nth-child(1) > section > span'
}, },
routeBasicData: { routeBasicData: {
workerAutoComplete: 'vn-route-basic-data vn-autocomplete[field="$ctrl.route.workerFk"]', workerAutoComplete: 'vn-route-basic-data vn-autocomplete[ng-model="$ctrl.route.workerFk"]',
vehicleAutoComplete: 'vn-route-basic-data vn-autocomplete[field="$ctrl.route.vehicleFk"]', vehicleAutoComplete: 'vn-route-basic-data vn-autocomplete[ng-model="$ctrl.route.vehicleFk"]',
agencyAutoComplete: 'vn-route-basic-data vn-autocomplete[field="$ctrl.route.agencyModeFk"]', agencyAutoComplete: 'vn-route-basic-data vn-autocomplete[ng-model="$ctrl.route.agencyModeFk"]',
kmStartInput: 'vn-route-basic-data vn-input-number[field="$ctrl.route.kmStart"] input', kmStartInput: 'vn-route-basic-data vn-input-number[ng-model="$ctrl.route.kmStart"] input',
kmEndInput: 'vn-route-basic-data vn-input-number[model="$ctrl.route.kmEnd"] input', kmEndInput: 'vn-route-basic-data vn-input-number[ng-model="$ctrl.route.kmEnd"] input',
createdDateInput: 'vn-route-basic-data vn-date-picker[model="$ctrl.route.created"] > div > input', createdDateInput: 'vn-route-basic-data vn-date-picker[ng-model="$ctrl.route.created"]',
startedHourInput: 'vn-route-basic-data vn-input-time[model="$ctrl.route.started"] input', startedHourInput: 'vn-route-basic-data vn-input-time[ng-model="$ctrl.route.started"] input',
finishedHourInput: 'vn-route-basic-data vn-input-time[model="$ctrl.route.finished"] input', finishedHourInput: 'vn-route-basic-data vn-input-time[ng-model="$ctrl.route.finished"] input',
saveButton: 'vn-route-basic-data vn-submit[label="Save"] input' saveButton: 'vn-route-basic-data vn-submit[label="Save"] input'
}, },
routeTickets: { routeTickets: {
firstTicketPriority: 'vn-route-tickets vn-tr:nth-child(1) vn-textfield[model="ticket.priority"] input', firstTicketPriority: 'vn-route-tickets vn-tr:nth-child(1) vn-textfield[ng-model="ticket.priority"] input',
secondTicketPriority: 'vn-route-tickets vn-tr:nth-child(2) vn-textfield[model="ticket.priority"] input', secondTicketPriority: 'vn-route-tickets vn-tr:nth-child(2) vn-textfield[ng-model="ticket.priority"] input',
thirdTicketPriority: 'vn-route-tickets vn-tr:nth-child(3) vn-textfield[model="ticket.priority"] input', thirdTicketPriority: 'vn-route-tickets vn-tr:nth-child(3) vn-textfield[ng-model="ticket.priority"] input',
fourthTicketPriority: 'vn-route-tickets vn-tr:nth-child(4) vn-textfield[model="ticket.priority"] input', fourthTicketPriority: 'vn-route-tickets vn-tr:nth-child(4) vn-textfield[ng-model="ticket.priority"] input',
eleventhTicketPriority: 'vn-route-tickets vn-tr:nth-child(11) vn-textfield[model="ticket.priority"] input', eleventhTicketPriority: 'vn-route-tickets vn-tr:nth-child(11) vn-textfield[ng-model="ticket.priority"] input',
firstTicketCheckbox: 'vn-route-tickets vn-tr:nth-child(1) vn-check', firstTicketCheckbox: 'vn-route-tickets vn-tr:nth-child(1) vn-check',
buscamanButton: 'vn-route-tickets vn-button[icon="icon-buscaman"]', buscamanButton: 'vn-route-tickets vn-button[icon="icon-buscaman"]',
firstTicketDeleteButton: 'vn-route-tickets vn-tr:nth-child(1) vn-icon[icon="delete"]', firstTicketDeleteButton: 'vn-route-tickets vn-tr:nth-child(1) vn-icon[icon="delete"]',
confirmButton: 'vn-route-tickets > vn-confirm button[response="ACCEPT"]' confirmButton: 'vn-route-tickets > vn-confirm button[response="ACCEPT"]'
}, },
workerPbx: { workerPbx: {
extensionInput: 'vn-worker-pbx vn-textfield[model="$ctrl.worker.sip.extension"] input', extensionInput: 'vn-worker-pbx vn-textfield[ng-model="$ctrl.worker.sip.extension"] input',
saveButton: 'vn-worker-pbx vn-submit[label="Save"] input' saveButton: 'vn-worker-pbx vn-submit[label="Save"] input'
}, },
workerTimeControl: { workerTimeControl: {

View File

@ -176,7 +176,7 @@ describe('Client lock verified data path', () => {
.wait(selectors.clientFiscalData.socialNameInput) .wait(selectors.clientFiscalData.socialNameInput)
.evaluate(selector => { .evaluate(selector => {
return document.querySelector(selector).disabled; return document.querySelector(selector).disabled;
}, 'vn-textfield[model="$ctrl.client.socialName"] > div'); }, 'vn-textfield[ng-model="$ctrl.client.socialName"] > div');
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });

View File

@ -1,8 +1,7 @@
import selectors from '../../helpers/selectors.js'; import selectors from '../../helpers/selectors.js';
import createNightmare from '../../helpers/nightmare'; import createNightmare from '../../helpers/nightmare';
// #1702 Autocomplete no siempre refresca al cancelar formulario describe('Item edit tax path', () => {
xdescribe('Item edit tax path', () => {
const nightmare = createNightmare(); const nightmare = createNightmare();
beforeAll(() => { beforeAll(() => {
@ -14,9 +13,9 @@ xdescribe('Item edit tax path', () => {
it(`should add the item tax to all countries`, async() => { it(`should add the item tax to all countries`, async() => {
const result = await nightmare const result = await nightmare
.autocompleteSearch(selectors.itemTax.firstClassAutocomplete, 'Reduced VAT') .autocompleteSearch(selectors.itemTax.firstClassAutocomplete, 'General VAT')
.autocompleteSearch(selectors.itemTax.secondClassAutocomplete, 'General VAT') .autocompleteSearch(selectors.itemTax.secondClassAutocomplete, 'General VAT')
.autocompleteSearch(selectors.itemTax.thirdClassAutocomplete, 'Reduced VAT') .autocompleteSearch(selectors.itemTax.thirdClassAutocomplete, 'General VAT')
.waitToClick(selectors.itemTax.submitTaxButton) .waitToClick(selectors.itemTax.submitTaxButton)
.waitForLastSnackbar(); .waitForLastSnackbar();
@ -28,7 +27,7 @@ xdescribe('Item edit tax path', () => {
.reloadSection('item.card.tax') .reloadSection('item.card.tax')
.waitToGetProperty(`${selectors.itemTax.firstClassAutocomplete} input`, 'value'); .waitToGetProperty(`${selectors.itemTax.firstClassAutocomplete} input`, 'value');
expect(firstVatType).toEqual('Reduced VAT'); expect(firstVatType).toEqual('General VAT');
}); });
it(`should confirm the second item tax class was edited`, async() => { it(`should confirm the second item tax class was edited`, async() => {
@ -42,6 +41,22 @@ xdescribe('Item edit tax path', () => {
const thirdVatType = await nightmare const thirdVatType = await nightmare
.waitToGetProperty(`${selectors.itemTax.thirdClassAutocomplete} input`, 'value'); .waitToGetProperty(`${selectors.itemTax.thirdClassAutocomplete} input`, 'value');
expect(thirdVatType).toEqual('Reduced VAT'); expect(thirdVatType).toEqual('General VAT');
});
it(`should edit the first class without saving the form`, async() => {
const firstVatType = await nightmare
.autocompleteSearch(selectors.itemTax.firstClassAutocomplete, 'Reduced VAT')
.waitToGetProperty(`${selectors.itemTax.firstClassAutocomplete} input`, 'value');
expect(firstVatType).toEqual('Reduced VAT');
});
it(`should now click the undo changes button and see the changes works`, async() => {
const firstVatType = await nightmare
.waitToClick(selectors.itemTax.undoChangesButton)
.waitToGetProperty(`${selectors.itemTax.firstClassAutocomplete} input`, 'value');
expect(firstVatType).toEqual('General VAT');
}); });
}); });

View File

@ -160,7 +160,7 @@ describe('Ticket descriptor path', () => {
it('should confirm the sixth weekly ticket was deleted', async() => { it('should confirm the sixth weekly ticket was deleted', async() => {
const result = await nightmare const result = await nightmare
.waitToClick('vn-ticket-weekly-index vn-searchbar i[class="material-icons clear"]') .waitToClick('vn-ticket-weekly-index vn-searchbar vn-icon[icon=clear]')
.waitToClick(selectors.ticketsIndex.searchWeeklyButton) .waitToClick(selectors.ticketsIndex.searchWeeklyButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchWeeklyResult, 5) .waitForNumberOfElements(selectors.ticketsIndex.searchWeeklyResult, 5)
.countElement(selectors.ticketsIndex.searchWeeklyResult); .countElement(selectors.ticketsIndex.searchWeeklyResult);

View File

@ -30,20 +30,11 @@ describe('Ticket descriptor path', () => {
expect(url.hash).toContain('/summary'); expect(url.hash).toContain('/summary');
}); });
it('should open the change shipped hours dialog by using the more menu option', async() => { it(`should update the shipped hour using the descriptor menu`, async() => {
const visible = await nightmare const result = await nightmare
.waitToClick(selectors.ticketDescriptor.moreMenu) .waitToClick(selectors.ticketDescriptor.moreMenu)
.waitToClick(selectors.ticketDescriptor.moreMenuChangeShippedHour) .waitToClick(selectors.ticketDescriptor.moreMenuChangeShippedHour)
.wait(selectors.ticketDescriptor.changeShippedHourInput) .write(selectors.ticketDescriptor.changeShippedHourInput, '08:15')
.isVisible(selectors.ticketDescriptor.changeShippedHourInput);
expect(visible).toBeTruthy();
});
it(`should update the shipped hour`, async() => {
const result = await nightmare
.pickTime(selectors.ticketDescriptor.changeShippedHourInput, '08:15')
.waitToClick(selectors.ticketDescriptor.acceptChangeHourButton) .waitToClick(selectors.ticketDescriptor.acceptChangeHourButton)
.waitForLastSnackbar(); .waitForLastSnackbar();
@ -127,16 +118,6 @@ describe('Ticket descriptor path', () => {
expect(result).toEqual('Data saved!'); expect(result).toEqual('Data saved!');
}); });
it(`should navigate to the added ticket using the descriptors ship button`, async() => {
const url = await nightmare
.waitToClick(selectors.ticketDescriptor.closeStowawayDialog)
.waitToClick(selectors.ticketDescriptor.shipButton)
.waitForURL('#!/ticket/17/summary')
.parsedUrl();
expect(url.hash).toContain('#!/ticket/17/summary');
});
it(`should check the state of the stowaway ticket is embarked`, async() => { it(`should check the state of the stowaway ticket is embarked`, async() => {
const state = await nightmare const state = await nightmare
.waitToGetProperty(selectors.ticketDescriptor.stateLabelValue, 'innerText'); .waitToGetProperty(selectors.ticketDescriptor.stateLabelValue, 'innerText');
@ -144,15 +125,6 @@ describe('Ticket descriptor path', () => {
expect(state).toEqual('State Embarcando'); expect(state).toEqual('State Embarcando');
}); });
it(`should navigate to the ship ticket using the descriptors ship button`, async() => {
const url = await nightmare
.waitToClick(selectors.ticketDescriptor.shipButton)
.waitForURL('#!/ticket/16/summary')
.parsedUrl();
expect(url.hash).toContain('#!/ticket/16/summary');
});
it(`should navigate back to the added ticket using the descriptors ship button`, async() => { it(`should navigate back to the added ticket using the descriptors ship button`, async() => {
const url = await nightmare const url = await nightmare
.waitToClick(selectors.ticketDescriptor.closeStowawayDialog) .waitToClick(selectors.ticketDescriptor.closeStowawayDialog)

View File

@ -49,7 +49,7 @@ xdescribe('Route basic Data path', () => {
it('should count how many tickets are in route', async() => { it('should count how many tickets are in route', async() => {
const result = await nightmare const result = await nightmare
.countElement('vn-route-tickets vn-textfield[model="ticket.priority"]'); .countElement('vn-route-tickets vn-textfield[ng-model="ticket.priority"]');
expect(result).toEqual(11); expect(result).toEqual(11);
}); });
@ -74,7 +74,7 @@ xdescribe('Route basic Data path', () => {
it('should now count how many tickets are in route to find one less', async() => { it('should now count how many tickets are in route to find one less', async() => {
const result = await nightmare const result = await nightmare
.countElement('vn-route-tickets vn-textfield[model="ticket.priority"]'); .countElement('vn-route-tickets vn-textfield[ng-model="ticket.priority"]');
expect(result).toEqual(9); expect(result).toEqual(9);
}); });

View File

@ -1,27 +0,0 @@
<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)"/>
<div class="icons">
<vn-icon
ng-show="!$ctrl.disabled"
icon="clear"
class="clear"
ng-click="$ctrl.onClearClick($event)"
translate-attr="{title: 'Clear'}">
</vn-icon>
</div>
<label class="mdl-textfield__label">
<span translate>{{::$ctrl.label}}</span>
<span translate ng-show="::$ctrl.required">*</span>
</label>
</div>
</div>
<vn-drop-down
vn-id="drop-down"
on-select="$ctrl.onDropDownSelect(item)"
on-data-ready="$ctrl.onDataReady()">
</vn-drop-down>

View File

@ -0,0 +1,52 @@
<div
class="container"
ng-click="$ctrl.onContainerMouseDown($event)"
ng-keydown="$ctrl.onContainerKeyDown($event)">
<div
ng-transclude="prepend"
class="prepend">
</div>
<div class="infix">
<div class="fix prefix"></div>
<div class="control">
<input
type="button">
</input>
</div>
<div class="fix suffix"></div>
<label>
<span translate>{{::$ctrl.label}}</span>
<span class="required">*</span>
</label>
</div>
<div class="icons pre">
<vn-icon
icon="clear"
translate-attr="{title: 'Clear'}"
ng-click="$ctrl.onClear($event)">
</vn-icon>
<vn-icon
ng-if="::$ctrl.info"
icon="info_outline"
vn-tooltip="{{::$ctrl.info}}">
</vn-icon>
</div>
<div
ng-transclude="append"
class="append">
</div>
<div class="icons post">
<vn-icon
icon="arrow_drop_down">
</vn-icon>
</div>
<div class="underline blur"></div>
<div class="underline focus"></div>
</div>
<div class="hint"></div>
<vn-drop-down
vn-id="drop-down"
on-select="$ctrl.onDropDownSelect(item)"
on-data-ready="$ctrl.onDataReady()"
on-close="$ctrl.focus()">
</vn-drop-down>

View File

@ -1,5 +1,5 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Input from '../../lib/input'; import Field from '../field';
import assignProps from '../../lib/assign-props'; import assignProps from '../../lib/assign-props';
import {mergeWhere} from 'vn-loopback/util/filter'; import {mergeWhere} from 'vn-loopback/util/filter';
import './style.scss'; import './style.scss';
@ -16,23 +16,18 @@ import './style.scss';
* *
* @event change Thrown when value is changed * @event change Thrown when value is changed
*/ */
export default class Autocomplete extends Input { export default class Autocomplete extends Field {
constructor($element, $scope, $http, $transclude, $translate, $interpolate) { constructor($element, $scope, $compile, $http, $transclude, $translate, $interpolate) {
super($element, $scope); super($element, $scope, $compile);
this.$http = $http; Object.assign(this, {
this.$interpolate = $interpolate; $http,
this.$transclude = $transclude; $interpolate,
this.$translate = $translate; $transclude,
this._field = undefined; $translate
});
this._selection = null; this._selection = null;
this.readonly = true; this.input = this.element.querySelector('input');
this.form = null;
this.input = this.element.querySelector('.mdl-textfield__input');
componentHandler.upgradeElement(
this.element.querySelector('.mdl-textfield'));
this.registerEvents();
} }
$postLink() { $postLink() {
@ -44,12 +39,16 @@ export default class Autocomplete extends Input {
} }
/** /**
* Registers all event emitters * @type {any} The autocomplete value.
*/ */
registerEvents() { get field() {
this.input.addEventListener('focus', event => { return super.field;
this.emit('focus', {event}); }
});
set field(value) {
super.field = value;
this.refreshSelection();
this.emit('change', {value});
} }
get model() { get model() {
@ -83,20 +82,6 @@ export default class Autocomplete extends Input {
Object.assign(this.$.dropDown, props); Object.assign(this.$.dropDown, props);
} }
/**
* @type {any} The autocomplete value.
*/
get field() {
return this._field;
}
set field(value) {
this._field = value;
this.refreshSelection();
this.emit('change', {value});
}
/** /**
* @type {Object} The selected data object, you can use this property * @type {Object} The selected data object, you can use this property
* to prevent requests to display the initial value. * to prevent requests to display the initial value.
@ -136,7 +121,7 @@ export default class Autocomplete extends Input {
return; return;
const selection = this.fetchSelection(); const selection = this.fetchSelection();
if (!this.selection)
this.selection = selection; this.selection = selection;
} }
@ -216,39 +201,21 @@ export default class Autocomplete extends Input {
if (this.translateFields.indexOf(this.showField) > -1) if (this.translateFields.indexOf(this.showField) > -1)
this.input.value = this.$translate.instant(display); this.input.value = this.$translate.instant(display);
} }
this.mdlUpdate();
}
mdlUpdate() {
let field = this.element.querySelector('.mdl-textfield');
let mdlField = field.MaterialTextfield;
if (mdlField) mdlField.updateClasses_();
}
setValue(value) {
this.field = value;
if (this.form) this.form.$setDirty();
} }
onDropDownSelect(item) { onDropDownSelect(item) {
const value = item[this.valueField]; const value = item[this.valueField];
this.selection = item; this.selection = item;
this.setValue(value); this.field = value;
} }
onClearClick(event) { onContainerKeyDown(event) {
event.preventDefault(); if (event.defaultPrevented) return;
this.setValue(null);
}
onKeyDown(event) { switch (event.code) {
// if (event.defaultPrevented) return; case 'ArrowUp':
case 'ArrowDown':
switch (event.keyCode) { case 'Enter':
case 38: // Up
case 40: // Down
case 13: // Enter
this.showDropDown(); this.showDropDown();
break; break;
default: default:
@ -261,7 +228,8 @@ export default class Autocomplete extends Input {
event.preventDefault(); event.preventDefault();
} }
onMouseDown(event) { onContainerMouseDown(event) {
if (event.defaultPrevented) return;
event.preventDefault(); event.preventDefault();
this.showDropDown(); this.showDropDown();
} }
@ -293,7 +261,7 @@ export default class Autocomplete extends Input {
showDropDown(search) { showDropDown(search) {
this.assignDropdownProps(); this.assignDropdownProps();
this.$.dropDown.show(this.input, search); this.$.dropDown.show(this.container, search);
} }
get fetchFunction() { get fetchFunction() {
@ -307,16 +275,12 @@ export default class Autocomplete extends Input {
this.refreshSelection(); this.refreshSelection();
} }
} }
Autocomplete.$inject = ['$element', '$scope', '$http', '$transclude', '$translate', '$interpolate']; Autocomplete.$inject = ['$element', '$scope', '$compile', '$http', '$transclude', '$translate', '$interpolate'];
ngModule.component('vnAutocomplete', { ngModule.vnComponent('vnAutocomplete', {
template: require('./autocomplete.html'), template: require('./index.html'),
controller: Autocomplete, controller: Autocomplete,
bindings: { bindings: {
label: '@',
field: '=?',
disabled: '<?',
required: '@?',
showField: '@?', showField: '@?',
valueField: '@?', valueField: '@?',
initialData: '<?', initialData: '<?',
@ -336,8 +300,5 @@ ngModule.component('vnAutocomplete', {
}, },
transclude: { transclude: {
tplItem: '?tplItem' tplItem: '?tplItem'
},
require: {
form: '?^form'
} }
}); });

View File

@ -1,66 +1,23 @@
@import "effects"; @import "effects";
vn-autocomplete { vn-autocomplete.vn-field {
overflow: hidden; overflow: hidden;
& > div > .mdl-textfield { & > .container {
position: relative; cursor: pointer;
width: 100%;
& > .infix > .control {
overflow: hidden;
& > input { & > input {
cursor: pointer; cursor: pointer;
height: 30px;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
} text-align: left;
& > .icons { padding-left: 0;
display: none; padding-right: 0;
position: absolute;
right: 0;
top: 1.3em;
height: 1em;
color: $color-font-secondary;
border-radius: .2em;
& > vn-icon {
cursor: pointer;
font-size: 18px;
&:hover {
color: $color-font;
} }
} }
} }
&:hover > .icons,
& > input:focus + .icons {
display: block;
}
}
label span:nth-child(2) {
color: $color-alert
}
}
ul.vn-autocomplete {
list-style-type: none;
padding: 1em;
margin: 0;
padding: 0;
overflow: auto;
max-height: 300px;
li {
@extend %clickable;
display: block;
padding: .8em;
margin: 0;
&.load-more {
color: $color-main;
font-family: vn-font-bold;
padding: .4em .8em;
}
}
} }

View File

@ -12,13 +12,13 @@ import './style.scss';
*/ */
export default class Check extends Toggle { export default class Check extends Toggle {
set field(value) { set field(value) {
this._field = value; super.field = value;
this.element.classList.toggle('checked', Boolean(value)); this.element.classList.toggle('checked', Boolean(value));
this.indeterminate = Boolean(value == null && this.tripleState); this.indeterminate = Boolean(value == null && this.tripleState);
} }
get field() { get field() {
return this._field; return super.field;
} }
set checked(value) { set checked(value) {
@ -64,14 +64,10 @@ export default class Check extends Toggle {
} }
} }
ngModule.component('vnCheck', { ngModule.vnComponent('vnCheck', {
template: require('./index.html'), template: require('./index.html'),
controller: Check, controller: Check,
bindings: { bindings: {
label: '@?',
field: '=?',
disabled: '<?',
checked: '<?',
tripleState: '<?', tripleState: '<?',
indeterminate: '<?', indeterminate: '<?',
info: '@?' info: '@?'

View File

@ -8,7 +8,7 @@ describe('Component vnCheck', () => {
})); }));
beforeEach(inject(($compile, $rootScope) => { beforeEach(inject(($compile, $rootScope) => {
$element = $compile(`<vn-check></vn-check`)($rootScope); $element = $compile(`<vn-check></vn-check>`)($rootScope);
$ctrl = $element.controller('vnCheck'); $ctrl = $element.controller('vnCheck');
element = $element[0]; element = $element[0];
})); }));

View File

@ -1,21 +0,0 @@
<div
class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label"
ng-focus="$ctrl.hasFocus = true"
ng-blur="$ctrl.hasFocus = false"
ng-mouseenter="$ctrl.hasMouseIn = true"
ng-mouseleave="$ctrl.hasMouseIn = false">
<input type="text"
class="mdl-textfield__input"
name="{{::$ctrl.name}}"
ng-disabled="$ctrl.disabled"
rule="{{::$ctrl.rule}}"/>
<div class="mdl-chip__action">
<i
class="material-icons pointer"
ng-show="!$ctrl.disabled && $ctrl.model && ($ctrl.hasFocus || $ctrl.hasMouseIn)"
ng-click="$ctrl.onClear()">
clear
</i>
</div>
<label class="mdl-textfield__label" translate>{{$ctrl.label}}</label>
</div>

View File

@ -1,107 +0,0 @@
import ngModule from '../../module';
import Component from '../../lib/component';
import {Flatpickr} from '../../vendor';
import './style.scss';
class DatePicker extends Component {
constructor($element, $scope, $translate, $attrs) {
super($element, $scope);
this.input = $element[0].querySelector('input');
this.$translate = $translate;
this.$attrs = $attrs;
this._model = undefined;
this.dateValue = undefined;
this.hasMouseIn = false;
let locale = this.$translate.use();
this.defaultOptions = {
locale: locale,
dateFormat: locale == 'es' ? 'd-m-Y' : 'Y-m-d',
enableTime: false,
disableMobile: true,
onValueUpdate: () => this.onValueUpdate()
};
this.userOptions = {};
this._iniOptions = this.defaultOptions;
componentHandler.upgradeElement($element[0].firstChild);
this.vp = new Flatpickr(this.input, this._iniOptions);
}
onValueUpdate() {
if (this.vp.selectedDates.length) {
let date = this.vp.selectedDates[0];
let offset = date.getTimezoneOffset() * 60000;
date.setTime(date.getTime() - offset);
this._model = date;
} else
this.model = null;
this.$.$apply();
}
set iniOptions(value) {
this.userOptions = value;
let options = Object.assign({}, this.defaultOptions, value);
this._iniOptions = options;
// TODO: When some properties change Flatpickr doesn't refresh the view
// for (let option in options)
// this.vp.set(option, options[option]);
if (this.vp) this.vp.destroy();
this.vp = new Flatpickr(this.input, this._iniOptions);
this.vp.setDate(this.dateValue);
this.mdlUpdate();
}
get iniOptions() {
return this.userOptions;
}
get model() {
return this._model;
}
set model(value) {
this._model = value;
this.dateValue = value;
let date;
if (value && this.iniOptions.enableTime) {
date = new Date(value);
let offset = date.getTimezoneOffset() * 60000;
date.setTime(date.getTime() + offset);
} else
date = value;
this.vp.setDate(date);
this.mdlUpdate();
}
onClear() {
this.model = null;
}
mdlUpdate() {
let mdlField = this.element.firstChild.MaterialTextfield;
if (mdlField)
mdlField.updateClasses_();
}
$onDestroy() {
this.vp.destroy();
this.dateValue = undefined;
}
}
DatePicker.$inject = ['$element', '$scope', '$translate', '$attrs'];
ngModule.component('vnDatePicker', {
template: require('./date-picker.html'),
bindings: {
iniOptions: '<?',
model: '=',
label: '@?',
name: '@?',
disabled: '<?',
rule: '<?',
isLocale: '<?'
},
controller: DatePicker
});

View File

@ -1,37 +0,0 @@
describe('Component vnDatePicker', () => {
let controller;
let $attrs;
let $element;
let today = new Date();
today.setHours(0, 0, 0, 0);
beforeEach(angular.mock.module('vnCore', $translateProvider => {
$translateProvider.translations('en', {});
}));
beforeEach(angular.mock.inject(($componentController, $translate) => {
$attrs = {};
$element = angular.element(`<vn-date-picker><div><input type="text" class="mdl-textfield__input" name="MyName" ng-disabled="$ctrl.disabled" rule=""></input></div></vn-date-picker>`);
controller = $componentController('vnDatePicker', {$element, $attrs, $translate});
}));
describe('onValueUpdate() while date is selected', () => {
it(`should store the selected date in the controller`, () => {
controller.vp = {selectedDates: [today]};
controller.isLocale = true;
controller.onValueUpdate();
expect(controller._model).toEqual(today);
});
it(`should format the date`, () => {
controller.vp = {selectedDates: [today], destroy: () => {}};
controller.isLocale = undefined;
controller._iniOptions.enableTime = undefined;
controller.onValueUpdate();
expect(controller._model).toEqual(today);
});
});
});

View File

@ -0,0 +1,99 @@
import ngModule from '../../module';
import Field from '../field';
import {Flatpickr} from '../../vendor';
import './style.scss';
class DatePicker extends Field {
constructor($element, $scope, $compile, $translate) {
super($element, $scope, $compile);
this.$translate = $translate;
this.input = $compile(`<input type="text"></input>`)($scope)[0];
this.initPicker();
}
get field() {
return super.field;
}
set field(value) {
super.field = value;
let date = value;
if (date && !(date instanceof Date))
date = new Date(date);
this.picker.setDate(fixDate(date));
}
set options(value) {
let selectedDates = this.picker.selectedDates || [];
this._options = value;
this.initPicker();
this.picker.setDate(selectedDates[0]);
}
get options() {
return this._options;
}
initPicker() {
let locale = this.$translate.use();
let format = locale == 'es' ? 'd-m-Y' : 'Y-m-d';
let options = this.options || {};
let defaultOptions = {
locale: locale,
dateFormat: format,
enableTime: false,
disableMobile: true,
onValueUpdate: () => this.onValueUpdate()
};
if (options.enableTime) {
Object.assign(defaultOptions, {
dateFormat: `${format} h:i`,
time_24hr: true
});
}
let mergedOptions = Object.assign({},
defaultOptions,
options
);
if (this.picker) this.picker.destroy();
this.picker = new Flatpickr(this.input, mergedOptions);
}
onValueUpdate() {
let date = null;
if (this.picker.selectedDates.length)
date = this.picker.selectedDates[0];
super.field = fixDate(date, -1);
this.$.$applyAsync();
}
$onDestroy() {
this.picker.destroy();
}
}
DatePicker.$inject = ['$element', '$scope', '$compile', '$translate'];
ngModule.vnComponent('vnDatePicker', {
controller: DatePicker,
bindings: {
options: '<?'
}
});
function fixDate(date, mult = 1) {
if (date) {
let offset = date.getTimezoneOffset() * 60000;
date.setTime(date.getTime() + (offset * mult));
}
return date;
}

View File

@ -0,0 +1,45 @@
describe('Component vnDatePicker', () => {
let $filter;
let $element;
let $ctrl;
let today;
beforeEach(angular.mock.module('vnCore', $translateProvider => {
$translateProvider.translations('en', {});
}));
beforeEach(angular.mock.inject(($compile, $rootScope, _$filter_) => {
$filter = _$filter_;
$element = $compile(`<vn-date-picker></vn-date-picker>`)($rootScope);
$ctrl = $element.controller('vnDatePicker');
today = new Date();
today.setUTCHours(0, 0, 0, 0);
}));
afterEach(() => {
$element.remove();
});
describe('field() setter', () => {
it(`should display the formated the date`, () => {
$ctrl.field = today;
let displayed = $filter('dateTime')(today, 'yyyy-MM-dd');
expect($ctrl.value).toEqual(displayed);
});
});
describe('options() setter', () => {
it(`should display the date with the new format`, () => {
$ctrl.options = {dateFormat: 'Y-m'};
$ctrl.field = today;
let displayed = $filter('dateTime')(today, 'yyyy-MM');
expect($ctrl.value).toEqual(displayed);
});
});
});

View File

@ -1,24 +1,5 @@
@import "variables"; @import "variables";
vn-date-picker {
.mdl-chip__action {
position: absolute;
width: auto;
top: 0px;
right: -6px;
margin: 22px 0px;
background-color: white;
}
.mdl-textfield {
width: 100%;
}
.material-icons {
font-size: 18px;
float: right;
margin-right: 5px;
}
}
.flatpickr-months .flatpickr-month, .flatpickr-months .flatpickr-month,
.flatpickr-weekdays, .flatpickr-weekdays,
span.flatpickr-weekday { span.flatpickr-weekday {

View File

@ -31,7 +31,7 @@
tpl-body { tpl-body {
display: block; display: block;
width: 20em; min-width: 20em;
} }
& > button.close { & > button.close {
@extend %clickable; @extend %clickable;

View File

@ -6,10 +6,10 @@
<div ng-show="$ctrl.showFilter" class="filter"> <div ng-show="$ctrl.showFilter" class="filter">
<vn-textfield <vn-textfield
vn-id="input" vn-id="input"
model="$ctrl.search" ng-model="$ctrl.search"
class="search" class="dense search"
ng-blur="$ctrl.onFocusOut()" ng-blur="$ctrl.onFocusOut()"
label="Search"> placeholder="{{::'Search' | translate}}">
</vn-textfield> </vn-textfield>
</div> </div>
<div vn-id="list" class="list" tabindex="-1"> <div vn-id="list" class="list" tabindex="-1">

View File

@ -106,8 +106,8 @@ export default class DropDown extends Component {
*/ */
show(parent, search) { show(parent, search) {
this._activeOption = -1; this._activeOption = -1;
this.search = search;
this.$.popover.show(parent || this.parent); this.$.popover.show(parent || this.parent);
this.search = search;
this.buildList(); this.buildList();
} }
@ -194,10 +194,12 @@ export default class DropDown extends Component {
this.document.addEventListener('keydown', this.docKeyDownHandler); this.document.addEventListener('keydown', this.docKeyDownHandler);
this.$.list.scrollTop = 0; this.$.list.scrollTop = 0;
this.$.input.focus(); this.$.input.focus();
this.emit('open');
} }
onClose() { onClose() {
this.document.removeEventListener('keydown', this.docKeyDownHandler); this.document.removeEventListener('keydown', this.docKeyDownHandler);
this.emit('close');
} }
onClearClick() { onClearClick() {

View File

@ -13,18 +13,7 @@
display: block; display: block;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
border: none; padding: $spacing-sm;
font-size: inherit;
padding: .6em;
margin: 0!important;
&.not-empty label{
display: none;
}
& .selected label {
font-size: inherit;
bottom: 2px;
color: $color-font-secondary;
}
} }
& > vn-icon[icon=clear] { & > vn-icon[icon=clear] {
display: none; display: none;

View File

@ -5,20 +5,18 @@
</div> </div>
<div class="infix"> <div class="infix">
<div class="fix prefix"></div> <div class="fix prefix"></div>
<div class="control"> <div class="control"></div>
<input type="text" ng-model="$ctrl.field"/>
</div>
<div class="fix suffix"></div> <div class="fix suffix"></div>
<label> <label>
<span translate>{{::$ctrl.label}}</span> <span translate>{{::$ctrl.label}}</span>
<span class="required">*</span> <span class="required">*</span>
</label> </label>
</div> </div>
<div class="icons"> <div class="icons pre">
<vn-icon <vn-icon
icon="clear" icon="clear"
translate-attr="{title: 'Clear'}" translate-attr="{title: 'Clear'}"
ng-click="$ctrl.onClear()"> ng-click="$ctrl.onClear($event)">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-if="::$ctrl.info" ng-if="::$ctrl.info"
@ -30,6 +28,8 @@
ng-transclude="append" ng-transclude="append"
class="append"> class="append">
</div> </div>
<div class="icons post">
</div>
<div class="underline blur"></div> <div class="underline blur"></div>
<div class="underline focus"></div> <div class="underline focus"></div>
</div> </div>

View File

@ -1,37 +1,63 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Component from '../../lib/component'; import FormInput from '../form-input';
import './style.scss'; import './style.scss';
export default class Field extends Component { export default class Field extends FormInput {
constructor($element, $scope) { constructor($element, $scope, $compile) {
super($element, $scope); super($element, $scope);
this._value = undefined; this.$compile = $compile;
this.prefix = null; this.prefix = null;
this.suffix = null; this.suffix = null;
this.input = this.element.querySelector('input'); this.control = this.element.querySelector('.control');
this.classList = this.element.classList; this.classList = this.element.classList;
this.classList.add('vn-field'); this.classList.add('vn-field');
this.element.addEventListener('click', e => this.onClick(e));
this.element.addEventListener('focusin', this.container = this.element.querySelector('.container');
() => this.onFocus(true)); this.container.addEventListener('mousedown', e => this.onMouseDown(e));
this.element.addEventListener('focusout',
() => this.onFocus(false));
this.element.addEventListener('click',
() => this.onClick());
} }
$onInit() { $onInit() {
super.$onInit();
if (this.info) this.classList.add('has-icons'); if (this.info) this.classList.add('has-icons');
this.input.addEventListener('focus', () => this.onFocus(true));
this.input.addEventListener('blur', () => this.onFocus(false));
this.input.addEventListener('change', e => {
this.emit('change', {event: e});
});
} }
set field(value) { set field(value) {
this._field = value; if (value === this.field) return;
super.field = value;
this.classList.toggle('not-empty', value != null && value !== ''); this.classList.toggle('not-empty', value != null && value !== '');
this.validateValue();
} }
get field() { get field() {
return this._field; return super.field;
}
set input(value) {
if (this.input) this.control.removeChild(this.input);
this._input = value;
this.control.appendChild(value);
}
get input() {
return this._input;
}
set value(value) {
this.field = value;
}
get value() {
return this.input.value;
} }
set type(value) { set type(value) {
@ -42,6 +68,30 @@ export default class Field extends Component {
return this.input.type; return this.input.type;
} }
set name(value) {
this.input.name = value;
}
get name() {
return this.input.name;
}
set placeholder(value) {
this.input.placeholder = value;
}
get placeholder() {
return this.input.placeholder;
}
set tabIndex(value) {
this.input.tabIndex = value;
}
get tabIndex() {
return this.input.tabIndex;
}
set disabled(value) { set disabled(value) {
this._disabled = boolTag(value); this._disabled = boolTag(value);
this.input.disabled = this._disabled; this.input.disabled = this._disabled;
@ -100,20 +150,28 @@ export default class Field extends Component {
} }
set error(value) { set error(value) {
if (value === this.error) return;
this._error = value; this._error = value;
this.refreshHint(); this.refreshHint();
this.classList.toggle('invalid', Boolean(value));
} }
get error() { get error() {
return this._error; return this._error;
} }
get shownError() {
return this.error || this.inputError || null;
}
refreshHint() { refreshHint() {
let hint = this.error || this.hint || ''; let error = this.shownError;
let hint = error || this.hint;
let hintEl = this.element.querySelector('.hint'); let hintEl = this.element.querySelector('.hint');
hintEl.innerText = hint; hintEl.innerText = hint || '';
hintEl.classList.toggle('filled', Boolean(hint)); hintEl.classList.toggle('filled', Boolean(hint));
this.classList.toggle('invalid', Boolean(error));
} }
refreshFix(selector, text) { refreshFix(selector, text) {
@ -123,16 +181,27 @@ export default class Field extends Component {
} }
onClick() { onClick() {
if (event.defaultPrevented) return;
event.preventDefault();
if (this.input !== document.activeElement) if (this.input !== document.activeElement)
this.input.focus(); this.focus();
}
onMouseDown(event) {
if (event.target == this.input) return;
event.preventDefault();
this.focus();
} }
onFocus(hasFocus) { onFocus(hasFocus) {
this.classList.toggle('focused', hasFocus); this.classList.toggle('focused', hasFocus);
} }
onClear() { onClear(event) {
this.input.value = ''; if (event.defaultPrevented) return;
event.preventDefault();
this.field = null;
this.input.dispatchEvent(new Event('change')); this.input.dispatchEvent(new Event('change'));
} }
@ -143,10 +212,28 @@ export default class Field extends Component {
select() { select() {
this.input.select(); this.input.select();
} }
}
Field.$inject = ['$element', '$scope'];
ngModule.component('vnField', { buildInput(type) {
let template = `<input type="${type}" ng-model="$ctrl.field"></input>`;
this.input = this.$compile(template)(this.$)[0];
}
/**
* If input value is invalid, sets the error message as hint.
*/
validateValue() {
let error = this.input.checkValidity()
? null
: this.input.validationMessage;
if (error === this.inputError) return;
this.inputError = error;
this.refreshHint();
}
}
Field.$inject = ['$element', '$scope', '$compile'];
ngModule.vnComponent('vnField', {
template: require('./index.html'), template: require('./index.html'),
transclude: { transclude: {
prepend: '?prepend', prepend: '?prepend',
@ -154,18 +241,17 @@ ngModule.component('vnField', {
}, },
controller: Field, controller: Field,
bindings: { bindings: {
field: '=?',
label: '@?',
name: '@?',
type: '@?', type: '@?',
placeholder: '@?',
value: '=?',
info: '@?', info: '@?',
disabled: '@?', required: '<?',
readonly: '@?',
required: '@?',
prefix: '@?', prefix: '@?',
suffix: '@?', suffix: '@?',
hint: '@?', hint: '@?',
error: '<?' error: '<?',
tabIndex: '<?',
rule: '@?'
} }
}); });

View File

@ -8,7 +8,6 @@
display: flex; display: flex;
align-items: stretch; align-items: stretch;
position: relative; position: relative;
height: 56px;
& > .infix { & > .infix {
position: relative; position: relative;
@ -50,8 +49,8 @@
& > .control { & > .control {
height: 100%; height: 100%;
flex: auto; flex: auto;
}
& > .control > input { & > * {
padding-top: 24px; padding-top: 24px;
padding-bottom: 8px; padding-bottom: 8px;
height: inherit; height: inherit;
@ -64,10 +63,13 @@
background: 0; background: 0;
color: inherit; color: inherit;
box-sizing: border-box; box-sizing: border-box;
min-height: 56px;
&[type=time], &[type=time],
&[type=date] { &[type=date] {
clip-path: inset(0 20px 0 0); clip-path: inset(0 20px 0 0);
opacity: 0;
transition: opacity 200ms ease-in-out;
} }
&[type=number] { &[type=number] {
-moz-appearance: textfield; -moz-appearance: textfield;
@ -83,6 +85,7 @@
} }
} }
} }
}
& > .prepend, & > .prepend,
& > .append, & > .append,
& > .icons { & > .icons {
@ -102,13 +105,17 @@
& > .prepend > prepend { & > .prepend > prepend {
padding-right: 12px; padding-right: 12px;
} }
& > .append > append { & > .icons {
&.pre {
padding-left: 12px; padding-left: 12px;
} }
& > .icons > vn-icon[icon=clear] { & > vn-icon {
display: none;
cursor: pointer; cursor: pointer;
} }
& > vn-icon[icon=clear] {
display: none;
}
}
& > .underline { & > .underline {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
@ -131,30 +138,58 @@
} }
} }
} }
&.not-empty > .container, &.dense {
&.focused > .container { & > .hint {
& > .infix { display: none;
& > .fix {
opacity: 1;
} }
& > .container > .infix {
& > label {
top: 8px;
}
& > .control > * {
padding-top: 8px;
min-height: 40px;
}
}
&.not-empty,
&.focused,
&.invalid {
& > .container > .infix > label {
top: 0;
line-height: 8px;
}
}
}
&.not-empty,
&.focused,
&.invalid {
& > .container > .infix {
& > label { & > label {
top: 5px; top: 5px;
color: $color-main; color: $color-main;
padding: 0; padding: 0;
font-size: 12px; font-size: 12px;
} }
& > .control > * {
&[type=time],
&[type=date] {
opacity: 1;
}
}
} }
} }
&.has-icons, &.has-icons,
&.not-empty:hover, &.not-empty:hover,
&.not-empty.focused { &.not-empty.focused {
& > .container > .append > append { & > .container {
& > .append > append {
padding-left: 0; padding-left: 0;
} }
& > .container > .icons { & > .icons.pre {
padding-left: 12px; padding-left: 12px;
} }
} }
}
&:not(.disabled):not(.readonly) { &:not(.disabled):not(.readonly) {
&.focused > .container > .underline.focus { &.focused > .container > .underline.focus {
left: 0; left: 0;
@ -170,14 +205,8 @@
} }
} }
} }
&:not(.not-empty):not(.focused) > .container > .infix > .control > input {
&[type=time],
&[type=date] {
opacity: 0;
}
}
&.readonly > .container { &.readonly > .container {
& > .infix > .control > input { & > .infix > .control > * {
caret-color: transparent; caret-color: transparent;
} }
& > .underline.blur { & > .underline.blur {
@ -186,11 +215,11 @@
} }
& > .hint { & > .hint {
z-index: -1; z-index: -1;
padding-top: 8px; padding: 4px 0;
height: 20px; height: 12px;
color: rgba(0, 0, 0, .4); color: rgba(0, 0, 0, .4);
font-size: 12px; font-size: 12px;
transform: translateY(-28px); transform: translateY(-12px);
transition-property: opacity, transform, color; transition-property: opacity, transform, color;
transition-duration: 200ms; transition-duration: 200ms;
transition-timing-function: ease-in-out; transition-timing-function: ease-in-out;
@ -220,8 +249,3 @@
} }
} }
} }
vn-table {
.vn-field {
margin: 0;
}
}

View File

@ -0,0 +1,58 @@
import ngModule from '../../module';
import Component from '../../lib/component';
/**
* Base component for form inputs.
*
* @property {String} label Label to display along the component
* @property {any} field The value with which the element is linked
* @property {Boolean} disabled Put component in disabled mode
*/
export default class FormInput extends Component {
$onInit() {
// XXX: Compatibility with old inputs
let attrs = this.$element[0].attributes;
if (!this.name && attrs['ng-model']) {
let split = attrs['ng-model'].nodeValue.split('.');
this.name = split[split.length - 1];
}
if (!this.ngModel) return;
this.ngModel.$render = () => {
this.field = this.ngModel.$viewValue;
};
}
set field(value) {
this._field = value;
if (this.ngModel)
this.ngModel.$setViewValue(value);
}
get field() {
return this._field;
}
set name(value) {
this.element.setAttribute('name', value);
}
get name() {
return this.element.getAttribute('name');
}
}
ngModule.vnComponent('vnFormInput', {
controller: FormInput,
bindings: {
label: '@?',
field: '=?',
name: '@?',
disabled: '<?',
readonly: '<?'
},
require: {
ngModel: '?ngModel'
}
});

View File

@ -1,9 +1,11 @@
@import "variables"; @import "effects";
vn-icon-button { vn-icon-button {
@extend %clickable-light;
outline: 0; outline: 0;
color: $color-main; color: $color-main;
display: inline-block; display: inline-flex;
align-items: center;
font-size: 18pt; font-size: 18pt;
padding: .25em; padding: .25em;

View File

@ -13,13 +13,10 @@ import './tooltip/tooltip';
import './icon-menu/icon-menu'; import './icon-menu/icon-menu';
import './button-menu/button-menu'; import './button-menu/button-menu';
import './popover/popover'; import './popover/popover';
import './autocomplete/autocomplete';
import './drop-down/drop-down'; import './drop-down/drop-down';
import './menu/menu'; import './menu/menu';
import './multi-check/multi-check'; import './multi-check/multi-check';
import './date-picker/date-picker';
import './button/button'; import './button/button';
import './textarea/textarea';
import './icon-button/icon-button'; import './icon-button/icon-button';
import './submit/submit'; import './submit/submit';
import './card/card'; import './card/card';
@ -29,19 +26,22 @@ import './label-value/label-value';
import './pagination/pagination'; import './pagination/pagination';
import './searchbar/searchbar'; import './searchbar/searchbar';
import './scroll-up/scroll-up'; import './scroll-up/scroll-up';
import './input-range'; import './autocomplete';
import './calendar'; import './calendar';
import './check'; import './check';
import './chip'; import './chip';
import './color-legend'; import './color-legend';
import './data-viewer'; import './data-viewer';
import './date-picker';
import './field'; import './field';
import './input-number'; import './input-number';
import './input-range';
import './input-time'; import './input-time';
import './input-file'; import './input-file';
import './list'; import './list';
import './radio'; import './radio';
import './table'; import './table';
import './td-editable'; import './td-editable';
import './textarea';
import './th'; import './th';
import './treeview'; import './treeview';

View File

@ -3,15 +3,13 @@ import Input from '../../lib/input';
import './style.scss'; import './style.scss';
export default class InputFile extends Input { export default class InputFile extends Input {
constructor($element, $scope, $attrs, vnTemplate) { constructor($element, $scope) {
super($element, $scope); super($element, $scope);
this.element = $element[0]; this.element = $element[0];
this.hasFocus = false; this.hasFocus = false;
this._multiple = false; this._multiple = false;
this._value = 'Select a file'; this._value = 'Select a file';
vnTemplate.normalizeInputAttrs($attrs);
this.registerEvents(); this.registerEvents();
} }
@ -108,7 +106,7 @@ export default class InputFile extends Input {
} }
} }
InputFile.$inject = ['$element', '$scope', '$attrs', 'vnTemplate']; InputFile.$inject = ['$element', '$scope'];
ngModule.component('vnInputFile', { ngModule.component('vnInputFile', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -14,7 +14,7 @@ describe('Component vnInputFile', () => {
beforeEach(angular.mock.inject(($componentController, $rootScope) => { beforeEach(angular.mock.inject(($componentController, $rootScope) => {
$scope = $rootScope.$new(); $scope = $rootScope.$new();
$attrs = {field: '$ctrl.dms.file'}; $attrs = {field: '$ctrl.dms.file'};
$element = angular.element('<vn-input-file label="File" field="$ctrl.dms.file"><input type="file"><div class="infix"><div class="rightIcons"></div></vn-input-file>'); $element = angular.element('<vn-input-file label="File" ng-model="$ctrl.dms.file"><input type="file"><div class="infix"><div class="rightIcons"></div></vn-input-file>');
controller = $componentController('vnInputFile', {$element, $scope, $attrs, $timeout, $transclude: () => {}}); controller = $componentController('vnInputFile', {$element, $scope, $attrs, $timeout, $transclude: () => {}});
controller.input = $element[0].querySelector('input'); controller.input = $element[0].querySelector('input');
controller.validate = () => {}; controller.validate = () => {};

View File

@ -1,8 +1,6 @@
@import "variables"; @import "variables";
@import '../textfield/style.scss';
vn-input-file { vn-input-file {
@extend vn-textfield;
.value { .value {
color: $color-font-secondary; color: $color-font-secondary;
cursor: pointer; cursor: pointer;
@ -12,4 +10,156 @@ vn-input-file {
input { input {
display: none !important display: none !important
} }
margin: 20px 0;
display: inline-block;
width: 100%;
& > .container {
width: 100%;
position: relative;
padding-bottom: 2px;
display: flex;
& > .textField {
width: 100%;
display: flex;
align-items: center;
position: relative;
padding-top: 4px;
}
}
.leftIcons, .rightIcons, .suffix {
display: flex;
color: $color-font-secondary;
.material-icons {
font-size: 20px !important
}
}
.suffix vn-icon-button {
padding: 0
}
t-left-icons {
padding-right: 0.5em
}
t-right-icons {
padding-left: 0.5em
}
.infix {
position: relative;
display: block;
flex: auto;
width: 100%;
min-width: 0;
}
i.clear {
visibility: hidden;
cursor: pointer;
outline: 0;
&:hover {
color: #222;
}
}
&:hover i.clear {
visibility: visible;
}
i.visible {
visibility: visible;
}
label {
position: absolute;
bottom: 0;
left: 0;
padding: 4px 0!important;
pointer-events: none;
color: $color-font-secondary;
transition-duration: .2s;
transition-timing-function: cubic-bezier(.4,0,.2,1);
}
&.not-empty label{
bottom: 24px;
color: $color-main;
padding: 0;
font-size: 12px;
}
input {
outline: none;
border: none;
font-family: "Helvetica", "Arial", sans-serif;
display: block;
font-size: 16px;
width: 100%;
background: 0 0;
color: inherit;
padding: 4px;
box-sizing: border-box;
border-bottom: 0!important;
&[type=number] {
-moz-appearance: textfield;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
}
&:invalid {
box-shadow: none;
}
}
.underline {
position: absolute;
bottom: 0;
height: 1px;
content: ' ';
pointer-events: none;
width: 100%;
background-color: $color-input-underline;
}
.selected.underline {
background-color: $color-main;
height: 2px;
left: 50%;
width: 0px !important;
transition-duration: 0.2s;
transition-timing-function: cubic-bezier(.4,0,.2,1);
}
div.selected {
&.container{
border-bottom: 0px;
}
label {
bottom: 24px;
color: $color-main;
font-size: 12px;
}
.selected.underline{
left: 0;
width: 100%!important;
}
}
& > div.container > div.textField > div.infix.invalid {
@extend div.selected;
& > span.mdl-textfield__error {
visibility: visible;
}
& > label {
color: #d50000;
}
}
.infix.invalid + .underline {
background-color: #d50000;
}
label span:nth-child(2) {
color: $color-alert
}
} }

View File

@ -1,48 +1,48 @@
<div class="container" <div class="container">
ng-class="{selected: $ctrl.hasFocus}"> <div
<div class="textField"> ng-transclude="prepend"
<div class="leftIcons"> class="prepend">
</div> </div>
<div class="infix"> <div class="infix">
<input <div class="fix prefix"></div>
class="mdl-textfield__input" <div class="control"></div>
type="number" <div class="fix suffix"></div>
name="{{::$ctrl.name}}" <label>
ng-model="$ctrl.value"
vn-validation="{{$ctrl.rule}}"
ng-disabled="$ctrl.disabled"
ng-readonly="$ctrl.readonly"
ng-focus="$ctrl.hasFocus = true"
ng-blur="$ctrl.hasFocus = false"
tabindex="{{$ctrl.input.tabindex}}"/>
<label class="label">
<span translate>{{::$ctrl.label}}</span> <span translate>{{::$ctrl.label}}</span>
<span translate ng-show="::$ctrl.required">*</span> <span class="required">*</span>
</label> </label>
</div> </div>
<div class="underline"></div> <div class="icons pre">
<div class="selected underline"></div> <vn-icon
<div class="suffix"> icon="clear"
<vn-icon-button translate-attr="{title: 'Clear'}"
ng-click="$ctrl.onClear($event)">
</vn-icon>
<vn-icon
ng-if="::$ctrl.info"
icon="info_outline"
vn-tooltip="{{::$ctrl.info}}">
</vn-icon>
</div>
<div
ng-transclude="append"
class="append">
</div>
<div class="icons post">
<vn-icon
ng-if="$ctrl.displayControls" ng-if="$ctrl.displayControls"
icon="remove" icon="remove"
ng-click="$ctrl.stepDown()" ng-click="$ctrl.onStep($event, 'down')"
tabindex="-1"
translate-attr="{title: 'Remove'}"> translate-attr="{title: 'Remove'}">
</vn-icon-button> </vn-icon>
<vn-icon-button <vn-icon
ng-if="$ctrl.displayControls" ng-if="$ctrl.displayControls"
icon="add" icon="add"
ng-click="$ctrl.stepUp()" ng-click="$ctrl.onStep($event, 'up')"
tabindex="-1"
translate-attr="{title: 'Add'}"> translate-attr="{title: 'Add'}">
</vn-icon-button> </vn-icon>
<i class="material-icons"
ng-if="::$ctrl.hasInfo"
vn-tooltip="{{::$ctrl.info}}">
info_outline
</i>
</div>
<div class="rightIcons"></div>
</div> </div>
<div class="underline blur"></div>
<div class="underline focus"></div>
</div> </div>
<div class="hint"></div>

View File

@ -1,48 +1,10 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Input from '../../lib/input'; import Field from '../field';
import './style.scss';
export default class InputNumber extends Input { export default class InputNumber extends Field {
constructor($element, $scope, $attrs, vnTemplate) { constructor($element, $scope, $compile) {
super($element, $scope); super($element, $scope, $compile);
this.displayControls = false; this.buildInput('number');
this.hasFocus = false;
vnTemplate.normalizeInputAttrs($attrs);
this.registerEvents();
}
/**
* Registers all event emitters
*/
registerEvents() {
this.input.addEventListener('change', event => {
this.validateValue();
this.emit('change', {event});
});
}
/**
* Gets current value
*/
get value() {
return this._value;
}
/**
* Sets input value
*
* @param {Number} value - Value
*/
set value(value) {
this.hasValue = !(value === null || value === undefined || value === '');
if (!this.hasOwnProperty('_value') && this.hasValue || value === '')
this.input.value = value;
this._value = value;
this.element.classList.toggle('not-empty', this.hasValue);
this.validateValue();
} }
/** /**
@ -58,7 +20,8 @@ export default class InputNumber extends Input {
* @param {Number} value - Value * @param {Number} value - Value
*/ */
set max(value) { set max(value) {
if (value) this.input.max = value; this.input.max = value;
this.validateValue();
} }
/** /**
@ -74,7 +37,8 @@ export default class InputNumber extends Input {
* @param {Number} value - Value * @param {Number} value - Value
*/ */
set min(value) { set min(value) {
if (value) this.input.min = value; this.input.min = value;
this.validateValue();
} }
/** /**
@ -90,47 +54,38 @@ export default class InputNumber extends Input {
* @param {Number} value - Value * @param {Number} value - Value
*/ */
set step(value) { set step(value) {
if (value) this.input.step = value; this.input.step = value;
this.validateValue();
} }
/**
* Increases the input value
*/
stepUp() { stepUp() {
this.input.stepUp(); this.input.stepUp();
this.input.dispatchEvent(new Event('change'));
} }
/**
* Decreases the input value
*/
stepDown() { stepDown() {
this.input.stepDown(); this.input.stepDown();
}
onStep(event, way) {
if (event.defaultPrevented) return;
event.preventDefault();
if (way == 'up')
this.stepUp();
else
this.stepDown();
this.input.dispatchEvent(new Event('change')); this.input.dispatchEvent(new Event('change'));
} }
} }
InputNumber.$inject = ['$element', '$scope', '$attrs', 'vnTemplate'];
ngModule.component('vnInputNumber', { ngModule.vnComponent('vnInputNumber', {
template: require('./index.html'), template: require('./index.html'),
controller: InputNumber, controller: InputNumber,
transclude: {
leftIcons: '?tLeftIcons',
rightIcons: '?tRightIcons'
},
bindings: { bindings: {
label: '@?',
name: '@?',
disabled: '<?',
required: '@?',
min: '<?', min: '<?',
max: '<?', max: '<?',
step: '<?', step: '<?',
displayControls: '<?', displayControls: '<?'
rule: '@?',
value: '=model',
validate: '&',
onChange: '&',
onClear: '&'
} }
}); });

View File

@ -1,68 +1,69 @@
import './index.js'; import './index.js';
describe('Component vnInputNumber', () => { describe('Component vnInputNumber', () => {
let $scope;
let $attrs;
let $timeout;
let $element; let $element;
let controller; let $ctrl;
beforeEach(angular.mock.module('vnCore', $translateProvider => { beforeEach(angular.mock.module('vnCore', $translateProvider => {
$translateProvider.translations('en', {}); $translateProvider.translations('en', {});
})); }));
beforeEach(angular.mock.inject(($componentController, $rootScope) => { beforeEach(angular.mock.inject(($compile, $rootScope) => {
$scope = $rootScope.$new(); $element = $compile(`<vn-input-number></vn-input-number>`)($rootScope);
$attrs = {field: '$ctrl.client.socialName'}; $ctrl = $element.controller('vnInputNumber');
$element = angular.element('<vn-input-number label="SocialName" field="$ctrl.client.socialName"><input type="number"><div class="infix"><div class="rightIcons"></div></vn-input-number>');
controller = $componentController('vnInputNumber', {$element, $scope, $attrs, $timeout, $transclude: () => {}});
controller.input = $element[0].querySelector('input');
controller.validate = () => {};
})); }));
describe('value() setter', () => { afterEach(() => {
it(`should set a value, add the class 'not-empty' and then call validateValue() method`, () => { $element.remove();
spyOn(controller, 'validateValue');
controller.value = 10;
let classes = controller.element.classList.toString();
expect(classes).toContain('not-empty');
expect(controller.validateValue).toHaveBeenCalledWith();
}); });
it(`should set an empty value, remove the class 'not-empty' and then call validateValue() method`, () => { describe('min() setter', () => {
spyOn(controller, 'validateValue'); it(`should set error property when value is lower than min`, () => {
$ctrl.field = -1;
$ctrl.min = 0;
controller.value = null; // FIXME: Input validation doesn't work with Jest?
// expect($ctrl.shownError).toContain('Please select a value that is no less than 0');
expect($ctrl.shownError).toBeNull();
});
let classes = controller.element.classList.toString(); it(`should unset error property when value is upper than min`, () => {
$ctrl.field = 1;
$ctrl.min = 0;
expect(classes).not.toContain('not-empty'); expect($ctrl.shownError).toBeNull();
expect(controller.validateValue).toHaveBeenCalledWith();
}); });
}); });
describe('validateValue()', () => { describe('max() setter', () => {
it(`should call hasValidValue() and not add the class invalid and validated`, () => { it(`should set error property when value is upper than max`, () => {
controller.input.min = 0; $ctrl.field = 1;
controller.input.value = 10; $ctrl.max = 0;
controller.validateValue(); // FIXME: Input validation doesn't work with Jest?
let classes = controller.element.querySelector('.infix').classList.toString(); // expect($ctrl.shownError).toContain('Please select a value that is no more than 0');
expect($ctrl.shownError).toBeNull();
expect(classes).not.toContain('validated invalid');
}); });
it(`should call hasValidValue() and add the class invalid and validated`, () => { // FIXME: Input validation doesn't work with Jest?
controller.input.min = 0; it(`should unset error property when value is lower than max`, () => {
controller.input.value = -10; $ctrl.field = -1;
$ctrl.min = 0;
controller.validateValue(); expect($ctrl.shownError).toBeNull();
let classes = controller.element.querySelector('.infix').classList.toString(); });
});
expect(classes).toContain('validated invalid'); describe('step() setter', () => {
it(`should increase value when add icon is clicked`, () => {
$ctrl.step = 1;
$ctrl.field = 1;
// FIXME: Doesn't work with Jest?
// $ctrl.stepUp();
// $element[0].querySelector('vn-icon[icon=add]').click();
expect($ctrl.field).toBe(1);
}); });
}); });
}); });

View File

@ -1,16 +0,0 @@
@import "variables";
@import '../textfield/style.scss';
vn-input-number {
@extend vn-textfield;
vn-icon[icon=add],
vn-icon[icon=remove] {
&:not(:hover){
color: $color-font-secondary;
}
i {
user-select: none;
}
}
}

View File

@ -1,26 +0,0 @@
<div class="container"
ng-class="{selected: $ctrl.hasFocus}">
<div class="textField">
<div class="leftIcons" ng-transclude="leftIcons"></div>
<div class="infix">
<input
class="mdl-textfield__input"
type="time"
ng-disabled="$ctrl.disabled"
ng-readonly="$ctrl.readonly"
ng-focus="$ctrl.hasFocus = true"
ng-blur="$ctrl.hasFocus = false"/>
<label class="label" translate>{{::$ctrl.label}}</label>
</div>
<div class="underline"></div>
<div class="selected underline"></div>
<div class="suffix">
<i class="material-icons"
ng-if="$ctrl.hasInfo"
vn-tooltip="{{$ctrl.info}}">
info_outline
</i>
</div>
<div class="rightIcons" ng-transclude="rightIcons"></div>
</div>
</div>

View File

@ -1,80 +1,41 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Input from '../../lib/input'; import Field from '../field';
import './style.scss';
export default class InputTime extends Input { export default class InputTime extends Field {
constructor($element, $scope, $filter) { constructor($element, $scope, $compile, $filter) {
super($element, $scope); super($element, $scope, $compile);
this.$filter = $filter; this.$filter = $filter;
this.registerEvents(); this.input = $compile(`<input type="time"></input>`)($scope)[0];
this.input.addEventListener('change', () => this.onValueUpdate());
} }
registerEvents() { get field() {
this.input.addEventListener('change', event => { return super.field;
this.onTimeChange();
this.emit('change', {event});
});
this.input.addEventListener('focus', event => {
this.emit('focus', {event});
});
} }
/** set field(value) {
* Gets current value
*/
get value() {
return this._value;
}
/**
* Sets input value
*
* @param {Number} value - Value
*/
set value(value) {
this.updateValue(value);
this.input.value = this.$filter('dateTime')(value, 'HH:mm'); this.input.value = this.$filter('dateTime')(value, 'HH:mm');
super.field = value;
} }
updateValue(value) { onValueUpdate() {
this._value = value;
this.element.classList.toggle('not-empty', value != null);
this.validateValue();
}
onTimeChange() {
let date = null; let date = null;
let value = this.input.value; let value = this.input.value;
if (value) { if (value) {
let split = value.split(':').map(i => parseInt(i) || null); let split = value.split(':').map(i => parseInt(i) || null);
date = new Date(this.value || null); date = new Date(this.field || null);
date.setHours(split[0], split[1], 0, 0); date.setHours(split[0], split[1], 0, 0);
} }
this.updateValue(date); super.field = date;
this.$.$applyAsync();
} }
} }
InputTime.$inject = ['$element', '$scope', '$filter']; InputTime.$inject = ['$element', '$scope', '$compile', '$filter'];
ngModule.component('vnInputTime', { ngModule.vnComponent('vnInputTime', {
template: require('./index.html'), controller: InputTime
controller: InputTime,
transclude: {
leftIcons: '?tLeftIcons',
rightIcons: '?tRightIcons'
},
bindings: {
label: '@?',
disabled: '<?',
readonly: '<?',
rule: '@?',
value: '=model',
vnTabIndex: '@?',
onChange: '&',
onClear: '&'
}
}); });

View File

@ -1,43 +1,32 @@
import './index.js'; import './index.js';
describe('Component vnInputTime', () => { describe('Component vnInputTime', () => {
let $scope; let $filter;
let $attrs;
let $timeout;
let $element; let $element;
let controller; let $ctrl;
beforeEach(angular.mock.module('vnCore', $translateProvider => { beforeEach(angular.mock.module('vnCore', $translateProvider => {
$translateProvider.translations('en', {}); $translateProvider.translations('en', {});
})); }));
beforeEach(angular.mock.inject(($componentController, $rootScope) => { beforeEach(angular.mock.inject(($compile, $rootScope, _$filter_) => {
$scope = $rootScope.$new(); $filter = _$filter_;
$attrs = {field: '$ctrl.zone.hour'};
$element = angular.element('<vn-input-time label="Hour" field="$ctrl.zone.hour"><input><div class="infix invalid validated"><div class="rightIcons"></div></vn-input-time>'); $element = $compile(`<vn-input-time></vn-input-time>`)($rootScope);
controller = $componentController('vnInputTime', {$element, $scope, $attrs, $timeout, $transclude: () => {}}); $ctrl = $element.controller('vnInputTime');
})); }));
describe('value() setter', () => { afterEach(() => {
it(`should set _value to a given value, add the class not-empty and remove invalid and validated`, () => { $element.remove();
const today = new Date();
controller.value = today;
let classes = controller.element.classList.toString();
expect(classes).toContain('not-empty');
expect(controller._value).toEqual(today);
classes = controller.element.querySelector('.infix').classList.toString();
expect(classes).not.toContain('invalid validated');
}); });
it(`should set _value to a given value and not add the class not-empty if the given value is null`, () => { describe('field() setter', () => {
controller.value = null; it(`should display the formated the date`, () => {
let classes = controller.element.classList.toString(); let date = new Date();
$ctrl.field = date;
let displayed = $filter('dateTime')(date, 'HH:mm');
expect(classes).not.toContain('not-empty'); expect($ctrl.value).toEqual(displayed);
expect(controller._value).toEqual(null);
}); });
}); });
}); });

View File

@ -1,12 +0,0 @@
@import "variables";
@import '../textfield/style.scss';
vn-input-time {
@extend vn-textfield;
input[type="time"] {
clip-path: inset(0 17px 0 0);
outline: none;
outline: 0;
}
}

View File

@ -1,5 +1,5 @@
<vn-check <vn-check
field="$ctrl.checked" ng-model="$ctrl.checked"
intermediate="$ctrl.isIntermediate" intermediate="$ctrl.isIntermediate"
translate-attr="{title: 'Check all'}"> translate-attr="{title: 'Check all'}">
</vn-check> </vn-check>

View File

@ -109,7 +109,6 @@ export default class Popover extends Component {
this.showTimeout = null; this.showTimeout = null;
this.element.style.display = 'none'; this.element.style.display = 'none';
this.document.body.removeChild(this.element); this.document.body.removeChild(this.element);
this.emit('close');
}, 250); }, 250);
this.document.removeEventListener('keydown', this.docKeyDownHandler); this.document.removeEventListener('keydown', this.docKeyDownHandler);
@ -118,8 +117,8 @@ export default class Popover extends Component {
this.element.removeEventListener('mousedown', this.bgMouseDownHandler); this.element.removeEventListener('mousedown', this.bgMouseDownHandler);
this.bgMouseDownHandler = null; this.bgMouseDownHandler = null;
if (this.deregisterCallback) if (this.deregisterCallback) this.deregisterCallback();
this.deregisterCallback(); this.emit('close');
} }
/** /**
@ -189,7 +188,8 @@ export default class Popover extends Component {
} }
onBgMouseDown(event) { onBgMouseDown(event) {
if (event != this.lastMouseEvent) if (event == this.lastMouseEvent || event.defaultPrevented) return;
event.preventDefault();
this.hide(); this.hide();
} }
} }

View File

@ -10,13 +10,13 @@ import './style.scss';
*/ */
export default class Radio extends Toggle { export default class Radio extends Toggle {
set field(value) { set field(value) {
this._field = value; super.field = value;
this.element.classList.toggle('checked', this.element.classList.toggle('checked',
Boolean(value) && value == this.val); Boolean(value) && value == this.val);
} }
get field() { get field() {
return this._field; return super.field;
} }
set checked(value) { set checked(value) {
@ -43,14 +43,10 @@ export default class Radio extends Toggle {
} }
} }
ngModule.component('vnRadio', { ngModule.vnComponent('vnRadio', {
template: require('../toggle/index.html'), template: require('../toggle/index.html'),
controller: Radio, controller: Radio,
bindings: { bindings: {
label: '@?',
field: '=?',
disabled: '<?',
checked: '<?',
val: '@?' val: '@?'
} }
}); });

View File

@ -1,29 +1,30 @@
<form ng-submit="$ctrl.onSubmit()"> <form ng-submit="$ctrl.onSubmit()">
<vn-horizontal> <vn-textfield
<vn-textfield vn-one label="Search" model="$ctrl.searchString"> class="dense vn-py-md"
<t-left-icons> placeholder="{{::'Search' | translate}}"
ng-model="$ctrl.searchString">
<prepend>
<vn-icon <vn-icon
icon="search" icon="search"
ng-click="$ctrl.clearFilter(); $ctrl.onSubmit()" ng-click="$ctrl.clearFilter(); $ctrl.onSubmit()"
pointer> pointer>
</vn-icon> </vn-icon>
</t-left-icons> </prepend>
<t-right-icons vn-horizontal> <append>
<vn-icon vn-one <vn-icon
ng-if="$ctrl.info" ng-if="$ctrl.info"
icon="info_outline" icon="info_outline"
vn-tooltip="{{$ctrl.info}}" vn-tooltip="{{$ctrl.info}}"
pointer> pointer>
</vn-icon> </vn-icon>
<vn-icon vn-one <vn-icon
ng-if="$ctrl.panel" ng-if="$ctrl.panel"
ng-click="$ctrl.openPanel($event)" ng-click="$ctrl.openPanel($event)"
icon="keyboard_arrow_down" icon="arrow_drop_down"
pointer> pointer>
</vn-icon> </vn-icon>
</t-right-icons> </append>
</vn-textfield> </vn-textfield>
</vn-horizontal>
</form> </form>
<vn-popover <vn-popover
vn-id="popover" vn-id="popover"

View File

@ -1,12 +1,7 @@
@import "variables"; @import "variables";
vn-searchbar { vn-searchbar {
padding-top: 6px;
display: block; display: block;
& > form > vn-horizontal > vn-icon-button {
color: black;
}
} }
.search-panel { .search-panel {

View File

@ -1,2 +1,2 @@
<div class="table" ng-transclude> <div class="vn-table" ng-transclude>
</div> </div>

View File

@ -5,16 +5,20 @@ vn-table {
display: block; display: block;
overflow: auto; overflow: auto;
width: 100%; width: 100%;
}
& > div { .vn-table {
width: inherit; width: 100%;
display: table; display: table;
border-collapse: collapse; border-collapse: collapse;
& > vn-thead { & > vn-thead,
& > thead {
display: table-header-group; display: table-header-group;
border-bottom: .15em solid $color-spacer; border-bottom: .15em solid $color-spacer;
& > * > th {
font-weight: normal;
}
& > * > vn-th[field] { & > * > vn-th[field] {
position: relative; position: relative;
overflow: visible; overflow: visible;
@ -42,34 +46,43 @@ vn-table {
} }
} }
} }
& > vn-tbody { & > vn-tbody,
& > tbody {
display: table-row-group; display: table-row-group;
} }
& > vn-tfoot { & > vn-tfoot,
& > tfoot {
border-top: .15em solid $color-spacer; border-top: .15em solid $color-spacer;
display: table-footer-group display: table-footer-group
} }
& > * > vn-tr, & > * > vn-tr,
& > * > a.vn-tr { & > * > a.vn-tr,
& > * > tr {
display: table-row; display: table-row;
height: 3em; height: 3em;
} }
vn-thead, vn-tbody, vn-tfoot { vn-thead, vn-tbody, vn-tfoot,
thead, tbody, tfoot {
& > * { & > * {
display: table-row; display: table-row;
& > vn-th { & > vn-th,
& > th {
color: $color-font-light; color: $color-font-light;
padding-top: 1em; padding-top: 1em;
padding-bottom: .8em; padding-bottom: .8em;
} }
& > vn-th, & > vn-th,
& > vn-td { & > vn-td,
& > th,
& > td {
overflow: hidden; overflow: hidden;
} }
& > vn-th, & > vn-th,
& > vn-td, & > vn-td,
& > vn-td-editable { & > vn-td-editable,
& > th,
& > td {
vertical-align: middle; vertical-align: middle;
display: table-cell; display: table-cell;
text-align: left; text-align: left;
@ -108,7 +121,8 @@ vn-table {
color: inherit; color: inherit;
} }
} }
vn-tbody > * { vn-tbody > *,
tbody > * {
border-bottom: .1em solid $color-spacer-light; border-bottom: .1em solid $color-spacer-light;
&:last-child { &:last-child {
@ -117,42 +131,36 @@ vn-table {
&.clickable { &.clickable {
@extend %clickable; @extend %clickable;
} }
& > vn-td .chip { & > vn-td,
& > td {
.chip {
padding: .3em; padding: .3em;
border-radius: .3em border-radius: .3em;
}
& > vn-td .chip.notice {
color: $color-font-bg; color: $color-font-bg;
&.notice {
background-color: $color-notice-medium background-color: $color-notice-medium
} }
&.success {
& > vn-td .chip.success { background-color: $color-success-medium;
color: $color-font-bg;
background-color: $color-success-medium
} }
&.warning {
& > vn-td .chip.warning {
color: $color-font-bg;
background-color: $color-main-medium; background-color: $color-main-medium;
} }
&.alert {
& > vn-td .chip.alert {
color: $color-font-bg;
background-color: $color-alert-medium; background-color: $color-alert-medium;
} }
&.message {
& > vn-td .chip.message {
color: $color-font-dark; color: $color-font-dark;
background-color: $color-bg-dark background-color: $color-bg-dark
} }
}
& > vn-td vn-icon-menu { vn-icon-menu {
display: inline-block; display: inline-block;
color: $color-main; color: $color-main;
padding: .25em padding: .25em
} }
}
& > [actions] { & > [actions] {
width: 1px; width: 1px;
@ -161,26 +169,6 @@ vn-table {
} }
} }
} }
& > vn-empty-rows {
display: table-caption;
caption-side: bottom;
text-align: center;
padding: 1.5em;
width: 100%;
box-sizing: border-box;
}
}
vn-autocomplete {
div.mdl-textfield {
padding: 0 !important;
}
label.mdl-textfield__label:after {
bottom: 0;
}
div.icons {
display: none !important;
}
}
vn-textfield { vn-textfield {
float: right; float: right;
margin: 0!important; margin: 0!important;

View File

@ -0,0 +1,26 @@
import ngModule from '../../module';
import Field from '../field';
export default class Textarea extends Field {
constructor($element, $scope, $compile) {
super($element, $scope, $compile);
let html = `<textarea ng-model="$ctrl.field"></textarea>`;
this.input = $compile(html)($scope)[0];
}
set rows(value) {
this.input.rows = typeof value == 'number' && value > 0 ? value : 3;
}
get rows() {
return this.input.rows;
}
}
ngModule.vnComponent('vnTextarea', {
controller: Textarea,
bindings: {
rows: '<?'
}
});

View File

@ -0,0 +1,39 @@
import './index.js';
describe('Component vnTextarea', () => {
let $element;
let $ctrl;
beforeEach(angular.mock.module('vnCore', $translateProvider => {
$translateProvider.translations('en', {});
}));
beforeEach(angular.mock.inject(($compile, $rootScope) => {
$element = $compile(`<vn-textarea></vn-textarea>`)($rootScope);
$ctrl = $element.controller('vnTextarea');
}));
afterEach(() => {
$element.remove();
});
describe('rows() setter', () => {
it(`should set rows property of the element to the given value if it's a valid number`, () => {
$ctrl.rows = 27;
expect($ctrl.rows).toEqual(27);
});
it(`should set rows property of the element to 3 if the given value if it's null`, () => {
$ctrl.rows = null;
expect($ctrl.rows).toEqual(3);
});
it(`should set rows property of the element to 3 if the given value if it's not a valid number`, () => {
$ctrl.rows = 'a';
expect($ctrl.rows).toEqual(3);
});
});
});

View File

@ -1,24 +0,0 @@
@import "variables";
vn-textarea {
& > .mdl-textfield {
width: initial;
display: block;
}
label {
position: absolute;
bottom: 0;
pointer-events: none;
color: $color-font-secondary;
transition-duration: .2s;
transition-timing-function: cubic-bezier(.4,0,.2,1);
}
& textarea.ng-not-empty+label.placeholder{
top: 5px;
color: $color-main;
padding: 0;
font-size: 12px;
visibility: visible!important;
content: normal!important;
}
}

View File

@ -1,9 +0,0 @@
<div class="mdl-textfield mdl-js-textfield">
<textarea
class="mdl-textfield__input"
type="text"
ng-disabled="$ctrl.disabled"
ng-model="$ctrl.model">
</textarea>
<label class="mdl-textfield__label placeholder" translate>{{$ctrl.label}}</label>
</div>

View File

@ -1,43 +0,0 @@
import ngModule from '../../module';
import './style.scss';
export default class Textarea {
constructor($element, $scope, $attrs, vnTemplate) {
this.$ = $scope;
this.$attrs = $attrs;
this.element = $element;
vnTemplate.normalizeInputAttrs($attrs);
this.textarea = this.element[0].querySelector('textarea');
this.rows = null;
}
set model(value) {
this._model = value;
this.mdlUpdate();
}
set rows(value) {
this.textarea.rows = value ? value : 3;
}
get model() {
return this._model;
}
mdlUpdate() {
let mdlField = this.element[0].firstChild.MaterialTextfield;
if (mdlField)
mdlField.updateClasses_();
componentHandler.upgradeElement(this.element[0].firstChild);
}
}
Textarea.$inject = ['$element', '$scope', '$attrs', 'vnTemplate'];
ngModule.component('vnTextarea', {
template: require('./textarea.html'),
controller: Textarea,
bindings: {
model: '=model',
label: '@?',
rows: '@?',
name: '@?',
disabled: '<?',
rule: '@?'
}
});

View File

@ -1,56 +0,0 @@
import './textarea.js';
describe('Component vnTextarea', () => {
let $scope;
let $attrs;
let $element;
let controller;
beforeEach(angular.mock.module('vnCore', $translateProvider => {
$translateProvider.translations('en', {});
}));
beforeEach(angular.mock.inject(($componentController, $rootScope) => {
$scope = $rootScope.$new();
$attrs = {field: '$ctrl.claim.observation'};
$element = angular.element('<vn-textarea vn-three label="Observation" field="$ctrl.claim.observation" rows="9"><textarea></vn-textarea>');
$element[0].firstChild.MaterialTextfield = {updateClasses_: () => {}};
controller = $componentController('vnTextarea', {$scope, $element, $attrs});
}));
describe('model() setter', () => {
it(`should set model and call mdlUpdate`, () => {
spyOn(controller, 'mdlUpdate');
let model = 'model';
controller.model = model;
expect(controller._model).toEqual(model);
expect(controller.mdlUpdate).toHaveBeenCalledWith();
});
});
describe('rows() setter', () => {
it(`should set rows property of the element to the given value if its not null`, () => {
controller.rows = 27;
expect(controller.textarea.rows).toEqual(27);
});
it(`should set rows property of the element to 3 if the given value if its null`, () => {
controller.rows = null;
expect(controller.textarea.rows).toEqual(3);
});
});
describe('mdlUpdate()', () => {
it(`should should call mdlField.updateClasses if theres an mdlField defined and call upgradeElement with the textarea element`, () => {
spyOn($element[0].firstChild.MaterialTextfield, 'updateClasses_');
spyOn(componentHandler, 'upgradeElement');
controller.mdlUpdate();
expect($element[0].firstChild.MaterialTextfield.updateClasses_).toHaveBeenCalledWith();
expect(componentHandler.upgradeElement).toHaveBeenCalledWith(controller.element[0].firstChild);
});
});
});

View File

@ -1,162 +0,0 @@
@import "variables";
vn-textfield {
margin: 20px 0;
display: inline-block;
width: 100%;
& > .container {
width: 100%;
position: relative;
padding-bottom: 2px;
display: flex;
& > .textField {
width: 100%;
display: flex;
align-items: center;
position: relative;
padding-top: 4px;
}
}
.leftIcons, .rightIcons, .suffix {
display: flex;
color: $color-font-secondary;
.material-icons {
font-size: 20px !important
}
}
.suffix vn-icon-button {
padding: 0
}
t-left-icons {
padding-right: 0.5em
}
t-right-icons {
padding-left: 0.5em
}
.infix {
position: relative;
display: block;
flex: auto;
width: 100%;
min-width: 0;
}
i.clear {
visibility: hidden;
cursor: pointer;
outline: 0;
&:hover {
color: #222;
}
}
&:hover i.clear {
visibility: visible;
}
i.visible {
visibility: visible;
}
label {
position: absolute;
bottom: 0;
left: 0;
padding: 4px 0!important;
pointer-events: none;
color: $color-font-secondary;
transition-duration: .2s;
transition-timing-function: cubic-bezier(.4,0,.2,1);
}
&.not-empty label{
bottom: 24px;
color: $color-main;
padding: 0;
font-size: 12px;
}
input {
outline: none;
border: none;
font-family: "Helvetica", "Arial", sans-serif;
display: block;
font-size: 16px;
width: 100%;
background: 0 0;
color: inherit;
padding: 4px;
box-sizing: border-box;
border-bottom: 0!important;
&[type=number] {
-moz-appearance: textfield;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
}
&:invalid {
box-shadow: none;
}
}
.underline {
position: absolute;
bottom: 0;
height: 1px;
content: ' ';
pointer-events: none;
width: 100%;
background-color: $color-input-underline;
}
.selected.underline {
background-color: $color-main;
height: 2px;
left: 50%;
width: 0px !important;
transition-duration: 0.2s;
transition-timing-function: cubic-bezier(.4,0,.2,1);
}
div.selected {
&.container{
border-bottom: 0px;
}
label {
bottom: 24px;
color: $color-main;
font-size: 12px;
}
.selected.underline{
left: 0;
width: 100%!important;
}
}
& > div.container > div.textField > div.infix.invalid {
@extend div.selected;
& > span.mdl-textfield__error {
visibility: visible;
}
& > label {
color: #d50000;
}
}
.infix.invalid + .underline {
background-color: #d50000;
}
label span:nth-child(2) {
color: $color-alert
}
}
vn-table {
vn-textfield {
margin: 0;
}
}

View File

@ -1,41 +0,0 @@
<div class="container"
ng-class="{selected: $ctrl.hasFocus}">
<div class="textField">
<div class="leftIcons" ng-transclude="leftIcons"></div>
<div class="infix">
<input
class="mdl-textfield__input"
type="{{$ctrl.type}}"
name="{{$ctrl.name}}"
ng-model="$ctrl.value"
vn-validation="{{$ctrl.rule}}"
ng-disabled="$ctrl.disabled"
ng-readonly="$ctrl.readonly"
ng-focus="$ctrl.hasFocus = true"
ng-blur="$ctrl.hasFocus = false"
tabindex="{{$ctrl.input.tabindex}}"/>
<label class="label">
<span translate>{{::$ctrl.label}}</span>
<span translate ng-show="::$ctrl.required">*</span>
</label>
</div>
<div class="underline"></div>
<div class="selected underline"></div>
<div class="suffix">
<i class="material-icons clear"
translate-attr="{title: 'Clear'}"
ng-show="!$ctrl.disabled
&& $ctrl.hasValue
&& !$ctrl.unclearable"
ng-click="$ctrl.clear()">
clear
</i>
<i class="material-icons"
ng-if="$ctrl.hasInfo"
vn-tooltip="{{$ctrl.info}}">
info_outline
</i>
</div>
<div class="rightIcons" ng-transclude="rightIcons"></div>
</div>
</div>

View File

@ -1,94 +1,13 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Input from '../../lib/input'; import Field from '../field';
import './style.scss';
export default class Textfield extends Input { export default class Textfield extends Field {
constructor($element, $scope, $attrs, vnTemplate) { constructor($element, $scope, $compile) {
super($element, $scope); super($element, $scope, $compile);
vnTemplate.normalizeInputAttrs($attrs); this.buildInput('text');
this._value = null; }
this.type = $attrs.type; }
this.showActions = false;
this.hasInfo = Boolean($attrs.info);
this.hasFocus = false;
this.hasMouseIn = false;
this.input.addEventListener('keyup', e => { ngModule.vnComponent('vnTextfield', {
if (e.defaultPrevented || e.key != 'Escape') controller: Textfield
return;
this.value = this.oldValue;
this.cancelled = true;
e.preventDefault();
});
this.input.addEventListener('change', e => {
if (this.onChange && !this.cancelled && (this.oldValue != this.value)) {
this.onChange();
this.saveOldValue();
} else
this.cancelled = false;
});
}
$postLink() {
this.saveOldValue();
}
saveOldValue() {
this.oldValue = this.value;
}
set value(value) {
this._value = (value === undefined || value === '') ? null : value;
this.input.value = this._value;
this.hasValue = this._value != null;
this.element.classList.toggle('not-empty', this.hasValue);
}
get value() {
return this._value;
}
set type(value) {
this._type = value || 'text';
}
get type() {
return this._type;
}
set vnTabIndex(value) {
this.input.tabindex = value;
}
clear() {
this.saveOldValue();
this.value = null;
if (this.onClear) this.onClear();
this.input.focus();
}
}
Textfield.$inject = ['$element', '$scope', '$attrs', 'vnTemplate'];
ngModule.component('vnTextfield', {
template: require('./textfield.html'),
transclude: {
leftIcons: '?tLeftIcons',
rightIcons: '?tRightIcons'
},
controller: Textfield,
bindings: {
value: '=model',
label: '@?',
name: '@?',
disabled: '<?',
required: '@?',
readonly: '<?',
rule: '@?',
type: '@?',
vnTabIndex: '@?',
onChange: '&',
onClear: '&',
info: '@?'
}
}); });

View File

@ -1,84 +0,0 @@
import './textfield.js';
describe('Component vnTextfield', () => {
let $scope;
let $attrs;
let $timeout;
let $element;
let controller;
beforeEach(angular.mock.module('vnCore', $translateProvider => {
$translateProvider.translations('en', {});
}));
beforeEach(angular.mock.inject(($componentController, $rootScope) => {
$scope = $rootScope.$new();
$attrs = {field: '$ctrl.client.phone'};
$element = angular.element('<vn-textfield label="Phone" field="$ctrl.client.phone"><input><div class="leftIcons"><div class="rightIcons"></div></vn-textfield>');
controller = $componentController('vnTextfield', {$scope, $element, $attrs, $timeout, $transclude: () => {}});
}));
describe('saveOldValue()', () => {
it(`should equal oldValue property to the actual input value`, () => {
controller.value = 'pepino';
controller.saveOldValue();
expect(controller.oldValue).toEqual('pepino');
});
});
describe('value() setter', () => {
it(`should set _value, input.value and hasValue properties to null, '' and false`, () => {
let testValue = '';
controller.value = testValue;
expect(controller._value).toEqual(null);
expect(controller.input.value).toEqual(testValue);
expect(controller.hasValue).toEqual(Boolean(testValue));
});
it(`should set _value, input.value and hasValue propertiest to test, test and true`, () => {
let testValue = 'test';
controller.value = testValue;
expect(controller._value).toEqual(testValue);
expect(controller.input.value).toEqual(testValue);
expect(controller.hasValue).toEqual(Boolean(testValue));
});
it(`should add the class not-empty to the div`, () => {
let testValue = 'test';
controller.value = testValue;
expect(controller.element.classList).toContain('not-empty');
});
});
describe('type() setter', () => {
it(`should set _type to 'text' if theres not given value`, () => {
controller.type = null;
expect(controller._type).toEqual('text');
});
});
describe('vnTabIndex() setter', () => {
it(`should set input.tabindex to a given value`, () => {
controller.vnTabIndex = 9;
expect(controller.input.tabindex).toEqual(9);
});
});
describe('clear()', () => {
it(`should set value property to null, call focus and saveOldValue`, () => {
spyOn(controller.input, 'focus');
spyOn(controller, 'saveOldValue');
controller.clear();
expect(controller.value).toEqual(null);
expect(controller.saveOldValue).toHaveBeenCalledWith();
expect(controller.input.focus).toHaveBeenCalledWith();
});
});
});

View File

@ -1,16 +1,13 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Component from '../../lib/component'; import FormInput from '../form-input';
import './style.scss'; import './style.scss';
/** /**
* Base component with common logic and styles for checkbox and radio button. * Base component with common logic and styles for checkbox and radio button.
* *
* @property {String} label Label to display along the component
* @property {any} field The value with which the element is linked
* @property {Boolean} checked Whether the checkbox is checked * @property {Boolean} checked Whether the checkbox is checked
* @property {Boolean} disabled Put component in disabled mode
*/ */
export default class Toggle extends Component { export default class Toggle extends FormInput {
constructor($element, $) { constructor($element, $) {
super($element, $); super($element, $);
@ -49,14 +46,10 @@ export default class Toggle extends Component {
this.emit('change', {value: this.field}); this.emit('change', {value: this.field});
} }
} }
Toggle.$inject = ['$element', '$scope'];
ngModule.component('vnToggle', { ngModule.vnComponent('vnToggle', {
controller: Toggle, controller: Toggle,
bindings: { bindings: {
label: '@?',
field: '=?',
disabled: '<?',
checked: '<?' checked: '<?'
} }
}); });

View File

@ -3,7 +3,8 @@
.vn-toggle { .vn-toggle {
position: relative; position: relative;
cursor: pointer; cursor: pointer;
display: inline-block; display: inline-flex;
align-items: center;
outline: none; outline: none;
&.disabled { &.disabled {
@ -19,8 +20,9 @@
vertical-align: middle; vertical-align: middle;
width: 20px; width: 20px;
height: 20px; height: 20px;
min-width: 20px;
margin: 6px 0; margin: 6px 0;
margin-right: .4em; margin-right: .6em;
border: 2px solid #666; border: 2px solid #666;
} }
&.checked > .btn { &.checked > .btn {

View File

@ -223,11 +223,8 @@ export default class Watcher extends Component {
} }
loadOriginalData() { loadOriginalData() {
Object.keys(this.data).forEach(key => { const orgData = JSON.parse(JSON.stringify(this.orgData));
delete this.data[key]; this.data = Object.assign(this.data, orgData);
});
this.data = Object.assign(this.data, this.orgData);
this.setPristine(); this.setPristine();
} }

View File

@ -69,7 +69,7 @@ function vnAcl(aclService, $timeout) {
if (!toAdd && position > -1) if (!toAdd && position > -1)
acls.splice(position, 1); acls.splice(position, 1);
// todo: add acl and enabled element if previusly was disabled // XXX: add acl and enabled element if previusly was disabled
} }
return { return {

View File

@ -2,7 +2,7 @@ import './id';
import './focus'; import './focus';
import './dialog'; import './dialog';
import './popover'; import './popover';
import './validation'; import './rule';
import './acl'; import './acl';
import './on-error-src'; import './on-error-src';
import './zoom-image'; import './zoom-image';

View File

@ -0,0 +1,72 @@
import ngModule from '../module';
import {validateAll} from '../lib/validator';
import {firstUpper} from '../lib/string';
directive.$inject = ['$translate', '$window'];
export function directive($translate, $window) {
return {
restrict: 'A',
link: link,
require: {
ngModel: 'ngModel',
form: '^^?form'
}
};
function link($scope, $element, $attrs, $ctrl) {
let vnValidations = $window.validations;
if (!vnValidations) return;
if (!/^([A-Z]\w+(\.[a-z]\w*)?)?$/.test($attrs.rule))
throw new Error(`rule: Attribute must have this syntax: [ModelName[.fieldName]]`);
let rule = $attrs.rule.split('.');
let modelName = rule.shift();
let fieldName = rule.shift();
let split = $attrs.ngModel.split('.');
if (!fieldName) fieldName = split.pop() || null;
if (!modelName) modelName = firstUpper(split.pop() || '');
if (!modelName || !fieldName)
throw new Error(`rule: Cannot retrieve model or field attribute`);
let modelValidations = vnValidations[modelName];
if (!modelValidations)
throw new Error(`rule: Model '${modelName}' doesn't exist`);
let validations = modelValidations.validations[fieldName];
if (!validations || validations.length == 0)
return;
let ngModel = $ctrl.ngModel;
let form = $ctrl.form;
let field = $element[0].$ctrl;
let error;
function refreshError() {
if (!field) return;
let canShow = ngModel.$dirty || (form && form.$submitted);
field.error = error && canShow ? error.message : null;
}
ngModel.$options.$$options.allowInvalid = true;
ngModel.$validators.entity = value => {
try {
error = null;
validateAll($translate, value, validations);
} catch (e) {
error = e;
}
refreshError();
return error == null;
};
if (form)
$scope.$watch(form.$submitted, refreshError);
}
}
ngModule.directive('rule', directive);

View File

@ -0,0 +1,115 @@
describe('Directive rule', () => {
let $scope;
let $element;
let element;
beforeEach(angular.mock.module('vnCore', $translateProvider => {
$translateProvider.translations('en', {});
}));
function compile(html, value) {
inject(($compile, $rootScope, $window) => {
$window.validations = {Model:
{validations: {field: [{validation: 'absence'}]}}
};
$scope = $rootScope.$new();
$element = angular.element(html);
$compile($element)($scope);
element = $element[0];
$scope.model = {field: value};
$scope.$digest();
});
}
afterEach(() => {
$scope.$destroy();
$element.remove();
});
describe('Errors', () => {
it(`should throw an error if the rule doesn't have the right syntax`, () => {
expect(() => {
compile(`
<form>
<input
type="text"
ng-model="model.field"
rule="invalidLowerCamelCaseModel">
</input>
</form>
`);
}).toThrow(new Error(`rule: Attribute must have this syntax: [ModelName[.fieldName]]`));
});
it('should throw an error if cannot retrieve model or field', () => {
expect(() => {
compile(`
<form>
<input
type="text"
ng-model="model"
rule>
</input>
</form>
`);
}).toThrow(new Error(`rule: Cannot retrieve model or field attribute`));
});
it('should throw an error if the model is not defined', () => {
expect(() => {
compile(`
<form>
<input
type="text"
ng-model="model.field"
rule="NonExistentModel.field">
</input>
</form>
`);
}).toThrow(new Error(`rule: Model 'NonExistentModel' doesn't exist`));
});
});
describe('Validator extended', () => {
let html = `
<form>
<input
type="text"
ng-model="model.field"
rule="Model.field">
</input>
</form>
`;
it('should not validate the entity as it has a wrong value', () => {
compile(html, 'invalidValue');
expect(element.classList).toContain('ng-invalid');
});
it('should validate the entity as it has a valid value', () => {
compile(html, '');
expect(element.classList).toContain('ng-valid');
});
});
describe('Validator minimal', () => {
let html = `
<input
type="text"
ng-model="model.field"
rule>
</input>
`;
it('should validate with empty rule and without specifying a parent form', () => {
compile(html, 'invalidValue');
expect(element.classList).toContain('ng-invalid');
});
});
});

View File

@ -1,186 +0,0 @@
describe('Directive validation', () => {
let scope;
let element;
let compile;
beforeEach(angular.mock.module('vnCore', $translateProvider => {
$translateProvider.translations('en', {});
}));
compile = (_element, validations, value) => {
inject(($compile, $rootScope, $window) => {
$window.validations = validations;
scope = $rootScope.$new();
scope.user = {name: value};
element = angular.element(_element);
$compile(element)(scope);
scope.$digest();
});
};
it(`should throw an error if the vnValidation doesn't have the right syntax`, () => {
let html = `<input type="name" ng-model="user.name" vn-validation="user"/>`;
expect(() => {
compile(html, {});
}).toThrow(new Error(`vnValidation: Attribute must have this syntax: [entity].[field]`));
});
it('should throw an error if the window.validations aint defined', () => {
let html = `<input type="name" ng-model="user.name" vn-validation="user.name"/>`;
expect(() => {
compile(html, {});
}).toThrow(new Error(`vnValidation: Entity 'User' doesn't exist`));
});
describe('Validator presence()', () => {
it('should not validate the user name as it is an empty string', () => {
let html = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
let validations = {User: {validations: {name: [{validation: 'presence'}]}}};
compile(html, validations, 'Spiderman');
scope.user.name = '';
scope.$digest();
expect(element[0].classList).toContain('ng-invalid');
expect(element[0].classList).not.toContain('ng-valid');
});
});
describe('Validator absence()', () => {
it('should not validate the entity as it should be an empty string', () => {
let html = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
let validations = {User: {validations: {name: [{validation: 'absence'}]}}};
compile(html, validations, 'Spiderman');
scope.$digest();
expect(element[0].classList).toContain('ng-invalid');
expect(element[0].classList).not.toContain('ng-valid');
});
it('should validate the entity as it is an empty string', () => {
let html = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
let validations = {User: {validations: {name: [{validation: 'absence'}]}}};
compile(html, validations, '');
scope.$digest();
expect(element[0].classList).toContain('ng-valid');
expect(element[0].classList).not.toContain('ng-invalid');
});
});
describe('Validator length()', () => {
it('should not validate the user name as it should have min length of 15', () => {
let html = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
let validations = {User: {validations: {name: [{validation: 'length', min: 10, max: 50, is: 15}]}}};
compile(html, validations, 'fifteen!');
scope.$digest();
expect(element[0].classList).toContain('ng-invalid');
expect(element[0].classList).not.toContain('ng-valid');
});
it('should validate the user name as it has length of 15', () => {
let html = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
let validations = {User: {validations: {name: [{validation: 'length', min: 10, max: 50, is: 15}]}}};
compile(html, validations, 'fifteen length!');
scope.$digest();
expect(element[0].classList).toContain('ng-valid');
expect(element[0].classList).not.toContain('ng-invalid');
});
it('should not validate the user name as it should have min length of 10', () => {
let html = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
let validations = {User: {validations: {name: [{validation: 'length', min: 10}]}}};
compile(html, validations, 'shortname');
scope.$digest();
expect(element[0].classList).toContain('ng-invalid');
expect(element[0].classList).not.toContain('ng-valid');
});
it('should validate the user name as its length is greater then the minimum', () => {
let html = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
let validations = {User: {validations: {name: [{validation: 'length', min: 10}]}}};
compile(html, validations, 'verylongname');
scope.$digest();
expect(element[0].classList).toContain('ng-valid');
expect(element[0].classList).not.toContain('ng-invalid');
});
it('should not validate the user name as its length is greater then the maximum', () => {
let html = `<form><input type="name" ng-model="user.name" vn-validation="user.name"/></form>`;
let validations = {User: {validations: {name: [{validation: 'length', max: 10}]}}};
compile(html, validations, 'toolongname');
scope.$digest();
expect(element[0].classList).toContain('ng-invalid');
expect(element[0].classList).not.toContain('ng-valid');
});
});
describe('Validator numericality()', () => {
it('should not validate the phone number as it should a integer', () => {
let html = `<form><input type="text" ng-model="user.phone" vn-validation="user.phone"/></form>`;
let validations = {User: {validations: {phone: [{validation: 'numericality', is: 'what is this?'}]}}};
compile(html, validations, 'spiderman');
scope.user.phone = 'this is not a phone number!';
scope.$digest();
expect(element[0].classList).toContain('ng-invalid');
expect(element[0].classList).not.toContain('ng-valid');
});
it('should validate the phone number as it an integer', () => {
let html = `<form><input type="text" ng-model="user.phone" vn-validation="user.phone"/></form>`;
let validations = {User: {validations: {phone: [{validation: 'numericality', is: 'what is this?'}]}}};
compile(html, validations, 'spiderman');
scope.user.phone = '555555555';
scope.$digest();
expect(element[0].classList).toContain('ng-valid');
expect(element[0].classList).not.toContain('ng-invalid');
});
});
describe('Validator inclusion()', () => {
it('should not validate the phone number as it is not an integer', () => {
let html = `<form><input type="text" ng-model="user.phone" vn-validation="user.phone"/></form>`;
let validations = {User: {validations: {phone: [{validation: 'inclusion'}]}}};
compile(html, validations, 'spiderman');
scope.user.phone = 'this is not a phone number!';
scope.$digest();
expect(element[0].classList).toContain('ng-invalid');
expect(element[0].classList).not.toContain('ng-valid');
});
});
describe('Validator exclusion()', () => {
it('should validate the phone number as it is an integer', () => {
let html = `<form><input type="text" ng-model="user.phone" vn-validation="user.phone"/></form>`;
let validations = {User: {validations: {phone: [{validation: 'exclusion'}]}}};
compile(html, validations, 'spiderman');
scope.user.phone = '555555555';
scope.$digest();
expect(element[0].classList).toContain('ng-valid');
expect(element[0].classList).not.toContain('ng-invalid');
});
});
describe('Validator format()', () => {
it('should not validate the email number as it doesnt contain @', () => {
let html = `<form><input type="text" ng-model="user.email" vn-validation="user.email"/></form>`;
let validations = {User: {validations: {email: [{validation: 'format', with: '@'}]}}};
compile(html, validations, 'spiderman');
scope.user.email = 'userverdnatura.es';
scope.$digest();
expect(element[0].classList).toContain('ng-invalid');
expect(element[0].classList).not.toContain('ng-valid');
});
});
});

View File

@ -15,7 +15,7 @@
<vn-horizontal ng-repeat="field in fields"> <vn-horizontal ng-repeat="field in fields">
<vn-check <vn-check
vn-one label="{{titles[field]}}" vn-one label="{{titles[field]}}"
field="tableConfiguration.configuration[field]"> ng-model="tableConfiguration.configuration[field]">
</vn-check> </vn-check>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>

View File

@ -1,80 +0,0 @@
import ngModule from '../module';
import {validateAll} from '../lib/validator';
import {firstUpper} from '../lib/string';
directive.$inject = ['$interpolate', '$compile', '$translate', '$window'];
export function directive(interpolate, compile, $translate, $window) {
return {
restrict: 'A',
require: ['ngModel', '^^?form'],
link: link
};
function link(scope, element, attrs, ctrl) {
let vnValidations = $window.validations;
if (!attrs.vnValidation || !vnValidations)
return;
let split = attrs.vnValidation.split('.');
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)
throw new Error(`vnValidation: Entity '${entityName}' doesn't exist`);
let validations = entity.validations[fieldName];
if (!validations || validations.length == 0)
return;
let ngModel = ctrl[0];
let form = ctrl[1];
let errorSpan = angular.element('<span class="mdl-textfield__error"></span>');
let errorMsg;
let errorShown = false;
ngModel.$options.$$options.allowInvalid = true;
ngModel.$validators.entity = value => {
try {
validateAll($translate, value, validations);
return true;
} catch (e) {
errorMsg = e.message;
if (errorShown) changeError();
return false;
}
};
scope.$watch(() => {
return (form.$submitted || ngModel.$dirty) && ngModel.$invalid;
}, value => {
let parent = element.parent();
if (value) {
changeError();
parent.addClass('invalid');
element.after(errorSpan);
} else if (errorShown) {
parent.removeClass('invalid');
parent.removeAttr('title');
errorSpan.remove();
errorSpan.empty();
}
errorShown = value;
});
function changeError() {
let parent = element.parent();
errorSpan.text(errorMsg);
parent.attr('title', errorMsg);
}
}
}
ngModule.directive('vnValidation', directive);

View File

@ -1,7 +1,5 @@
import './module-loader'; import './module-loader';
import './crud'; import './crud';
import './acl-service';
import './template';
import './copy'; import './copy';
import './equals'; import './equals';
import './modified'; import './modified';

View File

@ -30,79 +30,13 @@ export default class Input extends Component {
if (this.mdlElement) if (this.mdlElement)
this.mdlElement.updateClasses_(); this.mdlElement.updateClasses_();
} }
get validationError() {
return this.input.validationMessage;
}
/**
* Validates a valid input value
*
* @return {Boolean} - True if has a valid value
*/
hasValidValue() {
return this.input.checkValidity();
}
/**
* Changes the input element
* if has a validation error
*/
validateValue() {
if (!this.hasValidValue()) {
this.hideError();
this.showError();
} else
this.hideError();
}
/**
* Shows the input validation error
*/
showError() {
const infixElement = this.element.querySelector('.infix');
const infixClassList = infixElement.classList;
const errorSpan = document.createElement('span');
errorSpan.className = 'mdl-textfield__error';
const errorText = document.createTextNode(this.validationError);
errorSpan.append(errorText);
infixElement.append(errorSpan);
infixClassList.add('validated', 'invalid');
}
/**
* Hides the input validation error
*/
hideError() {
const infixElement = this.element.querySelector('.infix');
const infixClassList = infixElement.classList;
const errorElement = this.element.querySelector('.infix span.mdl-textfield__error');
if (errorElement)
errorElement.remove();
infixClassList.remove('validated', 'invalid');
}
} }
Input.$inject = ['$element', '$scope']; Input.$inject = ['$element', '$scope'];
export const component = { export const $options = {
transclude: {
leftIcons: '?tLeftIcons',
rightIcons: '?tRightIcons'
},
bindings: { bindings: {
label: '@?', label: '@?',
disabled: '<?', disabled: '<?',
readonly: '<?', readonly: '<?'
rule: '@?',
value: '=model',
vnTabIndex: '@?',
onChange: '&',
onClear: '&'
} }
}; };

View File

@ -0,0 +1,186 @@
import {validate} from '../validator.js';
describe('Validator', () => {
let $translate;
beforeEach(angular.mock.module('vnCore', $translateProvider => {
$translateProvider.translations('en', {});
}));
beforeEach(inject(_$translate_ => {
$translate = _$translate_;
}));
describe('presence', () => {
let conf = {validation: 'presence'};
it('should not validate the value as it should be defined', () => {
expect(() => {
validate($translate, '', conf);
}).toThrowError();
});
it('should validate the value as it should be defined', () => {
expect(() => {
validate($translate, 'aDefinedValue', conf);
}).not.toThrowError();
});
});
describe('absence', () => {
let conf = {validation: 'absence'};
it('should not validate the value as it should be undefined', () => {
expect(() => {
validate($translate, 'aDefinedValue', conf);
}).toThrowError();
});
it('should validate the value as it should be undefined', () => {
expect(() => {
validate($translate, '', conf);
}).not.toThrowError();
});
});
describe('length', () => {
it('should not validate the value as it should have an specific valid length', () => {
let conf = {
validation: 'length',
is: 25
};
expect(() => {
validate($translate, 'invalidSpecificLengthString', conf);
}).toThrowError();
});
it('should validate the value as it should have an specific valid length', () => {
let conf = {
validation: 'length',
is: 25
};
expect(() => {
validate($translate, 'validSpecificLengthString', conf);
}).not.toThrowError();
});
it('should not validate the value as it should have a min length', () => {
let conf = {
validation: 'length',
min: 10
};
expect(() => {
validate($translate, 'shortName', conf);
}).toThrowError();
});
it('should validate the value as it should have a min length', () => {
let conf = {
validation: 'length',
min: 10
};
expect(() => {
validate($translate, 'veryLongName', conf);
}).not.toThrowError();
});
it('should not validate the value as it should be smaller than the maximum', () => {
let conf = {
validation: 'length',
max: 20
};
expect(() => {
validate($translate, 'chainThatExceedsMaxLength', conf);
}).toThrowError();
});
it('should validate the value as it should be smaller than the maximum', () => {
let conf = {
validation: 'length',
max: 20
};
expect(() => {
validate($translate, 'shortString', conf);
}).not.toThrowError();
});
});
describe('numericality', () => {
let conf = {validation: 'numericality'};
it('should not validate the value as it should be an integer', () => {
expect(() => {
validate($translate, 'notANumber', conf);
}).toThrowError();
});
it('should validate the value as it should be an integer', () => {
expect(() => {
validate($translate, '123456789', conf);
}).not.toThrowError();
});
});
describe('inclusion', () => {
let conf = {
validation: 'inclusion',
in: ['firstValue', 'seekValue', 'lastValue']
};
it('should not validate the value as it should be in array', () => {
expect(() => {
validate($translate, 'notIncludedValue', conf);
}).toThrowError();
});
it('should validate the value as it should be in array', () => {
expect(() => {
validate($translate, 'seekValue', conf);
}).not.toThrowError();
});
});
describe('exclusion', () => {
let conf = {
validation: 'exclusion',
in: ['firstValue', 'seekValue', 'lastValue']
};
it('should not validate the value as it should not be in array', () => {
expect(() => {
validate($translate, 'seekValue', conf);
}).toThrowError();
});
it('should validate the value as it should not be in array', () => {
expect(() => {
validate($translate, 'notIncludedValue', conf);
}).not.toThrowError();
});
});
describe('format', () => {
let conf = {
validation: 'format',
with: /[a-z-]+@[a-z-]+\.[a-z]+/
};
it('should not validate the value as it should match regexp', () => {
expect(() => {
validate($translate, 'wrongValue', conf);
}).toThrowError();
});
it('should validate the value as it should match regexp', () => {
expect(() => {
validate($translate, 'valid-value@domain.com', conf);
}).not.toThrowError();
});
});
});

View File

@ -6,7 +6,7 @@
* @return {String} The camelized string * @return {String} The camelized string
*/ */
export function kebabToCamel(str) { export function kebabToCamel(str) {
var camelCased = str.replace(/-([a-z])/g, function(g) { let camelCased = str.replace(/-([a-z])/g, function(g) {
return g[1].toUpperCase(); return g[1].toUpperCase();
}); });
return camelCased; return camelCased;

View File

@ -1,40 +0,0 @@
import ngModule from '../module';
export default class Template {
getNormalized(template, $attrs, defaults) {
this.normalizeInputAttrs($attrs);
return this.get(template, $attrs, defaults);
}
normalizeInputAttrs($attrs) {
const field = $attrs.field || $attrs.model;
const split = field.split('.');
const len = split.length;
let i = len - 1;
const fieldName = split[i--];
const entity = i >= 0 ? split[i--] : 'model';
const ctrl = i >= 0 ? split[i--] : '$ctrl';
if ($attrs.field) {
if (len == 0)
throw new Error(`Attribute 'field' can not be empty`);
if (len > 3)
throw new Error(`Attribute 'field' must have this syntax: [ctrl].[entity].[field]`);
if ($attrs.model === undefined)
$attrs.model = `${ctrl}.${entity}.${fieldName}`;
if ($attrs.rule === undefined && len >= 2)
$attrs.rule = `${entity}.${fieldName}`;
if ($attrs.label === undefined && len >= 2)
$attrs.label = `${entity}.${fieldName}`;
}
if ($attrs.name === undefined)
$attrs.name = fieldName;
if ($attrs.focus !== undefined)
$attrs.focus = 'vn-focus';
}
}
ngModule.service('vnTemplate', Template);

View File

@ -4,6 +4,43 @@ const ngModule = ng.module('vnCore', ngDeps);
ngModule.constant('moment', require('moment-timezone')); ngModule.constant('moment', require('moment-timezone'));
export default ngModule; export default ngModule;
/**
* Acts like native Module.component() function but merging component options
* with parent component options. This method establishes the $options property
* to the component controller class with the merged component options. To
* retrieve parent options, it reads the same property of the parent class, so
* for the parent options to be copied, it must have been declared using this
* same function. If any of the options (template, transclude, bindings ...) is
* redeclared in the child component, it has preference.
*
* @param {String} name Coponent name in camelCase
* @param {Object} options The component options
* @return {angularModule} The same angular module
*/
ngModule.vnComponent = function(name, options) {
let controller = options.controller;
let parent = Object.getPrototypeOf(controller);
let parentOptions = parent.$options || {};
let mergedOptions = Object.assign({},
parentOptions,
options,
{
transclude: Object.assign({},
parentOptions.transclude,
options.transclude
),
bindings: Object.assign({},
parentOptions.bindings,
options.bindings
)
}
);
controller.$options = mergedOptions;
return this.component(name, mergedOptions);
};
config.$inject = ['$translateProvider', '$translatePartialLoaderProvider']; config.$inject = ['$translateProvider', '$translatePartialLoaderProvider'];
export function config($translateProvider, $translatePartialLoaderProvider) { export function config($translateProvider, $translatePartialLoaderProvider) {
$translatePartialLoaderProvider.addPart('core'); $translatePartialLoaderProvider.addPart('core');

View File

@ -0,0 +1,79 @@
import ngModule from '../module';
/**
* Saves and loads the application configuration.
*/
export default class Config {
constructor($http, vnApp, $translate, $window) {
Object.assign(this, {
$http,
vnApp,
$translate,
storage: $window.localStorage,
user: {},
local: {}
});
this.params = [
'warehouseFk',
'companyFk',
'bankFk'
];
}
initialize() {
for (let param of this.params)
this.local[param] = this.getItem(param);
return this.$http.get('api/UserConfigs/getUserConfig')
.then(res => {
for (let param of this.params)
this.user[param] = res.data[param];
})
.finally(() => this.mergeParams());
}
mergeParams() {
for (let param of this.params) {
let local = this.local[param];
this[param] = local != null && local != ''
? local
: this.user[param];
}
}
setUser(param, value) {
this.user[param] = value;
this.mergeParams();
let params = {[param]: value};
return this.$http.post('api/UserConfigs/setUserConfig', params)
.then(() => this.showSaved());
}
setLocal(param, value) {
this.setItem(param, value);
this.local[param] = value;
this.mergeParams();
this.showSaved();
}
showSaved() {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
}
getItem(key) {
let value = this.storage[key];
return value !== undefined ? JSON.parse(value) : value;
}
setItem(key, value) {
if (value == null)
this.storage.removeItem(key);
else
this.storage[key] = JSON.stringify(value);
}
}
Config.$inject = ['$http', 'vnApp', '$translate', '$window'];
ngModule.service('vnConfig', Config);

View File

@ -1,6 +1,8 @@
import './acl-service';
import './app'; import './app';
import './auth'; import './auth';
import './token'; import './token';
import './modules'; import './modules';
import './interceptor'; import './interceptor';
import './config';

View File

@ -64,14 +64,6 @@ vn-app {
box-sizing: border-box; box-sizing: border-box;
padding: $spacing-md; padding: $spacing-md;
height: inherit; height: inherit;
form vn-horizontal {
align-items: center;
& > * {
padding: .2em;
}
}
} }
vn-main-block { vn-main-block {
display: block; display: block;
@ -102,11 +94,6 @@ vn-app {
.content-block { .content-block {
margin-left: 0; margin-left: 0;
margin-right: 0; margin-right: 0;
form vn-horizontal {
flex-direction: column;
align-items: initial;
}
} }
vn-main-block { vn-main-block {
padding-left: 0; padding-left: 0;
@ -117,3 +104,37 @@ vn-app {
} }
} }
} }
form vn-horizontal {
align-items: center;
& > * {
box-sizing: border-box;
min-height: 2.8em;
padding: 0 $spacing-sm;
&:first-child {
padding-left: 0;
padding-right: $spacing-xs;
}
&:last-child {
padding-left: $spacing-xs;
padding-right: 0;
}
&:first-child:last-child {
padding: 0;
}
}
@media screen and (max-width: $mobile-width) {
flex-direction: column;
align-items: initial;
& > * {
&,
&:first-child,
&:last-child {
padding: 0;
}
}
}
}

View File

@ -6,6 +6,6 @@ import './background/background';
import './side-menu/side-menu'; import './side-menu/side-menu';
import './left-menu/left-menu'; import './left-menu/left-menu';
import './topbar/topbar'; import './topbar/topbar';
import './user-configuration-popover'; import './user-popover';
import './descriptor'; import './descriptor';
import './summary'; import './summary';

View File

@ -3,20 +3,20 @@
<form name="form" ng-submit="$ctrl.submit()"> <form name="form" ng-submit="$ctrl.submit()">
<vn-textfield <vn-textfield
label="User" label="User"
model="$ctrl.user" ng-model="$ctrl.user"
name="user" name="user"
vn-id="userField" vn-id="userField"
vn-focus> vn-focus>
</vn-textfield> </vn-textfield>
<vn-textfield <vn-textfield
label="Password" label="Password"
model="$ctrl.password" ng-model="$ctrl.password"
name="password" name="password"
type="password"> type="password">
</vn-textfield> </vn-textfield>
<vn-check <vn-check
label="Do not close session" label="Do not close session"
field="$ctrl.remember" ng-model="$ctrl.remember"
name="remember"> name="remember">
</vn-check> </vn-check>
<div class="footer"> <div class="footer">

View File

@ -32,10 +32,8 @@ vn-login {
& > form { & > form {
& > vn-textfield { & > vn-textfield {
width: 100%; width: 100%;
margin: 1em 0;
} }
& > vn-check { & > vn-check {
padding-top: .5em;
display: block; display: block;
.md-label { .md-label {
white-space: inherit; white-space: inherit;

View File

@ -1,6 +1,6 @@
<div> <div>
<div <div
ng-click="$ctrl.openUserConfiguration($event)" ng-click="userPopover.show($event)"
id="user" id="user"
class="unselectable"> class="unselectable">
{{$root.user.nickname}} {{$root.user.nickname}}
@ -15,7 +15,7 @@
id="logout" id="logout"
icon="exit_to_app" icon="exit_to_app"
translate-attr="{title: 'Logout'}" translate-attr="{title: 'Logout'}"
ng-click="$ctrl.onLogoutClick()"> ng-click="$ctrl.vnAuth.logout()">
</vn-icon-button> </vn-icon-button>
</div> </div>
<vn-menu vn-id="apps-menu"> <vn-menu vn-id="apps-menu">
@ -26,6 +26,6 @@
</li> </li>
</ul> </ul>
</vn-menu> </vn-menu>
<vn-user-configuration-popover <vn-user-popover
vn-id="popover"> vn-id="user-popover">
</vn-user-configuration-popover> </vn-user-popover>

View File

@ -21,14 +21,6 @@ export default class MainMenu {
window.localStorage.currentUserWorkerId = json.data.workerId; window.localStorage.currentUserWorkerId = json.data.workerId;
}); });
} }
openUserConfiguration(event) {
this.$.popover.show(event);
}
onLogoutClick() {
this.vnAuth.logout();
}
} }
MainMenu.$inject = ['$scope', '$http', 'vnAuth', 'vnModules']; MainMenu.$inject = ['$scope', '$http', 'vnAuth', 'vnModules'];

View File

@ -1,158 +0,0 @@
import ngModule from '../../module';
import './style.scss';
let languages = {
es: 'Español',
en: 'English',
ca: 'Català',
pt: 'Português',
fr: 'Français',
nl: 'Nederlands',
mn: 'Монгол хэл'
};
class Controller {
constructor($scope, $http, $state, vnApp, $translate) {
this.$scope = $scope;
this.$http = $http;
this.$state = $state;
this.vnApp = vnApp;
this.$translate = $translate;
this.getUserConfig();
this.lang = $translate.use();
this.langs = [];
for (let code of $translate.getAvailableLanguageKeys()) {
this.langs.push({
code: code,
name: languages[code] ? languages[code] : code
});
}
}
$onInit() {
if (window.localStorage.localBankFk && window.localStorage.localBankFk !== 'null')
window.localStorage.defaultBankFk = window.localStorage.localBankFk;
else
localStorage.removeItem('defaultBankFk');
}
set lang(value) {
this._lang = value;
this.$translate.use(value);
}
get lang() {
return this._lang;
}
set localBankFk(value) {
window.localStorage.localBankFk = value;
window.localStorage.defaultBankFk = value;
this.showOk();
}
get localBankFk() {
return parseInt(window.localStorage.localBankFk);
}
set localWarehouseFk(value) {
window.localStorage.localWarehouseFk = value;
if (!value && this.warehouseFk)
window.localStorage.defaultWarehouseFk = this.warehouseFk;
else
window.localStorage.defaultWarehouseFk = value;
this.showOk();
}
get localWarehouseFk() {
return parseInt(window.localStorage.localWarehouseFk);
}
set localCompanyFk(value) {
window.localStorage.localCompanyFk = value;
if (!value && this.companyFk)
window.localStorage.defaultCompanyFk = this.companyFk;
else
window.localStorage.defaultCompanyFk = value;
this.showOk();
}
get localCompanyFk() {
return parseInt(window.localStorage.localCompanyFk);
}
set warehouseFk(value) {
this.warehouse = value;
if (value &&
(!window.localStorage.localWarehouseFk || window.localStorage.localWarehouseFk === 'null'))
window.localStorage.defaultWarehouseFk = value;
this.setUserConfig('warehouseFk', value);
}
get warehouseFk() {
return this.warehouse;
}
set companyFk(value) {
this.company = value;
if (value &&
(!window.localStorage.localCompanyFk || window.localStorage.localCompanyFk === 'null'))
window.localStorage.setItem('defaultCompanyFk', value);
this.setUserConfig('companyFk', value);
}
get companyFk() {
return this.company;
}
showOk() {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
}
show(event) {
this.$scope.warehouses.refresh();
this.$scope.companies.refresh();
this.$scope.popover.parent = event.target;
this.$scope.popover.show();
}
getUserConfig() {
this.$http.get('/api/UserConfigs/getUserConfig')
.then(res => {
if (res.data && res.data.warehouseFk) {
this.warehouse = res.data.warehouseFk;
if (res.data.warehouseFk && !window.localStorage.localWarehouseFk)
window.localStorage.setItem('defaultWarehouseFk', res.data.warehouseFk);
}
if (res.data && res.data.companyFk) {
this.company = res.data.companyFk;
if (res.data.companyFk && !window.localStorage.localCompanyFk)
window.localStorage.setItem('defaultCompanyFk', res.data.companyFk);
}
});
}
setUserConfig(property, value) {
let params = {};
params[property] = value;
this.$http.post('/api/UserConfigs/setUserConfig', params)
.then(() => {
this.showOk();
});
}
searchLocalBank(a, b) {
return angular.equals(a.id, b.id);
}
}
Controller.$inject = ['$scope', '$http', '$state', 'vnApp', '$translate'];
ngModule.component('vnUserConfigurationPopover', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,96 +0,0 @@
import './index.js';
describe('Salix', () => {
describe('Component vnUserConfigurationPopover', () => {
let controller;
let $httpBackend;
let $scope;
beforeEach(angular.mock.module('salix', $translateProvider => {
$translateProvider.translations('en', {});
}));
beforeEach(angular.mock.inject(($componentController, _$httpBackend_, $rootScope) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
controller = $componentController('vnUserConfigurationPopover', {$scope});
}));
describe('localBankFk() setter', () => {
it('should set window.localStorage.localBank and call showOk', () => {
spyOn(controller, 'showOk');
controller.localBankFk = 4;
expect(window.localStorage.localBankFk).toBe('4');
expect(controller.showOk).toHaveBeenCalledWith();
});
});
describe('localWarehouseFk() setter', () => {
it('should set window.localStorage.localWarehouse and call showOk', () => {
spyOn(controller, 'showOk');
controller.localWarehouseFk = 4;
expect(window.localStorage.localWarehouseFk).toBe('4');
expect(controller.showOk).toHaveBeenCalledWith();
});
});
describe('localCompanyFk() setter', () => {
it('should set window.localStorage.localCompany and call showOk', () => {
spyOn(controller, 'showOk');
controller.localCompanyFk = 4;
expect(window.localStorage.localCompanyFk).toBe('4');
expect(controller.showOk).toHaveBeenCalledWith();
});
});
describe('warehouseFk() setter', () => {
it('should set warehouse and call setUserConfig', () => {
spyOn(controller, 'setUserConfig');
controller.warehouseFk = 4;
expect(controller.warehouse).toBe(4);
expect(controller.setUserConfig).toHaveBeenCalledWith('warehouseFk', 4);
});
});
describe('companyFk() setter', () => {
it('should set company and call setUserConfig', () => {
spyOn(controller, 'setUserConfig');
controller.companyFk = 4;
expect(controller.company).toBe(4);
expect(controller.setUserConfig).toHaveBeenCalledWith('companyFk', 4);
});
});
describe('getUserConfig()', () => {
it('should make a query, set company and not set warehouse if its not in the response', () => {
$httpBackend.when('GET', `/api/UserConfigs/getUserConfig`).respond({companyFk: 2});
$httpBackend.expect('GET', `/api/UserConfigs/getUserConfig`);
controller.getUserConfig();
$httpBackend.flush();
expect(controller.warehouse).toBeUndefined();
expect(controller.company).toEqual(2);
});
});
describe('setUserConfig()', () => {
it('should make a query with the property given and call showOk', () => {
spyOn(controller, 'showOk');
controller.company = 1;
$httpBackend.when('GET', `/api/UserConfigs/getUserConfig`).respond({companyFk: 2});
$httpBackend.expect('GET', `/api/UserConfigs/getUserConfig`);
$httpBackend.when('POST', `/api/UserConfigs/setUserConfig`, {companyFk: 1}).respond(200);
$httpBackend.expect('POST', `/api/UserConfigs/setUserConfig`);
controller.setUserConfig('companyFk', 1);
$httpBackend.flush();
expect(controller.showOk).toHaveBeenCalledWith();
});
});
});
});

View File

@ -11,7 +11,7 @@
order="code"> order="code">
</vn-crud-model> </vn-crud-model>
<vn-popover vn-id="popover"> <vn-popover vn-id="popover">
<vn-vertical class="user-configuration vn-pa-md"> <vn-vertical class="user-popover vn-pa-md">
<div class="profile-card vn-pb-md"> <div class="profile-card vn-pb-md">
<vn-icon icon="person"></vn-icon> <vn-icon icon="person"></vn-icon>
<div class="vn-pl-md"> <div class="vn-pl-md">
@ -29,20 +29,18 @@
</div> </div>
</div> </div>
<vn-autocomplete <vn-autocomplete
vn-one
label="Local warehouse" label="Local warehouse"
id="localWarehouse" id="localWarehouse"
field="$ctrl.localWarehouseFk" ng-model="$ctrl.localWarehouseFk"
data="warehousesData" data="warehousesData"
select-fields="['id','name']" select-fields="['id','name']"
show-field="name" show-field="name"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-autocomplete
vn-one
label="Local bank" label="Local bank"
id="localBank" id="localBank"
field="$ctrl.localBankFk" ng-model="$ctrl.localBankFk"
url="/api/Banks" url="/api/Banks"
select-fields="['id','bank']" select-fields="['id','bank']"
show-field="bank" show-field="bank"
@ -52,39 +50,35 @@
<tpl-item>{{id}}: {{bank}}</tpl-item> <tpl-item>{{id}}: {{bank}}</tpl-item>
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-autocomplete
vn-one
label="Local company" label="Local company"
id="localCompany" id="localCompany"
field="$ctrl.localCompanyFk" ng-model="$ctrl.localCompanyFk"
data="companiesData" data="companiesData"
select-fields="['id','code']" select-fields="['id','code']"
show-field="code" show-field="code"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-autocomplete
vn-one
label="User warehouse" label="User warehouse"
id="userWarehouse" id="userWarehouse"
field="$ctrl.warehouseFk" ng-model="$ctrl.warehouseFk"
data="warehousesData" data="warehousesData"
select-fields="['id', 'name']" select-fields="['id', 'name']"
show-field="name" show-field="name"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-autocomplete
vn-one
label="User company" label="User company"
id="userCompany" id="userCompany"
field="$ctrl.companyFk" ng-model="$ctrl.companyFk"
data="companiesData" data="companiesData"
select-fields="['id', 'code']" select-fields="['id', 'code']"
show-field="code" show-field="code"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-autocomplete
vn-one
label="Language" label="Language"
field="$ctrl.lang" ng-model="$ctrl.lang"
data="$ctrl.langs" data="$ctrl.langs"
show-field="name" show-field="name"
value-field="code"> value-field="code">

View File

@ -0,0 +1,94 @@
import ngModule from '../../module';
import './style.scss';
let languages = {
es: 'Español',
en: 'English',
ca: 'Català',
pt: 'Português',
fr: 'Français',
nl: 'Nederlands',
mn: 'Монгол хэл'
};
class Controller {
constructor($, $translate, vnConfig) {
Object.assign(this, {
$,
$translate,
vnConfig,
lang: $translate.use(),
langs: []
});
for (let code of $translate.getAvailableLanguageKeys()) {
this.langs.push({
code: code,
name: languages[code] ? languages[code] : code
});
}
vnConfig.initialize();
}
set lang(value) {
this._lang = value;
this.$translate.use(value);
}
get lang() {
return this._lang;
}
set localBankFk(value) {
this.vnConfig.setLocal('bankFk', value);
}
get localBankFk() {
return this.vnConfig.local.bankFk;
}
set localWarehouseFk(value) {
this.vnConfig.setLocal('warehouseFk', value);
}
get localWarehouseFk() {
return this.vnConfig.local.warehouseFk;
}
set localCompanyFk(value) {
this.vnConfig.setLocal('companyFk', value);
}
get localCompanyFk() {
return this.vnConfig.local.companyFk;
}
set warehouseFk(value) {
this.vnConfig.setUser('warehouseFk', value);
}
get warehouseFk() {
return this.vnConfig.user.warehouseFk;
}
set companyFk(value) {
this.vnConfig.setUser('companyFk', value);
}
get companyFk() {
return this.vnConfig.user.companyFk;
}
show(event) {
this.$.warehouses.refresh();
this.$.companies.refresh();
this.$.popover.show(event.target);
}
}
Controller.$inject = ['$scope', '$translate', 'vnConfig'];
ngModule.component('vnUserPopover', {
template: require('./index.html'),
controller: Controller
});

Some files were not shown because too many files have changed in this diff Show More