merge
gitea/salix/dev This commit looks good
Details
gitea/salix/dev This commit looks good
Details
This commit is contained in:
commit
e267bf93a4
|
@ -94,7 +94,7 @@ module.exports = Self => {
|
|||
const models = Self.app.models;
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
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 newDms = await Self.create({
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
ALTER TABLE `vn2008`.`department`
|
||||
ADD COLUMN `parentFk` INT UNSIGNED NULL AFTER `sons`,
|
||||
ADD COLUMN `path` VARCHAR(255) NULL AFTER `parentFk`,
|
||||
CHANGE COLUMN `sons` `sons` DECIMAL(10,0) NOT NULL DEFAULT '0' ;
|
||||
|
||||
USE `vn`;
|
||||
CREATE
|
||||
OR REPLACE ALGORITHM = UNDEFINED
|
||||
DEFINER = `root`@`%`
|
||||
SQL SECURITY DEFINER
|
||||
VIEW `department` AS
|
||||
SELECT
|
||||
`b`.`department_id` AS `id`,
|
||||
`b`.`name` AS `name`,
|
||||
`b`.`production` AS `isProduction`,
|
||||
`b`.`parentFk` AS `parentFk`,
|
||||
`b`.`path` AS `path`,
|
||||
`b`.`lft` AS `lft`,
|
||||
`b`.`rgt` AS `rgt`,
|
||||
`b`.`isSelected` AS `isSelected`,
|
||||
`b`.`depth` AS `depth`,
|
||||
`b`.`sons` AS `sons`
|
||||
FROM
|
||||
`vn2008`.`department` `b`;
|
||||
|
||||
DROP TRIGGER IF EXISTS `vn2008`.`department_AFTER_DELETE`;
|
||||
|
||||
DELIMITER $$
|
||||
USE `vn2008`$$
|
||||
CREATE DEFINER = CURRENT_USER TRIGGER `vn2008`.`department_AFTER_DELETE`
|
||||
AFTER DELETE ON `department` FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE vn.department_recalc SET isChanged = TRUE;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
DROP TRIGGER IF EXISTS `vn2008`.`department_BEFORE_INSERT`;
|
||||
|
||||
DELIMITER $$
|
||||
USE `vn2008`$$
|
||||
CREATE DEFINER = CURRENT_USER TRIGGER `vn2008`.`department_BEFORE_INSERT`
|
||||
BEFORE INSERT ON `department` FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE vn.department_recalc SET isChanged = TRUE;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
DROP TRIGGER IF EXISTS `vn2008`.`department_AFTER_UPDATE`;
|
||||
|
||||
DELIMITER $$
|
||||
USE `vn2008`$$
|
||||
CREATE DEFINER = CURRENT_USER TRIGGER `vn2008`.`department_AFTER_UPDATE`
|
||||
AFTER UPDATE ON `department` FOR EACH ROW
|
||||
BEGIN
|
||||
IF !(OLD.parentFk <=> NEW.parentFk) THEN
|
||||
UPDATE vn.department_recalc SET isChanged = TRUE;
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
CREATE TABLE `vn`.`department_recalc` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`isChanged` TINYINT(4) NOT NULL,
|
||||
PRIMARY KEY (`id`));
|
||||
|
||||
INSERT INTO `vn`.`department_recalc` (`id`, `isChanged`) VALUES ('1', '0');
|
||||
|
||||
|
||||
ALTER TABLE `vn2008`.`department`
|
||||
CHANGE COLUMN `lft` `lft` INT(11) NULL ,
|
||||
CHANGE COLUMN `rgt` `rgt` INT(11) NULL ;
|
||||
|
||||
ALTER TABLE `vn2008`.`department`
|
||||
DROP INDEX `rgt_UNIQUE` ,
|
||||
DROP INDEX `lft_UNIQUE` ;
|
||||
;
|
||||
|
||||
ALTER TABLE `vn2008`.`department`
|
||||
ADD INDEX `lft_rgt_depth_idx` (`lft` ASC, `rgt` ASC, `depth` ASC);
|
||||
;
|
||||
|
||||
|
||||
UPDATE vn.department SET lft = NULL, rgt = NULL;
|
|
@ -0,0 +1,37 @@
|
|||
USE `vn`;
|
||||
DROP procedure IF EXISTS `department_calcTree`;
|
||||
|
||||
DELIMITER $$
|
||||
USE `vn`$$
|
||||
CREATE DEFINER=`root`@`%` PROCEDURE `department_calcTree`()
|
||||
BEGIN
|
||||
/**
|
||||
* Calculates the #path, #lft, #rgt, #sons and #depth columns of
|
||||
* the #department table. To build the tree, it uses the #parentFk
|
||||
* column.
|
||||
*/
|
||||
DECLARE vIndex INT DEFAULT 0;
|
||||
DECLARE vSons INT;
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS tNestedTree;
|
||||
CREATE TEMPORARY TABLE tNestedTree
|
||||
SELECT id, path, lft, rgt, depth, sons
|
||||
FROM department LIMIT 0;
|
||||
|
||||
SET max_sp_recursion_depth = 5;
|
||||
CALL department_calcTreeRec(NULL, '/', 0, vIndex, vSons);
|
||||
SET max_sp_recursion_depth = 0;
|
||||
|
||||
UPDATE department z
|
||||
JOIN tNestedTree t ON t.id = z.id
|
||||
SET z.path = t.path,
|
||||
z.lft = t.lft,
|
||||
z.rgt = t.rgt,
|
||||
z.depth = t.depth,
|
||||
z.sons = t.sons;
|
||||
|
||||
DROP TEMPORARY TABLE tNestedTree;
|
||||
END$$
|
||||
|
||||
DELIMITER ;
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
USE `vn`;
|
||||
DROP procedure IF EXISTS `department_calcTreeRec`;
|
||||
|
||||
DELIMITER $$
|
||||
USE `vn`$$
|
||||
CREATE DEFINER=`root`@`%` PROCEDURE `department_calcTreeRec`(
|
||||
vSelf INT,
|
||||
vPath VARCHAR(255),
|
||||
vDepth INT,
|
||||
INOUT vIndex INT,
|
||||
OUT vSons INT
|
||||
)
|
||||
BEGIN
|
||||
/**
|
||||
* Calculates and sets the #path, #lft, #rgt, #sons and #depth
|
||||
* columns for all children of the passed node. Once calculated
|
||||
* the last node rgt index and the number of sons are returned.
|
||||
* To update it's children, this procedure calls itself recursively
|
||||
* for each one.
|
||||
*
|
||||
* @vSelf The node identifier
|
||||
* @vPath The initial path
|
||||
* @vDepth The initial depth
|
||||
* @vIndex The initial lft index
|
||||
* @vSons The number of direct sons
|
||||
*/
|
||||
DECLARE vChildFk INT;
|
||||
DECLARE vLft INT;
|
||||
DECLARE vMySons INT;
|
||||
DECLARE vDone BOOL;
|
||||
DECLARE vChildren CURSOR FOR
|
||||
SELECT id FROM department
|
||||
WHERE (vSelf IS NULL AND parentFk IS NULL)
|
||||
OR (vSelf IS NOT NULL AND parentFk = vSelf);
|
||||
|
||||
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
|
||||
|
||||
SET vSons = 0;
|
||||
|
||||
OPEN vChildren;
|
||||
myLoop: LOOP
|
||||
SET vDone = FALSE;
|
||||
FETCH vChildren INTO vChildFk;
|
||||
|
||||
IF vDone THEN
|
||||
LEAVE myLoop;
|
||||
END IF;
|
||||
|
||||
SET vIndex = vIndex + 1;
|
||||
SET vLft = vIndex;
|
||||
SET vSons = vSons + 1;
|
||||
|
||||
CALL department_calcTreeRec(
|
||||
vChildFk,
|
||||
CONCAT(vPath, vChildFk, '/'),
|
||||
vDepth + 1,
|
||||
vIndex,
|
||||
vMySons
|
||||
);
|
||||
|
||||
SET vIndex = vIndex + 1;
|
||||
|
||||
INSERT INTO tNestedTree
|
||||
SET id = vChildFk,
|
||||
path = vPath,
|
||||
lft = vLft,
|
||||
rgt = vIndex,
|
||||
depth = vDepth,
|
||||
sons = vMySons;
|
||||
END LOOP;
|
||||
CLOSE vChildren;
|
||||
END$$
|
||||
|
||||
DELIMITER ;
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
USE `vn`;
|
||||
DROP procedure IF EXISTS `department_doCalc`;
|
||||
|
||||
DELIMITER $$
|
||||
USE `vn`$$
|
||||
CREATE DEFINER=`root`@`%` PROCEDURE `department_doCalc`()
|
||||
proc: BEGIN
|
||||
/**
|
||||
* Recalculates the department tree.
|
||||
*/
|
||||
DECLARE vIsChanged BOOL;
|
||||
|
||||
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
|
||||
BEGIN
|
||||
DO RELEASE_LOCK('vn.department_doCalc');
|
||||
RESIGNAL;
|
||||
END;
|
||||
|
||||
IF !GET_LOCK('vn.department_doCalc', 0) THEN
|
||||
LEAVE proc;
|
||||
END IF;
|
||||
|
||||
SELECT isChanged INTO vIsChanged
|
||||
FROM department_recalc;
|
||||
|
||||
IF vIsChanged THEN
|
||||
UPDATE department_recalc SET isChanged = FALSE;
|
||||
CALL vn.department_calcTree;
|
||||
END IF;
|
||||
|
||||
DO RELEASE_LOCK('vn.department_doCalc');
|
||||
END$$
|
||||
|
||||
DELIMITER ;
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
USE `vn`;
|
||||
DROP procedure IF EXISTS `department_getLeaves`;
|
||||
|
||||
DELIMITER $$
|
||||
USE `vn`$$
|
||||
CREATE DEFINER=`root`@`%` PROCEDURE `department_getLeaves`(
|
||||
vParentFk INT,
|
||||
vSearch VARCHAR(255)
|
||||
)
|
||||
BEGIN
|
||||
DECLARE vIsNumber BOOL;
|
||||
DECLARE vIsSearch BOOL DEFAULT vSearch IS NOT NULL AND vSearch != '';
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS tNodes;
|
||||
CREATE TEMPORARY TABLE tNodes
|
||||
(UNIQUE (id))
|
||||
ENGINE = MEMORY
|
||||
SELECT id FROM department LIMIT 0;
|
||||
|
||||
IF vIsSearch THEN
|
||||
SET vIsNumber = vSearch REGEXP '^[0-9]+$';
|
||||
|
||||
INSERT INTO tNodes
|
||||
SELECT id FROM department
|
||||
WHERE (vIsNumber AND `name` = vSearch)
|
||||
OR (!vIsNumber AND `name` LIKE CONCAT('%', vSearch, '%'))
|
||||
LIMIT 1000;
|
||||
END IF;
|
||||
|
||||
IF vParentFk IS NULL THEN
|
||||
DROP TEMPORARY TABLE IF EXISTS tChilds;
|
||||
CREATE TEMPORARY TABLE tChilds
|
||||
ENGINE = MEMORY
|
||||
SELECT id FROM tNodes;
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS tParents;
|
||||
CREATE TEMPORARY TABLE tParents
|
||||
ENGINE = MEMORY
|
||||
SELECT id FROM department LIMIT 0;
|
||||
|
||||
myLoop: LOOP
|
||||
DELETE FROM tParents;
|
||||
INSERT INTO tParents
|
||||
SELECT parentFk id
|
||||
FROM department g
|
||||
JOIN tChilds c ON c.id = g.id
|
||||
WHERE g.parentFk IS NOT NULL;
|
||||
|
||||
INSERT IGNORE INTO tNodes
|
||||
SELECT id FROM tParents;
|
||||
|
||||
IF ROW_COUNT() = 0 THEN
|
||||
LEAVE myLoop;
|
||||
END IF;
|
||||
|
||||
DELETE FROM tChilds;
|
||||
INSERT INTO tChilds
|
||||
SELECT id FROM tParents;
|
||||
END LOOP;
|
||||
|
||||
DROP TEMPORARY TABLE
|
||||
tChilds,
|
||||
tParents;
|
||||
END IF;
|
||||
|
||||
IF !vIsSearch THEN
|
||||
INSERT IGNORE INTO tNodes
|
||||
SELECT id FROM department
|
||||
WHERE parentFk <=> vParentFk;
|
||||
END IF;
|
||||
|
||||
SELECT d.id,
|
||||
d.`name`,
|
||||
d.parentFk,
|
||||
d.sons
|
||||
FROM department d
|
||||
JOIN tNodes n ON n.id = d.id
|
||||
ORDER BY depth, `name`;
|
||||
|
||||
DROP TEMPORARY TABLE tNodes;
|
||||
END$$
|
||||
|
||||
DELIMITER ;
|
||||
|
|
@ -471,7 +471,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()),
|
||||
(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()),
|
||||
(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()),
|
||||
(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)),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
vnTextfield: 'vn-textfield > div > div > div > input',
|
||||
vnInputNumber: 'vn-input-number > div > div > div > input',
|
||||
vnTextfield: 'vn-textfield input',
|
||||
vnInputNumber: 'vn-input-number input',
|
||||
vnSubmit: 'vn-submit > input',
|
||||
vnFloatButton: 'vn-float-button > button'
|
||||
};
|
||||
|
|
|
@ -18,8 +18,11 @@ let actions = {
|
|||
|
||||
clearInput: function(selector, done) {
|
||||
this.wait(selector)
|
||||
.evaluate(inputSelector => {
|
||||
return document.querySelector(inputSelector).closest('*[model], *[field], *[value]').$ctrl.value = '';
|
||||
.evaluate(selector => {
|
||||
let $ctrl = document.querySelector(selector).closest('.vn-field').$ctrl;
|
||||
$ctrl.field = null;
|
||||
$ctrl.$.$apply();
|
||||
$ctrl.input.dispatchEvent(new Event('change'));
|
||||
}, selector)
|
||||
.then(done)
|
||||
.catch(done);
|
||||
|
@ -31,6 +34,7 @@ let actions = {
|
|||
|
||||
let doLogin = () => {
|
||||
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=password]`, 'nightmare')
|
||||
.click(`vn-login input[type=submit]`)
|
||||
|
@ -75,7 +79,7 @@ let actions = {
|
|||
},
|
||||
|
||||
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')
|
||||
.wait(langSelector)
|
||||
|
@ -167,8 +171,8 @@ let actions = {
|
|||
|
||||
focusElement: function(selector, done) {
|
||||
this.wait(selector)
|
||||
.evaluate_now(elemenetSelector => {
|
||||
let element = document.querySelector(elemenetSelector);
|
||||
.evaluate_now(selector => {
|
||||
let element = document.querySelector(selector);
|
||||
element.focus();
|
||||
}, done, selector)
|
||||
.then(done)
|
||||
|
@ -401,8 +405,7 @@ let actions = {
|
|||
},
|
||||
|
||||
autocompleteSearch: function(autocompleteSelector, searchValue, done) {
|
||||
this.wait(`${autocompleteSelector} input`)
|
||||
.waitToClick(`${autocompleteSelector} input`)
|
||||
this.waitToClick(`${autocompleteSelector} input`)
|
||||
.write(`.vn-popover.shown .vn-drop-down input`, searchValue)
|
||||
.waitToClick(`.vn-popover.shown .vn-drop-down li.active`)
|
||||
.wait((autocompleteSelector, searchValue) => {
|
||||
|
@ -412,7 +415,7 @@ let actions = {
|
|||
}, autocompleteSelector, searchValue)
|
||||
.then(done)
|
||||
.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);
|
||||
},
|
||||
|
||||
datePicker: function(datePickerSelector, changeMonth, day, done) {
|
||||
this.wait(datePickerSelector)
|
||||
.mousedown(datePickerSelector)
|
||||
.wait('div.flatpickr-calendar.open');
|
||||
datePicker: function(selector, changeMonth, day, done) {
|
||||
this.wait(selector)
|
||||
.mousedown(`${selector} input`)
|
||||
.wait('.flatpickr-calendar.open');
|
||||
|
||||
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)
|
||||
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;
|
||||
|
||||
if (!day)
|
||||
daySelector = 'div.flatpickr-calendar.open span.flatpickr-day:nth-child(16)';
|
||||
|
||||
daySelector = `.flatpickr-calendar.open .flatpickr-day:nth-child(16)`;
|
||||
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 => {
|
||||
return document.querySelector(selector);
|
||||
|
|
|
@ -11,14 +11,14 @@ export default {
|
|||
claimsButton: '.modules-menu > li[ui-sref="claim.index"]',
|
||||
returnToModuleIndexButton: 'a[ui-sref="order.index"]',
|
||||
userMenuButton: 'vn-topbar #user',
|
||||
userLocalWarehouse: '.user-configuration vn-autocomplete[field="$ctrl.localWarehouseFk"]',
|
||||
userLocalBank: '.user-configuration vn-autocomplete[field="$ctrl.localBankFk"]',
|
||||
userLocalCompany: '.user-configuration vn-autocomplete[field="$ctrl.localCompanyFk"]',
|
||||
userWarehouse: '.user-configuration vn-autocomplete[field="$ctrl.warehouseFk"]',
|
||||
userCompany: '.user-configuration vn-autocomplete[field="$ctrl.companyFk"]',
|
||||
userConfigFirstAutocompleteClear: '#localWarehouse > div > div > div > vn-icon.clear',
|
||||
userConfigSecondAutocompleteClear: '#localBank > div > div > div > vn-icon.clear',
|
||||
userConfigThirdAutocompleteClear: '#localCompany > div > div > div > vn-icon.clear',
|
||||
userLocalWarehouse: '.user-popover vn-autocomplete[ng-model="$ctrl.localWarehouseFk"]',
|
||||
userLocalBank: '.user-popover vn-autocomplete[ng-model="$ctrl.localBankFk"]',
|
||||
userLocalCompany: '.user-popover vn-autocomplete[ng-model="$ctrl.localCompanyFk"]',
|
||||
userWarehouse: '.user-popover vn-autocomplete[ng-model="$ctrl.warehouseFk"]',
|
||||
userCompany: '.user-popover vn-autocomplete[ng-model="$ctrl.companyFk"]',
|
||||
userConfigFirstAutocompleteClear: '#localWarehouse .icons > vn-icon[icon=clear]',
|
||||
userConfigSecondAutocompleteClear: '#localBank .icons > vn-icon[icon=clear]',
|
||||
userConfigThirdAutocompleteClear: '#localCompany .icons > vn-icon[icon=clear]',
|
||||
acceptButton: 'vn-confirm button[response=ACCEPT]'
|
||||
},
|
||||
clientsIndex: {
|
||||
|
@ -35,11 +35,11 @@ export default {
|
|||
street: `${components.vnTextfield}[name="street"]`,
|
||||
postcode: `${components.vnTextfield}[name="postcode"]`,
|
||||
city: `${components.vnTextfield}[name="city"]`,
|
||||
province: `vn-autocomplete[field="$ctrl.client.provinceFk"]`,
|
||||
country: `vn-autocomplete[field="$ctrl.client.countryFk"]`,
|
||||
province: `vn-autocomplete[ng-model="$ctrl.client.provinceFk"]`,
|
||||
country: `vn-autocomplete[ng-model="$ctrl.client.countryFk"]`,
|
||||
userName: `${components.vnTextfield}[name="userName"]`,
|
||||
email: `${components.vnTextfield}[name="email"]`,
|
||||
salesPersonAutocomplete: `vn-autocomplete[field="$ctrl.client.salesPersonFk"]`,
|
||||
salesPersonAutocomplete: `vn-autocomplete[ng-model="$ctrl.client.salesPersonFk"]`,
|
||||
createButton: `${components.vnSubmit}`,
|
||||
cancelButton: 'vn-button[href="#!/client/index"]'
|
||||
},
|
||||
|
@ -49,13 +49,13 @@ export default {
|
|||
},
|
||||
clientBasicData: {
|
||||
basicDataButton: 'vn-left-menu a[ui-sref="client.card.basicData"]',
|
||||
nameInput: 'vn-textfield[field="$ctrl.client.name"] input',
|
||||
contactInput: 'vn-textfield[field="$ctrl.client.contact"] input',
|
||||
phoneInput: 'vn-textfield[field="$ctrl.client.phone"] input',
|
||||
mobileInput: 'vn-textfield[field="$ctrl.client.mobile"] input',
|
||||
emailInput: 'vn-textfield[field="$ctrl.client.email"] input',
|
||||
salesPersonAutocomplete: 'vn-autocomplete[field="$ctrl.client.salesPersonFk"]',
|
||||
channelAutocomplete: 'vn-autocomplete[field="$ctrl.client.contactChannelFk"]',
|
||||
nameInput: 'vn-textfield[ng-model="$ctrl.client.name"] input',
|
||||
contactInput: 'vn-textfield[ng-model="$ctrl.client.contact"] input',
|
||||
phoneInput: 'vn-textfield[ng-model="$ctrl.client.phone"] input',
|
||||
mobileInput: 'vn-textfield[ng-model="$ctrl.client.mobile"] input',
|
||||
emailInput: 'vn-textfield[ng-model="$ctrl.client.email"] input',
|
||||
salesPersonAutocomplete: 'vn-autocomplete[ng-model="$ctrl.client.salesPersonFk"]',
|
||||
channelAutocomplete: 'vn-autocomplete[ng-model="$ctrl.client.contactChannelFk"]',
|
||||
saveButton: `${components.vnSubmit}`
|
||||
},
|
||||
clientFiscalData: {
|
||||
|
@ -67,8 +67,8 @@ export default {
|
|||
addressInput: `${components.vnTextfield}[name="street"]`,
|
||||
postcodeInput: `${components.vnTextfield}[name="postcode"]`,
|
||||
cityInput: `${components.vnTextfield}[name="city"]`,
|
||||
provinceAutocomplete: 'vn-autocomplete[field="$ctrl.client.provinceFk"]',
|
||||
countryAutocomplete: 'vn-autocomplete[field="$ctrl.client.countryFk"]',
|
||||
provinceAutocomplete: 'vn-autocomplete[ng-model="$ctrl.client.provinceFk"]',
|
||||
countryAutocomplete: 'vn-autocomplete[ng-model="$ctrl.client.countryFk"]',
|
||||
activeCheckbox: 'vn-check[label="Active"]',
|
||||
frozenCheckbox: 'vn-check[label="Frozen"]',
|
||||
invoiceByAddressCheckbox: 'vn-check[label="Invoice by address"]',
|
||||
|
@ -79,14 +79,14 @@ export default {
|
|||
saveButton: `${components.vnSubmit}`
|
||||
},
|
||||
clientBillingData: {
|
||||
payMethodAutocomplete: 'vn-autocomplete[field="$ctrl.client.payMethodFk"]',
|
||||
IBANInput: `${components.vnTextfield}[name="iban"]`,
|
||||
dueDayInput: `${components.vnInputNumber}[name="dueDay"]`,
|
||||
receivedCoreLCRCheckbox: 'vn-check[label="Received LCR"]',
|
||||
receivedCoreVNLCheckbox: 'vn-check[label="Received core VNL"]',
|
||||
receivedB2BVNLCheckbox: 'vn-check[label="Received B2B VNL"]',
|
||||
swiftBicAutocomplete: 'vn-client-billing-data vn-autocomplete[field="$ctrl.client.bankEntityFk"]',
|
||||
clearswiftBicButton: 'vn-client-billing-data vn-autocomplete[field="$ctrl.client.bankEntityFk"] > div > div > div > vn-icon > i',
|
||||
payMethodAutocomplete: 'vn-client-billing-data vn-autocomplete[ng-model="$ctrl.client.payMethodFk"]',
|
||||
IBANInput: `vn-client-billing-data ${components.vnTextfield}[name="iban"]`,
|
||||
dueDayInput: `vn-client-billing-data ${components.vnInputNumber}[name="dueDay"]`,
|
||||
receivedCoreLCRCheckbox: 'vn-client-billing-data vn-check[label="Received LCR"]',
|
||||
receivedCoreVNLCheckbox: 'vn-client-billing-data vn-check[label="Received core VNL"]',
|
||||
receivedB2BVNLCheckbox: 'vn-client-billing-data vn-check[label="Received B2B VNL"]',
|
||||
swiftBicAutocomplete: 'vn-client-billing-data vn-autocomplete[ng-model="$ctrl.client.bankEntityFk"]',
|
||||
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',
|
||||
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',
|
||||
|
@ -102,8 +102,8 @@ export default {
|
|||
streetAddressInput: `${components.vnTextfield}[name="street"]`,
|
||||
postcodeInput: `${components.vnTextfield}[name="postalCode"]`,
|
||||
cityInput: `${components.vnTextfield}[name="city"]`,
|
||||
provinceAutocomplete: 'vn-autocomplete[field="$ctrl.address.provinceFk"]',
|
||||
agencyAutocomplete: 'vn-autocomplete[field="$ctrl.address.agencyModeFk"]',
|
||||
provinceAutocomplete: 'vn-autocomplete[ng-model="$ctrl.address.provinceFk"]',
|
||||
agencyAutocomplete: 'vn-autocomplete[ng-model="$ctrl.address.agencyModeFk"]',
|
||||
phoneInput: `${components.vnTextfield}[name="phone"]`,
|
||||
mobileInput: `${components.vnTextfield}[name="mobile"]`,
|
||||
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',
|
||||
activeCheckbox: 'vn-check[label="Enabled"]',
|
||||
equalizationTaxCheckbox: 'vn-client-address-edit vn-check[label="Is equalizated"]',
|
||||
firstObservationTypeAutocomplete: 'vn-client-address-edit [name=observations] :nth-child(1) [field="observation.observationTypeFk"]',
|
||||
firstObservationDescriptionInput: 'vn-client-address-edit [name=observations] :nth-child(1) [model="observation.description"] input',
|
||||
secondObservationTypeAutocomplete: 'vn-client-address-edit [name=observations] :nth-child(2) [field="observation.observationTypeFk"]',
|
||||
secondObservationDescriptionInput: 'vn-client-address-edit [name=observations] :nth-child(2) [model="observation.description"] input',
|
||||
firstObservationTypeAutocomplete: 'vn-client-address-edit [name=observations] :nth-child(1) [ng-model="observation.observationTypeFk"]',
|
||||
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) [ng-model="observation.observationTypeFk"]',
|
||||
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"]',
|
||||
saveButton: `${components.vnSubmit}`,
|
||||
cancelCreateAddressButton: 'button[ui-sref="client.card.address.index"]',
|
||||
|
@ -143,7 +143,7 @@ export default {
|
|||
addGreugeFloatButton: `${components.vnFloatButton}`,
|
||||
amountInput: `${components.vnInputNumber}[name="amount"]`,
|
||||
descriptionInput: `${components.vnTextfield}[name="description"]`,
|
||||
typeAutocomplete: 'vn-autocomplete[field="$ctrl.greuge.greugeTypeFk"]',
|
||||
typeAutocomplete: 'vn-autocomplete[ng-model="$ctrl.greuge.greugeTypeFk"]',
|
||||
saveButton: `${components.vnSubmit}`,
|
||||
firstGreugeText: 'vn-client-greuge-index vn-card > div vn-table vn-tbody > vn-tr'
|
||||
},
|
||||
|
@ -162,10 +162,10 @@ export default {
|
|||
},
|
||||
clientBalance: {
|
||||
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}`,
|
||||
newPaymentBank: 'vn-client-balance-create vn-autocomplete[field="$ctrl.receipt.bankFk"]',
|
||||
newPaymentAmountInput: 'vn-client-balance-create vn-input-number[field="$ctrl.receipt.amountPaid"] input',
|
||||
newPaymentBank: 'vn-client-balance-create vn-autocomplete[ng-model="$ctrl.receipt.bankFk"]',
|
||||
newPaymentAmountInput: 'vn-client-balance-create vn-input-number[ng-model="$ctrl.receipt.amountPaid"] input',
|
||||
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)'
|
||||
|
||||
|
@ -209,9 +209,9 @@ export default {
|
|||
},
|
||||
itemCreateView: {
|
||||
temporalName: `${components.vnTextfield}[name="provisionalName"]`,
|
||||
typeAutocomplete: 'vn-autocomplete[field="$ctrl.item.typeFk"]',
|
||||
intrastatAutocomplete: 'vn-autocomplete[field="$ctrl.item.intrastatFk"]',
|
||||
originAutocomplete: 'vn-autocomplete[field="$ctrl.item.originFk"]',
|
||||
typeAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.typeFk"]',
|
||||
intrastatAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.intrastatFk"]',
|
||||
originAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.originFk"]',
|
||||
createButton: `${components.vnSubmit}`,
|
||||
cancelButton: 'button[ui-sref="item.index"]'
|
||||
},
|
||||
|
@ -219,8 +219,8 @@ export default {
|
|||
goBackToModuleIndexButton: 'vn-item-descriptor a[href="#!/item/index"]',
|
||||
moreMenu: 'vn-item-descriptor vn-icon-menu > div > vn-icon',
|
||||
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',
|
||||
regularizeWarehouseAutocomplete: 'vn-item-descriptor vn-dialog vn-autocomplete[field="$ctrl.warehouseFk"]',
|
||||
regularizeQuantityInput: 'vn-item-descriptor vn-dialog tpl-body > div > vn-textfield input',
|
||||
regularizeWarehouseAutocomplete: 'vn-item-descriptor vn-dialog vn-autocomplete[ng-model="$ctrl.warehouseFk"]',
|
||||
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',
|
||||
inactiveIcon: 'vn-item-descriptor vn-icon[icon="icon-unavailable"]',
|
||||
|
@ -229,13 +229,13 @@ export default {
|
|||
itemBasicData: {
|
||||
basicDataButton: 'vn-left-menu a[ui-sref="item.card.basicData"]',
|
||||
goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]',
|
||||
typeAutocomplete: 'vn-autocomplete[field="$ctrl.item.typeFk"]',
|
||||
intrastatAutocomplete: 'vn-autocomplete[field="$ctrl.item.intrastatFk"]',
|
||||
typeAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.typeFk"]',
|
||||
intrastatAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.intrastatFk"]',
|
||||
nameInput: 'vn-textfield[label="Name"] input',
|
||||
relevancyInput: 'vn-input-number[label="Relevancy"] input',
|
||||
originAutocomplete: 'vn-autocomplete[field="$ctrl.item.originFk"]',
|
||||
expenceAutocomplete: 'vn-autocomplete[field="$ctrl.item.expenceFk"]',
|
||||
longNameInput: 'vn-textfield[field="$ctrl.item.longName"] input',
|
||||
relevancyInput: 'vn-input-number[ng-model="$ctrl.item.relevancy"] input',
|
||||
originAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.originFk"]',
|
||||
expenceAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.expenceFk"]',
|
||||
longNameInput: 'vn-textfield[ng-model="$ctrl.item.longName"] input',
|
||||
isActiveCheckbox: 'vn-check[label="Active"]',
|
||||
priceInKgCheckbox: 'vn-check[label="Price in kg"]',
|
||||
submitBasicDataButton: `${components.vnSubmit}`
|
||||
|
@ -243,26 +243,27 @@ export default {
|
|||
itemTags: {
|
||||
goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]',
|
||||
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',
|
||||
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"]',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
seventhRelevancyInput: 'vn-item-tags vn-horizontal:nth-child(7) > vn-textfield[label="Relevancy"] input',
|
||||
addItemTagButton: 'vn-item-tags vn-icon-button[icon="add_circle"]',
|
||||
submitItemTagsButton: `vn-item-tags ${components.vnSubmit}`
|
||||
},
|
||||
itemTax: {
|
||||
firstClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(1) > vn-autocomplete[field="tax.taxClassFk"]',
|
||||
secondClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(2) > vn-autocomplete[field="tax.taxClassFk"]',
|
||||
thirdClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(3) > vn-autocomplete[field="tax.taxClassFk"]',
|
||||
undoChangesButton: 'vn-item-tax vn-button-bar > vn-button[label="Undo changes"]',
|
||||
firstClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(1) > vn-autocomplete[ng-model="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}`
|
||||
},
|
||||
itemBarcodes: {
|
||||
|
@ -273,19 +274,19 @@ export default {
|
|||
},
|
||||
itemNiches: {
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
submitNichesButton: `vn-item-niche ${components.vnSubmit}`
|
||||
},
|
||||
itemBotanical: {
|
||||
botanicalInput: `vn-item-botanical vn-horizontal:nth-child(1) > ${components.vnTextfield}`,
|
||||
genusAutocomplete: 'vn-item-botanical vn-autocomplete[field="$ctrl.botanical.genusFk"]',
|
||||
speciesAutocomplete: 'vn-item-botanical vn-autocomplete[field="$ctrl.botanical.specieFk"]',
|
||||
genusAutocomplete: 'vn-item-botanical vn-autocomplete[ng-model="$ctrl.botanical.genusFk"]',
|
||||
speciesAutocomplete: 'vn-item-botanical vn-autocomplete[ng-model="$ctrl.botanical.specieFk"]',
|
||||
submitBotanicalButton: `vn-item-botanical ${components.vnSubmit}`
|
||||
},
|
||||
itemSummary: {
|
||||
|
@ -300,7 +301,7 @@ export default {
|
|||
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',
|
||||
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: {
|
||||
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'
|
||||
},
|
||||
ticketsIndex: {
|
||||
openAdvancedSearchButton: 'vn-ticket-index vn-searchbar t-right-icons > vn-icon[icon="keyboard_arrow_down"]',
|
||||
advancedSearchInvoiceOut: 'vn-ticket-search-panel vn-textfield[model="filter.refFk"] input',
|
||||
openAdvancedSearchButton: 'vn-ticket-index vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
|
||||
advancedSearchInvoiceOut: 'vn-ticket-search-panel vn-textfield[ng-model="filter.refFk"] input',
|
||||
newTicketButton: 'vn-ticket-index > a',
|
||||
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',
|
||||
searchResultDate: 'vn-ticket-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(5)',
|
||||
searchTicketInput: `vn-ticket-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',
|
||||
searchButton: 'vn-ticket-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',
|
||||
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',
|
||||
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"]'
|
||||
},
|
||||
createTicketView: {
|
||||
clientAutocomplete: 'vn-ticket-create vn-autocomplete[field="$ctrl.clientFk"]',
|
||||
addressAutocomplete: 'vn-ticket-create vn-autocomplete[field="$ctrl.addressFk"]',
|
||||
deliveryDateInput: 'vn-ticket-create > div > div > vn-card > div > vn-ticket-create-card > vn-date-picker > div > input',
|
||||
warehouseAutocomplete: 'vn-ticket-create vn-autocomplete[field="$ctrl.warehouseFk"]',
|
||||
agencyAutocomplete: 'vn-ticket-create vn-autocomplete[field="$ctrl.ticket.agencyModeFk"]',
|
||||
clientAutocomplete: 'vn-ticket-create vn-autocomplete[ng-model="$ctrl.clientFk"]',
|
||||
addressAutocomplete: 'vn-ticket-create vn-autocomplete[ng-model="$ctrl.addressFk"]',
|
||||
deliveryDateInput: 'vn-ticket-create vn-date-picker[ng-model="$ctrl.landed"]',
|
||||
warehouseAutocomplete: 'vn-ticket-create vn-autocomplete[ng-model="$ctrl.warehouseFk"]',
|
||||
agencyAutocomplete: 'vn-ticket-create vn-autocomplete[ng-model="$ctrl.ticket.agencyModeFk"]',
|
||||
createButton: `${components.vnSubmit}`
|
||||
},
|
||||
ticketDescriptor: {
|
||||
|
@ -376,7 +377,7 @@ export default {
|
|||
ticketNotes: {
|
||||
firstNoteRemoveButton: 'vn-icon[icon="delete"]',
|
||||
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',
|
||||
submitNotesButton: `${components.vnSubmit}`
|
||||
},
|
||||
|
@ -389,10 +390,10 @@ export default {
|
|||
ticketPackages: {
|
||||
packagesButton: 'vn-left-menu a[ui-sref="ticket.card.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"]',
|
||||
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}`
|
||||
},
|
||||
ticketSales: {
|
||||
|
@ -408,7 +409,7 @@ export default {
|
|||
moreMenuReserve: '.vn-popover.shown .vn-drop-down li[name="Mark 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"]',
|
||||
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',
|
||||
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"]',
|
||||
|
@ -416,11 +417,11 @@ export default {
|
|||
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',
|
||||
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)',
|
||||
firstSaleQuantityClearInput: 'vn-textfield[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',
|
||||
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',
|
||||
firstSaleQuantityClearInput: 'vn-textfield[ng-model="sale.quantity"] div.suffix > i',
|
||||
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-table vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > vn-autocomplete',
|
||||
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',
|
||||
firstSalePriceInput: '.vn-popover.shown vn-input-number input',
|
||||
|
@ -430,7 +431,7 @@ export default {
|
|||
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',
|
||||
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',
|
||||
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)',
|
||||
|
@ -438,18 +439,18 @@ export default {
|
|||
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',
|
||||
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',
|
||||
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',
|
||||
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-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',
|
||||
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',
|
||||
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',
|
||||
secondSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(2) vn-check[field="sale.checked"]',
|
||||
thirdSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(3) 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[ng-model="sale.checked"]',
|
||||
deleteSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="delete"]',
|
||||
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"]',
|
||||
moveToTicketButton: '.vn-popover.shown vn-icon[icon="arrow_forward_ios"]',
|
||||
moveToNewTicketButton: '.vn-popover.shown vn-button[label="New ticket"]',
|
||||
|
@ -460,36 +461,34 @@ export default {
|
|||
ticketTracking: {
|
||||
trackingButton: 'vn-left-menu a[ui-sref="ticket.card.tracking.index"]',
|
||||
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}`,
|
||||
cancelButton: 'vn-ticket-tracking-edit vn-button[ui-sref="ticket.card.tracking.index"]'
|
||||
},
|
||||
ticketBasicData: {
|
||||
basicDataButton: 'vn-left-menu a[ui-sref="ticket.card.basicData.stepOne"]',
|
||||
clientAutocomplete: 'vn-autocomplete[field="$ctrl.clientFk"]',
|
||||
addressAutocomplete: 'vn-autocomplete[field="$ctrl.ticket.addressFk"]',
|
||||
agencyAutocomplete: 'vn-autocomplete[field="$ctrl.agencyModeId"]',
|
||||
zoneAutocomplete: 'vn-autocomplete[field="$ctrl.zoneId"]',
|
||||
clientAutocomplete: 'vn-autocomplete[ng-model="$ctrl.clientFk"]',
|
||||
addressAutocomplete: 'vn-autocomplete[ng-model="$ctrl.ticket.addressFk"]',
|
||||
agencyAutocomplete: 'vn-autocomplete[ng-model="$ctrl.agencyModeId"]',
|
||||
zoneAutocomplete: 'vn-autocomplete[ng-model="$ctrl.zoneId"]',
|
||||
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',
|
||||
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: {
|
||||
base: 'vn-ticket-components tfoot > tr:nth-child(1) > td',
|
||||
margin: 'vn-ticket-components tfoot > tr:nth-child(2) > td',
|
||||
total: 'vn-ticket-components tfoot > tr:nth-child(3) > td'
|
||||
base: 'vn-ticket-components [name="base-sum"]'
|
||||
},
|
||||
ticketRequests: {
|
||||
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',
|
||||
descriptionInput: 'vn-ticket-request-create > form > div > vn-card > div > vn-horizontal:nth-child(1) > vn-textfield > div > div > div.infix > input',
|
||||
atenderAutocomplete: 'vn-ticket-request-create vn-autocomplete[field="$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',
|
||||
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',
|
||||
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 input',
|
||||
atenderAutocomplete: 'vn-ticket-request-create vn-autocomplete[ng-model="$ctrl.ticketRequest.atenderFk"]',
|
||||
quantityInput: 'vn-ticket-request-create vn-input-number input[name=quantity]',
|
||||
priceInput: 'vn-ticket-request-create vn-input-number input[name=price]',
|
||||
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',
|
||||
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: {
|
||||
|
@ -501,20 +500,20 @@ export default {
|
|||
ticketService: {
|
||||
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',
|
||||
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',
|
||||
firstPriceInput: 'vn-ticket-service vn-input-number[label="Price"] input',
|
||||
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"]',
|
||||
newDescriptionInput: 'vn-ticket-service > vn-dialog vn-textfield[model="$ctrl.newServiceType.name"] input',
|
||||
fistDeleteServiceButton: 'vn-ticket-service form vn-horizontal:nth-child(1) vn-icon-button[icon="delete"]',
|
||||
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',
|
||||
saveServiceButton: `${components.vnSubmit}`,
|
||||
saveDescriptionButton: 'vn-ticket-service > vn-dialog[vn-id="createServiceTypeDialog"] > div > form > div.buttons > tpl-buttons > button'
|
||||
},
|
||||
createStateView: {
|
||||
stateAutocomplete: 'vn-autocomplete[field="$ctrl.stateFk"]',
|
||||
workerAutocomplete: 'vn-autocomplete[field="$ctrl.workerFk"]',
|
||||
clearStateInputButton: 'vn-autocomplete[field="$ctrl.stateFk"] > div > div > div > vn-icon > i',
|
||||
stateAutocomplete: 'vn-autocomplete[ng-model="$ctrl.stateFk"]',
|
||||
workerAutocomplete: 'vn-autocomplete[ng-model="$ctrl.workerFk"]',
|
||||
clearStateInputButton: 'vn-autocomplete[ng-model="$ctrl.stateFk"] .icons > vn-icon[icon=clear]',
|
||||
saveStateButton: `${components.vnSubmit}`
|
||||
},
|
||||
claimsIndex: {
|
||||
|
@ -530,7 +529,7 @@ export default {
|
|||
claimSummary: {
|
||||
header: 'vn-claim-summary > vn-card > div > h5',
|
||||
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',
|
||||
firstSaleDescriptorImage: '.vn-popover.shown vn-item-descriptor img',
|
||||
itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor',
|
||||
|
@ -541,45 +540,45 @@ export default {
|
|||
firstActionTicketDescriptor: '.vn-popover.shown vn-ticket-descriptor'
|
||||
},
|
||||
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',
|
||||
observationInput: 'vn-textarea[field="$ctrl.claim.observation"] textarea',
|
||||
observationInput: 'vn-textarea[ng-model="$ctrl.claim.observation"] textarea',
|
||||
saveButton: `${components.vnSubmit}`
|
||||
},
|
||||
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',
|
||||
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',
|
||||
addItemButton: 'vn-claim-detail a vn-float-button',
|
||||
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',
|
||||
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',
|
||||
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: {
|
||||
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',
|
||||
firstClaimReasonAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[field="claimDevelopment.claimReasonFk"]',
|
||||
firstClaimResultAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[field="claimDevelopment.claimResultFk"]',
|
||||
firstClaimResponsibleAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[field="claimDevelopment.claimResponsibleFk"]',
|
||||
firstClaimWorkerAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[field="claimDevelopment.workerFk"]',
|
||||
firstClaimRedeliveryAutocomplete: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[field="claimDevelopment.claimRedeliveryFk"]',
|
||||
secondClaimReasonAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[field="claimDevelopment.claimReasonFk"]',
|
||||
secondClaimResultAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[field="claimDevelopment.claimResultFk"]',
|
||||
secondClaimResponsibleAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[field="claimDevelopment.claimResponsibleFk"]',
|
||||
secondClaimWorkerAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[field="claimDevelopment.workerFk"]',
|
||||
secondClaimRedeliveryAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[field="claimDevelopment.claimRedeliveryFk"]',
|
||||
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[ng-model="claimDevelopment.claimResultFk"]',
|
||||
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[ng-model="claimDevelopment.workerFk"]',
|
||||
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[ng-model="claimDevelopment.claimReasonFk"]',
|
||||
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[ng-model="claimDevelopment.claimResponsibleFk"]',
|
||||
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[ng-model="claimDevelopment.claimRedeliveryFk"]',
|
||||
saveDevelopmentButton: `${components.vnSubmit}`
|
||||
},
|
||||
claimAction: {
|
||||
importClaimButton: 'vn-claim-action vn-button[label="Import claim"]',
|
||||
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)',
|
||||
firstLineDestination: 'vn-claim-action vn-tr:nth-child(1) vn-autocomplete[field="saleClaimed.claimDestinationFk"]',
|
||||
secondLineDestination: 'vn-claim-action vn-tr:nth-child(2) 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[ng-model="saleClaimed.claimDestinationFk"]',
|
||||
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: {
|
||||
searchResult: 'vn-order-index vn-card > div > vn-table > div > vn-tbody > a.vn-tr',
|
||||
|
@ -597,7 +596,7 @@ export default {
|
|||
clientAutocomplete: 'vn-autocomplete[label="Client"]',
|
||||
addressAutocomplete: 'vn-autocomplete[label="Address"]',
|
||||
agencyAutocomplete: 'vn-autocomplete[label="Agency"]',
|
||||
landedDatePicker: 'vn-date-picker[label="Landed"] input',
|
||||
landedDatePicker: 'vn-date-picker[label="Landed"]',
|
||||
createButton: `${components.vnSubmit}`,
|
||||
cancelButton: 'vn-button[href="#!/client/index"]'
|
||||
},
|
||||
|
@ -605,14 +604,14 @@ export default {
|
|||
orderByAutocomplete: 'vn-autocomplete[label="Order by"]',
|
||||
plantRealmButton: 'vn-order-catalog > vn-side-menu vn-icon[icon="icon-plant"]',
|
||||
typeAutocomplete: 'vn-autocomplete[data="$ctrl.itemTypes"]',
|
||||
itemIdInput: 'vn-order-catalog > vn-side-menu vn-catalog-filter vn-textfield[model="$ctrl.itemFk"] input',
|
||||
itemTagValueInput: 'vn-order-catalog > vn-side-menu vn-catalog-filter vn-textfield[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',
|
||||
tagAutocomplete: 'vn-order-catalog-search-panel vn-autocomplete[field="filter.tagFk"]',
|
||||
tagValueInput: 'vn-order-catalog-search-panel vn-textfield[model="filter.value"] input',
|
||||
itemIdInput: 'vn-catalog-filter vn-textfield[ng-model="$ctrl.itemFk"] input',
|
||||
itemTagValueInput: 'vn-catalog-filter vn-textfield[ng-model="$ctrl.value"] input',
|
||||
openTagSearch: 'vn-catalog-filter > div > vn-vertical > vn-textfield[ng-model="$ctrl.value"] .append i',
|
||||
tagAutocomplete: 'vn-order-catalog-search-panel vn-autocomplete[ng-model="filter.tagFk"]',
|
||||
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',
|
||||
thirdFilterRemoveButton: 'vn-order-catalog > vn-side-menu 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',
|
||||
thirdFilterRemoveButton: 'vn-catalog-filter > div > vn-horizontal.chips > vn-chip:nth-child(3) button',
|
||||
fourthFilterRemoveButton: 'vn-catalog-filter > div > vn-horizontal.chips > vn-chip:nth-child(4) button',
|
||||
},
|
||||
orderBasicData: {
|
||||
clientAutocomplete: 'vn-autocomplete[label="Client"]',
|
||||
|
@ -632,11 +631,11 @@ export default {
|
|||
addNewRouteButton: 'vn-route-index > a[ui-sref="route.create"]'
|
||||
},
|
||||
createRouteView: {
|
||||
workerAutocomplete: 'vn-route-create vn-autocomplete[field="$ctrl.route.workerFk"]',
|
||||
createdDatePicker: 'vn-route-create vn-date-picker[model="$ctrl.route.created"] > div > input',
|
||||
vehicleAutoComplete: 'vn-route-create vn-autocomplete[field="$ctrl.route.vehicleFk"]',
|
||||
agencyAutoComplete: 'vn-route-create vn-autocomplete[field="$ctrl.route.agencyModeFk"]',
|
||||
descriptionInput: 'vn-route-create vn-textfield[field="$ctrl.route.description"] input',
|
||||
workerAutocomplete: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.workerFk"]',
|
||||
createdDatePicker: 'vn-route-create vn-date-picker[ng-model="$ctrl.route.created"]',
|
||||
vehicleAutoComplete: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.vehicleFk"]',
|
||||
agencyAutoComplete: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.agencyModeFk"]',
|
||||
descriptionInput: 'vn-route-create vn-textfield[ng-model="$ctrl.route.description"] input',
|
||||
submitButton: 'vn-route-create vn-submit > input[type="submit"]'
|
||||
},
|
||||
routeDescriptor: {
|
||||
|
@ -646,29 +645,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'
|
||||
},
|
||||
routeBasicData: {
|
||||
workerAutoComplete: 'vn-route-basic-data vn-autocomplete[field="$ctrl.route.workerFk"]',
|
||||
vehicleAutoComplete: 'vn-route-basic-data vn-autocomplete[field="$ctrl.route.vehicleFk"]',
|
||||
agencyAutoComplete: 'vn-route-basic-data vn-autocomplete[field="$ctrl.route.agencyModeFk"]',
|
||||
kmStartInput: 'vn-route-basic-data vn-input-number[field="$ctrl.route.kmStart"] input',
|
||||
kmEndInput: 'vn-route-basic-data vn-input-number[model="$ctrl.route.kmEnd"] input',
|
||||
createdDateInput: 'vn-route-basic-data vn-date-picker[model="$ctrl.route.created"] > div > input',
|
||||
startedHourInput: 'vn-route-basic-data vn-input-time[model="$ctrl.route.started"] input',
|
||||
finishedHourInput: 'vn-route-basic-data vn-input-time[model="$ctrl.route.finished"] input',
|
||||
workerAutoComplete: 'vn-route-basic-data vn-autocomplete[ng-model="$ctrl.route.workerFk"]',
|
||||
vehicleAutoComplete: 'vn-route-basic-data vn-autocomplete[ng-model="$ctrl.route.vehicleFk"]',
|
||||
agencyAutoComplete: 'vn-route-basic-data vn-autocomplete[ng-model="$ctrl.route.agencyModeFk"]',
|
||||
kmStartInput: 'vn-route-basic-data vn-input-number[ng-model="$ctrl.route.kmStart"] input',
|
||||
kmEndInput: 'vn-route-basic-data vn-input-number[ng-model="$ctrl.route.kmEnd"] input',
|
||||
createdDateInput: 'vn-route-basic-data vn-date-picker[ng-model="$ctrl.route.created"]',
|
||||
startedHourInput: 'vn-route-basic-data vn-input-time[ng-model="$ctrl.route.started"] 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'
|
||||
},
|
||||
routeTickets: {
|
||||
firstTicketPriority: 'vn-route-tickets vn-tr:nth-child(1) vn-textfield[model="ticket.priority"] input',
|
||||
secondTicketPriority: 'vn-route-tickets vn-tr:nth-child(2) vn-textfield[model="ticket.priority"] input',
|
||||
thirdTicketPriority: 'vn-route-tickets vn-tr:nth-child(3) vn-textfield[model="ticket.priority"] input',
|
||||
fourthTicketPriority: 'vn-route-tickets vn-tr:nth-child(4) vn-textfield[model="ticket.priority"] input',
|
||||
eleventhTicketPriority: 'vn-route-tickets vn-tr:nth-child(11) 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[ng-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[ng-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',
|
||||
buscamanButton: 'vn-route-tickets vn-button[icon="icon-buscaman"]',
|
||||
firstTicketDeleteButton: 'vn-route-tickets vn-tr:nth-child(1) vn-icon[icon="delete"]',
|
||||
confirmButton: 'vn-route-tickets > vn-confirm button[response="ACCEPT"]'
|
||||
},
|
||||
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'
|
||||
},
|
||||
workerTimeControl: {
|
||||
|
|
|
@ -176,7 +176,7 @@ describe('Client lock verified data path', () => {
|
|||
.wait(selectors.clientFiscalData.socialNameInput)
|
||||
.evaluate(selector => {
|
||||
return document.querySelector(selector).disabled;
|
||||
}, 'vn-textfield[model="$ctrl.client.socialName"] > div');
|
||||
}, 'vn-textfield[ng-model="$ctrl.client.socialName"] > div');
|
||||
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import selectors from '../../helpers/selectors.js';
|
||||
import createNightmare from '../../helpers/nightmare';
|
||||
|
||||
// #1702 Autocomplete no siempre refresca al cancelar formulario
|
||||
xdescribe('Item edit tax path', () => {
|
||||
describe('Item edit tax path', () => {
|
||||
const nightmare = createNightmare();
|
||||
|
||||
beforeAll(() => {
|
||||
|
@ -14,9 +13,9 @@ xdescribe('Item edit tax path', () => {
|
|||
|
||||
it(`should add the item tax to all countries`, async() => {
|
||||
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.thirdClassAutocomplete, 'Reduced VAT')
|
||||
.autocompleteSearch(selectors.itemTax.thirdClassAutocomplete, 'General VAT')
|
||||
.waitToClick(selectors.itemTax.submitTaxButton)
|
||||
.waitForLastSnackbar();
|
||||
|
||||
|
@ -28,7 +27,7 @@ xdescribe('Item edit tax path', () => {
|
|||
.reloadSection('item.card.tax')
|
||||
.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() => {
|
||||
|
@ -42,6 +41,22 @@ xdescribe('Item edit tax path', () => {
|
|||
const thirdVatType = await nightmare
|
||||
.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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -160,7 +160,7 @@ describe('Ticket descriptor path', () => {
|
|||
|
||||
it('should confirm the sixth weekly ticket was deleted', async() => {
|
||||
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)
|
||||
.waitForNumberOfElements(selectors.ticketsIndex.searchWeeklyResult, 5)
|
||||
.countElement(selectors.ticketsIndex.searchWeeklyResult);
|
||||
|
|
|
@ -78,7 +78,7 @@ describe('Ticket services path', () => {
|
|||
.waitToClick(selectors.ticketService.saveDescriptionButton)
|
||||
.waitForLastSnackbar();
|
||||
|
||||
expect(result).toEqual(`Name can't be empty`);
|
||||
expect(result).toEqual(`can't be blank`);
|
||||
});
|
||||
|
||||
it('should create a new description then add price then create the service', async() => {
|
||||
|
|
|
@ -49,7 +49,7 @@ xdescribe('Route basic Data path', () => {
|
|||
|
||||
it('should count how many tickets are in route', async() => {
|
||||
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);
|
||||
});
|
||||
|
@ -74,7 +74,7 @@ xdescribe('Route basic Data path', () => {
|
|||
|
||||
it('should now count how many tickets are in route to find one less', async() => {
|
||||
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);
|
||||
});
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,5 +1,5 @@
|
|||
import ngModule from '../../module';
|
||||
import Input from '../../lib/input';
|
||||
import Field from '../field';
|
||||
import assignProps from '../../lib/assign-props';
|
||||
import {mergeWhere} from 'vn-loopback/util/filter';
|
||||
import './style.scss';
|
||||
|
@ -16,23 +16,18 @@ import './style.scss';
|
|||
*
|
||||
* @event change Thrown when value is changed
|
||||
*/
|
||||
export default class Autocomplete extends Input {
|
||||
constructor($element, $scope, $http, $transclude, $translate, $interpolate) {
|
||||
super($element, $scope);
|
||||
this.$http = $http;
|
||||
this.$interpolate = $interpolate;
|
||||
this.$transclude = $transclude;
|
||||
this.$translate = $translate;
|
||||
this._field = undefined;
|
||||
export default class Autocomplete extends Field {
|
||||
constructor($element, $scope, $compile, $http, $transclude, $translate, $interpolate) {
|
||||
super($element, $scope, $compile);
|
||||
Object.assign(this, {
|
||||
$http,
|
||||
$interpolate,
|
||||
$transclude,
|
||||
$translate
|
||||
});
|
||||
|
||||
this._selection = null;
|
||||
this.readonly = true;
|
||||
this.form = null;
|
||||
this.input = this.element.querySelector('.mdl-textfield__input');
|
||||
|
||||
componentHandler.upgradeElement(
|
||||
this.element.querySelector('.mdl-textfield'));
|
||||
|
||||
this.registerEvents();
|
||||
this.input = this.element.querySelector('input');
|
||||
}
|
||||
|
||||
$postLink() {
|
||||
|
@ -44,12 +39,16 @@ export default class Autocomplete extends Input {
|
|||
}
|
||||
|
||||
/**
|
||||
* Registers all event emitters
|
||||
* @type {any} The autocomplete value.
|
||||
*/
|
||||
registerEvents() {
|
||||
this.input.addEventListener('focus', event => {
|
||||
this.emit('focus', {event});
|
||||
});
|
||||
get field() {
|
||||
return super.field;
|
||||
}
|
||||
|
||||
set field(value) {
|
||||
super.field = value;
|
||||
this.refreshSelection();
|
||||
this.emit('change', {value});
|
||||
}
|
||||
|
||||
get model() {
|
||||
|
@ -83,20 +82,6 @@ export default class Autocomplete extends Input {
|
|||
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
|
||||
* to prevent requests to display the initial value.
|
||||
|
@ -136,7 +121,7 @@ export default class Autocomplete extends Input {
|
|||
return;
|
||||
|
||||
const selection = this.fetchSelection();
|
||||
if (!this.selection)
|
||||
|
||||
this.selection = selection;
|
||||
}
|
||||
|
||||
|
@ -216,39 +201,21 @@ export default class Autocomplete extends Input {
|
|||
if (this.translateFields.indexOf(this.showField) > -1)
|
||||
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) {
|
||||
const value = item[this.valueField];
|
||||
this.selection = item;
|
||||
this.setValue(value);
|
||||
this.field = value;
|
||||
}
|
||||
|
||||
onClearClick(event) {
|
||||
event.preventDefault();
|
||||
this.setValue(null);
|
||||
}
|
||||
onContainerKeyDown(event) {
|
||||
if (event.defaultPrevented) return;
|
||||
|
||||
onKeyDown(event) {
|
||||
// if (event.defaultPrevented) return;
|
||||
|
||||
switch (event.keyCode) {
|
||||
case 38: // Up
|
||||
case 40: // Down
|
||||
case 13: // Enter
|
||||
switch (event.code) {
|
||||
case 'ArrowUp':
|
||||
case 'ArrowDown':
|
||||
case 'Enter':
|
||||
this.showDropDown();
|
||||
break;
|
||||
default:
|
||||
|
@ -261,7 +228,8 @@ export default class Autocomplete extends Input {
|
|||
event.preventDefault();
|
||||
}
|
||||
|
||||
onMouseDown(event) {
|
||||
onContainerMouseDown(event) {
|
||||
if (event.defaultPrevented) return;
|
||||
event.preventDefault();
|
||||
this.showDropDown();
|
||||
}
|
||||
|
@ -293,7 +261,7 @@ export default class Autocomplete extends Input {
|
|||
|
||||
showDropDown(search) {
|
||||
this.assignDropdownProps();
|
||||
this.$.dropDown.show(this.input, search);
|
||||
this.$.dropDown.show(this.container, search);
|
||||
}
|
||||
|
||||
get fetchFunction() {
|
||||
|
@ -307,16 +275,12 @@ export default class Autocomplete extends Input {
|
|||
this.refreshSelection();
|
||||
}
|
||||
}
|
||||
Autocomplete.$inject = ['$element', '$scope', '$http', '$transclude', '$translate', '$interpolate'];
|
||||
Autocomplete.$inject = ['$element', '$scope', '$compile', '$http', '$transclude', '$translate', '$interpolate'];
|
||||
|
||||
ngModule.component('vnAutocomplete', {
|
||||
template: require('./autocomplete.html'),
|
||||
ngModule.vnComponent('vnAutocomplete', {
|
||||
template: require('./index.html'),
|
||||
controller: Autocomplete,
|
||||
bindings: {
|
||||
label: '@',
|
||||
field: '=?',
|
||||
disabled: '<?',
|
||||
required: '@?',
|
||||
showField: '@?',
|
||||
valueField: '@?',
|
||||
initialData: '<?',
|
||||
|
@ -336,8 +300,5 @@ ngModule.component('vnAutocomplete', {
|
|||
},
|
||||
transclude: {
|
||||
tplItem: '?tplItem'
|
||||
},
|
||||
require: {
|
||||
form: '?^form'
|
||||
}
|
||||
});
|
|
@ -1,66 +1,23 @@
|
|||
@import "effects";
|
||||
|
||||
vn-autocomplete {
|
||||
vn-autocomplete.vn-field {
|
||||
overflow: hidden;
|
||||
|
||||
& > div > .mdl-textfield {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
& > .container {
|
||||
cursor: pointer;
|
||||
|
||||
& > .infix > .control {
|
||||
overflow: hidden;
|
||||
|
||||
& > input {
|
||||
cursor: pointer;
|
||||
height: 30px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
& > .icons {
|
||||
display: none;
|
||||
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;
|
||||
text-align: left;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
&: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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,13 +12,13 @@ import './style.scss';
|
|||
*/
|
||||
export default class Check extends Toggle {
|
||||
set field(value) {
|
||||
this._field = value;
|
||||
super.field = value;
|
||||
this.element.classList.toggle('checked', Boolean(value));
|
||||
this.indeterminate = Boolean(value == null && this.tripleState);
|
||||
}
|
||||
|
||||
get field() {
|
||||
return this._field;
|
||||
return super.field;
|
||||
}
|
||||
|
||||
set checked(value) {
|
||||
|
@ -64,14 +64,10 @@ export default class Check extends Toggle {
|
|||
}
|
||||
}
|
||||
|
||||
ngModule.component('vnCheck', {
|
||||
ngModule.vnComponent('vnCheck', {
|
||||
template: require('./index.html'),
|
||||
controller: Check,
|
||||
bindings: {
|
||||
label: '@?',
|
||||
field: '=?',
|
||||
disabled: '<?',
|
||||
checked: '<?',
|
||||
tripleState: '<?',
|
||||
indeterminate: '<?',
|
||||
info: '@?'
|
||||
|
|
|
@ -8,7 +8,7 @@ describe('Component vnCheck', () => {
|
|||
}));
|
||||
|
||||
beforeEach(inject(($compile, $rootScope) => {
|
||||
$element = $compile(`<vn-check></vn-check`)($rootScope);
|
||||
$element = $compile(`<vn-check></vn-check>`)($rootScope);
|
||||
$ctrl = $element.controller('vnCheck');
|
||||
element = $element[0];
|
||||
}));
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,24 +1,5 @@
|
|||
@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-weekdays,
|
||||
span.flatpickr-weekday {
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
tpl-body {
|
||||
display: block;
|
||||
width: 20em;
|
||||
min-width: 20em;
|
||||
}
|
||||
& > button.close {
|
||||
@extend %clickable;
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
<div ng-show="$ctrl.showFilter" class="filter">
|
||||
<vn-textfield
|
||||
vn-id="input"
|
||||
model="$ctrl.search"
|
||||
class="search"
|
||||
ng-model="$ctrl.search"
|
||||
class="dense search"
|
||||
ng-blur="$ctrl.onFocusOut()"
|
||||
label="Search">
|
||||
placeholder="{{::'Search' | translate}}">
|
||||
</vn-textfield>
|
||||
</div>
|
||||
<div vn-id="list" class="list" tabindex="-1">
|
||||
|
|
|
@ -106,8 +106,8 @@ export default class DropDown extends Component {
|
|||
*/
|
||||
show(parent, search) {
|
||||
this._activeOption = -1;
|
||||
this.search = search;
|
||||
this.$.popover.show(parent || this.parent);
|
||||
this.search = search;
|
||||
this.buildList();
|
||||
}
|
||||
|
||||
|
@ -194,10 +194,12 @@ export default class DropDown extends Component {
|
|||
this.document.addEventListener('keydown', this.docKeyDownHandler);
|
||||
this.$.list.scrollTop = 0;
|
||||
this.$.input.focus();
|
||||
this.emit('open');
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.document.removeEventListener('keydown', this.docKeyDownHandler);
|
||||
this.emit('close');
|
||||
}
|
||||
|
||||
onClearClick() {
|
||||
|
|
|
@ -13,18 +13,7 @@
|
|||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
font-size: inherit;
|
||||
padding: .6em;
|
||||
margin: 0!important;
|
||||
&.not-empty label{
|
||||
display: none;
|
||||
}
|
||||
& .selected label {
|
||||
font-size: inherit;
|
||||
bottom: 2px;
|
||||
color: $color-font-secondary;
|
||||
}
|
||||
padding: $spacing-sm;
|
||||
}
|
||||
& > vn-icon[icon=clear] {
|
||||
display: none;
|
||||
|
|
|
@ -5,20 +5,18 @@
|
|||
</div>
|
||||
<div class="infix">
|
||||
<div class="fix prefix"></div>
|
||||
<div class="control">
|
||||
<input type="text" ng-model="$ctrl.field"/>
|
||||
</div>
|
||||
<div class="control"></div>
|
||||
<div class="fix suffix"></div>
|
||||
<label>
|
||||
<span translate>{{::$ctrl.label}}</span>
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="icons">
|
||||
<div class="icons pre">
|
||||
<vn-icon
|
||||
icon="clear"
|
||||
translate-attr="{title: 'Clear'}"
|
||||
ng-click="$ctrl.onClear()">
|
||||
ng-click="$ctrl.onClear($event)">
|
||||
</vn-icon>
|
||||
<vn-icon
|
||||
ng-if="::$ctrl.info"
|
||||
|
@ -30,6 +28,8 @@
|
|||
ng-transclude="append"
|
||||
class="append">
|
||||
</div>
|
||||
<div class="icons post">
|
||||
</div>
|
||||
<div class="underline blur"></div>
|
||||
<div class="underline focus"></div>
|
||||
</div>
|
||||
|
|
|
@ -1,37 +1,63 @@
|
|||
import ngModule from '../../module';
|
||||
import Component from '../../lib/component';
|
||||
import FormInput from '../form-input';
|
||||
import './style.scss';
|
||||
|
||||
export default class Field extends Component {
|
||||
constructor($element, $scope) {
|
||||
export default class Field extends FormInput {
|
||||
constructor($element, $scope, $compile) {
|
||||
super($element, $scope);
|
||||
this._value = undefined;
|
||||
this.$compile = $compile;
|
||||
|
||||
this.prefix = null;
|
||||
this.suffix = null;
|
||||
|
||||
this.input = this.element.querySelector('input');
|
||||
this.control = this.element.querySelector('.control');
|
||||
this.classList = this.element.classList;
|
||||
this.classList.add('vn-field');
|
||||
this.element.addEventListener('click', e => this.onClick(e));
|
||||
|
||||
this.element.addEventListener('focusin',
|
||||
() => this.onFocus(true));
|
||||
this.element.addEventListener('focusout',
|
||||
() => this.onFocus(false));
|
||||
this.element.addEventListener('click',
|
||||
() => this.onClick());
|
||||
this.container = this.element.querySelector('.container');
|
||||
this.container.addEventListener('mousedown', e => this.onMouseDown(e));
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
super.$onInit();
|
||||
|
||||
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) {
|
||||
this._field = value;
|
||||
if (value === this.field) return;
|
||||
super.field = value;
|
||||
this.classList.toggle('not-empty', value != null && value !== '');
|
||||
this.validateValue();
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -42,6 +68,30 @@ export default class Field extends Component {
|
|||
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) {
|
||||
this._disabled = boolTag(value);
|
||||
this.input.disabled = this._disabled;
|
||||
|
@ -100,20 +150,28 @@ export default class Field extends Component {
|
|||
}
|
||||
|
||||
set error(value) {
|
||||
if (value === this.error) return;
|
||||
this._error = value;
|
||||
this.refreshHint();
|
||||
this.classList.toggle('invalid', Boolean(value));
|
||||
}
|
||||
|
||||
get error() {
|
||||
return this._error;
|
||||
}
|
||||
|
||||
get shownError() {
|
||||
return this.error || this.inputError || null;
|
||||
}
|
||||
|
||||
refreshHint() {
|
||||
let hint = this.error || this.hint || '';
|
||||
let error = this.shownError;
|
||||
let hint = error || this.hint;
|
||||
|
||||
let hintEl = this.element.querySelector('.hint');
|
||||
hintEl.innerText = hint;
|
||||
hintEl.innerText = hint || '';
|
||||
hintEl.classList.toggle('filled', Boolean(hint));
|
||||
|
||||
this.classList.toggle('invalid', Boolean(error));
|
||||
}
|
||||
|
||||
refreshFix(selector, text) {
|
||||
|
@ -123,16 +181,27 @@ export default class Field extends Component {
|
|||
}
|
||||
|
||||
onClick() {
|
||||
if (event.defaultPrevented) return;
|
||||
event.preventDefault();
|
||||
|
||||
if (this.input !== document.activeElement)
|
||||
this.input.focus();
|
||||
this.focus();
|
||||
}
|
||||
|
||||
onMouseDown(event) {
|
||||
if (event.target == this.input) return;
|
||||
event.preventDefault();
|
||||
this.focus();
|
||||
}
|
||||
|
||||
onFocus(hasFocus) {
|
||||
this.classList.toggle('focused', hasFocus);
|
||||
}
|
||||
|
||||
onClear() {
|
||||
this.input.value = '';
|
||||
onClear(event) {
|
||||
if (event.defaultPrevented) return;
|
||||
event.preventDefault();
|
||||
this.field = null;
|
||||
this.input.dispatchEvent(new Event('change'));
|
||||
}
|
||||
|
||||
|
@ -143,10 +212,28 @@ export default class Field extends Component {
|
|||
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'),
|
||||
transclude: {
|
||||
prepend: '?prepend',
|
||||
|
@ -154,18 +241,17 @@ ngModule.component('vnField', {
|
|||
},
|
||||
controller: Field,
|
||||
bindings: {
|
||||
field: '=?',
|
||||
label: '@?',
|
||||
name: '@?',
|
||||
type: '@?',
|
||||
placeholder: '@?',
|
||||
value: '=?',
|
||||
info: '@?',
|
||||
disabled: '@?',
|
||||
readonly: '@?',
|
||||
required: '@?',
|
||||
required: '<?',
|
||||
prefix: '@?',
|
||||
suffix: '@?',
|
||||
hint: '@?',
|
||||
error: '<?'
|
||||
error: '<?',
|
||||
tabIndex: '<?',
|
||||
rule: '@?'
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
display: flex;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
height: 56px;
|
||||
|
||||
& > .infix {
|
||||
position: relative;
|
||||
|
@ -50,8 +49,8 @@
|
|||
& > .control {
|
||||
height: 100%;
|
||||
flex: auto;
|
||||
}
|
||||
& > .control > input {
|
||||
|
||||
& > * {
|
||||
padding-top: 24px;
|
||||
padding-bottom: 8px;
|
||||
height: inherit;
|
||||
|
@ -64,10 +63,13 @@
|
|||
background: 0;
|
||||
color: inherit;
|
||||
box-sizing: border-box;
|
||||
min-height: 56px;
|
||||
|
||||
&[type=time],
|
||||
&[type=date] {
|
||||
clip-path: inset(0 20px 0 0);
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease-in-out;
|
||||
}
|
||||
&[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
|
@ -83,6 +85,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
& > .prepend,
|
||||
& > .append,
|
||||
& > .icons {
|
||||
|
@ -102,13 +105,17 @@
|
|||
& > .prepend > prepend {
|
||||
padding-right: 12px;
|
||||
}
|
||||
& > .append > append {
|
||||
& > .icons {
|
||||
&.pre {
|
||||
padding-left: 12px;
|
||||
}
|
||||
& > .icons > vn-icon[icon=clear] {
|
||||
display: none;
|
||||
& > vn-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
& > vn-icon[icon=clear] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
& > .underline {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
@ -131,30 +138,58 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
&.not-empty > .container,
|
||||
&.focused > .container {
|
||||
& > .infix {
|
||||
& > .fix {
|
||||
opacity: 1;
|
||||
&.dense {
|
||||
& > .hint {
|
||||
display: none;
|
||||
}
|
||||
& > .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 {
|
||||
top: 5px;
|
||||
color: $color-main;
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
& > .control > * {
|
||||
&[type=time],
|
||||
&[type=date] {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.has-icons,
|
||||
&.not-empty:hover,
|
||||
&.not-empty.focused {
|
||||
& > .container > .append > append {
|
||||
& > .container {
|
||||
& > .append > append {
|
||||
padding-left: 0;
|
||||
}
|
||||
& > .container > .icons {
|
||||
& > .icons.pre {
|
||||
padding-left: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:not(.disabled):not(.readonly) {
|
||||
&.focused > .container > .underline.focus {
|
||||
left: 0;
|
||||
|
@ -170,14 +205,8 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
&:not(.not-empty):not(.focused) > .container > .infix > .control > input {
|
||||
&[type=time],
|
||||
&[type=date] {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
&.readonly > .container {
|
||||
& > .infix > .control > input {
|
||||
& > .infix > .control > * {
|
||||
caret-color: transparent;
|
||||
}
|
||||
& > .underline.blur {
|
||||
|
@ -186,11 +215,11 @@
|
|||
}
|
||||
& > .hint {
|
||||
z-index: -1;
|
||||
padding-top: 8px;
|
||||
height: 20px;
|
||||
padding: 4px 0;
|
||||
height: 12px;
|
||||
color: rgba(0, 0, 0, .4);
|
||||
font-size: 12px;
|
||||
transform: translateY(-28px);
|
||||
transform: translateY(-12px);
|
||||
transition-property: opacity, transform, color;
|
||||
transition-duration: 200ms;
|
||||
transition-timing-function: ease-in-out;
|
||||
|
@ -220,8 +249,3 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
vn-table {
|
||||
.vn-field {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
});
|
|
@ -3,23 +3,28 @@ import './style.scss';
|
|||
|
||||
export default class IconButton {
|
||||
constructor($element) {
|
||||
if ($element[0].getAttribute('tabindex') == null)
|
||||
$element[0].tabIndex = 0;
|
||||
this.element = $element[0];
|
||||
|
||||
$element.on('keyup', event => this.onKeyDown(event, $element));
|
||||
let button = $element[0].querySelector('button');
|
||||
$element[0].addEventListener('click', event => {
|
||||
if (this.element.getAttribute('tabindex') == null)
|
||||
this.element.tabIndex = 0;
|
||||
|
||||
this.element.addEventListener('keyup', e => this.onKeyup(e));
|
||||
this.element.addEventListener('click', e => this.onClick(e));
|
||||
}
|
||||
|
||||
onKeyup(event) {
|
||||
if (event.code == 'Space')
|
||||
this.onClick(event);
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
if (event.defaultPrevented) return;
|
||||
event.preventDefault();
|
||||
|
||||
// FIXME: Don't use Event.stopPropagation()
|
||||
let button = this.element.querySelector('button');
|
||||
if (this.disabled || button.disabled)
|
||||
event.stopImmediatePropagation();
|
||||
});
|
||||
}
|
||||
|
||||
onKeyDown(event, $element) {
|
||||
if (event.defaultPrevented) return;
|
||||
if (event.keyCode == 13) {
|
||||
event.preventDefault();
|
||||
$element.triggerHandler('click');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
@import "variables";
|
||||
@import "effects";
|
||||
|
||||
vn-icon-button {
|
||||
@extend %clickable-light;
|
||||
outline: 0;
|
||||
color: $color-main;
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 18pt;
|
||||
padding: .25em;
|
||||
|
||||
|
|
|
@ -13,13 +13,10 @@ import './tooltip/tooltip';
|
|||
import './icon-menu/icon-menu';
|
||||
import './button-menu/button-menu';
|
||||
import './popover/popover';
|
||||
import './autocomplete/autocomplete';
|
||||
import './drop-down/drop-down';
|
||||
import './menu/menu';
|
||||
import './multi-check/multi-check';
|
||||
import './date-picker/date-picker';
|
||||
import './button/button';
|
||||
import './textarea/textarea';
|
||||
import './icon-button/icon-button';
|
||||
import './submit/submit';
|
||||
import './card/card';
|
||||
|
@ -29,20 +26,22 @@ import './label-value/label-value';
|
|||
import './pagination/pagination';
|
||||
import './searchbar/searchbar';
|
||||
import './scroll-up/scroll-up';
|
||||
import './input-range';
|
||||
import './autocomplete';
|
||||
import './calendar';
|
||||
import './check';
|
||||
import './chip';
|
||||
import './color-legend';
|
||||
import './data-viewer';
|
||||
import './date-picker';
|
||||
import './field';
|
||||
import './input-number';
|
||||
import './input-range';
|
||||
import './input-time';
|
||||
import './input-file';
|
||||
import './list';
|
||||
import './radio';
|
||||
import './table';
|
||||
import './td-editable';
|
||||
import './textarea';
|
||||
import './th';
|
||||
import './treeview';
|
||||
import './treeview/child';
|
||||
|
|
|
@ -3,15 +3,13 @@ import Input from '../../lib/input';
|
|||
import './style.scss';
|
||||
|
||||
export default class InputFile extends Input {
|
||||
constructor($element, $scope, $attrs, vnTemplate) {
|
||||
constructor($element, $scope) {
|
||||
super($element, $scope);
|
||||
this.element = $element[0];
|
||||
this.hasFocus = false;
|
||||
this._multiple = false;
|
||||
this._value = 'Select a file';
|
||||
|
||||
vnTemplate.normalizeInputAttrs($attrs);
|
||||
|
||||
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', {
|
||||
template: require('./index.html'),
|
||||
|
|
|
@ -14,7 +14,7 @@ describe('Component vnInputFile', () => {
|
|||
beforeEach(angular.mock.inject(($componentController, $rootScope) => {
|
||||
$scope = $rootScope.$new();
|
||||
$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.input = $element[0].querySelector('input');
|
||||
controller.validate = () => {};
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
@import "variables";
|
||||
@import '../textfield/style.scss';
|
||||
|
||||
vn-input-file {
|
||||
@extend vn-textfield;
|
||||
.value {
|
||||
color: $color-font-secondary;
|
||||
cursor: pointer;
|
||||
|
@ -12,4 +10,156 @@ vn-input-file {
|
|||
input {
|
||||
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
|
||||
}
|
||||
}
|
|
@ -1,48 +1,48 @@
|
|||
<div class="container"
|
||||
ng-class="{selected: $ctrl.hasFocus}">
|
||||
<div class="textField">
|
||||
<div class="leftIcons">
|
||||
<div class="container">
|
||||
<div
|
||||
ng-transclude="prepend"
|
||||
class="prepend">
|
||||
</div>
|
||||
<div class="infix">
|
||||
<input
|
||||
class="mdl-textfield__input"
|
||||
type="number"
|
||||
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">
|
||||
<div class="fix prefix"></div>
|
||||
<div class="control"></div>
|
||||
<div class="fix suffix"></div>
|
||||
<label>
|
||||
<span translate>{{::$ctrl.label}}</span>
|
||||
<span translate ng-show="::$ctrl.required">*</span>
|
||||
<span class="required">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="underline"></div>
|
||||
<div class="selected underline"></div>
|
||||
<div class="suffix">
|
||||
<vn-icon-button
|
||||
<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
|
||||
ng-if="$ctrl.displayControls"
|
||||
icon="remove"
|
||||
ng-click="$ctrl.stepDown()"
|
||||
tabindex="-1"
|
||||
ng-click="$ctrl.onStep($event, 'down')"
|
||||
translate-attr="{title: 'Remove'}">
|
||||
</vn-icon-button>
|
||||
<vn-icon-button
|
||||
</vn-icon>
|
||||
<vn-icon
|
||||
ng-if="$ctrl.displayControls"
|
||||
icon="add"
|
||||
ng-click="$ctrl.stepUp()"
|
||||
tabindex="-1"
|
||||
ng-click="$ctrl.onStep($event, 'up')"
|
||||
translate-attr="{title: 'Add'}">
|
||||
</vn-icon-button>
|
||||
<i class="material-icons"
|
||||
ng-if="::$ctrl.hasInfo"
|
||||
vn-tooltip="{{::$ctrl.info}}">
|
||||
info_outline
|
||||
</i>
|
||||
</div>
|
||||
<div class="rightIcons"></div>
|
||||
</vn-icon>
|
||||
</div>
|
||||
<div class="underline blur"></div>
|
||||
<div class="underline focus"></div>
|
||||
</div>
|
||||
<div class="hint"></div>
|
|
@ -1,48 +1,10 @@
|
|||
import ngModule from '../../module';
|
||||
import Input from '../../lib/input';
|
||||
import './style.scss';
|
||||
import Field from '../field';
|
||||
|
||||
export default class InputNumber extends Input {
|
||||
constructor($element, $scope, $attrs, vnTemplate) {
|
||||
super($element, $scope);
|
||||
this.displayControls = false;
|
||||
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();
|
||||
export default class InputNumber extends Field {
|
||||
constructor($element, $scope, $compile) {
|
||||
super($element, $scope, $compile);
|
||||
this.buildInput('number');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,7 +20,8 @@ export default class InputNumber extends Input {
|
|||
* @param {Number} value - 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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
set step(value) {
|
||||
if (value) this.input.step = value;
|
||||
this.input.step = value;
|
||||
this.validateValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the input value
|
||||
*/
|
||||
stepUp() {
|
||||
this.input.stepUp();
|
||||
this.input.dispatchEvent(new Event('change'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decreases the input value
|
||||
*/
|
||||
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'));
|
||||
}
|
||||
}
|
||||
InputNumber.$inject = ['$element', '$scope', '$attrs', 'vnTemplate'];
|
||||
|
||||
ngModule.component('vnInputNumber', {
|
||||
ngModule.vnComponent('vnInputNumber', {
|
||||
template: require('./index.html'),
|
||||
controller: InputNumber,
|
||||
transclude: {
|
||||
leftIcons: '?tLeftIcons',
|
||||
rightIcons: '?tRightIcons'
|
||||
},
|
||||
bindings: {
|
||||
label: '@?',
|
||||
name: '@?',
|
||||
disabled: '<?',
|
||||
required: '@?',
|
||||
min: '<?',
|
||||
max: '<?',
|
||||
step: '<?',
|
||||
displayControls: '<?',
|
||||
rule: '@?',
|
||||
value: '=model',
|
||||
validate: '&',
|
||||
onChange: '&',
|
||||
onClear: '&'
|
||||
displayControls: '<?'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,68 +1,69 @@
|
|||
import './index.js';
|
||||
|
||||
describe('Component vnInputNumber', () => {
|
||||
let $scope;
|
||||
let $attrs;
|
||||
let $timeout;
|
||||
let $element;
|
||||
let controller;
|
||||
let $ctrl;
|
||||
|
||||
beforeEach(angular.mock.module('vnCore', $translateProvider => {
|
||||
$translateProvider.translations('en', {});
|
||||
}));
|
||||
|
||||
beforeEach(angular.mock.inject(($componentController, $rootScope) => {
|
||||
$scope = $rootScope.$new();
|
||||
$attrs = {field: '$ctrl.client.socialName'};
|
||||
$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 = () => {};
|
||||
beforeEach(angular.mock.inject(($compile, $rootScope) => {
|
||||
$element = $compile(`<vn-input-number></vn-input-number>`)($rootScope);
|
||||
$ctrl = $element.controller('vnInputNumber');
|
||||
}));
|
||||
|
||||
describe('value() setter', () => {
|
||||
it(`should set a value, add the class 'not-empty' and then call validateValue() method`, () => {
|
||||
spyOn(controller, 'validateValue');
|
||||
|
||||
controller.value = 10;
|
||||
|
||||
let classes = controller.element.classList.toString();
|
||||
|
||||
expect(classes).toContain('not-empty');
|
||||
expect(controller.validateValue).toHaveBeenCalledWith();
|
||||
afterEach(() => {
|
||||
$element.remove();
|
||||
});
|
||||
|
||||
it(`should set an empty value, remove the class 'not-empty' and then call validateValue() method`, () => {
|
||||
spyOn(controller, 'validateValue');
|
||||
describe('min() setter', () => {
|
||||
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(controller.validateValue).toHaveBeenCalledWith();
|
||||
expect($ctrl.shownError).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateValue()', () => {
|
||||
it(`should call hasValidValue() and not add the class invalid and validated`, () => {
|
||||
controller.input.min = 0;
|
||||
controller.input.value = 10;
|
||||
describe('max() setter', () => {
|
||||
it(`should set error property when value is upper than max`, () => {
|
||||
$ctrl.field = 1;
|
||||
$ctrl.max = 0;
|
||||
|
||||
controller.validateValue();
|
||||
let classes = controller.element.querySelector('.infix').classList.toString();
|
||||
|
||||
expect(classes).not.toContain('validated invalid');
|
||||
// FIXME: Input validation doesn't work with Jest?
|
||||
// expect($ctrl.shownError).toContain('Please select a value that is no more than 0');
|
||||
expect($ctrl.shownError).toBeNull();
|
||||
});
|
||||
|
||||
it(`should call hasValidValue() and add the class invalid and validated`, () => {
|
||||
controller.input.min = 0;
|
||||
controller.input.value = -10;
|
||||
// FIXME: Input validation doesn't work with Jest?
|
||||
it(`should unset error property when value is lower than max`, () => {
|
||||
$ctrl.field = -1;
|
||||
$ctrl.min = 0;
|
||||
|
||||
controller.validateValue();
|
||||
let classes = controller.element.querySelector('.infix').classList.toString();
|
||||
expect($ctrl.shownError).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -1,80 +1,41 @@
|
|||
import ngModule from '../../module';
|
||||
import Input from '../../lib/input';
|
||||
import './style.scss';
|
||||
import Field from '../field';
|
||||
|
||||
export default class InputTime extends Input {
|
||||
constructor($element, $scope, $filter) {
|
||||
super($element, $scope);
|
||||
export default class InputTime extends Field {
|
||||
constructor($element, $scope, $compile, $filter) {
|
||||
super($element, $scope, $compile);
|
||||
this.$filter = $filter;
|
||||
|
||||
this.registerEvents();
|
||||
this.input = $compile(`<input type="time"></input>`)($scope)[0];
|
||||
this.input.addEventListener('change', () => this.onValueUpdate());
|
||||
}
|
||||
|
||||
registerEvents() {
|
||||
this.input.addEventListener('change', event => {
|
||||
this.onTimeChange();
|
||||
this.emit('change', {event});
|
||||
});
|
||||
|
||||
this.input.addEventListener('focus', event => {
|
||||
this.emit('focus', {event});
|
||||
});
|
||||
get field() {
|
||||
return super.field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current value
|
||||
*/
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets input value
|
||||
*
|
||||
* @param {Number} value - Value
|
||||
*/
|
||||
set value(value) {
|
||||
this.updateValue(value);
|
||||
set field(value) {
|
||||
this.input.value = this.$filter('dateTime')(value, 'HH:mm');
|
||||
super.field = value;
|
||||
}
|
||||
|
||||
updateValue(value) {
|
||||
this._value = value;
|
||||
this.element.classList.toggle('not-empty', value != null);
|
||||
this.validateValue();
|
||||
}
|
||||
|
||||
onTimeChange() {
|
||||
onValueUpdate() {
|
||||
let date = null;
|
||||
let value = this.input.value;
|
||||
|
||||
if (value) {
|
||||
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);
|
||||
}
|
||||
|
||||
this.updateValue(date);
|
||||
super.field = date;
|
||||
this.$.$applyAsync();
|
||||
}
|
||||
}
|
||||
|
||||
InputTime.$inject = ['$element', '$scope', '$filter'];
|
||||
InputTime.$inject = ['$element', '$scope', '$compile', '$filter'];
|
||||
|
||||
ngModule.component('vnInputTime', {
|
||||
template: require('./index.html'),
|
||||
controller: InputTime,
|
||||
transclude: {
|
||||
leftIcons: '?tLeftIcons',
|
||||
rightIcons: '?tRightIcons'
|
||||
},
|
||||
bindings: {
|
||||
label: '@?',
|
||||
disabled: '<?',
|
||||
readonly: '<?',
|
||||
rule: '@?',
|
||||
value: '=model',
|
||||
vnTabIndex: '@?',
|
||||
onChange: '&',
|
||||
onClear: '&'
|
||||
}
|
||||
ngModule.vnComponent('vnInputTime', {
|
||||
controller: InputTime
|
||||
});
|
||||
|
|
|
@ -1,43 +1,32 @@
|
|||
import './index.js';
|
||||
|
||||
describe('Component vnInputTime', () => {
|
||||
let $scope;
|
||||
let $attrs;
|
||||
let $timeout;
|
||||
let $filter;
|
||||
let $element;
|
||||
let controller;
|
||||
let $ctrl;
|
||||
|
||||
beforeEach(angular.mock.module('vnCore', $translateProvider => {
|
||||
$translateProvider.translations('en', {});
|
||||
}));
|
||||
|
||||
beforeEach(angular.mock.inject(($componentController, $rootScope) => {
|
||||
$scope = $rootScope.$new();
|
||||
$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>');
|
||||
controller = $componentController('vnInputTime', {$element, $scope, $attrs, $timeout, $transclude: () => {}});
|
||||
beforeEach(angular.mock.inject(($compile, $rootScope, _$filter_) => {
|
||||
$filter = _$filter_;
|
||||
|
||||
$element = $compile(`<vn-input-time></vn-input-time>`)($rootScope);
|
||||
$ctrl = $element.controller('vnInputTime');
|
||||
}));
|
||||
|
||||
describe('value() setter', () => {
|
||||
it(`should set _value to a given value, add the class not-empty and remove invalid and validated`, () => {
|
||||
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');
|
||||
afterEach(() => {
|
||||
$element.remove();
|
||||
});
|
||||
|
||||
it(`should set _value to a given value and not add the class not-empty if the given value is null`, () => {
|
||||
controller.value = null;
|
||||
let classes = controller.element.classList.toString();
|
||||
describe('field() setter', () => {
|
||||
it(`should display the formated the date`, () => {
|
||||
let date = new Date();
|
||||
$ctrl.field = date;
|
||||
let displayed = $filter('dateTime')(date, 'HH:mm');
|
||||
|
||||
expect(classes).not.toContain('not-empty');
|
||||
expect(controller._value).toEqual(null);
|
||||
expect($ctrl.value).toEqual(displayed);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<vn-check
|
||||
field="$ctrl.checked"
|
||||
ng-model="$ctrl.checked"
|
||||
intermediate="$ctrl.isIntermediate"
|
||||
translate-attr="{title: 'Check all'}">
|
||||
</vn-check>
|
|
@ -109,7 +109,6 @@ export default class Popover extends Component {
|
|||
this.showTimeout = null;
|
||||
this.element.style.display = 'none';
|
||||
this.document.body.removeChild(this.element);
|
||||
this.emit('close');
|
||||
}, 250);
|
||||
|
||||
this.document.removeEventListener('keydown', this.docKeyDownHandler);
|
||||
|
@ -118,8 +117,8 @@ export default class Popover extends Component {
|
|||
this.element.removeEventListener('mousedown', this.bgMouseDownHandler);
|
||||
this.bgMouseDownHandler = null;
|
||||
|
||||
if (this.deregisterCallback)
|
||||
this.deregisterCallback();
|
||||
if (this.deregisterCallback) this.deregisterCallback();
|
||||
this.emit('close');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -189,7 +188,8 @@ export default class Popover extends Component {
|
|||
}
|
||||
|
||||
onBgMouseDown(event) {
|
||||
if (event != this.lastMouseEvent)
|
||||
if (event == this.lastMouseEvent || event.defaultPrevented) return;
|
||||
event.preventDefault();
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,13 +10,13 @@ import './style.scss';
|
|||
*/
|
||||
export default class Radio extends Toggle {
|
||||
set field(value) {
|
||||
this._field = value;
|
||||
super.field = value;
|
||||
this.element.classList.toggle('checked',
|
||||
Boolean(value) && value == this.val);
|
||||
}
|
||||
|
||||
get field() {
|
||||
return this._field;
|
||||
return super.field;
|
||||
}
|
||||
|
||||
set checked(value) {
|
||||
|
@ -43,14 +43,10 @@ export default class Radio extends Toggle {
|
|||
}
|
||||
}
|
||||
|
||||
ngModule.component('vnRadio', {
|
||||
ngModule.vnComponent('vnRadio', {
|
||||
template: require('../toggle/index.html'),
|
||||
controller: Radio,
|
||||
bindings: {
|
||||
label: '@?',
|
||||
field: '=?',
|
||||
disabled: '<?',
|
||||
checked: '<?',
|
||||
val: '@?'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,29 +1,30 @@
|
|||
<form ng-submit="$ctrl.onSubmit()">
|
||||
<vn-horizontal>
|
||||
<vn-textfield vn-one label="Search" model="$ctrl.searchString">
|
||||
<t-left-icons>
|
||||
<vn-textfield
|
||||
class="dense vn-py-md"
|
||||
placeholder="{{::'Search' | translate}}"
|
||||
ng-model="$ctrl.searchString">
|
||||
<prepend>
|
||||
<vn-icon
|
||||
icon="search"
|
||||
ng-click="$ctrl.clearFilter(); $ctrl.onSubmit()"
|
||||
pointer>
|
||||
</vn-icon>
|
||||
</t-left-icons>
|
||||
<t-right-icons vn-horizontal>
|
||||
<vn-icon vn-one
|
||||
</prepend>
|
||||
<append>
|
||||
<vn-icon
|
||||
ng-if="$ctrl.info"
|
||||
icon="info_outline"
|
||||
vn-tooltip="{{$ctrl.info}}"
|
||||
pointer>
|
||||
</vn-icon>
|
||||
<vn-icon vn-one
|
||||
<vn-icon
|
||||
ng-if="$ctrl.panel"
|
||||
ng-click="$ctrl.openPanel($event)"
|
||||
icon="keyboard_arrow_down"
|
||||
icon="arrow_drop_down"
|
||||
pointer>
|
||||
</vn-icon>
|
||||
</t-right-icons>
|
||||
</append>
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
</form>
|
||||
<vn-popover
|
||||
vn-id="popover"
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
@import "variables";
|
||||
|
||||
vn-searchbar {
|
||||
padding-top: 6px;
|
||||
display: block;
|
||||
|
||||
& > form > vn-horizontal > vn-icon-button {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
.search-panel {
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
<div class="table" ng-transclude>
|
||||
<div class="vn-table" ng-transclude>
|
||||
</div>
|
|
@ -5,16 +5,20 @@ vn-table {
|
|||
display: block;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
|
||||
& > div {
|
||||
width: inherit;
|
||||
}
|
||||
.vn-table {
|
||||
width: 100%;
|
||||
display: table;
|
||||
border-collapse: collapse;
|
||||
|
||||
& > vn-thead {
|
||||
& > vn-thead,
|
||||
& > thead {
|
||||
display: table-header-group;
|
||||
border-bottom: .15em solid $color-spacer;
|
||||
|
||||
& > * > th {
|
||||
font-weight: normal;
|
||||
}
|
||||
& > * > vn-th[field] {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
|
@ -42,34 +46,43 @@ vn-table {
|
|||
}
|
||||
}
|
||||
}
|
||||
& > vn-tbody {
|
||||
& > vn-tbody,
|
||||
& > tbody {
|
||||
display: table-row-group;
|
||||
}
|
||||
& > vn-tfoot {
|
||||
& > vn-tfoot,
|
||||
& > tfoot {
|
||||
border-top: .15em solid $color-spacer;
|
||||
display: table-footer-group
|
||||
}
|
||||
& > * > vn-tr,
|
||||
& > * > a.vn-tr {
|
||||
& > * > a.vn-tr,
|
||||
& > * > tr {
|
||||
display: table-row;
|
||||
height: 3em;
|
||||
}
|
||||
vn-thead, vn-tbody, vn-tfoot {
|
||||
vn-thead, vn-tbody, vn-tfoot,
|
||||
thead, tbody, tfoot {
|
||||
& > * {
|
||||
display: table-row;
|
||||
|
||||
& > vn-th {
|
||||
& > vn-th,
|
||||
& > th {
|
||||
color: $color-font-light;
|
||||
padding-top: 1em;
|
||||
padding-bottom: .8em;
|
||||
}
|
||||
& > vn-th,
|
||||
& > vn-td {
|
||||
& > vn-td,
|
||||
& > th,
|
||||
& > td {
|
||||
overflow: hidden;
|
||||
}
|
||||
& > vn-th,
|
||||
& > vn-td,
|
||||
& > vn-td-editable {
|
||||
& > vn-td-editable,
|
||||
& > th,
|
||||
& > td {
|
||||
vertical-align: middle;
|
||||
display: table-cell;
|
||||
text-align: left;
|
||||
|
@ -108,7 +121,8 @@ vn-table {
|
|||
color: inherit;
|
||||
}
|
||||
}
|
||||
vn-tbody > * {
|
||||
vn-tbody > *,
|
||||
tbody > * {
|
||||
border-bottom: .1em solid $color-spacer-light;
|
||||
|
||||
&:last-child {
|
||||
|
@ -117,42 +131,36 @@ vn-table {
|
|||
&.clickable {
|
||||
@extend %clickable;
|
||||
}
|
||||
& > vn-td .chip {
|
||||
& > vn-td,
|
||||
& > td {
|
||||
.chip {
|
||||
padding: .3em;
|
||||
border-radius: .3em
|
||||
}
|
||||
|
||||
& > vn-td .chip.notice {
|
||||
border-radius: .3em;
|
||||
color: $color-font-bg;
|
||||
|
||||
&.notice {
|
||||
background-color: $color-notice-medium
|
||||
}
|
||||
|
||||
& > vn-td .chip.success {
|
||||
color: $color-font-bg;
|
||||
background-color: $color-success-medium
|
||||
&.success {
|
||||
background-color: $color-success-medium;
|
||||
}
|
||||
|
||||
& > vn-td .chip.warning {
|
||||
color: $color-font-bg;
|
||||
&.warning {
|
||||
background-color: $color-main-medium;
|
||||
}
|
||||
|
||||
& > vn-td .chip.alert {
|
||||
color: $color-font-bg;
|
||||
&.alert {
|
||||
background-color: $color-alert-medium;
|
||||
}
|
||||
|
||||
& > vn-td .chip.message {
|
||||
&.message {
|
||||
color: $color-font-dark;
|
||||
background-color: $color-bg-dark
|
||||
}
|
||||
|
||||
& > vn-td vn-icon-menu {
|
||||
}
|
||||
vn-icon-menu {
|
||||
display: inline-block;
|
||||
color: $color-main;
|
||||
padding: .25em
|
||||
}
|
||||
|
||||
}
|
||||
& > [actions] {
|
||||
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 {
|
||||
float: right;
|
||||
margin: 0!important;
|
||||
|
|
|
@ -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: '<?'
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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: '@?'
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -1,94 +1,13 @@
|
|||
import ngModule from '../../module';
|
||||
import Input from '../../lib/input';
|
||||
import './style.scss';
|
||||
import Field from '../field';
|
||||
|
||||
export default class Textfield extends Input {
|
||||
constructor($element, $scope, $attrs, vnTemplate) {
|
||||
super($element, $scope);
|
||||
vnTemplate.normalizeInputAttrs($attrs);
|
||||
this._value = null;
|
||||
this.type = $attrs.type;
|
||||
this.showActions = false;
|
||||
this.hasInfo = Boolean($attrs.info);
|
||||
this.hasFocus = false;
|
||||
this.hasMouseIn = false;
|
||||
export default class Textfield extends Field {
|
||||
constructor($element, $scope, $compile) {
|
||||
super($element, $scope, $compile);
|
||||
this.buildInput('text');
|
||||
}
|
||||
}
|
||||
|
||||
this.input.addEventListener('keyup', e => {
|
||||
if (e.defaultPrevented || e.key != 'Escape')
|
||||
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: '@?'
|
||||
}
|
||||
ngModule.vnComponent('vnTextfield', {
|
||||
controller: Textfield
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,16 +1,13 @@
|
|||
import ngModule from '../../module';
|
||||
import Component from '../../lib/component';
|
||||
import FormInput from '../form-input';
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* 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} disabled Put component in disabled mode
|
||||
*/
|
||||
export default class Toggle extends Component {
|
||||
export default class Toggle extends FormInput {
|
||||
constructor($element, $) {
|
||||
super($element, $);
|
||||
|
||||
|
@ -49,14 +46,10 @@ export default class Toggle extends Component {
|
|||
this.emit('change', {value: this.field});
|
||||
}
|
||||
}
|
||||
Toggle.$inject = ['$element', '$scope'];
|
||||
|
||||
ngModule.component('vnToggle', {
|
||||
ngModule.vnComponent('vnToggle', {
|
||||
controller: Toggle,
|
||||
bindings: {
|
||||
label: '@?',
|
||||
field: '=?',
|
||||
disabled: '<?',
|
||||
checked: '<?'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
.vn-toggle {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
outline: none;
|
||||
|
||||
&.disabled {
|
||||
|
@ -19,8 +20,9 @@
|
|||
vertical-align: middle;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
min-width: 20px;
|
||||
margin: 6px 0;
|
||||
margin-right: .4em;
|
||||
margin-right: .6em;
|
||||
border: 2px solid #666;
|
||||
}
|
||||
&.checked > .btn {
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
<ul ng-if="::$ctrl.items">
|
||||
<li
|
||||
ng-repeat="item in $ctrl.items"
|
||||
on-drop="$ctrl.onDrop(item, dragged, dropped)"
|
||||
vn-draggable="{{::$ctrl.draggable}}"
|
||||
vn-droppable="{{::$ctrl.droppable}}"
|
||||
ng-class="{expanded: item.active}">
|
||||
<div
|
||||
ng-click="$ctrl.toggle($event, item)"
|
||||
class="node clickable">
|
||||
<vn-icon
|
||||
class="arrow"
|
||||
ng-class="{invisible: item.sons == 0}"
|
||||
icon="keyboard_arrow_down"
|
||||
translate-attr="{title: 'Toggle'}">
|
||||
</vn-icon>
|
||||
<vn-check
|
||||
vn-acl="{{$ctrl.aclRole}}"
|
||||
ng-if="$ctrl.selectable"
|
||||
field="item.selected"
|
||||
disabled="$ctrl.disabled"
|
||||
on-change="$ctrl.select(item, value)"
|
||||
triple-state="true"
|
||||
label="{{::item.name}}">
|
||||
</vn-check>
|
||||
<vn-icon-button
|
||||
icon="{{icon.icon}}"
|
||||
ng-repeat="icon in $ctrl.icons"
|
||||
ng-click="$ctrl.onIconClick(icon, item, $ctrl.parent, $parent.$index)"
|
||||
vn-acl="{{$ctrl.aclRole}}"
|
||||
vn-acl-action="remove">
|
||||
</vn-icon-button>
|
||||
</div>
|
||||
<vn-treeview-child
|
||||
items="item.childs"
|
||||
parent="item"
|
||||
selectable="$ctrl.selectable"
|
||||
disabled="$ctrl.disabled"
|
||||
editable="$ctrl.editable"
|
||||
draggable="::$ctrl.draggable"
|
||||
droppable="::$ctrl.droppable"
|
||||
icons="::$ctrl.icons"
|
||||
parent-scope="::$ctrl.parentScope"
|
||||
acl-role="$ctrl.aclRole">
|
||||
</vn-treeview-child>
|
||||
</li>
|
||||
<li
|
||||
ng-if="$ctrl.isInsertable && $ctrl.editable"
|
||||
ng-click="$ctrl.onCreate($ctrl.parent)"
|
||||
vn-acl="{{$ctrl.aclRole}}"
|
||||
vn-acl-action="remove">
|
||||
<div class="node">
|
||||
<vn-icon-button
|
||||
icon="add_circle">
|
||||
</vn-icon-button>
|
||||
<div class="description" translate>
|
||||
Create new one
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
|
@ -1,56 +0,0 @@
|
|||
import ngModule from '../../module';
|
||||
import Component from '../../lib/component';
|
||||
|
||||
class Controller extends Component {
|
||||
constructor($element, $scope) {
|
||||
super($element, $scope);
|
||||
this.$scope = $scope;
|
||||
}
|
||||
|
||||
toggle(event, item) {
|
||||
if (event.defaultPrevented || !item.sons) return;
|
||||
event.preventDefault();
|
||||
this.treeview.onToggle(item);
|
||||
}
|
||||
|
||||
select(item, value) {
|
||||
this.treeview.onSelection(item, value);
|
||||
}
|
||||
|
||||
onIconClick(icon, item, parent, index) {
|
||||
let parentController = this.parentScope.$ctrl;
|
||||
icon.callback.call(parentController, item, parent, index);
|
||||
}
|
||||
|
||||
onCreate(parent) {
|
||||
this.treeview.onCreate(parent);
|
||||
}
|
||||
|
||||
onDrop(item, dragged, dropped) {
|
||||
this.treeview.onDrop(item, dragged, dropped);
|
||||
}
|
||||
|
||||
get isInsertable() {
|
||||
return Array.isArray(this.parent) || this.parent.childs;
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.component('vnTreeviewChild', {
|
||||
template: require('./child.html'),
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
items: '<',
|
||||
parent: '<',
|
||||
icons: '<?',
|
||||
disabled: '<?',
|
||||
selectable: '<?',
|
||||
editable: '<?',
|
||||
draggable: '<?',
|
||||
droppable: '<?',
|
||||
aclRole: '<?',
|
||||
parentScope: '<'
|
||||
},
|
||||
require: {
|
||||
treeview: '^vnTreeview'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
<ul ng-if="$ctrl.items">
|
||||
<li ng-repeat="item in $ctrl.items" >
|
||||
<div
|
||||
ng-class="{expanded: item.active}"
|
||||
ng-click="$ctrl.onClick($event, item)"
|
||||
class="node clickable">
|
||||
<vn-icon
|
||||
class="arrow"
|
||||
ng-class="{invisible: !item.sons}"
|
||||
icon="keyboard_arrow_down"
|
||||
translate-attr="::{title: 'Toggle'}">
|
||||
</vn-icon>
|
||||
<vn-treeview-content
|
||||
item="::item">
|
||||
</vn-treeview-content>
|
||||
<section class="buttons" ng-if="::!$ctrl.treeview.readOnly">
|
||||
<vn-icon-button translate-attr="::{title: 'Remove'}"
|
||||
icon="delete"
|
||||
ng-click="$ctrl.treeview.onRemove(item)"
|
||||
ng-if="item.parent">
|
||||
</vn-icon-button>
|
||||
<vn-icon-button translate-attr="::{title: 'Create'}"
|
||||
icon="add_circle"
|
||||
ng-click="$ctrl.treeview.onCreate(item)">
|
||||
</vn-icon-button>
|
||||
</section>
|
||||
</div>
|
||||
<vn-treeview-childs
|
||||
items="item.childs"
|
||||
parent="::item">
|
||||
</vn-treeview-childs>
|
||||
</li>
|
||||
</ul>
|
|
@ -0,0 +1,26 @@
|
|||
import ngModule from '../../module';
|
||||
import Component from '../../lib/component';
|
||||
|
||||
class Controller extends Component {
|
||||
onClick(event, item) {
|
||||
if (event.defaultPrevented || !item.sons) return;
|
||||
event.preventDefault();
|
||||
this.treeview.onToggle(item);
|
||||
}
|
||||
|
||||
onDrop(item, dragged, dropped) {
|
||||
this.treeview.onDrop(item, dragged, dropped);
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.component('vnTreeviewChilds', {
|
||||
template: require('./childs.html'),
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
items: '<',
|
||||
parent: '<?'
|
||||
},
|
||||
require: {
|
||||
treeview: '^vnTreeview'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
import ngModule from '../../module';
|
||||
|
||||
class Controller {
|
||||
constructor($element, $scope, $compile) {
|
||||
this.$element = $element;
|
||||
this.$scope = $scope;
|
||||
this.$compile = $compile;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
if (this.item.parent) {
|
||||
this.treeview.$transclude(($clone, $scope) => {
|
||||
this.$contentScope = $scope;
|
||||
$scope.item = this.item;
|
||||
this.$element.append($clone);
|
||||
});
|
||||
} else {
|
||||
let template = `<span translate>{{$ctrl.treeview.rootLabel}}</span>`;
|
||||
let $clone = this.$compile(template)(this.$scope);
|
||||
this.$element.append($clone);
|
||||
}
|
||||
}
|
||||
|
||||
$onDestroy() {
|
||||
if (this.$contentScope)
|
||||
this.$contentScope.$destroy();
|
||||
}
|
||||
}
|
||||
Controller.$inject = ['$element', '$scope', '$compile'];
|
||||
|
||||
ngModule.component('vnTreeviewContent', {
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
item: '<'
|
||||
},
|
||||
require: {
|
||||
treeview: '^vnTreeview'
|
||||
}
|
||||
});
|
|
@ -1,13 +1,3 @@
|
|||
<vn-treeview-child
|
||||
acl-role="$ctrl.aclRole"
|
||||
items="$ctrl.data"
|
||||
parent="$ctrl.data"
|
||||
selectable="$ctrl.selectable"
|
||||
editable="$ctrl.editable"
|
||||
disabled="$ctrl.disabled"
|
||||
icons="$ctrl.icons"
|
||||
parent-scope="$ctrl.$scope.$parent"
|
||||
draggable="$ctrl.draggable"
|
||||
droppable="$ctrl.droppable"
|
||||
vn-droppable="{{$ctrl.droppable}}">
|
||||
</vn-treeview-child>
|
||||
<vn-treeview-childs
|
||||
items="$ctrl.items">
|
||||
</vn-treeview-childs>
|
||||
|
|
|
@ -2,50 +2,71 @@ import ngModule from '../../module';
|
|||
import Component from '../../lib/component';
|
||||
import './style.scss';
|
||||
|
||||
import './childs';
|
||||
import './content';
|
||||
|
||||
/**
|
||||
* Treeview
|
||||
*
|
||||
* @property {String} position The relative position to the parent
|
||||
*/
|
||||
export default class Treeview extends Component {
|
||||
constructor($element, $scope) {
|
||||
constructor($element, $scope, $transclude) {
|
||||
super($element, $scope);
|
||||
this.$scope = $scope;
|
||||
this.data = [];
|
||||
this.$transclude = $transclude;
|
||||
this.readOnly = true;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.refresh();
|
||||
get data() {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.model.refresh().then(() => {
|
||||
this.data = this.model.data;
|
||||
set data(value) {
|
||||
this._data = value;
|
||||
|
||||
const sons = value.length;
|
||||
const rootElement = [{
|
||||
childs: value,
|
||||
active: true,
|
||||
sons: sons
|
||||
}];
|
||||
|
||||
this.setParent(rootElement[0], value);
|
||||
|
||||
this.items = rootElement;
|
||||
}
|
||||
|
||||
fetch() {
|
||||
return this.fetchFunc().then(res =>
|
||||
this.data = res
|
||||
);
|
||||
}
|
||||
|
||||
setParent(parent, childs) {
|
||||
childs.forEach(child => {
|
||||
child.parent = parent;
|
||||
|
||||
if (child.childs)
|
||||
this.setParent(parent, child.childs);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits selection event
|
||||
* @param {Object} item - Selected item
|
||||
* @param {Boolean} value - Changed value
|
||||
*/
|
||||
onSelection(item, value) {
|
||||
this.emit('selection', {item, value});
|
||||
}
|
||||
|
||||
onCreate(parent) {
|
||||
this.emit('create', {parent});
|
||||
}
|
||||
|
||||
onToggle(item) {
|
||||
if (item.active)
|
||||
item.childs = undefined;
|
||||
else {
|
||||
this.model.applyFilter({}, {parentFk: item.id}).then(() => {
|
||||
const newData = this.model.data;
|
||||
this.fold(item);
|
||||
else
|
||||
this.unfold(item);
|
||||
}
|
||||
|
||||
if (item.childs) {
|
||||
let childs = item.childs;
|
||||
fold(item) {
|
||||
item.childs = undefined;
|
||||
item.active = false;
|
||||
}
|
||||
|
||||
unfold(item) {
|
||||
return this.fetchFunc({$item: item}).then(newData => {
|
||||
this.setParent(item, newData);
|
||||
|
||||
const childs = item.childs;
|
||||
if (childs) {
|
||||
childs.forEach(child => {
|
||||
let index = newData.findIndex(newChild => {
|
||||
return newChild.id == child.id;
|
||||
|
@ -54,21 +75,49 @@ export default class Treeview extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
item.childs = newData.sort((a, b) => {
|
||||
if (b.selected !== a.selected) {
|
||||
if (a.selected == null)
|
||||
return 1;
|
||||
if (b.selected == null)
|
||||
return -1;
|
||||
return b.selected - a.selected;
|
||||
if (this.sortFunc) {
|
||||
item.childs = newData.sort((a, b) =>
|
||||
this.sortFunc({$a: a, $b: b})
|
||||
);
|
||||
}
|
||||
}).then(() => item.active = true);
|
||||
}
|
||||
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
});
|
||||
onRemove(item) {
|
||||
if (this.removeFunc)
|
||||
this.removeFunc({$item: item});
|
||||
}
|
||||
|
||||
item.active = !item.active;
|
||||
remove(item) {
|
||||
const parent = item.parent;
|
||||
let childs = parent.childs;
|
||||
|
||||
if (!childs) childs = [];
|
||||
|
||||
let index = childs.indexOf(item);
|
||||
childs.splice(index, 1);
|
||||
if (parent) parent.sons--;
|
||||
}
|
||||
|
||||
onCreate(parent) {
|
||||
if (this.createFunc)
|
||||
this.createFunc({$parent: parent});
|
||||
}
|
||||
|
||||
create(item) {
|
||||
const parent = item.parent;
|
||||
let childs = parent.childs;
|
||||
if (!childs) childs = [];
|
||||
|
||||
childs.push(item);
|
||||
|
||||
if (this.sortFunc) {
|
||||
childs = childs.sort((a, b) =>
|
||||
this.sortFunc({$a: a, $b: b})
|
||||
);
|
||||
}
|
||||
|
||||
if (parent) parent.sons++;
|
||||
}
|
||||
|
||||
onDrop(item, dragged, dropped) {
|
||||
|
@ -76,19 +125,21 @@ export default class Treeview extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
Treeview.$inject = ['$element', '$scope'];
|
||||
Treeview.$inject = ['$element', '$scope', '$transclude'];
|
||||
|
||||
ngModule.component('vnTreeview', {
|
||||
template: require('./index.html'),
|
||||
controller: Treeview,
|
||||
bindings: {
|
||||
model: '<',
|
||||
icons: '<?',
|
||||
disabled: '<?',
|
||||
selectable: '<?',
|
||||
editable: '<?',
|
||||
rootLabel: '@',
|
||||
data: '<?',
|
||||
draggable: '<?',
|
||||
droppable: '<?',
|
||||
aclRole: '@?'
|
||||
}
|
||||
fetchFunc: '&',
|
||||
removeFunc: '&?',
|
||||
createFunc: '&?',
|
||||
sortFunc: '&?',
|
||||
readOnly: '<?'
|
||||
},
|
||||
transclude: true
|
||||
});
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
@import "effects";
|
||||
|
||||
vn-treeview {
|
||||
vn-treeview-child {
|
||||
display: block
|
||||
}
|
||||
vn-treeview-childs {
|
||||
display: block;
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
@ -11,37 +10,43 @@ vn-treeview {
|
|||
li {
|
||||
list-style: none;
|
||||
|
||||
& > div > .arrow {
|
||||
min-width: 24px;
|
||||
margin-right: 10px;
|
||||
transition: transform 200ms;
|
||||
}
|
||||
&.expanded > div > .arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
& > .node {
|
||||
@extend %clickable;
|
||||
display: flex;
|
||||
padding: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
& > vn-check:not(.indeterminate) {
|
||||
color: $color-main;
|
||||
& > div > .arrow {
|
||||
min-width: 24px;
|
||||
margin-right: 10px;
|
||||
transition: transform 200ms;
|
||||
}
|
||||
|
||||
& > .btn {
|
||||
border-color: $color-main;
|
||||
}
|
||||
}
|
||||
& > vn-check.checked {
|
||||
color: $color-main;
|
||||
}
|
||||
& > div.expanded > .arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
ul {
|
||||
padding-left: 2.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vn-icon-button {
|
||||
padding: 0
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.node > .buttons {
|
||||
display: none
|
||||
}
|
||||
|
||||
.node:hover > .buttons {
|
||||
display: block
|
||||
}
|
||||
}
|
||||
|
||||
vn-treeview-content {
|
||||
flex-grow: 1
|
||||
}
|
|
@ -223,11 +223,8 @@ export default class Watcher extends Component {
|
|||
}
|
||||
|
||||
loadOriginalData() {
|
||||
Object.keys(this.data).forEach(key => {
|
||||
delete this.data[key];
|
||||
});
|
||||
|
||||
this.data = Object.assign(this.data, this.orgData);
|
||||
const orgData = JSON.parse(JSON.stringify(this.orgData));
|
||||
this.data = Object.assign(this.data, orgData);
|
||||
this.setPristine();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import './id';
|
|||
import './focus';
|
||||
import './dialog';
|
||||
import './popover';
|
||||
import './validation';
|
||||
import './rule';
|
||||
import './acl';
|
||||
import './on-error-src';
|
||||
import './zoom-image';
|
||||
|
|
|
@ -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);
|
||||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -15,7 +15,7 @@
|
|||
<vn-horizontal ng-repeat="field in fields">
|
||||
<vn-check
|
||||
vn-one label="{{titles[field]}}"
|
||||
field="tableConfiguration.configuration[field]">
|
||||
ng-model="tableConfiguration.configuration[field]">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
|
|
|
@ -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);
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
import './module-loader';
|
||||
import './crud';
|
||||
import './acl-service';
|
||||
import './template';
|
||||
import './copy';
|
||||
import './equals';
|
||||
import './modified';
|
||||
|
|
|
@ -30,79 +30,13 @@ export default class Input extends Component {
|
|||
if (this.mdlElement)
|
||||
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'];
|
||||
|
||||
export const component = {
|
||||
transclude: {
|
||||
leftIcons: '?tLeftIcons',
|
||||
rightIcons: '?tRightIcons'
|
||||
},
|
||||
export const $options = {
|
||||
bindings: {
|
||||
label: '@?',
|
||||
disabled: '<?',
|
||||
readonly: '<?',
|
||||
rule: '@?',
|
||||
value: '=model',
|
||||
vnTabIndex: '@?',
|
||||
onChange: '&',
|
||||
onClear: '&'
|
||||
readonly: '<?'
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,7 +6,7 @@
|
|||
* @return {String} The camelized string
|
||||
*/
|
||||
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 camelCased;
|
||||
|
|
|
@ -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);
|
|
@ -4,6 +4,43 @@ const ngModule = ng.module('vnCore', ngDeps);
|
|||
ngModule.constant('moment', require('moment-timezone'));
|
||||
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'];
|
||||
export function config($translateProvider, $translatePartialLoaderProvider) {
|
||||
$translatePartialLoaderProvider.addPart('core');
|
||||
|
|
|
@ -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);
|
|
@ -1,6 +1,8 @@
|
|||
|
||||
import './acl-service';
|
||||
import './app';
|
||||
import './auth';
|
||||
import './token';
|
||||
import './modules';
|
||||
import './interceptor';
|
||||
import './config';
|
||||
|
|
|
@ -64,14 +64,6 @@ vn-app {
|
|||
box-sizing: border-box;
|
||||
padding: $spacing-md;
|
||||
height: inherit;
|
||||
|
||||
form vn-horizontal {
|
||||
align-items: center;
|
||||
|
||||
& > * {
|
||||
padding: .2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
vn-main-block {
|
||||
display: block;
|
||||
|
@ -102,11 +94,6 @@ vn-app {
|
|||
.content-block {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
|
||||
form vn-horizontal {
|
||||
flex-direction: column;
|
||||
align-items: initial;
|
||||
}
|
||||
}
|
||||
vn-main-block {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,6 @@ import './background/background';
|
|||
import './side-menu/side-menu';
|
||||
import './left-menu/left-menu';
|
||||
import './topbar/topbar';
|
||||
import './user-configuration-popover';
|
||||
import './user-popover';
|
||||
import './descriptor';
|
||||
import './summary';
|
||||
|
|
|
@ -3,20 +3,20 @@
|
|||
<form name="form" ng-submit="$ctrl.submit()">
|
||||
<vn-textfield
|
||||
label="User"
|
||||
model="$ctrl.user"
|
||||
ng-model="$ctrl.user"
|
||||
name="user"
|
||||
vn-id="userField"
|
||||
vn-focus>
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
label="Password"
|
||||
model="$ctrl.password"
|
||||
ng-model="$ctrl.password"
|
||||
name="password"
|
||||
type="password">
|
||||
</vn-textfield>
|
||||
<vn-check
|
||||
label="Do not close session"
|
||||
field="$ctrl.remember"
|
||||
ng-model="$ctrl.remember"
|
||||
name="remember">
|
||||
</vn-check>
|
||||
<div class="footer">
|
||||
|
|
|
@ -32,10 +32,8 @@ vn-login {
|
|||
& > form {
|
||||
& > vn-textfield {
|
||||
width: 100%;
|
||||
margin: 1em 0;
|
||||
}
|
||||
& > vn-check {
|
||||
padding-top: .5em;
|
||||
display: block;
|
||||
.md-label {
|
||||
white-space: inherit;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div>
|
||||
<div
|
||||
ng-click="$ctrl.openUserConfiguration($event)"
|
||||
ng-click="userPopover.show($event)"
|
||||
id="user"
|
||||
class="unselectable">
|
||||
{{$root.user.nickname}}
|
||||
|
@ -15,7 +15,7 @@
|
|||
id="logout"
|
||||
icon="exit_to_app"
|
||||
translate-attr="{title: 'Logout'}"
|
||||
ng-click="$ctrl.onLogoutClick()">
|
||||
ng-click="$ctrl.vnAuth.logout()">
|
||||
</vn-icon-button>
|
||||
</div>
|
||||
<vn-menu vn-id="apps-menu">
|
||||
|
@ -26,6 +26,6 @@
|
|||
</li>
|
||||
</ul>
|
||||
</vn-menu>
|
||||
<vn-user-configuration-popover
|
||||
vn-id="popover">
|
||||
</vn-user-configuration-popover>
|
||||
<vn-user-popover
|
||||
vn-id="user-popover">
|
||||
</vn-user-popover>
|
|
@ -21,14 +21,6 @@ export default class MainMenu {
|
|||
window.localStorage.currentUserWorkerId = json.data.workerId;
|
||||
});
|
||||
}
|
||||
|
||||
openUserConfiguration(event) {
|
||||
this.$.popover.show(event);
|
||||
}
|
||||
|
||||
onLogoutClick() {
|
||||
this.vnAuth.logout();
|
||||
}
|
||||
}
|
||||
MainMenu.$inject = ['$scope', '$http', 'vnAuth', 'vnModules'];
|
||||
|
||||
|
|
|
@ -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
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue