vnDialog opens in body, nightmare extensions with detailed errors

This commit is contained in:
Juan Ferrer 2019-10-28 17:31:33 +01:00
parent 47998aaa97
commit 1b2c383502
104 changed files with 1133 additions and 1408 deletions

View File

@ -6,7 +6,7 @@ import config from './config.js';
let currentUser;
let asyncActions = {
let actions = {
// Generic extensions
clickIfExists: async function(selector) {
@ -15,16 +15,16 @@ let asyncActions = {
return exists;
},
hasClass: async function(selector, className) {
return await this.evaluate((selector, className) => {
document.querySelector(selector).classList.contains(className);
}, selector, className);
},
parsedUrl: async function() {
return new URL(await this.url());
},
waitUntilNotPresent: async function(selector) {
await this.wait(selector => {
return document.querySelector(selector) == null;
}, selector);
},
// Salix specific extensions
changeLanguageToEnglish: async function() {
@ -116,31 +116,21 @@ let asyncActions = {
}, selector, time);
},
isDisabled: async function(selector) {
return await this.hasClass(selector, 'disabled');
}
};
let actions = {
clearTextarea: function(selector, done) {
this.wait(selector)
clearTextarea: function(selector) {
return this.wait(selector)
.evaluate(inputSelector => {
return document.querySelector(inputSelector).value = '';
}, selector)
.then(done)
.catch(done);
}, selector);
},
clearInput: function(selector, done) {
this.wait(selector)
clearInput: function(selector) {
return this.wait(selector)
.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);
}, selector);
},
getProperty: function(selector, property, done) {
@ -149,47 +139,39 @@ let actions = {
}, done, selector, property);
},
waitPropertyLength: function(selector, property, minLength, done) {
this.wait((selector, property, minLength) => {
waitPropertyLength: function(selector, property, minLength) {
return this.wait((selector, property, minLength) => {
const element = document.querySelector(selector);
return element && element[property] != null && element[property] !== '' && element[property].length >= minLength;
}, selector, property, minLength)
.getProperty(selector, property)
.then(result => done(null, result), done);
.getProperty(selector, property);
},
waitPropertyValue: function(selector, property, status, done) {
this.wait(selector)
waitPropertyValue: function(selector, property, status) {
return this.wait(selector)
.wait((selector, property, status) => {
const element = document.querySelector(selector);
return element[property] === status;
}, selector, property, status)
.then(done)
.catch(done);
}, selector, property, status);
},
waitToGetProperty: function(selector, property, done) {
this.wait((selector, property) => {
waitToGetProperty: function(selector, property) {
return this.wait((selector, property) => {
const element = document.querySelector(selector);
return element && element[property] != null && element[property] !== '';
}, selector, property)
.getProperty(selector, property)
.then(result => done(null, result), done);
.getProperty(selector, property);
},
write: function(selector, text, done) {
this.wait(selector)
.type(selector, text)
.then(done)
.catch(done);
write: function(selector, text) {
return this.wait(selector)
.type(selector, text);
},
waitToClick: function(selector, done) {
this.wait(selector)
.click(selector)
.then(done)
.catch(done);
waitToClick: function(selector) {
return this.wait(selector)
.click(selector);
},
focusElement: function(selector, done) {
@ -197,9 +179,7 @@ let actions = {
.evaluate_now(selector => {
let element = document.querySelector(selector);
element.focus();
}, done, selector)
.then(done)
.catch(done);
}, done, selector);
},
isVisible: function(selector, done) {
@ -245,29 +225,23 @@ let actions = {
}, done, selector);
},
waitImgLoad: function(selector, done) {
this.wait(selector)
waitImgLoad: function(selector) {
return this.wait(selector)
.wait(selector => {
const imageReady = document.querySelector(selector).complete;
return imageReady;
}, selector)
.then(done)
.catch(() => {
done(new Error(`image ${selector}, load timed out`));
});
}, selector);
},
clickIfVisible: function(selector, done) {
this.wait(selector)
clickIfVisible: function(selector) {
return this.wait(selector)
.isVisible(selector)
.then(visible => {
if (visible)
return this.click(selector);
throw new Error(`invisible selector: ${selector}`);
})
.then(done)
.catch(done);
});
},
countElement: function(selector, done) {
@ -276,56 +250,40 @@ let actions = {
}, done, selector);
},
waitForNumberOfElements: function(selector, count, done) {
this.wait((selector, count) => {
waitForNumberOfElements: function(selector, count) {
return this.wait((selector, count) => {
return document.querySelectorAll(selector).length === count;
}, selector, count)
.then(done)
.catch(() => {
done(new Error(`.waitForNumberOfElements() for ${selector}, count ${count} timed out`));
});
}, selector, count);
},
waitForClassNotPresent: function(selector, className, done) {
this.wait(selector)
waitForClassNotPresent: function(selector, className) {
return this.wait(selector)
.wait((selector, className) => {
if (!document.querySelector(selector).classList.contains(className))
return true;
}, selector, className)
.then(done)
.catch(() => {
done(new Error(`.waitForClassNotPresent() for ${selector}, class ${className} timed out`));
});
}, selector, className);
},
waitForClassPresent: function(selector, className, done) {
this.wait(selector)
waitForClassPresent: function(selector, className) {
return this.wait(selector)
.wait((elementSelector, targetClass) => {
if (document.querySelector(elementSelector).classList.contains(targetClass))
return true;
}, selector, className)
.then(done)
.catch(() => {
done(new Error(`.waitForClassPresent() for ${selector}, class ${className} timed out`));
});
}, selector, className);
},
waitForTextInElement: function(selector, text, done) {
this.wait(selector)
waitForTextInElement: function(selector, text) {
return this.wait(selector)
.wait((selector, text) => {
return document.querySelector(selector).innerText.toLowerCase().includes(text.toLowerCase());
}, selector, text)
.then(done)
.catch(done);
}, selector, text);
},
waitForTextInInput: function(selector, text, done) {
this.wait(selector)
waitForTextInInput: function(selector, text) {
return this.wait(selector)
.wait((selector, text) => {
return document.querySelector(selector).value.toLowerCase().includes(text.toLowerCase());
}, selector, text)
.then(done)
.catch(done);
}, selector, text);
},
waitForInnerText: function(selector, done) {
@ -339,20 +297,16 @@ let actions = {
}, done, selector);
},
waitForEmptyInnerText: function(selector, done) {
this.wait(selector => {
waitForEmptyInnerText: function(selector) {
return this.wait(selector => {
return document.querySelector(selector).innerText == '';
}, selector)
.then(done)
.catch(done);
}, selector);
},
waitForURL: function(hashURL, done) {
this.wait(hash => {
waitForURL: function(hashURL) {
return this.wait(hash => {
return document.location.hash.includes(hash);
}, hashURL)
.then(done)
.catch(done);
}, hashURL);
},
waitForShapes: function(selector, done) {
@ -368,11 +322,9 @@ let actions = {
return shapesList;
}, done, selector);
},
waitForSnackbar: function(done) {
this.wait(500).waitForShapes('vn-snackbar .shape .text')
.then(shapes => {
done(null, shapes);
}).catch(done);
waitForSnackbar: function() {
return this.wait(500)
.waitForShapes('vn-snackbar .shape .text');
},
waitForLastShape: function(selector, done) {
@ -384,15 +336,13 @@ let actions = {
}, done, selector);
},
waitForLastSnackbar: function(done) {
this.wait(500).waitForLastShape('vn-snackbar .shape .text')
.then(shapes => {
done(null, shapes);
}).catch(done);
waitForLastSnackbar: function() {
return this.wait(500)
.waitForLastShape('vn-snackbar .shape .text');
},
accessToSearchResult: function(searchValue, done) {
this.clearInput('vn-searchbar input')
accessToSearchResult: function(searchValue) {
return this.clearInput('vn-searchbar input')
.write('vn-searchbar input', searchValue)
.click('vn-searchbar vn-icon[icon="search"]')
.wait(100)
@ -405,13 +355,11 @@ let actions = {
return this.waitToClick('ui-view vn-card vn-td');
return this.waitToClick('ui-view vn-card a');
})
.then(done)
.catch(done);
});
},
accessToSection: function(sectionRoute, done) {
this.wait(`vn-left-menu`)
accessToSection: function(sectionRoute) {
return this.wait(`vn-left-menu`)
.evaluate(sectionRoute => {
return document.querySelector(`vn-left-menu ul li ul li > a[ui-sref="${sectionRoute}"]`) != null;
}, sectionRoute)
@ -422,45 +370,35 @@ let actions = {
return this.waitToClick('vn-left-menu .collapsed')
.wait('vn-left-menu .expanded')
.waitToClick(`vn-left-menu li > a[ui-sref="${sectionRoute}"]`);
})
.then(done)
.catch(done);
});
},
autocompleteSearch: function(autocompleteSelector, searchValue, done) {
this.waitToClick(`${autocompleteSelector} input`)
.write(`.vn-popover.shown .vn-drop-down input`, searchValue)
.waitToClick(`.vn-popover.shown .vn-drop-down li.active`)
autocompleteSearch: function(autocompleteSelector, searchValue) {
return this.waitToClick(`${autocompleteSelector} input`)
.write(`.vn-drop-down.shown input`, searchValue)
.waitToClick(`.vn-drop-down.shown li.active`)
.wait((autocompleteSelector, searchValue) => {
return document.querySelector(`${autocompleteSelector} input`).value
.toLowerCase()
.includes(searchValue.toLowerCase());
}, autocompleteSelector, searchValue)
.then(done)
.catch(() => {
done(new Error(`.autocompleteSearch() for value ${searchValue} in ${autocompleteSelector} timed out`));
});
}, autocompleteSelector, searchValue);
},
reloadSection: function(sectionRoute, done) {
this.waitToClick('vn-icon[icon="desktop_windows"]')
reloadSection: function(sectionRoute) {
return this.waitToClick('vn-icon[icon="desktop_windows"]')
.wait('vn-card.summary')
.waitToClick(`vn-left-menu li > a[ui-sref="${sectionRoute}"]`)
.then(done)
.catch(done);
.waitToClick(`vn-left-menu li > a[ui-sref="${sectionRoute}"]`);
},
forceReloadSection: function(sectionRoute, done) {
this.waitToClick('vn-icon[icon="desktop_windows"]')
forceReloadSection: function(sectionRoute) {
return this.waitToClick('vn-icon[icon="desktop_windows"]')
.waitToClick('button[response="ACCEPT"]')
.wait('vn-card.summary')
.waitToClick(`vn-left-menu li > a[ui-sref="${sectionRoute}"]`)
.then(done)
.catch(done);
.waitToClick(`vn-left-menu li > a[ui-sref="${sectionRoute}"]`);
},
checkboxState: function(selector, done) {
this.wait(selector)
checkboxState: function(selector) {
return this.wait(selector)
.evaluate(selector => {
let checkbox = document.querySelector(selector);
switch (checkbox.$ctrl.field) {
@ -471,40 +409,38 @@ let actions = {
default:
return 'unchecked';
}
}, selector)
.then(res => done(null, res))
.catch(done);
}, selector);
},
isDisabled: function(selector, done) {
this.wait(selector)
isDisabled: function(selector) {
return this.wait(selector)
.evaluate(selector => {
let element = document.querySelector(selector);
return element.$ctrl.disabled;
}, selector)
.then(res => done(null, res))
.catch(done);
}, selector);
},
waitForSpinnerLoad: function(done) {
let spinnerSelector = 'vn-spinner > div';
this.waitForClassNotPresent(spinnerSelector, 'is-active')
.then(done)
.catch(done);
waitForSpinnerLoad: function() {
return this.waitForClassNotPresent('vn-spinner > div', 'is-active');
},
};
for (let name in asyncActions) {
let fn = asyncActions[name];
for (let name in actions) {
Nightmare.action(name, function(...args) {
let done = args[args.length - 1];
fn.apply(this, args)
.then(res => done(null, res), done);
let promise = actions[name].apply(this, args);
if (promise) {
promise
.then(res => done(null, res))
.catch(err => {
let errArgs = args.slice(0, args.length - 1);
errArgs = errArgs
.map(i => JSON.stringify(i))
.join(', ');
done(new Error(`.${name}(${errArgs}) failed: ${err.message}`));
});
}
});
}
Object.keys(actions).forEach(function(name) {
let fn = actions[name];
Nightmare.action(name, fn);
});

View File

@ -18,7 +18,7 @@ export default {
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]'
acceptButton: '.vn-confirm.shown button[response=ACCEPT]'
},
clientsIndex: {
searchClientInput: `vn-textfield input`,
@ -44,7 +44,7 @@ export default {
},
clientDescriptor: {
moreMenu: 'vn-client-descriptor vn-icon-menu[icon=more_vert]',
simpleTicketButton: '.vn-popover.shown .vn-drop-down li'
simpleTicketButton: '.vn-drop-down.shown li'
},
clientBasicData: {
basicDataButton: 'vn-left-menu a[ui-sref="client.card.basicData"]',
@ -62,7 +62,7 @@ export default {
socialNameInput: `vn-textfield input[name="socialName"]`,
fiscalIdInput: `vn-textfield input[name="fi"]`,
equalizationTaxCheckbox: 'vn-check[ng-model="$ctrl.client.isEqualizated"]',
acceptPropagationButton: 'vn-client-fiscal-data > vn-confirm button[response=ACCEPT]',
acceptPropagationButton: '.vn-confirm.shown button[response=ACCEPT]',
addressInput: `vn-textfield input[name="street"]`,
postcodeInput: `vn-textfield input[name="postcode"]`,
cityInput: `vn-textfield input[name="city"]`,
@ -87,10 +87,10 @@ export default {
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',
newBankEntityCode: 'vn-client-billing-data > vn-dialog vn-textfield[label="Entity Code"] input',
acceptBankEntityButton: 'vn-client-billing-data > vn-dialog button[response="ACCEPT"]',
newBankEntityName: '.vn-dialog.shown vn-textfield[label="Name"] input',
newBankEntityBIC: '.vn-dialog.shown vn-textfield[label="Swift / BIC"] input',
newBankEntityCode: '.vn-dialog.shown vn-textfield[label="Entity Code"] input',
acceptBankEntityButton: '.vn-dialog.shown button[response="ACCEPT"]',
saveButton: `button[type=submit]`
},
clientAddresses: {
@ -163,9 +163,9 @@ export default {
balanceButton: 'vn-left-menu a[ui-sref="client.card.balance.index"]',
companyAutocomplete: 'vn-client-balance-index vn-autocomplete[ng-model="$ctrl.companyFk"]',
newPaymentButton: `vn-float-button`,
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"]',
newPaymentBank: '.vn-dialog.shown vn-autocomplete[ng-model="$ctrl.receipt.bankFk"]',
newPaymentAmountInput: '.vn-dialog.shown vn-input-number[ng-model="$ctrl.receipt.amountPaid"] input',
saveButton: '.vn-dialog.shown vn-button[label="Save"]',
firstBalanceLine: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(8)'
},
@ -177,7 +177,7 @@ export default {
deleteFileButton: 'vn-client-dms-index vn-tr:nth-child(1) vn-icon-button[icon="delete"]',
firstDocWorker: 'vn-client-dms-index vn-td:nth-child(8) > span',
firstDocWorkerDescriptor: '.vn-popover.shown vn-worker-descriptor',
acceptDeleteButton: 'vn-client-dms-index > vn-confirm button[response="ACCEPT"]'
acceptDeleteButton: '.vn-confirm.shown button[response="ACCEPT"]'
},
itemsIndex: {
searchIcon: 'vn-item-index vn-searchbar vn-icon[icon="search"]',
@ -185,26 +185,26 @@ export default {
searchResult: 'vn-item-index a.vn-tr',
searchResultPreviewButton: 'vn-item-index .buttons > [icon="desktop_windows"]',
searchResultCloneButton: 'vn-item-index .buttons > [icon="icon-clone"]',
acceptClonationAlertButton: 'vn-item-index [vn-id="clone"] [response="ACCEPT"]',
acceptClonationAlertButton: '.vn-confirm.shown [response="ACCEPT"]',
searchItemInput: 'vn-searchbar vn-textfield input',
searchButton: 'vn-searchbar vn-icon[icon="search"]',
closeItemSummaryPreview: 'vn-item-index [vn-id="preview"] button.close',
closeItemSummaryPreview: '.vn-popup.shown',
fieldsToShowButton: 'vn-item-index vn-table > div > div > vn-icon-button[icon="menu"]',
fieldsToShowForm: 'vn-item-index vn-table > div > div > vn-dialog > div > form',
fieldsToShowForm: '.vn-dialog.shown form',
firstItemImage: 'vn-item-index vn-tbody > a:nth-child(1) > vn-td:nth-child(1)',
firstItemId: 'vn-item-index vn-tbody > a:nth-child(1) > vn-td:nth-child(2)',
idCheckbox: 'vn-item-index vn-dialog form vn-horizontal:nth-child(2) > vn-check',
stemsCheckbox: 'vn-item-index vn-dialog form vn-horizontal:nth-child(3) > vn-check',
sizeCheckbox: 'vn-item-index vn-dialog form vn-horizontal:nth-child(4) > vn-check',
nicheCheckbox: 'vn-item-index vn-dialog form vn-horizontal:nth-child(5) > vn-check',
typeCheckbox: 'vn-item-index vn-dialog form vn-horizontal:nth-child(6) > vn-check',
categoryCheckbox: 'vn-item-index vn-dialog form vn-horizontal:nth-child(7) > vn-check',
intrastadCheckbox: 'vn-item-index vn-dialog form vn-horizontal:nth-child(8) > vn-check',
originCheckbox: 'vn-item-index vn-dialog form vn-horizontal:nth-child(9) > vn-check',
buyerCheckbox: 'vn-item-index vn-dialog form vn-horizontal:nth-child(10) > vn-check',
destinyCheckbox: 'vn-item-index vn-dialog form vn-horizontal:nth-child(11) > vn-check',
taxClassCheckbox: 'vn-item-index vn-dialog form vn-horizontal:nth-child(12) > vn-check',
saveFieldsButton: 'vn-item-index vn-dialog vn-horizontal:nth-child(16) > vn-button > button'
idCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(2) > vn-check',
stemsCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(3) > vn-check',
sizeCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(4) > vn-check',
nicheCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(5) > vn-check',
typeCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(6) > vn-check',
categoryCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(7) > vn-check',
intrastadCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(8) > vn-check',
originCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(9) > vn-check',
buyerCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(10) > vn-check',
destinyCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(11) > vn-check',
taxClassCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(12) > vn-check',
saveFieldsButton: '.vn-dialog.shown vn-horizontal:nth-child(16) > vn-button > button'
},
itemCreateView: {
temporalName: `vn-textfield input[name="provisionalName"]`,
@ -217,11 +217,11 @@ export default {
itemDescriptor: {
goBackToModuleIndexButton: 'vn-item-descriptor a[href="#!/item/index"]',
moreMenu: 'vn-item-descriptor vn-icon-menu[icon=more_vert]',
moreMenuRegularizeButton: '.vn-popover.shown .vn-drop-down li[name="Regularize stock"]',
regularizeQuantityInput: 'vn-item-descriptor vn-dialog tpl-body > div > vn-textfield input',
regularizeWarehouseAutocomplete: 'vn-item-descriptor vn-dialog vn-autocomplete[ng-model="$ctrl.warehouseFk"]',
moreMenuRegularizeButton: '.vn-drop-down.shown li[name="Regularize stock"]',
regularizeQuantityInput: '.vn-dialog.shown tpl-body > div > vn-textfield input',
regularizeWarehouseAutocomplete: '.vn-dialog.shown 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',
regularizeSaveButton: '.vn-dialog.shown tpl-buttons > button',
inactiveIcon: 'vn-item-descriptor vn-icon[icon="icon-unavailable"]',
navigateBackToIndex: 'vn-item-descriptor vn-icon[icon="chevron_left"]'
},
@ -336,11 +336,11 @@ export default {
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[icon=more_vert]',
moreMenuWeeklyTickets: '.vn-popover.shown .vn-drop-down li:nth-child(2)',
moreMenuWeeklyTickets: '.vn-drop-down.shown li:nth-child(2)',
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"]'
acceptDeleteTurn: '.vn-confirm.shown button[response="ACCEPT"]'
},
createTicketView: {
clientAutocomplete: 'vn-ticket-create vn-autocomplete[ng-model="$ctrl.clientFk"]',
@ -355,24 +355,24 @@ export default {
stateLabelValue: 'vn-ticket-descriptor vn-label-value[label="State"]',
goBackToModuleIndexButton: 'vn-ticket-descriptor a[ui-sref="ticket.index"]',
moreMenu: 'vn-ticket-descriptor vn-icon-menu[icon=more_vert]',
moreMenuAddStowaway: '.vn-popover.shown .vn-drop-down li[name="Add stowaway"]',
moreMenuDeleteStowawayButton: '.vn-popover.shown .vn-drop-down li[name="Remove stowaway"]',
moreMenuAddToTurn: '.vn-popover.shown .vn-drop-down li[name="Add turn"]',
moreMenuDeleteTicket: '.vn-popover.shown .vn-drop-down li[name="Delete ticket"]',
moreMenuMakeInvoice: '.vn-popover.shown .vn-drop-down li[name="Make invoice"]',
moreMenuChangeShippedHour: '.vn-popover.shown .vn-drop-down li[name="Change shipped hour"]',
changeShippedHourDialog: 'vn-ticket-descriptor vn-dialog[vn-id="changeShippedDialog"]',
changeShippedHourInput: 'vn-dialog[vn-id="changeShippedDialog"] [ng-model="$ctrl.newShipped"]',
addStowawayDialogFirstTicket: 'vn-ticket-descriptor > vn-add-stowaway > vn-dialog vn-table vn-tbody vn-tr',
moreMenuAddStowaway: '.vn-drop-down.shown li[name="Add stowaway"]',
moreMenuDeleteStowawayButton: '.vn-drop-down.shown li[name="Remove stowaway"]',
moreMenuAddToTurn: '.vn-drop-down.shown li[name="Add turn"]',
moreMenuDeleteTicket: '.vn-drop-down.shown li[name="Delete ticket"]',
moreMenuMakeInvoice: '.vn-drop-down.shown li[name="Make invoice"]',
moreMenuChangeShippedHour: '.vn-drop-down.shown li[name="Change shipped hour"]',
changeShippedHourDialog: '.vn-dialog.shown',
changeShippedHourInput: '.vn-dialog.shown [ng-model="$ctrl.newShipped"]',
addStowawayDialogFirstTicket: '.vn-dialog.shown vn-table vn-tbody vn-tr',
shipButton: 'vn-ticket-descriptor vn-icon[icon="icon-stowaway"]',
thursdayButton: 'vn-ticket-descriptor > vn-dialog > div > form > div.body > tpl-body > div > vn-tool-bar > vn-button:nth-child(4)',
saturdayButton: 'vn-ticket-descriptor > vn-dialog > div > form > div.body > tpl-body > div > vn-tool-bar > vn-button:nth-child(6)',
closeStowawayDialog: 'vn-ticket-descriptor > vn-add-stowaway > vn-dialog > div > button[class="close"]',
acceptDeleteButton: 'vn-ticket-descriptor button[response="ACCEPT"]',
acceptChangeHourButton: 'vn-ticket-descriptor vn-dialog[vn-id="changeShippedDialog"] button[response="ACCEPT"]',
thursdayButton: '.vn-popup.shown vn-tool-bar > vn-button:nth-child(4)',
saturdayButton: '.vn-popup.shown vn-tool-bar > vn-button:nth-child(6)',
closeStowawayDialog: '.vn-dialog.shown button[class="close"]',
acceptDeleteButton: '.vn-dialog.shown button[response="ACCEPT"]',
acceptChangeHourButton: '.vn-dialog.shown button[response="ACCEPT"]',
descriptorDeliveryDate: 'vn-ticket-descriptor > div > div.body > div.attributes > vn-label-value:nth-child(6) > section > span',
acceptInvoiceOutButton: 'vn-ticket-descriptor vn-confirm[vn-id="makeInvoiceConfirmation"] button[response="ACCEPT"]',
acceptDeleteStowawayButton: 'vn-ticket-descriptor > vn-remove-stowaway button[response="ACCEPT"]'
acceptInvoiceOutButton: '.vn-confirm.shown button[response="ACCEPT"]',
acceptDeleteStowawayButton: '.vn-dialog.shown button[response="ACCEPT"]'
},
ticketNotes: {
firstNoteRemoveButton: 'vn-icon[icon="delete"]',
@ -384,7 +384,7 @@ export default {
ticketExpedition: {
expeditionButton: 'vn-left-menu a[ui-sref="ticket.card.expedition"]',
secondExpeditionRemoveButton: 'vn-ticket-expedition vn-table div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(1) > vn-icon-button[icon="delete"]',
acceptDeleteRowButton: 'vn-ticket-expedition > vn-confirm[vn-id="delete-expedition"] button[response=ACCEPT]',
acceptDeleteRowButton: '.vn-confirm.shown button[response=ACCEPT]',
expeditionRow: 'vn-ticket-expedition vn-table vn-tbody > vn-tr'
},
ticketPackages: {
@ -405,11 +405,11 @@ export default {
newItemFromCatalogButton: 'vn-ticket-sale vn-float-button[icon="add"]',
newItemButton: 'vn-ticket-sale > vn-vertical > vn-card > vn-vertical > vn-one > vn-icon-button > button > vn-icon > i',
moreMenu: 'vn-ticket-sale vn-tool-bar > vn-button-menu[vn-id="more-button"] > div > button',
moreMenuCreateClaim: '.vn-popover.shown .vn-drop-down li[name="Add claim"]',
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[ng-model="$ctrl.newDiscount"] input',
moreMenuCreateClaim: '.vn-drop-down.shown li[name="Add claim"]',
moreMenuReserve: '.vn-drop-down.shown li[name="Mark as reserved"]',
moreMenuUnmarkReseved: '.vn-drop-down.shown li[name="Unmark as reserved"]',
moreMenuUpdateDiscount: '.vn-drop-down.shown li[name="Update discount"]',
moreMenuUpdateDiscountInput: '.vn-dialog.shown 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"]',
@ -421,7 +421,7 @@ export default {
firstSaleQuantityCell: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td-editable:nth-child(5)',
firstSaleQuantityClearInput: 'vn-textfield[ng-model="sale.quantity"] div.suffix > i',
firstSaleIdAutocomplete: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > vn-autocomplete',
idAutocompleteFirstResult: '.vn-popover.shown .vn-drop-down li',
idAutocompleteFirstResult: '.vn-drop-down.shown 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',
firstSaleDiscount: 'vn-ticket-sale vn-table vn-tr:nth-child(1) > vn-td:nth-child(8) > span',
@ -453,8 +453,8 @@ export default {
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"]',
acceptDeleteLineButton: 'vn-ticket-sale > vn-confirm[vn-id="delete-lines"] button[response=ACCEPT]',
acceptDeleteTicketButton: 'vn-ticket-sale > vn-confirm[vn-id="delete-ticket"] button[response=ACCEPT]',
acceptDeleteLineButton: '.vn-confirm.shown button[response=ACCEPT]',
acceptDeleteTicketButton: '.vn-confirm.shown button[response=ACCEPT]',
stateMenuButton: 'vn-ticket-sale vn-tool-bar > vn-button-menu[label="State"]'
},
ticketTracking: {
@ -504,10 +504,10 @@ export default {
firstPriceInput: 'vn-ticket-service vn-input-number[label="Price"] input',
firstVatTypeAutocomplete: 'vn-ticket-service vn-autocomplete[label="Tax class"]',
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',
newDescriptionInput: '.vn-dialog.shown vn-textfield[ng-model="$ctrl.newServiceType.name"] input',
serviceLine: 'vn-ticket-service > form > vn-card > vn-one:nth-child(2) > vn-horizontal',
saveServiceButton: `button[type=submit]`,
saveDescriptionButton: 'vn-ticket-service > vn-dialog[vn-id="createServiceTypeDialog"] > div > form > div.buttons > tpl-buttons > button'
saveDescriptionButton: '.vn-dialog.shown tpl-buttons > button'
},
createStateView: {
stateAutocomplete: 'vn-autocomplete[ng-model="$ctrl.stateFk"]',
@ -522,8 +522,8 @@ export default {
},
claimDescriptor: {
moreMenu: 'vn-claim-descriptor vn-icon-menu[icon=more_vert]',
moreMenuDeleteClaim: '.vn-popover.shown .vn-drop-down li[name="Delete claim"]',
acceptDeleteClaim: 'vn-claim-descriptor > vn-confirm[vn-id="confirm-delete-claim"] button[response="ACCEPT"]'
moreMenuDeleteClaim: '.vn-drop-down.shown li[name="Delete claim"]',
acceptDeleteClaim: '.vn-confirm.shown button[response="ACCEPT"]'
},
claimSummary: {
header: 'vn-claim-summary > vn-card > h5',
@ -549,7 +549,7 @@ export default {
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',
firstClaimableSaleFromTicket: '.vn-dialog.shown vn-tbody > vn-tr',
claimDetailLine: 'vn-claim-detail > vn-vertical > vn-card > vn-vertical > vn-table > div > vn-tbody > vn-tr',
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 > vn-vertical > vn-horizontal > div > vn-label-value:nth-child(2) > section > span',
@ -589,7 +589,7 @@ export default {
},
orderDescriptor: {
returnToModuleIndexButton: 'vn-order-descriptor a[ui-sref="order.index"]',
acceptNavigationButton: 'vn-order-basic-data vn-confirm button[response=ACCEPT]'
acceptNavigationButton: '.vn-confirm.shown button[response=ACCEPT]'
},
createOrderView: {
clientAutocomplete: 'vn-autocomplete[label="Client"]',
@ -618,13 +618,13 @@ export default {
agencyAutocomplete: 'vn-autocomplete[label="Agency"]',
observationInput: 'vn-textarea[label="Observation"] textarea',
saveButton: `button[type=submit]`,
acceptButton: 'vn-order-basic-data vn-confirm[vn-id="confirm"] button[response="ACCEPT"]'
acceptButton: '.vn-confirm.shown button[response="ACCEPT"]'
},
orderLine: {
orderSubtotal: 'vn-order-line vn-horizontal.header p:nth-child(1)',
orderSubtotal: 'vn-order-line .header :first-child',
firstLineDeleteButton: 'vn-order-line vn-tbody > vn-tr:nth-child(1) vn-icon[icon="delete"]',
confirmOrder: 'vn-order-line > vn-vertical > vn-button-bar > vn-button > button',
confirmButton: 'vn-order-line > vn-confirm button[response="ACCEPT"]',
confirmOrder: 'vn-order-line vn-float-button',
confirmButton: '.vn-confirm.shown button[response="ACCEPT"]',
},
routeIndex: {
addNewRouteButton: 'vn-route-index > a[ui-sref="route.create"]'
@ -663,7 +663,7 @@ export default {
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"]'
confirmButton: '.vn-confirm.shown button[response="ACCEPT"]'
},
workerPbx: {
extensionInput: 'vn-worker-pbx vn-textfield[ng-model="$ctrl.worker.sip.extension"] input',
@ -678,7 +678,7 @@ export default {
fridayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(5) > vn-icon-button',
saturdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(6) > vn-icon-button',
sundayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(7) > vn-icon-button',
confirmButton: 'vn-worker-time-control > vn-dialog > div > form > div.buttons > tpl-buttons > button',
confirmButton: '.vn-dialog.shown tpl-buttons > button',
firstEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(1) > span',
firstEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(1) > span',
firstEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(1) > span',
@ -726,11 +726,11 @@ export default {
},
invoiceOutDescriptor: {
moreMenu: 'vn-invoice-out-descriptor vn-icon-menu[icon=more_vert]',
moreMenuDeleteInvoiceOut: '.vn-popover.shown .vn-drop-down li[name="Delete Invoice"]',
moreMenuBookInvoiceOut: '.vn-popover.shown .vn-drop-down li[name="Book invoice"]',
moreMenuShowInvoiceOutPdf: '.vn-popover.shown .vn-drop-down li[name="Show invoice PDF"]',
acceptDeleteButton: 'vn-invoice-out-descriptor > vn-confirm[vn-id="deleteConfirmation"] button[response="ACCEPT"]',
acceptBookingButton: 'vn-invoice-out-descriptor > vn-confirm[vn-id="bookConfirmation"] button[response="ACCEPT"]'
moreMenuDeleteInvoiceOut: '.vn-drop-down.shown li[name="Delete Invoice"]',
moreMenuBookInvoiceOut: '.vn-drop-down.shown li[name="Book invoice"]',
moreMenuShowInvoiceOutPdf: '.vn-drop-down.shown li[name="Show invoice PDF"]',
acceptDeleteButton: '.vn-confirm.shown button[response="ACCEPT"]',
acceptBookingButton: '.vn-confirm.shown button[response="ACCEPT"]'
},
invoiceOutSummary: {
bookedLabel: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-one > vn-label-value:nth-child(4) > section > span'

View File

@ -6,6 +6,7 @@ describe('Login path', () => {
it('should receive an error when the username is incorrect', async() => {
const result = await nightmare
.doLogin('badUser', null)
.waitUntilNotPresent('body', 'jose')
.waitForLastSnackbar();
expect(result.length).toBeGreaterThan(0);

View File

@ -110,7 +110,7 @@ describe('Client Add address path', () => {
it(`should go back to the addreses section by clicking the cancel button`, async() => {
const url = await nightmare
.waitToClick(selectors.clientAddresses.cancelEditAddressButton)
.waitToClick('vn-confirm button[response="ACCEPT"]')
.waitToClick('.vn-confirm.shown button[response="ACCEPT"]')
.waitForURL('address/index')
.parsedUrl();

View File

@ -21,16 +21,12 @@ describe('Item summary path', () => {
});
it(`should click on the search result summary button to open the item summary popup`, async() => {
const isVisibleBefore = await nightmare
const isVisible = await nightmare
.waitForTextInElement(selectors.itemsIndex.searchResult, 'Ranged weapon longbow 2m')
.isVisible(selectors.itemSummary.basicData);
const isVisibleAfter = await nightmare
.waitToClick(selectors.itemsIndex.searchResultPreviewButton)
.isVisible(selectors.itemSummary.basicData);
expect(isVisibleBefore).toBeFalsy();
expect(isVisibleAfter).toBeTruthy();
expect(isVisible).toBeTruthy();
});
it(`should check the item summary preview shows fields from basic data`, async() => {
@ -75,8 +71,9 @@ describe('Item summary path', () => {
it(`should close the summary popup`, async() => {
const result = await nightmare
.waitToClick(selectors.itemsIndex.closeItemSummaryPreview)
.isVisible(selectors.itemSummary.basicData);
.mousedown(selectors.itemsIndex.closeItemSummaryPreview)
.waitUntilNotPresent(selectors.itemSummary.basicData)
.visible(selectors.itemSummary.basicData);
expect(result).toBeFalsy();
});
@ -94,17 +91,13 @@ describe('Item summary path', () => {
});
it(`should now click on the search result summary button to open the item summary popup`, async() => {
const isVisibleBefore = await nightmare
const isVisible = await nightmare
.waitForTextInElement(selectors.itemsIndex.searchResult, 'Melee weapon combat fist 15cm')
.isVisible(selectors.itemSummary.basicData);
const isVisibleAfter = await nightmare
.waitToClick(selectors.itemsIndex.searchResultPreviewButton)
.isVisible(selectors.itemSummary.basicData);
expect(isVisibleBefore).toBeFalsy();
expect(isVisibleAfter).toBeTruthy();
expect(isVisible).toBeTruthy();
});
it(`should now check the item summary preview shows fields from basic data`, async() => {
@ -149,8 +142,9 @@ describe('Item summary path', () => {
it(`should now close the summary popup`, async() => {
const result = await nightmare
.waitToClick(selectors.itemsIndex.closeItemSummaryPreview)
.isVisible(selectors.itemSummary.basicData);
.mousedown(selectors.itemsIndex.closeItemSummaryPreview)
.waitUntilNotPresent(selectors.itemSummary.basicData)
.visible(selectors.itemSummary.basicData);
expect(result).toBeFalsy();
});

View File

@ -65,7 +65,7 @@ describe('Ticket services path', () => {
it('should click on the add new description to open the dialog', async() => {
const result = await nightmare
.waitToClick(selectors.ticketService.firstAddDescriptionButton)
.waitForClassPresent('vn-ticket-service > vn-dialog', 'shown')
.wait('.vn-dialog.shown')
.isVisible(selectors.ticketService.newDescriptionInput);
expect(result).toBeTruthy();

View File

@ -100,7 +100,7 @@ export default class ArrayModel extends ModelProxy {
addFilter(user, params) {
this.userFilter = this.mergeFilters(user, this.userFilter);
Object.assign(this.userParams, params);
this.userParams = Object.assign({}, this.userParams, params);
return this.refresh();
}

View File

@ -26,7 +26,7 @@
ng-click="$ctrl.onClear($event)">
</vn-icon>
<vn-icon
ng-if="::$ctrl.info"
ng-if="::$ctrl.info != null"
icon="info_outline"
vn-tooltip="{{::$ctrl.info}}">
</vn-icon>

View File

@ -17,14 +17,9 @@ import './style.scss';
* @event change Thrown when value is changed
*/
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
});
constructor($element, $, $compile, $transclude) {
super($element, $, $compile);
this.$transclude = $transclude;
this._selection = null;
this.input = this.element.querySelector('input');
@ -48,7 +43,6 @@ export default class Autocomplete extends Field {
set field(value) {
super.field = value;
this.refreshSelection();
this.emit('change', {value});
}
get model() {
@ -121,7 +115,6 @@ export default class Autocomplete extends Field {
return;
const selection = this.fetchSelection();
this.selection = selection;
}
@ -206,7 +199,7 @@ export default class Autocomplete extends Field {
onDropDownSelect(item) {
const value = item[this.valueField];
this.selection = item;
this.field = value;
this.change(value);
}
onDropDownClose() {
@ -216,7 +209,7 @@ export default class Autocomplete extends Field {
onContainerKeyDown(event) {
if (event.defaultPrevented) return;
switch (event.code) {
switch (event.key) {
case 'ArrowUp':
case 'ArrowDown':
case 'Enter':
@ -241,12 +234,13 @@ export default class Autocomplete extends Field {
assignDropdownProps() {
if (!this.$.dropDown) return;
this.$.dropDown.copySlot('tplItem', this.$transclude);
assignProps(this, this.$.dropDown, [
'valueField',
'showField',
'showFilter',
'multiple',
'$transclude',
'translateFields',
'model',
'data',
@ -277,7 +271,7 @@ export default class Autocomplete extends Field {
this.refreshSelection();
}
}
Autocomplete.$inject = ['$element', '$scope', '$compile', '$http', '$transclude', '$translate', '$interpolate'];
Autocomplete.$inject = ['$element', '$scope', '$compile', '$transclude'];
ngModule.vnComponent('vnAutocomplete', {
template: require('./index.html'),

View File

@ -1,6 +1,6 @@
@import "effects";
vn-autocomplete.vn-field {
.vn-autocomplete {
overflow: hidden;
& > .container {

View File

@ -55,12 +55,13 @@ export default class ButtonMenu extends Button {
}
showDropDown() {
this.$.dropDown.copySlot('tplItem', this.$transclude);
assignProps(this, this.$.dropDown, [
'valueField',
'showField',
'showFilter',
'multiple',
'$transclude',
'translateFields',
'model',
'data',

View File

@ -1,3 +1,3 @@
vn-button-menu {
.vn-button-menu {
position: relative;
}

View File

@ -7,7 +7,6 @@ export default class Button extends FormInput {
super($element, $scope);
this.design = 'colored';
this.initTabIndex();
this.classList.add('vn-button');
this.element.addEventListener('keyup', e => this.onKeyup(e));
this.element.addEventListener('click', e => this.onClick(e));
}
@ -19,8 +18,8 @@ export default class Button extends FormInput {
onKeyup(event) {
if (event.defaultPrevented) return;
switch (event.code) {
case 'Space':
switch (event.key) {
case ' ':
case 'Enter':
return this.element.click();
}

View File

@ -129,7 +129,7 @@ export default class Calendar extends FormInput {
*/
select(day) {
if (!this.editable) return;
this.field = day;
this.change(day);
this.emit('selection', {
$days: [day],
$type: 'day'

View File

@ -1,6 +1,6 @@
@import "variables";
vn-calendar {
.vn-calendar {
display: block;
& > div {

View File

@ -49,18 +49,19 @@ export default class Check extends Toggle {
onClick(event) {
if (super.onClick(event)) return;
let value;
if (this.tripleState) {
if (this.field == null)
this.field = true;
value = true;
else if (this.field)
this.field = false;
value = false;
else
this.field = null;
value = null;
} else
this.field = !this.field;
value = !this.field;
this.changed();
this.change(value);
}
}

View File

@ -1,6 +1,6 @@
@import "variables";
vn-check {
.vn-check {
& > .btn {
border-radius: 2px;
transition: background 250ms;
@ -12,8 +12,13 @@ vn-check {
border-width: 0;
}
}
& > vn-icon {
margin-left: 5px;
color: $color-font-secondary;
vertical-align: middle;
}
&.checked > .btn {
border-color: $color-main;
border-color: transparent;
background-color: $color-main;
& > .mark {
@ -35,9 +40,7 @@ vn-check {
height: 2px;
border-bottom: 2px solid #666;
}
& > vn-icon {
margin-left: 5px;
color: $color-font-secondary;
vertical-align: middle;
&.disabled.checked > .btn {
background-color: $color-font-secondary;
}
}

View File

@ -9,7 +9,7 @@ export default class Chip extends Component {
}
Chip.$inject = ['$element', '$scope', '$transclude'];
ngModule.component('vnChip', {
ngModule.vnComponent('vnChip', {
template: require('./index.html'),
controller: Chip,
transclude: true,

View File

@ -7,13 +7,8 @@ export default class Confirm extends Dialog {
super($element, $, $transclude);
let $template = angular.element(template);
let slots = $transclude.$$boundTransclude.$$slots;
let bodyLinkFn = this.$compile($template.find('tpl-body'));
slots.body = this.createBoundTranscludeFn(bodyLinkFn);
let buttonsLinkFn = this.$compile($template.find('tpl-buttons'));
slots.buttons = this.createBoundTranscludeFn(buttonsLinkFn);
this.fillSlot('body', $template.find('tpl-body'));
this.fillSlot('buttons', $template.find('tpl-buttons'));
}
}

View File

@ -95,7 +95,7 @@ export default class CrudModel extends ModelProxy {
*/
addFilter(filter, params) {
this.userFilter = mergeFilters(filter, this.userFilter);
Object.assign(this.userParams, params);
this.userParams = Object.assign({}, this.userParams, params);
return this.refresh();
}

View File

@ -1,12 +1,9 @@
import ngModule from '../../module';
import Field from '../field';
import './style.scss';
class DatePicker extends Field {
constructor($element, $scope, $compile, $translate, $filter) {
constructor($element, $scope, $compile) {
super($element, $scope, $compile);
this.$translate = $translate;
this.$filter = $filter;
this.input = $compile(`<input type="date"></input>`)($scope)[0];
this.input.addEventListener('change', () => this.onValueUpdate());
@ -46,7 +43,7 @@ class DatePicker extends Field {
this.input.value = this.$filter('date')(value, 'yyyy-MM-dd');
}
}
DatePicker.$inject = ['$element', '$scope', '$compile', '$translate', '$filter'];
DatePicker.$inject = ['$element', '$scope', '$compile'];
ngModule.vnComponent('vnDatePicker', {
controller: DatePicker,

View File

@ -1,7 +0,0 @@
@import "variables";
.flatpickr-months .flatpickr-month,
.flatpickr-weekdays,
span.flatpickr-weekday {
background-color: $color-main;
}

View File

@ -12,16 +12,14 @@ import './style.scss';
export default class Dialog extends Popup {
constructor($element, $, $transclude) {
super($element, $, $transclude);
let linkFn = this.$compile(template);
$transclude.$$boundTransclude = this.createBoundTranscludeFn(linkFn);
this.fillDefaultSlot(template);
}
/**
* Hides the dialog calling the response handler.
*/
hide() {
// this.fireResponse();
this.fireResponse();
super.hide();
}

View File

@ -4,31 +4,12 @@ describe('Component vnDialog', () => {
beforeEach(ngModule('vnCore'));
beforeEach(angular.mock.inject($componentController => {
$element = angular.element('<vn-dialog></vn-dialog>');
controller = $componentController('vnDialog', {$element, $transclude: null});
beforeEach(angular.mock.inject(($rootScope, $compile) => {
$element = $compile('<vn-dialog><tpl-body>Body</tpl-body></vn-dialog>')($rootScope);
controller = $element.controller('vnDialog');
controller.emit = jasmine.createSpy('emit');
}));
describe('show()', () => {
it(`should do nothing if controller.shown is defined`, () => {
controller.element = {style: {display: 'none'}};
controller.shown = true;
controller.show();
expect(controller.element.style.display).toEqual('none');
expect(controller.emit).not.toHaveBeenCalledWith('open');
});
it(`should set shown on the controller, set style.display on the element and emit onOpen() event`, () => {
controller.show();
expect(controller.element.style.display).toEqual('flex');
expect(controller.shown).toBeTruthy();
expect(controller.emit).toHaveBeenCalledWith('open');
});
});
describe('hide()', () => {
describe('fireResponse()', () => {
it(`should call onResponse() if it's defined in the controller`, () => {
@ -51,23 +32,5 @@ describe('Component vnDialog', () => {
expect(responseRet).toEqual(false);
});
});
describe('realHide()', () => {
it(`should do nothing if controller.shown is not defined`, () => {
controller.element = {style: {display: 'not none'}};
controller.hide();
expect(controller.element.style.display).toEqual('not none');
});
it(`should set lastEvent, shown and element.style.display to their expected values`, () => {
controller.shown = true;
controller.hide();
expect(controller.lastEvent).toBeFalsy();
expect(controller.shown).toBeFalsy();
expect(controller.element.style.display).toEqual('none');
});
});
});
});

View File

@ -1,31 +0,0 @@
<vn-popover
vn-id="popover"
on-open="$ctrl.onOpen()"
on-close="$ctrl.onClose()"
on-close-start="$ctrl.emit('closeStart')">
<div class="vn-drop-down">
<div ng-show="$ctrl.showFilter" class="filter">
<vn-textfield
vn-id="input"
ng-model="$ctrl.search"
class="dense search"
ng-blur="$ctrl.onFocusOut()"
placeholder="{{::'Search' | translate}}">
</vn-textfield>
</div>
<div vn-id="list" class="list" tabindex="-1">
<ul
vn-id="ul"
class="dropdown"
ng-click="$ctrl.onContainerClick($event)">
</ul>
<div
ng-if="$ctrl.statusText"
ng-click="$ctrl.onLoadMoreClick($event)"
class="status"
translate>
{{$ctrl.statusText}}
</div>
</div>
</div>
</vn-popover>

View File

@ -0,0 +1,21 @@
<div ng-show="$ctrl.showFilter" class="filter">
<vn-textfield
ng-model="$ctrl.search"
class="dense search"
ng-blur="$ctrl.onFocusOut()"
placeholder="{{::'Search' | translate}}">
</vn-textfield>
</div>
<div class="list" tabindex="-1">
<ul
class="dropdown"
ng-click="$ctrl.onContainerClick($event)">
</ul>
<div
ng-if="$ctrl.statusText"
ng-click="$ctrl.onLoadMoreClick($event)"
class="status"
translate>
{{$ctrl.statusText}}
</div>
</div>

View File

@ -1,6 +1,7 @@
import './style.scss';
import ngModule from '../../module';
import Component from '../../lib/component';
import Popover from '../popover';
import template from './index.html';
import ArrayModel from '../array-model/array-model';
import CrudModel from '../crud-model/crud-model';
import {mergeWhere} from 'vn-loopback/util/filter';
@ -9,34 +10,17 @@ import {mergeWhere} from 'vn-loopback/util/filter';
* @event select Thrown when model item is selected
* @event change Thrown when model data is ready
*/
export default class DropDown extends Component {
constructor($element, $scope, $transclude, $timeout, $translate, $http, $q, $filter) {
super($element, $scope);
this.$transclude = $transclude;
this.$timeout = $timeout;
this.$translate = $translate;
this.$http = $http;
this.$q = $q;
this.$filter = $filter;
export default class DropDown extends Popover {
constructor($element, $, $transclude) {
super($element, $, $transclude);
this.valueField = 'id';
this.showField = 'name';
this._search = undefined;
this._activeOption = -1;
this.showLoadMore = true;
this.showFilter = true;
}
$postLink() {
super.$postLink();
}
get shown() {
return this.$.popover && this.$.popover.shown;
}
set shown(value) {
this.$.popover.shown = value;
this.searchDelay = 300;
this.fillDefaultSlot(template);
}
get search() {
@ -64,7 +48,7 @@ export default class DropDown extends Component {
this.searchTimeout = this.$timeout(() => {
this.refreshModel();
this.searchTimeout = null;
}, 350);
}, value != null ? this.searchDelay : 0);
} else
this.refreshModel();
}
@ -103,16 +87,37 @@ export default class DropDown extends Component {
*/
show(parent, search) {
this._activeOption = -1;
this.$.popover.show(parent || this.parent);
super.show(parent);
this.list = this.popup.querySelector('.list');
this.ul = this.popup.querySelector('ul');
this.docKeyDownHandler = e => this.onDocKeyDown(e);
this.document.addEventListener('keydown', this.docKeyDownHandler);
this.listScrollHandler = e => this.onScroll(e);
this.list.addEventListener('scroll', this.listScrollHandler);
this.list.scrollTop = 0;
this.search = search;
this.buildList();
let input = this.popup.querySelector('input');
setTimeout(() => input.focus());
}
/**
* Hides the drop-down.
*/
hide() {
this.$.popover.hide();
onClose() {
this.document.removeEventListener('keydown', this.docKeyDownHandler);
this.docKeyDownHandler = null;
this.list.removeEventListener('scroll', this.listScrollHandler);
this.listScrollHandler = null;
this.list = null;
this.ul = null;
this.destroyList();
super.onClose();
}
/**
@ -123,7 +128,7 @@ export default class DropDown extends Component {
moveToOption(option) {
this.activateOption(option);
let list = this.$.list;
let list = this.list;
let li = this.activeLi;
if (!li) return;
@ -150,7 +155,7 @@ export default class DropDown extends Component {
let data = this.modelData;
if (option >= 0 && data && option < data.length) {
this.activeLi = this.$.ul.children[option];
this.activeLi = this.ul.children[option];
this.activeLi.className = 'active';
}
}
@ -184,29 +189,7 @@ export default class DropDown extends Component {
}
if (!this.multiple)
this.$.popover.hide();
}
onOpen() {
this.docKeyDownHandler = e => this.onDocKeyDown(e);
this.document.addEventListener('keydown', this.docKeyDownHandler);
this.listScrollHandler = e => this.onScroll(e);
this.$.list.addEventListener('scroll', this.listScrollHandler);
this.$.list.scrollTop = 0;
setTimeout(() => this.$.input.focus());
this.emit('open');
}
onClose() {
this.document.removeEventListener('keydown', this.docKeyDownHandler);
this.docKeyDownHandler = null;
this.$.list.removeEventListener('scroll', this.listScrollHandler);
this.listScrollHandler = null;
this.emit('close');
this.hide();
}
onClearClick() {
@ -214,7 +197,7 @@ export default class DropDown extends Component {
}
onScroll() {
let list = this.$.list;
let list = this.list;
let shouldLoad =
list.scrollTop + list.clientHeight >= (list.scrollHeight - 40)
&& !this.model.isLoading;
@ -230,9 +213,8 @@ export default class DropDown extends Component {
onContainerClick(event) {
if (event.defaultPrevented) return;
let index = getPosition(this.$.ul, event);
if (index != -1)
this.selectOption(index);
let index = getPosition(this.ul, event);
if (index != -1) this.selectOption(index);
}
onDocKeyDown(event) {
@ -242,23 +224,23 @@ export default class DropDown extends Component {
let option = this.activeOption;
let nOpts = data ? data.length - 1 : 0;
switch (event.keyCode) {
case 9: // Tab
switch (event.key) {
case 'Tab':
this.selectOption(option);
return;
case 13: // Enter
case 'Enter':
this.selectOption(option);
break;
case 38: // Up
case 'ArrowUp':
this.moveToOption(option <= 0 ? nOpts : option - 1);
break;
case 40: // Down
case 'ArrowDown':
this.moveToOption(option >= nOpts ? 0 : option + 1);
break;
case 35: // End
case 'End':
this.moveToOption(nOpts);
break;
case 36: // Start
case 'Home':
this.moveToOption(0);
break;
default:
@ -315,13 +297,14 @@ export default class DropDown extends Component {
}
}
this.$.ul.appendChild(fragment);
this.ul.appendChild(fragment);
this.activateOption(this._activeOption);
this.$.$applyAsync(() => this.$.popover.relocate());
this.$.$applyAsync(() => this.relocate());
}
destroyList() {
this.$.ul.innerHTML = '';
if (this.ul)
this.ul.innerHTML = '';
if (this.scopes) {
for (let scope of this.scopes)
@ -344,10 +327,6 @@ export default class DropDown extends Component {
return fields;
}
$onDestroy() {
this.destroyList();
}
// Model related code
onDataChange() {
@ -436,7 +415,6 @@ export default class DropDown extends Component {
return {[this.showField]: scope.$search};
}
}
DropDown.$inject = ['$element', '$scope', '$transclude', '$timeout', '$translate', '$http', '$q', '$filter'];
/**
* Gets the position of an event element relative to a parent.
@ -463,9 +441,11 @@ function getPosition(parent, event) {
return -1;
}
ngModule.component('vnDropDown', {
template: require('./drop-down.html'),
ngModule.vnComponent('vnDropDown', {
controller: DropDown,
transclude: {
tplItem: '?tplItem'
},
bindings: {
field: '=?',
selection: '=?',
@ -482,9 +462,7 @@ ngModule.component('vnDropDown', {
where: '<?',
order: '@?',
limit: '<?',
searchFunction: '&?'
},
transclude: {
tplItem: '?tplItem'
searchFunction: '&?',
searchDelay: '<?'
}
});

View File

@ -1,7 +1,7 @@
@import "effects";
@import "variables";
.vn-drop-down {
.vn-drop-down > .window > .content {
display: flex;
flex-direction: column;
height: inherit;

View File

@ -19,7 +19,7 @@
ng-click="$ctrl.onClear($event)">
</vn-icon>
<vn-icon
ng-if="::$ctrl.info"
ng-if="::$ctrl.info != null"
icon="info_outline"
vn-tooltip="{{::$ctrl.info}}">
</vn-icon>

View File

@ -11,7 +11,6 @@ export default class Field extends FormInput {
this.suffix = null;
this.control = this.element.querySelector('.control');
this.classList.add('vn-field');
this.element.addEventListener('click', e => this.onClick(e));
this.container = this.element.querySelector('.container');
@ -25,9 +24,7 @@ export default class Field extends FormInput {
this.input.addEventListener('focus', () => this.onFocus(true));
this.input.addEventListener('blur', () => this.onFocus(false));
this.input.addEventListener('change', e => {
this.emit('change', {event: e});
});
this.input.addEventListener('change', () => this.onChange());
}
set field(value) {
@ -172,6 +169,7 @@ export default class Field extends FormInput {
onClear(event) {
if (event.defaultPrevented) return;
event.preventDefault();
this.field = null;
this.input.dispatchEvent(new Event('change'));
}
@ -193,6 +191,10 @@ export default class Field extends FormInput {
this.inputError = error;
this.refreshHint();
}
onChange() {
this.emit('change', {value: this.field});
}
}
Field.$inject = ['$element', '$scope', '$compile'];

View File

@ -35,7 +35,7 @@
& > .fix {
padding-top: 24px;
line-height: 24px;
font-size: $font-size;
font-size: 1rem;
opacity: 0;
transition: opacity 200ms ease-in-out;
@ -58,7 +58,7 @@
border: none;
font-family: Arial, sans-serif;
display: block;
font-size: $font-size;
font-size: 1rem;
width: 100%;
background: 0;
color: inherit;

View File

@ -12,6 +12,7 @@ export default class FormInput extends Component {
constructor($element, $scope) {
super($element, $scope);
this.classList = this.element.classList;
this.classList.add(...this.constructor.$classNames);
}
$onInit() {
@ -104,6 +105,12 @@ export default class FormInput extends Component {
refreshTabIndex() {
this.inputEl.tabIndex = this.disabled ? -1 : this.tabIndex;
}
change(value) {
this.field = value;
this.element.dispatchEvent(new Event('change'));
this.emit('change', {value: this.field});
}
}
ngModule.vnComponent('vnFormInput', {

View File

@ -1,6 +1,6 @@
@import "effects";
vn-icon-button {
.vn-icon-button {
@extend %clickable-light;
color: $color-main;

View File

@ -2,8 +2,8 @@ import ngModule from '../../module';
import ButtonMenu from '../button-menu';
export default class IconMenu extends ButtonMenu {
constructor($element, $scope) {
super($element, $scope);
constructor($element, $scope, $transclude) {
super($element, $scope, $transclude);
this.element.classList.add('flat');
}
}

View File

@ -9,8 +9,6 @@ import './subtitle/subtitle';
import './spinner/spinner';
import './snackbar/snackbar';
import './tooltip/tooltip';
import './popover/popover';
import './drop-down/drop-down';
import './menu/menu';
import './multi-check/multi-check';
import './card/card';
@ -26,6 +24,7 @@ import './check';
import './chip';
import './data-viewer';
import './date-picker';
import './drop-down';
import './debug-info';
import './dialog';
import './field';
@ -39,6 +38,7 @@ import './input-time';
import './input-file';
import './label';
import './list';
import './popover';
import './popup';
import './radio';
import './submit';

View File

@ -19,7 +19,7 @@
ng-click="$ctrl.onClear($event)">
</vn-icon>
<vn-icon
ng-if="::$ctrl.info"
ng-if="::$ctrl.info != null"
icon="info_outline"
vn-tooltip="{{::$ctrl.info}}">
</vn-icon>

View File

@ -1,20 +1,13 @@
import ngModule from '../../module';
import Popover from '../popover/popover';
import Popover from '../popover';
export default class Menu extends Popover {
$postLink() {
super.$postLink();
this.popover.addEventListener('click',
() => this.hide());
show(parent) {
super.show(parent);
this.windowEl.addEventListener('click', () => this.hide());
}
}
ngModule.component('vnMenu', {
template: require('../popover/popover.html'),
controller: Menu,
transclude: true,
bindings: {
onOpen: '&?',
onClose: '&?'
}
ngModule.vnComponent('vnMenu', {
controller: Menu
});

View File

@ -1,5 +1,5 @@
vn-multi-check {
vn-check {
.vn-check {
margin-bottom: 0.8em
}
}

View File

@ -0,0 +1,8 @@
<div ng-mousedown="$ctrl.onBgMouseDown($event)">
<div
class="window"
ng-mousedown="$ctrl.onWindowMouseDown($event)">
<div class="arrow"></div>
<div class="content" ng-transclude></div>
</div>
</div>

View File

@ -0,0 +1,101 @@
import ngModule from '../../module';
import Popup from '../popup';
import template from './index.html';
import isMobile from '../../lib/is-mobile';
import './style.scss';
/**
* A simple popover.
*
* @property {HTMLElement} parent The parent element to show drop down relative to
*
* @event open Thrown when popover is displayed
* @event close Thrown when popover is hidden
*/
export default class Popover extends Popup {
constructor($element, $, $transclude) {
super($element, $, $transclude);
this.displayMode = isMobile ? 'centered' : 'relative';
this.template = template;
}
/**
* Shows the popover emitting the open signal. If a parent is specified
* it is shown in a visible relative position to it.
*
* @param {HTMLElement} parent Overrides the parent property
*/
show(parent) {
if (parent) this.parent = parent;
super.show();
this.content = this.popup.querySelector('.content');
this.$timeout(() => this.relocate(), 10);
}
hide() {
this.content = null;
super.hide();
}
/**
* Repositions the popover to a correct location relative to the parent.
*/
relocate() {
if (!(this.parent && this._shown && this.displayMode == 'relative'))
return;
let margin = 10;
let arrow = this.popup.querySelector('.arrow');
let style = this.windowEl.style;
style.width = '';
style.height = '';
let arrowStyle = arrow.style;
arrowStyle.top = '';
arrowStyle.bottom = '';
let parentRect = this.parent.getBoundingClientRect();
let popoverRect = this.windowEl.getBoundingClientRect();
let arrowRect = arrow.getBoundingClientRect();
let clamp = (value, min, max) => Math.min(Math.max(value, min), max);
let arrowHeight = Math.floor(arrowRect.height / 2);
let arrowOffset = arrowHeight + margin / 2;
let docEl = this.document.documentElement;
let maxRight = Math.min(window.innerWidth, docEl.clientWidth) - margin;
let maxBottom = Math.min(window.innerHeight, docEl.clientHeight) - margin;
let maxWith = maxRight - margin;
let maxHeight = maxBottom - margin - arrowHeight;
let width = clamp(popoverRect.width, parentRect.width, maxWith);
let height = popoverRect.height;
let left = parentRect.left + parentRect.width / 2 - width / 2;
left = clamp(left, margin, maxRight - width);
let top = parentRect.top + parentRect.height + arrowOffset;
let showTop = top + height > maxBottom;
if (showTop) top = parentRect.top - height - arrowOffset;
top = Math.max(top, margin);
if (showTop)
arrowStyle.bottom = `0`;
else
arrowStyle.top = `0`;
let arrowLeft = (parentRect.left - left) + parentRect.width / 2;
arrowLeft = clamp(arrowLeft, arrowHeight, width - arrowHeight);
arrowStyle.left = `${arrowLeft}px`;
style.top = `${top}px`;
style.left = `${left}px`;
style.width = `${width}px`;
if (height > maxHeight) style.height = `${maxHeight}px`;
}
}
ngModule.vnComponent('vnPopover', {
controller: Popover
});

View File

@ -1,6 +0,0 @@
<div class="vn-popover">
<div class="popover">
<div class="arrow"></div>
<div class="content"></div>
</div>
</div>

View File

@ -1,201 +0,0 @@
import ngModule from '../../module';
import Component from '../../lib/component';
import template from './popover.html';
import './style.scss';
/**
* A simple popover.
*
* @property {HTMLElement} parent The parent element to show drop down relative to
*
* @event open Thrown when popover is displayed
* @event close Thrown when popover is hidden
*/
export default class Popover extends Component {
constructor($element, $scope, $timeout, $transitions, $transclude, $compile) {
super($element, $scope);
this.$timeout = $timeout;
this.$transitions = $transitions;
this._shown = false;
this.element = $compile(template)($scope)[0];
this.popover = this.element.querySelector('.popover');
this.popover.addEventListener('mousedown', e => this.onMouseDown(e));
this.arrow = this.element.querySelector('.arrow');
this.content = this.element.querySelector('.content');
$transclude($scope.$parent,
clone => angular.element(this.content).append(clone));
}
$onDestroy() {
this.hide();
}
/**
* @type {HTMLElement} The popover child.
*/
get child() {
return this.content.firstChild;
}
set child(value) {
this.content.innerHTML = '';
this.content.appendChild(value);
}
/**
* @type {Boolean} Wether to show or hide the popover.
*/
get shown() {
return this._shown;
}
set shown(value) {
if (value)
this.show();
else
this.hide();
}
/**
* Shows the popover emitting the open signal. If a parent is specified
* it is shown in a visible relative position to it.
*
* @param {HTMLElement} parent Overrides the parent property
*/
show(parent) {
if (this._shown) return;
if (parent) this.parent = parent;
this._shown = true;
if (!this.showTimeout) {
this.document.body.appendChild(this.element);
this.element.style.display = 'block';
}
this.$timeout.cancel(this.showTimeout);
this.showTimeout = this.$timeout(() => {
this.showTimeout = null;
this.element.classList.add('shown');
}, 30);
this.docKeyDownHandler = e => this.onDocKeyDown(e);
this.document.addEventListener('keydown', this.docKeyDownHandler);
this.bgMouseDownHandler = e => this.onBgMouseDown(e);
this.element.addEventListener('mousedown', this.bgMouseDownHandler);
this.deregisterCallback = this.$transitions.onStart({}, () => this.hide());
this.relocate();
this.emit('open');
}
/**
* Hides the popover emitting the close signal.
*/
hide() {
if (!this._shown) return;
this._shown = false;
this.element.classList.remove('shown');
this.$timeout.cancel(this.showTimeout);
this.showTimeout = this.$timeout(() => {
this.showTimeout = null;
this.element.style.display = 'none';
this.document.body.removeChild(this.element);
this.emit('close');
}, 250);
this.document.removeEventListener('keydown', this.docKeyDownHandler);
this.docKeyDownHandler = null;
this.element.removeEventListener('mousedown', this.bgMouseDownHandler);
this.bgMouseDownHandler = null;
if (this.deregisterCallback) this.deregisterCallback();
this.emit('closeStart');
}
/**
* Repositions the popover to a correct location relative to the parent.
*/
relocate() {
if (!(this.parent && this._shown)) return;
let margin = 10;
let style = this.popover.style;
style.width = '';
style.height = '';
let arrowStyle = this.arrow.style;
arrowStyle.top = '';
arrowStyle.bottom = '';
let parentRect = this.parent.getBoundingClientRect();
let popoverRect = this.popover.getBoundingClientRect();
let arrowRect = this.arrow.getBoundingClientRect();
let clamp = (value, min, max) => Math.min(Math.max(value, min), max);
let arrowHeight = Math.floor(arrowRect.height / 2);
let arrowOffset = arrowHeight + margin / 2;
let docEl = this.document.documentElement;
let maxRight = Math.min(window.innerWidth, docEl.clientWidth) - margin;
let maxBottom = Math.min(window.innerHeight, docEl.clientHeight) - margin;
let maxWith = maxRight - margin;
let maxHeight = maxBottom - margin - arrowHeight;
let width = clamp(popoverRect.width, parentRect.width, maxWith);
let height = popoverRect.height;
let left = parentRect.left + parentRect.width / 2 - width / 2;
left = clamp(left, margin, maxRight - width);
let top = parentRect.top + parentRect.height + arrowOffset;
let showTop = top + height > maxBottom;
if (showTop) top = parentRect.top - height - arrowOffset;
top = Math.max(top, margin);
if (showTop)
arrowStyle.bottom = `0`;
else
arrowStyle.top = `0`;
let arrowLeft = (parentRect.left - left) + parentRect.width / 2;
arrowLeft = clamp(arrowLeft, arrowHeight, width - arrowHeight);
arrowStyle.left = `${arrowLeft}px`;
style.top = `${top}px`;
style.left = `${left}px`;
style.width = `${width}px`;
if (height > maxHeight) style.height = `${maxHeight}px`;
}
onDocKeyDown(event) {
if (event.defaultPrevented) return;
if (event.code == 'Escape')
this.hide();
}
onMouseDown(event) {
this.lastMouseEvent = event;
}
onBgMouseDown(event) {
if (event == this.lastMouseEvent || event.defaultPrevented) return;
this.hide();
}
}
Popover.$inject = ['$element', '$scope', '$timeout', '$transitions', '$transclude', '$compile'];
ngModule.component('vnPopover', {
controller: Popover,
transclude: true
});

View File

@ -1,7 +1,6 @@
@import "variables";
.vn-popover {
display: none;
z-index: 20;
position: fixed;
top: 0;
@ -20,7 +19,7 @@
transform: translateY(0);
opacity: 1;
}
& > .popover {
& > .window {
position: absolute;
box-shadow: 0 .1em .4em $color-shadow;
z-index: 0;

View File

@ -12,6 +12,7 @@ export default class Popup extends Component {
this.$transclude = $transclude;
this._shown = false;
this.displayMode = 'centered';
this.template = template;
}
$onDestroy() {
@ -39,11 +40,12 @@ export default class Popup extends Component {
if (this.shown) return;
this._shown = true;
let linkFn = this.$compile(template);
let linkFn = this.$compile(this.template);
this.$contentScope = this.$.$new();
this.popup = linkFn(this.$contentScope, null,
{parentBoundTranscludeFn: this.$transclude}
)[0];
this.windowEl = this.popup.querySelector('.window');
let classList = this.popup.classList;
classList.add(this.displayMode);
@ -84,22 +86,26 @@ export default class Popup extends Component {
this.popup.classList.remove('shown');
this.$timeout.cancel(this.transitionTimeout);
this.transitionTimeout = this.$timeout(() => {
this.transitionTimeout = null;
this.document.body.removeChild(this.popup);
this.$contentScope.$destroy();
this.popup.remove();
this.popup = null;
this.emit('close');
}, 200);
this.transitionTimeout = this.$timeout(
() => this.onClose(), 200);
this.lastEvent = null;
this._shown = false;
this.emit('closeStart');
}
onClose() {
this.transitionTimeout = null;
this.document.body.removeChild(this.popup);
this.$contentScope.$destroy();
this.popup.remove();
this.popup = null;
this.windowEl = null;
this.emit('close');
}
onWindowMouseDown(event) {
this.lastEvent = event;
}

View File

@ -30,7 +30,7 @@
overflow: auto;
box-sizing: border-box;
max-height: 100%;
transform: scale3d(.5, .5, .5);
transform: scale3d(.9, .9, .9);
transition: transform 200ms ease-in-out;
}
&.shown > .window {

View File

@ -1,6 +1,6 @@
@import "variables";
vn-radio {
.vn-radio {
& > .btn {
border-radius: 50%;
@ -22,4 +22,7 @@ vn-radio {
background-color: $color-main;
}
}
&.disabled.checked > .btn > .mark {
background-color: $color-font-secondary;
}
}

View File

@ -35,7 +35,7 @@
}
}
vn-range {
.vn-range {
& > label {
font-size: 12px;

View File

@ -1,6 +1,6 @@
import Component from '../../lib/component';
export default class extends Component {
export default class SearchPanel extends Component {
set filter(value) {
this.$.filter = value;
}
@ -13,6 +13,6 @@ export default class extends Component {
if (!this.onSubmit)
throw new Error('SearchPanel::onSubmit() method not defined');
this.onSubmit(this.filter);
this.onSubmit({$filter: this.filter});
}
}

View File

@ -65,23 +65,24 @@ export default class Controller extends Component {
openPanel(event) {
if (event.defaultPrevented) return;
this.$.popover.show(this.element);
this.$panelScope = this.$.$new();
this.$panel = this.$compile(`<${this.panel}/>`)(this.$panelScope);
let panel = this.$panel[0].$ctrl;
this.panelEl = this.$compile(`<${this.panel}/>`)(this.$panelScope)[0];
let panel = this.panelEl.$ctrl;
if (this.shownFilter)
panel.filter = JSON.parse(JSON.stringify(this.shownFilter));
panel.onSubmit = filter => this.onPanelSubmit(filter);
panel.onSubmit = filter => this.onPanelSubmit(filter.$filter);
this.$.popover.parent = this.element;
this.$.popover.child = this.$panel[0];
this.$.popover.show();
this.$.popover.content.appendChild(this.panelEl);
}
onPopoverClose() {
this.$panelScope.$destroy();
this.$panel.remove();
this.$panel = null;
this.$panelScope = null;
this.panelEl.remove();
this.panelEl = null;
}
onPanelSubmit(filter) {

View File

@ -8,11 +8,11 @@ describe('Component vnSearchbar', () => {
beforeEach(ngModule('vnCore'));
beforeEach(angular.mock.inject(($componentController, _$state_, $rootScope, $compile) => {
beforeEach(angular.mock.inject(($componentController, _$state_, $rootScope) => {
$scope = $rootScope.$new();
$state = _$state_;
$element = angular.element(`<vn-textfield><input></input></vn-textfield>`);
controller = $componentController('vnSearchbar', {$element, $state, $scope, $compile});
controller = $componentController('vnSearchbar', {$element, $scope});
controller.panel = 'vn-client-search-panel';
}));
@ -39,7 +39,7 @@ describe('Component vnSearchbar', () => {
describe('onStateChange()', () => {
it(`should set a formated _filter in the controller`, () => {
spyOn(controller, 'doSearch');
Object.assign(controller.$state.params, {q: '{"id": 999}'});
Object.assign($state.params, {q: '{"id": 999}'});
controller.onStateChange();
@ -71,27 +71,11 @@ describe('Component vnSearchbar', () => {
describe('filter() setter', () => {
it(`should call $state.go() to replace the current state location instead of creating a new one`, () => {
controller._filter = {};
spyOn(controller.$state, 'go');
spyOn($state, 'go');
controller.filter = {expected: 'filter'};
expect(controller._filter).toEqual(controller.filter);
expect(controller.$state.go).toHaveBeenCalledWith('.', Object({q: '{"expected":"filter"}'}), Object({location: 'replace'}));
});
});
describe('onPopoverClose()', () => {
it(`should get rid of $panel and $panelScope`, () => {
controller.$panel = {
I: 'should disappear',
remove: () => {}
};
controller.$panelScope = {$destroy: jasmine.createSpy('$destroy')};
controller.onPopoverClose();
expect(controller.$panelScope.$destroy).toHaveBeenCalledWith();
expect(controller.$panel).toBeNull();
expect($state.go).toHaveBeenCalledWith('.', Object({q: '{"expected":"filter"}'}), Object({location: 'replace'}));
});
});

View File

@ -169,11 +169,11 @@ vn-table {
}
}
}
vn-textfield {
.vn-textfield {
float: right;
margin: 0!important;
}
vn-check {
.vn-check {
margin: 0;
}
}

View File

@ -11,13 +11,12 @@ export default class Toggle extends FormInput {
constructor($element, $) {
super($element, $);
this.initTabIndex();
this.classList.add('vn-toggle');
this.element.addEventListener('click', e => this.onClick(e));
this.element.addEventListener('keydown', e => this.onKeydown(e));
}
onKeydown(event) {
if (!event.defaultPrevented && event.code == 'Space')
if (!event.defaultPrevented && event.key == ' ')
this.element.click();
}
@ -26,10 +25,9 @@ export default class Toggle extends FormInput {
return true;
}
changed() {
change(value) {
this.$.$applyAsync();
this.element.dispatchEvent(new Event('change'));
this.emit('change', {value: this.field});
super.change(value);
}
}

View File

@ -7,11 +7,8 @@
align-items: center;
outline: none;
&.disabled {
cursor: inherit;
}
& > span {
font-size: $font-size;
font-size: 1rem;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
@ -28,13 +25,6 @@
margin-right: .6em;
border: 2px solid #666;
}
&.checked > .btn {
border-color: $color-main;
& > .focus-mark {
background-color: rgba($color-main, .15);
}
}
& > .btn > .focus-mark {
position: absolute;
top: 50%;
@ -49,6 +39,23 @@
transition: transform 250ms;
background-color: rgba(0, 0, 0, .1);
}
&.checked > .btn {
border-color: $color-main;
& > .focus-mark {
background-color: rgba($color-main, .15);
}
}
&.disabled {
cursor: inherit;
&.checked > .btn {
border-color: $color-font-secondary;
}
}
&.readonly {
cursor: inherit;
}
&:focus:not(.disabled) > .btn > .focus-mark {
transform: scale3d(1, 1, 1);
}

View File

@ -1,6 +1,6 @@
@import "effects";
vn-wday-picker {
.vn-wday-picker {
text-align: center;
&:focus {

View File

@ -1,7 +1,5 @@
import ngModule from '../module';
const regex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i;
export const isMobile = regex.test(navigator.userAgent);
import isMobile from '../lib/is-mobile';
export function focus($scope, input) {
if (isMobile) return;

View File

@ -1,5 +1,5 @@
import ngModule from '../module';
import Popover from '../components/popover/popover';
import Popover from '../components/popover';
import {kebabToCamel} from '../lib/string';
/**
@ -15,10 +15,8 @@ export function directive() {
if (event.defaultPrevented) return;
let popoverKey = kebabToCamel($attrs.vnPopover);
let popover = $scope[popoverKey];
if (popover instanceof Popover) {
popover.parent = $element[0];
popover.show();
}
if (popover instanceof Popover)
popover.show($element[0]);
});
}
};

View File

@ -17,9 +17,9 @@ describe('Directive dialog', () => {
});
};
beforeEach(angular.mock.inject($componentController => {
$element = angular.element('<div></div>');
controller = $componentController('vnDialog', {$element: $element, $transclude: null});
beforeEach(angular.mock.inject(($rootScope, $compile) => {
$element = $compile('<vn-dialog><tpl-body></tpl-body></vn-dialog>')($rootScope);
controller = $element.controller('vnDialog');
}));
it('should call show() function if dialog is a instance of vnDialog', () => {

View File

@ -25,7 +25,7 @@ export function directive($timeout) {
}
function onKeyDown(event) {
if (event.keyCode === 27)
if (event.key === 'Escape')
destroyContainers();
}

View File

@ -39,6 +39,7 @@ export default class Component extends EventEmitter {
get window() {
return this.document.defaultView;
}
/**
* The component owner document.
*/
@ -57,21 +58,16 @@ export default class Component extends EventEmitter {
return this.$translate.instant(string, params);
}
createBoundTranscludeFn(transcludeFn) {
createBoundTranscludeFn(linkFn) {
let scope = this.$;
let previousBoundTranscludeFn = this.$transclude.$$boundTransclude;
function vnBoundTranscludeFn(
transcludedScope,
cloneFn,
controllers,
futureParentElement,
containingScope) {
function vnBoundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
if (!transcludedScope) {
transcludedScope = scope.$new(false, containingScope);
transcludedScope.$$transcluded = true;
}
return transcludeFn(transcludedScope, cloneFn, {
return linkFn(transcludedScope, cloneFn, {
parentBoundTranscludeFn: previousBoundTranscludeFn,
transcludeControllers: controllers,
futureParentElement: futureParentElement
@ -81,10 +77,37 @@ export default class Component extends EventEmitter {
return vnBoundTranscludeFn;
}
fillDefaultSlot(template) {
let linkFn = this.$compile(template);
this.$transclude.$$boundTransclude = this.createBoundTranscludeFn(linkFn);
}
fillSlot(slot, template) {
let slots = this.$transclude.$$boundTransclude.$$slots;
let linkFn = this.$compile(template);
slots[slot] = this.createBoundTranscludeFn(linkFn);
}
copySlot(slot, $transclude) {
this.$transclude.$$boundTransclude.$$slots[slot] =
$transclude.$$boundTransclude.$$slots[slot];
}
}
Component.$inject = ['$element', '$scope'];
function runFn($translate, $q, $http, $state, $stateParams, $timeout, $transitions, $compile, vnApp) {
function runFn(
$translate,
$q,
$http,
$state,
$stateParams,
$timeout,
$transitions,
$compile,
$filter,
$interpolate,
vnApp) {
Object.assign(Component.prototype, {
$translate,
$q,
@ -94,6 +117,8 @@ function runFn($translate, $q, $http, $state, $stateParams, $timeout, $transitio
$timeout,
$transitions,
$compile,
$filter,
$interpolate,
vnApp
});
}
@ -106,6 +131,8 @@ runFn.$inject = [
'$timeout',
'$transitions',
'$compile',
'$filter',
'$interpolate',
'vnApp'
];

View File

@ -51,17 +51,17 @@ export default class EventEmitter {
* Emits an event.
*
* @param {String} eventName The event name
* @param {...*} args Arguments to pass to the callbacks
* @param {Object} args Arguments to pass to the callbacks
*/
emit(eventName) {
emit(eventName, args) {
if (!this.$events || !this.$events[eventName])
return;
let args = Array.prototype.slice.call(arguments, 1);
args = Object.assign({$this: this}, args);
let callbacks = this.$events[eventName];
for (let callback of callbacks)
callback.callback.apply(callback.thisArg, args);
callback.callback.call(callback.thisArg, args);
}
/**

View File

@ -0,0 +1,4 @@
const regex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i;
const isMobile = regex.test(navigator.userAgent);
export default isMobile;

View File

@ -11,7 +11,6 @@ Hide: Ocultar
Next: Siguiente
Finalize: Finalizar
Previous: Anterior
Load more: Cargar más
Auto-scroll interrupted, please adjust the search: Auto-scroll interrumpido, por favor ajusta la búsqueda
Value can't be empty: El valor no puede estar vacío
Value should be empty: El valor debe estar vacío
@ -60,3 +59,7 @@ No records found: No se han encontrado elementos
Day: Día
Days: Días
Go up: Ir arriba
Loading...: Cargando...
No results found: Sin resultados
No data: Sin datos
Load more: Cargar más

View File

@ -30,10 +30,10 @@ vn-login {
padding-bottom: 1em;
}
& > form {
& > vn-textfield {
& > .vn-textfield {
width: 100%;
}
& > vn-check {
& > .vn-check {
display: block;
.md-label {
white-space: inherit;

View File

@ -52,3 +52,4 @@ List: Listado
# Misc
Phone: Teléfono
Id: Id

View File

@ -3,22 +3,22 @@
form vn-horizontal {
align-items: center;
min-height: 2.8em;
& > * {
box-sizing: border-box;
min-height: 2.8em;
padding: 0 $spacing-sm;
margin: 0 $spacing-xs;
&:first-child {
padding-left: 0;
padding-right: $spacing-xs;
margin-left: 0;
margin-right: $spacing-sm;
}
&:last-child {
padding-left: $spacing-xs;
padding-right: 0;
margin-left: $spacing-sm;
margin-right: 0;
}
&:first-child:last-child {
padding: 0;
margin: 0;
}
}
@ -30,7 +30,7 @@ form vn-horizontal {
&,
&:first-child,
&:last-child {
padding: 0;
margin: 0;
}
}
}

View File

@ -16,7 +16,7 @@ vn-dialog.modal-form {
& > div {
padding: 0 !important;
}
vn-textfield {
.vn-textfield {
width: 100%;
}
.buttons {

View File

@ -23,7 +23,7 @@ vn-zone-calendar {
flex-wrap: wrap;
justify-content: space-evenly;
& > vn-calendar {
& > .vn-calendar {
max-width: 18em;
.day {

View File

@ -1,14 +1,14 @@
@import "variables";
vn-treeview-child {
.content > vn-check:not(.indeterminate) {
.content > .vn-check:not(.indeterminate) {
color: $color-main;
& > .btn {
border-color: $color-main;
}
}
.content > vn-check.checked {
.content > .vn-check.checked {
color: $color-main;
}
}

View File

@ -9,7 +9,7 @@ vn-claim-action {
flex: 1
}
vn-check {
.vn-check {
flex: none;
}
}

View File

@ -1,7 +1,7 @@
@import "variables";
vn-claim-detail {
vn-textfield {
.vn-textfield {
margin: 0!important;
max-width: 100px;
}

View File

@ -1,5 +1,5 @@
vn-ticket-request {
vn-textfield {
.vn-textfield {
margin: 0!important;
max-width: 100px;
}

View File

@ -1,5 +1,5 @@
vn-ticket-request {
vn-textfield {
.vn-textfield {
margin: 0!important;
max-width: 100px;
}

View File

@ -14,53 +14,68 @@
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
"description": "Id"
},
"name": {
"type": "String"
"type": "String",
"description": "Name"
},
"size": {
"type": "Number"
"type": "Number",
"description": "Size"
},
"category": {
"type": "String"
"type": "String",
"description": "Category"
},
"typeFk": {
"type": "Number",
"description": "Type",
"required": true
},
"stems": {
"type": "Number"
"type": "Number",
"description": "Stems"
},
"description": {
"type": "String"
"type": "String",
"description": "Description"
},
"isOnOffer": {
"type": "Boolean"
"type": "Boolean",
"description": "Offer"
},
"isBargain": {
"type": "Boolean"
"type": "Boolean",
"description": "Bargain"
},
"isActive": {
"type": "Boolean"
"type": "Boolean",
"description": "Active"
},
"comment": {
"type": "String"
"type": "String",
"description": "Comment"
},
"relevancy": {
"type": "Number"
"type": "Number",
"description": "Relevancy"
},
"density": {
"type": "Number"
"type": "Number",
"description": "Density"
},
"image": {
"type": "String"
"type": "String",
"description": "Image"
},
"longName": {
"type": "String"
"type": "String",
"description": "Long name"
},
"subName": {
"type": "String"
"type": "String",
"description": "Subname"
},
"tag5": {
"type": "String"
@ -99,7 +114,8 @@
"type": "String"
},
"hasKgPrice": {
"type": "Boolean"
"type": "Boolean",
"description": "Price per Kg"
}
},
"relations": {

View File

@ -8,7 +8,7 @@ vn-item-descriptor {
display: block;
}
vn-dialog[vn-id=regularize]{
vn-textfield{
.vn-textfield{
width: 100%
}
}

View File

@ -22,8 +22,9 @@
</vn-autocomplete>
<vn-autocomplete
vn-one
url="{{$ctrl.itemTypes}}"
url="ItemTypes"
label="Type"
where="{categoryFk: filter.categoryFk}"
show-field="name"
value-field="id"
ng-model="filter.typeFk">
@ -42,13 +43,17 @@
label="Buyer">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-check
vn-one
label="Active"
ng-model="filter.isActive"
triple-state="true">
</vn-check>
<vn-horizontal class="vn-pt-sm">
<vn-one class="text-subtitle1" translate>
Tags
</vn-one>
<vn-icon-button
vn-none
vn-bind="+"
vn-tooltip="Add tag"
icon="add_circle"
ng-click="filter.tags.push({})">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal ng-repeat="itemTag in filter.tags">
<vn-autocomplete
@ -61,14 +66,14 @@
on-change="itemTag.value = null">
</vn-autocomplete>
<vn-textfield
vn-two
vn-one
ng-show="tag.selection.isFree !== false"
vn-id="text"
label="Value"
ng-model="itemTag.value">
</vn-textfield>
<vn-autocomplete
vn-two
vn-one
ng-show="tag.selection.isFree === false"
url="{{$ctrl.getSourceTable(tag.selection)}}"
label="Value"
@ -84,55 +89,65 @@
tabindex="-1">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal>
<vn-horizontal class="vn-pt-sm">
<vn-one class="text-subtitle1" translate>
More fields
</vn-one>
<vn-icon-button
vn-none
vn-bind="+"
vn-tooltip="Add tag"
vn-tooltip="Add field"
icon="add_circle"
ng-click="filter.tags.push({})">
ng-click="$ctrl.fieldFilters.push({})">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal class="vn-my-md">
<span
ng-click="$ctrl.initializeMoreFields()"
vn-one translate
class="unselectable"
tabindex="-1"
ng-class="{link: !filter.moreFields || !filter.moreFields.length}">
More fields
</span>
</vn-horizontal>
<vn-horizontal ng-repeat="field in filter.moreFields">
<vn-horizontal ng-repeat="fieldFilter in $ctrl.fieldFilters">
<vn-autocomplete
vn-one
ng-model="field.field"
data="$ctrl.moreFields"
value-field="field"
show-field="field"
label="Field"
on-change="field.value = null">
ng-model="fieldFilter.name"
data="$ctrl.moreFields"
value-field="name"
show-field="label"
show-filter="false"
translate-fields="['label']"
selection="info"
on-change="fieldFilter.value = null">
</vn-autocomplete>
<vn-textfield
vn-two
label="Value"
ng-model="field.value">
</vn-textfield>
<vn-one ng-switch="info.type">
<div ng-switch-when="Number">
<vn-input-number
label="Value"
ng-model="fieldFilter.value">
</vn-input-number>
</div>
<div ng-switch-when="Boolean">
<vn-check
label="Value"
ng-model="fieldFilter.value">
</vn-check>
</div>
<div ng-switch-when="Date">
<vn-date-picker
label="Value"
ng-model="fieldFilter.value">
</vn-date-picker>
</div>
<div ng-switch-default>
<vn-textfield
label="Value"
ng-model="fieldFilter.value">
</vn-textfield>
</div>
</vn-one>
<vn-icon-button
vn-none
vn-tooltip="Remove field"
icon="delete"
ng-click="filter.moreFields.splice($index, 1)"
ng-click="$ctrl.removeField($index, fieldFilter.name)"
tabindex="-1">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal ng-show="filter.moreFields && filter.moreFields.length">
<vn-icon-button
vn-bind="+"
vn-tooltip="Add field"
icon="add_circle"
ng-click="filter.moreFields.push({})">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal class="vn-mt-lg">
<vn-submit label="Search"></vn-submit>
</vn-horizontal>

View File

@ -2,19 +2,37 @@ import ngModule from '../module';
import SearchPanel from 'core/components/searchbar/search-panel';
class Controller extends SearchPanel {
constructor($scope, $element) {
super($scope, $element);
this.$ = $scope;
this.moreFields = [
{field: 'id'},
{field: 'description'},
{field: 'name'}
];
constructor($element, $) {
super($element, $);
let model = 'Item';
let moreFields = ['id', 'description', 'name', 'isActive'];
let properties;
let validations = window.validations;
if (validations && validations[model])
properties = validations[model].properties;
else
properties = {};
this.moreFields = [];
for (let field of moreFields) {
let prop = properties[field];
this.moreFields.push({
name: field,
label: prop ? prop.description : field,
type: prop ? prop.type : null
});
}
}
initializeMoreFields() {
if (!this.$.filter.moreFields || !this.$.filter.moreFields.length)
this.$.filter.moreFields = [{}];
get filter() {
let filter = this.$.filter;
for (let fieldFilter of this.fieldFilters)
filter[fieldFilter.name] = fieldFilter.value;
return filter;
}
set filter(value) {
@ -23,28 +41,20 @@ class Controller extends SearchPanel {
if (!value.tags)
value.tags = [{}];
this.fieldFilters = [];
for (let field of this.moreFields) {
if (value[field.name] != undefined) {
this.fieldFilters.push({
name: field.name,
value: value[field.name],
info: field
});
}
}
this.$.filter = value;
}
get itemTypes() {
if (this.$.filter) {
if (!this.$.filter.categoryFk)
return 'ItemTypes';
return `ItemCategories/${this.$.filter.categoryFk}/itemTypes`;
}
}
get filter() {
if (this.$.filter.moreFields) {
this.$.filter.moreFields.forEach(element => {
this.$.filter[element.field] = element.value;
});
}
let filter = Object.assign({}, this.$.filter);
delete filter.moreFields;
return filter;
}
getSourceTable(selection) {
if (!selection || selection.isFree === true)
return null;
@ -56,6 +66,11 @@ class Controller extends SearchPanel {
} else if (selection.sourceTable == null)
return `ItemTags/filterItemTags/${selection.id}`;
}
removeField(index, field) {
this.fieldFilters.splice(index, 1);
this.$.filter[field] = undefined;
}
}
ngModule.component('vnItemSearchPanel', {

View File

@ -3,6 +3,6 @@ Origin: Origen
Producer: Productor.
With visible: Con visible
Field: Campo
More fields: Mas campos
More fields: Más campos
Add field: Añadir campo
Remove field: Quitar campo

View File

@ -3,5 +3,8 @@ import SearchPanel from 'core/components/searchbar/search-panel';
ngModule.component('vnOrderCatalogSearchPanel', {
template: require('./index.html'),
controller: SearchPanel
controller: SearchPanel,
bindings: {
onSubmit: '&?'
}
});

View File

@ -1,111 +1,69 @@
<vn-crud-model
vn-id="model"
url="Orders/CatalogFilter"
filter="$ctrl.filter"
params="{orderFk: $ctrl.$state.params.id}"
limit="50"
data="items"
on-data-change="$ctrl.onDataChange()">
</vn-crud-model>
<div class="main-with-right-menu">
<vn-card compact>
<vn-horizontal class="catalog-header vn-px-md">
<vn-one>
<div ng-if="model.moreRows">
<span translate>More than</span> {{model.limit}} <span translate>results</span>
</div>
</vn-one>
<vn-auto>
<vn-autocomplete vn-id="field" vn-one
data="$ctrl.fieldList"
initial-data="$ctrl.field"
ng-model="$ctrl.field"
translate-fields="['name']"
order="name"
show-field="name"
value-field="field"
label="Order by"
disabled="!model.data">
</vn-autocomplete>
<vn-autocomplete vn-one
data="$ctrl.wayList"
initial-data="$ctrl.way"
ng-model="$ctrl.way"
translate-fields="['name']"
show-field="name"
value-field="way"
label="Order"
disabled="!model.data">
</vn-autocomplete>
</vn-auto>
</vn-horizontal>
</vn-card>
<vn-card class="vn-mt-md">
<vn-empty-rows ng-if="$ctrl.isRefreshing">
<vn-spinner enable="$ctrl.isRefreshing"></vn-spinner>
</vn-empty-rows>
<vn-empty-rows ng-if="!$ctrl.isRefreshing && !model.data" translate>
Enter a new search
</vn-empty-rows>
<vn-empty-rows ng-if="!$ctrl.isRefreshing && model.data.length === 0" translate>
No results
</vn-empty-rows>
</vn-card>
<vn-data-viewer
model="model"
class="main-with-right-menu">
<vn-horizontal class="catalog-list">
<section class="product" ng-repeat="item in items">
<vn-card>
<div class="image">
<img
ng-src="//verdnatura.es/vn-image-data/catalog/200x200/{{::item.image}}"
zoom-image="//verdnatura.es/vn-image-data/catalog/1600x900/{{::item.image}}"
on-error-src/>
<section class="product" ng-repeat="item in items">
<vn-card>
<div class="image">
<img
ng-src="//verdnatura.es/vn-image-data/catalog/200x200/{{::item.image}}"
zoom-image="//verdnatura.es/vn-image-data/catalog/1600x900/{{::item.image}}"
on-error-src/>
</div>
<div class="description">
<h3>
{{::item.name}}
</h3>
<h4 class="ellipsize">
<span translate-attr="::{title: item.subName}">{{::item.subName}}</span>
</h4>
<div class="tags">
<vn-label-value
ng-if="::item.value5"
label="{{::item.tag5}}"
value="{{::item.value5}}">
</vn-label-value>
<vn-label-value
ng-if="::item.value6"
label="{{::item.tag6}}"
value="{{::item.value6}}">
</vn-label-value>
<vn-label-value
ng-if="::item.value7"
label="{{::item.tag7}}"
value="{{::item.value7}}">
</vn-label-value>
</div>
<div class="description">
<h3>
{{::item.name}}
</h3>
<h4 class="ellipsize">
<span translate-attr="::{title: item.subName}">{{::item.subName}}</span>
</h4>
<div class="tags">
<vn-label-value
ng-if="::item.value5"
label="{{::item.tag5}}"
value="{{::item.value5}}">
</vn-label-value>
<vn-label-value
ng-if="::item.value6"
label="{{::item.tag6}}"
value="{{::item.value6}}">
</vn-label-value>
<vn-label-value
ng-if="::item.value7"
label="{{::item.tag7}}"
value="{{::item.value7}}">
</vn-label-value>
<div class="footer">
<div class="price">
<vn-one>
<span>{{::item.available}}</span>
<span translate>from</span>
<span>{{::item.price | currency:'EUR':2}}</span>
</vn-one>
<vn-icon-button vn-none
icon="add_circle"
ng-click="$ctrl.preview($event, item)"
vn-tooltip="Add">
</vn-icon-button>
</div>
<div class="footer">
<div class="price">
<vn-one>
<span>{{::item.available}}</span>
<span translate>from</span>
<span>{{::item.price | currency:'EUR':2}}</span>
</vn-one>
<vn-icon-button vn-none
icon="add_circle"
ng-click="$ctrl.preview($event, item)"
vn-tooltip="Add">
</vn-icon-button>
</div>
<div class="priceKg" ng-show="::item.priceKg">
<span>Precio por kilo {{::item.priceKg | currency: 'EUR'}}</span>
</div>
<div class="priceKg" ng-show="::item.priceKg">
<span>Precio por kilo {{::item.priceKg | currency: 'EUR'}}</span>
</div>
</div>
</vn-card>
</section>
</div>
</vn-card>
</section>
</vn-horizontal>
<vn-pagination class="vn-my-sm" model="model"></vn-pagination>
</div>
</vn-data-viewer>
<vn-side-menu side="right">
<vn-catalog-filter order="$ctrl.order"></vn-catalog-filter>
</vn-side-menu>

View File

@ -8,20 +8,19 @@ class Controller {
this.$stateParams = $state.params;
// Static autocomplete data
this.wayList = [
this.orderWays = [
{way: 'ASC', name: 'Ascendant'},
{way: 'DESC', name: 'Descendant'},
];
this.defaultFieldList = [
{field: 'relevancy DESC, name', name: 'Default'},
this.defaultOrderFields = [
{field: 'relevancy DESC, name', name: 'Relevancy'},
{field: 'showOrder, price', name: 'Color'},
{field: 'name', name: 'Name'},
{field: 'price', name: 'Price'}
];
this.fieldList = [];
this.fieldList = this.fieldList.concat(this.defaultFieldList);
this._way = this.wayList[0].way;
this._field = this.fieldList[0].field;
this.orderFields = [].concat(this.defaultOrderFields);
this._orderWay = this.orderWays[0].way;
this._orderField = this.orderFields[0].field;
}
/**
@ -51,43 +50,41 @@ class Controller {
});
// Add default filters - Replaces tags with same name
this.defaultFieldList.forEach(defaultField => {
this.defaultOrderFields.forEach(orderField => {
const index = newFilterList.findIndex(newfield => {
return newfield.name == defaultField.name;
return newfield.name == orderField.name;
});
if (index > -1)
newFilterList[index] = defaultField;
newFilterList[index] = orderField;
else
newFilterList.push(defaultField);
newFilterList.push(orderField);
});
this.fieldList = newFilterList;
this.orderFields = newFilterList;
}
/**
* Get order way ASC/DESC
*/
get way() {
return this._way;
get orderWay() {
return this._orderWay;
}
set way(value) {
this._way = value;
set orderWay(value) {
this._orderWay = value;
if (value) this.applyOrder();
}
/**
* Get order fields
*/
get field() {
return this._field;
get orderField() {
return this._orderField;
}
set field(value) {
this._field = value;
set orderField(value) {
this._orderField = value;
if (value) this.applyOrder();
}
@ -97,16 +94,11 @@ class Controller {
* @return {Object} - Order param
*/
getOrderBy() {
let field = this.$scope.field;
let args = {
field: this.field,
way: this.way
return {
field: this.orderField,
way: this.orderWay,
isTag: !!(this.orderSelection && this.orderSelection.isTag)
};
if (field.selection && field.selection.isTag)
args.isTag = true;
return args;
}
/**
@ -129,10 +121,6 @@ class Controller {
if (this.order && this.order.isConfirmed)
this.$state.go('order.card.line');
}
get isRefreshing() {
return this.$scope.model && this.$scope.model.status == 'loading';
}
}
Controller.$inject = ['$scope', '$state'];

View File

@ -25,17 +25,21 @@ describe('Order', () => {
let unexpectedResult = [{tagFk: 5, name: 'Color'}];
controller.onDataChange();
expect(controller.fieldList.length).toEqual(5);
expect(controller.fieldList).toEqual(jasmine.arrayContaining(expectedResult));
expect(controller.fieldList).not.toEqual(jasmine.arrayContaining(unexpectedResult));
expect(controller.orderFields.length).toEqual(5);
expect(controller.orderFields).toEqual(jasmine.arrayContaining(expectedResult));
expect(controller.orderFields).not.toEqual(jasmine.arrayContaining(unexpectedResult));
});
});
describe('getOrderBy()', () => {
it(`should return an object with order params`, () => {
controller.field = 'relevancy DESC, name';
controller.way = 'DESC';
let expectedResult = {field: 'relevancy DESC, name', way: 'DESC'};
controller.orderField = 'relevancy DESC, name';
controller.orderWay = 'DESC';
let expectedResult = {
field: 'relevancy DESC, name',
way: 'DESC',
isTag: false
};
let result = controller.getOrderBy();
expect(result).toEqual(expectedResult);

View File

@ -1,5 +1,4 @@
import ngModule from '../module';
import './style.scss';
class Controller {
constructor($translate, $scope, vnApp, $http, $state) {

View File

@ -1,5 +0,0 @@
vn-label-value[label=Total]{
padding-top: 10px;
display: block;
font-size: 1.2em;
}

View File

@ -7,19 +7,20 @@
<div>
<vn-horizontal class="item-category">
<vn-one ng-repeat="category in categories">
<vn-icon
ng-class="{'active': $ctrl.category.id == category.id}"
icon="{{::category.icon}}"
vn-tooltip="{{::category.name}}"
ng-click="$ctrl.category = {
id: category.id,
value: category.name
}">
</vn-icon>
<vn-icon
ng-class="{'active': $ctrl.category.id == category.id}"
icon="{{::category.icon}}"
vn-tooltip="{{::category.name}}"
ng-click="$ctrl.category = {
id: category.id,
value: category.name
}">
</vn-icon>
</vn-one>
</vn-horizontal>
<vn-horizontal class="input">
<vn-autocomplete vn-id="type" vn-one
<vn-vertical class="input">
<vn-autocomplete
vn-id="type"
data="$ctrl.itemTypes"
on-change="$ctrl.type = {
id: value,
@ -30,20 +31,38 @@
value-field="id"
label="Type">
<prepend>
<i class="material-icons">search</i>
<vn-icon icon="search"></vn-icon>
</prepend>
<append>
<i class="material-icons"
ng-click="$ctrl.openPanel($event)"
style="cursor: pointer; color: #aaa">
keyboard_arrow_down
</i>
</append>
</vn-autocomplete>
</vn-horizontal>
<vn-vertical class="input">
</vn-vertical>
<vn-vertical class="input vn-pt-md">
<vn-autocomplete
vn-id="field"
data="$ctrl.catalog.orderFields"
ng-model="$ctrl.catalog.orderField"
selection="$ctrl.catalog.orderSelection"
translate-fields="['name']"
order="name"
show-field="name"
value-field="field"
label="Order by"
disabled="!model.data">
</vn-autocomplete>
<vn-autocomplete
data="$ctrl.catalog.orderWays"
ng-model="$ctrl.catalog.orderWay"
translate-fields="['name']"
show-field="name"
value-field="way"
label="Order"
disabled="!model.data">
</vn-autocomplete>
<div ng-if="false && model.moreRows">
<span translate>More than</span> {{model.limit}} <span translate>results</span>
</div>
</vn-vertical>
<vn-vertical class="input vn-pt-md">
<vn-textfield
vn-one
ng-keyUp="$ctrl.onSearchById($event)"
label="Item id"
ng-model="$ctrl.itemFk">
@ -58,21 +77,24 @@
label="Search tag"
ng-model="$ctrl.value">
<prepend>
<i class="material-icons">search</i>
<vn-icon icon="search"></vn-icon>
</prepend>
<append>
<i class="material-icons"
ng-click="$ctrl.openPanel($event)"
style="cursor: pointer; color: #aaa">
keyboard_arrow_down
</i>
<vn-icon
icon="keyboard_arrow_down"
ng-click="$ctrl.openPanel($event)"
style="cursor: pointer;">
</vn-icon>
</append>
</vn-textfield>
</vn-vertical>
<vn-popover
vn-id="popover"
on-close="$ctrl.onPopoverClose()">
<vn-order-catalog-search-panel/>
<vn-order-catalog-search-panel
filter="panelFilter"
on-submit="$ctrl.onPanelSubmit($filter)">
</vn-order-catalog-search-panel>
</vn-popover>
<div class="chips">
<vn-chip

View File

@ -5,7 +5,7 @@ class Controller {
constructor($element, $http, $scope, $state, $compile, $transitions) {
this.$element = $element;
this.$http = $http;
this.$scope = $scope;
this.$ = $scope;
this.$state = $state;
this.$stateParams = $state.params;
this.$compile = $compile;
@ -28,7 +28,7 @@ class Controller {
this._order = value;
this.$scope.$applyAsync(() => {
this.$.$applyAsync(() => {
let category;
let type;
@ -111,7 +111,7 @@ class Controller {
this.tags.push({
value: this.value,
});
this.$scope.search.value = null;
this.$.search.value = null;
this.applyFilters();
}
@ -149,29 +149,16 @@ class Controller {
if (event.defaultPrevented) return;
event.preventDefault();
this.$panelScope = this.$scope.$new();
this.$panel = this.$compile(`<vn-order-catalog-search-panel/>`)(this.$panelScope);
const panel = this.$panel[0].$ctrl;
panel.filter = this.filter;
panel.onSubmit = filter => this.onPanelSubmit(filter);
this.$scope.popover.parent = this.$scope.search.element;
this.$scope.popover.child = this.$panel[0];
this.$scope.popover.show();
this.panelFilter = {};
this.$.popover.show(this.$.search.element);
}
onPanelSubmit(filter) {
this.$scope.popover.hide();
this.$.popover.hide();
this.tags.push(filter);
this.applyFilters();
}
onPopoverClose() {
this.$panelScope.$destroy();
this.$panel.remove();
this.$panel = null;
}
/**
* Updates url state params from filter values
*/

View File

@ -31,13 +31,10 @@ describe('Order', () => {
describe('order() setter', () => {
it(`should call scope $applyAsync() method and apply filters from state params`, () => {
$httpBackend.expect('GET', `Orders/4/getItemTypeAvailable?itemCategoryId=1`).respond();
spyOn(controller.$scope, '$applyAsync').and.callThrough();
controller.order = {id: 4};
expect(controller.$scope.$applyAsync).toHaveBeenCalledWith(jasmine.any(Function));
$scope.$apply();
expect(controller.category).toEqual({id: 1, value: 'My Category'});
expect(controller.type).toEqual({id: 1, value: 'My type'});
});

View File

@ -2,77 +2,83 @@
vn-id="watcher"
data="$ctrl.rows">
</vn-watcher>
<vn-vertical>
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-horizontal class="header">
<vn-one class="taxes" ng-if="$ctrl.rows.length > 0">
<p><vn-label translate>Subtotal</vn-label> {{$ctrl.subtotal | currency: 'EUR':2}}</p>
<p><vn-label translate>VAT</vn-label> {{$ctrl.VAT | currency: 'EUR':2}}</p>
<p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.order.total | currency: 'EUR':2}}</strong></p>
</vn-one>
</vn-horizontal>
<vn-table>
<vn-thead>
<vn-tr>
<vn-th></vn-th>
<vn-th number>Id</vn-th>
<vn-th>Description</vn-th>
<vn-th>Warehouse</vn-th>
<vn-th>Shipped</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th number>Price</vn-th>
<vn-th ng-if="!$ctrl.order.isConfirmed"></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="row in $ctrl.rows">
<vn-td shrink>
<img
ng-src="//verdnatura.es/vn-image-data/catalog/50x50/{{::row.item.image}}"
zoom-image="//verdnatura.es/vn-image-data/catalog/1600x900/{{::row.item.image}}"
on-error-src/>
</vn-td>
<vn-td number>
<span ng-click="$ctrl.showDescriptor($event, row.itemFk)"
class="link">
{{::row.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td expand>
<vn-fetched-tags
max-length="6"
item="::row.item"
name="::row.item.name"
sub-name="::row.item.subName">
</vn-fetched-tags>
</vn-td>
<vn-td>{{::row.warehouse.name}}</vn-td>
<vn-td>{{::row.shipped | date: 'dd/MM/yyyy'}}</vn-td>
<vn-td number>{{::row.quantity}}</vn-td>
<vn-td number>
{{::row.price | currency: 'EUR':2}}
</vn-td>
<vn-td shrink ng-if="!$ctrl.order.isConfirmed">
<vn-icon-button
vn-tooltip="Remove item"
icon="delete"
ng-click="$ctrl.showDeleteRow($index)"
tabindex="-1">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-vertical>
<vn-data-viewer data="$ctrl.rows" class="vn-w-lg">
<vn-card class="vn-pa-lg header" ng-if="$ctrl.rows.length > 0">
<div>
<vn-label translate>Subtotal</vn-label>
{{$ctrl.subtotal | currency: 'EUR':2}}
</div>
<div>
<vn-label translate>VAT</vn-label>
{{$ctrl.VAT | currency: 'EUR':2}}
</div>
<div>
<vn-label>Total</vn-label>
{{$ctrl.order.total | currency: 'EUR':2}}
</div>
</vn-card>
<vn-button-bar ng-if="!$ctrl.order.isConfirmed">
<vn-button
label="Confirm"
ng-click="$ctrl.save()">
</vn-button>
</vn-button-bar>
</vn-vertical>
<vn-card class="vn-mt-md">
<vn-table>
<vn-thead>
<vn-tr>
<vn-th></vn-th>
<vn-th number>Id</vn-th>
<vn-th>Description</vn-th>
<vn-th>Warehouse</vn-th>
<vn-th>Shipped</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th number>Price</vn-th>
<vn-th ng-if="!$ctrl.order.isConfirmed"></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="row in $ctrl.rows">
<vn-td shrink>
<img
ng-src="//verdnatura.es/vn-image-data/catalog/50x50/{{::row.item.image}}"
zoom-image="//verdnatura.es/vn-image-data/catalog/1600x900/{{::row.item.image}}"
on-error-src/>
</vn-td>
<vn-td number>
<span ng-click="$ctrl.showDescriptor($event, row.itemFk)"
class="link">
{{::row.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td expand>
<vn-fetched-tags
max-length="6"
item="::row.item"
name="::row.item.name"
sub-name="::row.item.subName">
</vn-fetched-tags>
</vn-td>
<vn-td shrink>{{::row.warehouse.name}}</vn-td>
<vn-td shrink>{{::row.shipped | date: 'dd/MM/yyyy'}}</vn-td>
<vn-td number>{{::row.quantity}}</vn-td>
<vn-td number>
{{::row.price | currency: 'EUR':2}}
</vn-td>
<vn-td shrink ng-if="!$ctrl.order.isConfirmed">
<vn-icon-button
vn-tooltip="Remove item"
icon="delete"
ng-click="$ctrl.showDeleteRow($index)"
tabindex="-1">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-float-button
icon="check"
vn-tooltip="Confirm"
ng-click="$ctrl.save()"
ng-if="!$ctrl.order.isConfirmed"
fixed-bottom-right>
</vn-float-button>
<vn-item-descriptor-popover
vn-id="descriptor">
</vn-item-descriptor-popover>

View File

@ -8,20 +8,11 @@ vn-order-line {
height: 50px;
}
}
.taxes {
max-width: 10em;
border: $border-thin-light;
.header {
text-align: right;
padding: .5em !important;
& > p {
font-size: 1.2em;
margin: .2em;
& > div {
margin-bottom: $spacing-xs;
}
}
vn-horizontal.header {
justify-content: flex-end;
margin-bottom: 0.5em;
}
}

View File

@ -1,5 +1,5 @@
<vn-popover vn-id="popover" on-close="$ctrl.clear()">
<div class="vn-descriptor">
<div class="vn-descriptor vn-order-prices-popover">
<div class="header">
<span></span>
<a translate-attr="{title: 'Preview'}" ui-sref="item.card.summary({id: $ctrl.item.id})">
@ -7,81 +7,73 @@
</a>
<span></span>
</div>
<div class="vn-pa-md">
<vn-horizontal>
<h5>{{$ctrl.item.id}}</h5>
</vn-horizontal>
<vn-horizontal>
<vn-vertical class="data">
<vn-auto>
<vn-label-value label="Name"
value="{{$ctrl.item.name}}">
</vn-label-value>
<vn-label-value label="Buyer"
value="{{$ctrl.item.firstName}} {{$ctrl.item.lastName}}">
</vn-label-value>
<vn-label-value
ng-repeat="tag in $ctrl.tags"
label="{{::tag.tag.name}}"
value="{{::tag.value}}">
</vn-label-value>
</vn-auto>
<vn-icon-button
class="button"
label="Save"
ng-click="$ctrl.saveQuantity($ctrl.prices)"></vn-icon-button>
</vn-vertical>
<vn-vertical class="prices">
<form name="form">
<vn-table>
<vn-tbody>
<vn-tr ng-repeat="price in $ctrl.prices">
<vn-td shrink>
<span class="ellipsize text"
title="{{::price.warehouse}}">
{{::price.warehouse}}
</span>
</vn-td>
<vn-td number shrink>
<div>
<span ng-click="$ctrl.addQuantity(price)"
class="link unselectable">{{::price.grouping}}</span>
<span> x {{::price.price | currency: 'EUR': 2}}</span>
</div>
<div class="priceKg" ng-show="::price.priceKg">
{{price.priceKg | currency: 'EUR'}}/Kg
</div>
</vn-td>
<vn-td>
<vn-input-number
min="0"
name="quantity"
label="Qty."
ng-model="price.quantity"
step="price.grouping"
on-change="$ctrl.validate()">
</vn-input-number>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</form>
<vn-one class="error">
<span translate>Wrong quantity</span>
</vn-one>
</vn-vertical>
</vn-horizontal>
<vn-horizontal class="buttons-bar">
<vn-quick-links
links="$ctrl.quicklinks">
</vn-quick-links>
<vn-one>
<vn-submit
label="Save"
ng-click="$ctrl.submit()">
</vn-submit>
</vn-one>
</vn-horizontal>
<div class="body vn-pa-md">
<div class="attributes">
<h5>{{$ctrl.item.name}}</h5>
<vn-label-value
label="Id"
value="{{$ctrl.item.id}}">
</vn-label-value>
<vn-label-value
label="Buyer"
value="{{$ctrl.item.firstName}} {{$ctrl.item.lastName}}">
</vn-label-value>
<vn-label-value
ng-repeat="tag in $ctrl.tags"
label="{{::tag.tag.name}}"
value="{{::tag.value}}">
</vn-label-value>
</div>
<vn-quick-links
links="$ctrl.quicklinks">
</vn-quick-links>
</div>
<form name="form" class="prices">
<vn-table>
<vn-tbody>
<vn-tr ng-repeat="price in $ctrl.prices">
<vn-td shrink class="warehouse">
<span
class="ellipsize text"
title="{{::price.warehouse}}">
{{::price.warehouse}}
</span>
</vn-td>
<vn-td number expand>
<div>
<span
ng-click="$ctrl.addQuantity(price)"
class="link unselectable">
{{::price.grouping}}
</span>
<span> x {{::price.price | currency: 'EUR': 2}}</span>
</div>
<div class="price-kg" ng-show="::price.priceKg">
{{price.priceKg | currency: 'EUR'}}/Kg
</div>
</vn-td>
<vn-td shrink>
<vn-input-number
min="0"
name="quantity"
ng-model="price.quantity"
step="price.grouping"
on-change="$ctrl.validate()"
class="dense">
</vn-input-number>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
<div class="footer vn-pa-md">
<div class="error">
<span translate>Wrong quantity</span>
</div>
<vn-submit
label="Save"
ng-click="$ctrl.submit()">
</vn-submit>
</div>
</form>
</div>
</vn-popover>

View File

@ -1,24 +1,23 @@
import ngModule from '../module';
import Component from 'core/lib/component';
import './style.scss';
class Controller {
constructor($scope, $http, $timeout, $element, $translate, vnApp) {
this.$ = $scope;
this.$timeout = $timeout;
this.$http = $http;
this.$element = $element;
this.$translate = $translate;
this.vnApp = vnApp;
class Controller extends Component {
constructor($element, $) {
super($element, $);
this.totalBasquet = 0;
}
set prices(value) {
this._prices = value;
if (value && value[0].grouping)
this.calculateTotal();
}
get prices() {
return this._prices;
}
getTags() {
let filter = {
where: {itemFk: this.item.id,
@ -40,6 +39,7 @@ class Controller {
});
});
}
show(event, item) {
this.item = JSON.parse(JSON.stringify(item));
this.prices = this.item.prices;
@ -48,15 +48,18 @@ class Controller {
this.$.popover.show();
this.$.popover.relocate();
}
clear() {
this.item = {};
this.tags = {};
this._prices = {};
this.total = 0;
}
calculateMax() {
this.max = this.item.available - this.total;
}
calculateTotal() {
this.total = 0;
@ -73,7 +76,9 @@ class Controller {
this.validate();
}
validate() {
/*
this.$timeout(() => {
this.calculateTotal();
let inputs = this.$element[0].querySelectorAll('vn-input-number[name="quantity"] div.infix:not(.validated)');
@ -91,7 +96,9 @@ class Controller {
else
this.$element[0].querySelector('vn-vertical.prices').classList.remove('invalid');
});
*/
}
getFilledLines() {
let filledLines = [];
let match;
@ -110,6 +117,7 @@ class Controller {
});
return filledLines;
}
submit() {
this.calculateTotal();
let filledLines = this.getFilledLines();
@ -128,14 +136,14 @@ class Controller {
this.$http.post(`OrderRows/addToOrder`, params).then(res => {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
this.$.popover.hide();
this.card.reload();
if (this.card)
this.card.reload();
});
});
}
}
Controller.$inject = ['$scope', '$http', '$timeout', '$element', '$translate', 'vnApp'];
ngModule.component('vnOrderPricesPopover', {
template: require('./index.html'),
controller: Controller,
@ -143,6 +151,6 @@ ngModule.component('vnOrderPricesPopover', {
order: '<'
},
require: {
card: '^vnOrderCard'
card: '?^vnOrderCard'
}
});

View File

@ -1,79 +1,44 @@
@import "variables";
vn-order-prices-popover {
width: 150px;
.vn-order-prices-popover {
display: block;
img[ng-src] {
height: 100%;
width: 100%;
}
.prices, .data{
width: 18em;
}
vn-vertical.data{
vn-vertical.data {
padding-right: 16px;
border-right: 1px solid $color-main;
}
vn-vertical.prices{
padding-left: 16px;
&.invalid {
vn-one.error {
display: block;
padding-top: 10px;
color: #d50000;
text-align: center!important;
.prices {
vn-table {
.warehouse {
width: 3em;
max-width: 3em;
}
.price-kg {
color: $color-font-secondary;
font-size: .8em
}
.vn-input-number {
width: 3.5em;
}
}
.footer {
text-align: center;
vn-tr {
border-bottom: 0
.error {
display: none;
}
&.invalid {
.error {
display: block;
padding-top: 10px;
color: $color-alert;
text-align: center;
}
}
}
vn-tr > vn-td:first-child {
vertical-align: top
}
vn-tr > vn-td:nth-child(3) {
vertical-align: top;
padding-top: 0
}
.priceKg {
color: $color-font-secondary;
font-size: .8em
}
}
vn-input-number{
margin: 0!important;
label {
display: none;
}
}
vn-one.number {
text-align: right;
padding-right: 8px;
}
vn-one.text{
padding-top:10px!important;
}
vn-horizontal.buttons-bar{
padding-top: 16px;
text-align: center;
}
.quicklinks vn-icon {
font-size: 1.8em !important;
padding: 0 !important;
& > i {
line-height: 36px
}
}
vn-one.error{
display: none;
}
}

View File

@ -1,59 +1,60 @@
<vn-crud-model auto-load="false"
<vn-crud-model
auto-load="true"
vn-id="model"
url="OrderRows"
filter="::$ctrl.filter"
link="{orderFk: $ctrl.$stateParams.id}"
limit="20"
data="rows" on-data-change="$ctrl.onDataChange()">
data="rows"
on-data-change="$ctrl.onDataChange()">
</vn-crud-model>
<mg-ajax path="Orders/{{$ctrl.$stateParams.id}}/getTotalVolume" options="mgEdit"></mg-ajax>
<vn-vertical>
<vn-data-viewer model="model" class="header vn-w-lg">
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-horizontal>
<div class="totalBox">
<vn-label-value label="Total"
value="{{::edit.model.totalVolume}} M³">
</vn-label-value>
<vn-label-value label="Cajas"
value="{{::edit.model.totalBoxes | dashIfEmpty}} U">
</vn-label-value>
</div>
</vn-horizontal>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="itemFk" default-order="ASC" number>Item</vn-th>
<vn-th>Description</vn-th>
<vn-th field="quantity" number>Quantity</vn-th>
<vn-th number>m³ per quantity</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="row in rows">
<vn-td number>
<span
ng-click="$ctrl.showDescriptor($event, row.itemFk)"
class="link">
{{::row.itemFk}}
</span>
</vn-td>
<vn-td expand>
<vn-fetched-tags
max-length="6"
item="::row.item"
name="::row.item.name"
sub-name="::row.item.subName">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::row.quantity}}</vn-td>
<vn-td number>{{::row.volume | number:3}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-vertical>
<vn-pagination model="model"></vn-pagination>
<vn-label-value
label="Total"
value="{{::edit.model.totalVolume}} M³">
</vn-label-value>
<vn-label-value
label="Cajas"
value="{{::edit.model.totalBoxes | dashIfEmpty}} U">
</vn-label-value>
</vn-card>
</vn-vertical>
<vn-item-descriptor-popover vn-id="descriptor"></vn-item-descriptor-popover>
<vn-card class="vn-mt-md">
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="itemFk" default-order="ASC" number>Item</vn-th>
<vn-th>Description</vn-th>
<vn-th field="quantity" number>Quantity</vn-th>
<vn-th number>m³ per quantity</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="row in rows">
<vn-td number>
<span
ng-click="$ctrl.showDescriptor($event, row.itemFk)"
class="link">
{{::row.itemFk}}
</span>
</vn-td>
<vn-td expand>
<vn-fetched-tags
max-length="6"
item="::row.item"
name="::row.item.name"
sub-name="::row.item.subName">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::row.quantity}}</vn-td>
<vn-td number>{{::row.volume | number:3}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-item-descriptor-popover
vn-id="descriptor">
</vn-item-descriptor-popover>

View File

@ -1,8 +1,12 @@
@import "./variables";
.totalBox {
border: $border-thin-light;
text-align: left;
align-self: flex-end;
vn-order-volume {
.header {
text-align: right;
& > div {
margin-bottom: $spacing-xs;
}
}
}

View File

@ -20,7 +20,7 @@ vn-dialog.modal-form {
& > div {
padding: 0 !important;
}
vn-textfield {
.vn-textfield {
width: 100%;
}
.buttons {

View File

@ -1,5 +1,4 @@
import ngModule from '../../module';
import './style.scss';
class Controller {
constructor($scope, $http, $state, $translate, vnApp, vnConfig) {

View File

@ -1,7 +0,0 @@
vn-ticket-request {
vn-textfield {
margin: 0!important;
max-width: 100px;
}
}

View File

@ -1,5 +1,4 @@
import ngModule from '../../module';
import './style.scss';
class Controller {
constructor($scope, $http, $state, $translate, vnApp) {

View File

@ -1,7 +0,0 @@
vn-ticket-request {
vn-textfield {
margin: 0!important;
max-width: 100px;
}
}

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