Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
related to selection based on dynamic select list
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2020 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
var equals = angular.equals,
forEach = angular.forEach,
isArray = angular.isArray,
isObject = angular.isObject,
isDate = angular.isDate;
function dummyEquals(a, b) {
if (a === b) return true;
if (a === null || b === null) return false;
if (a !== a && b !== b) return true; // NaN === NaN
var keys = _.keys(a).filter(function (k) { return k.indexOf('$') === 0; });
if (keys.length === 0) {
return true;
}
for (var i = 0; i < keys.length; i++) {
var k = keys[i];
if (!equals(a[k], b[k])) {
return false;
}
}
return true;
}
function updateValues(source, target, itemScope, formScope) {
if (equals(source, target) && dummyEquals(source, target) && (!source || !source.$force)) {
return;
}
// handle json records
if (source && formScope && formScope._model === 'com.axelor.meta.db.MetaJsonRecord') {
if (source.attrs || source.id) {
source = source.id > 0
? _.pick(source, 'jsonModel', 'name', 'attrs', 'id', 'version')
: _.pick(source, 'jsonModel', 'name', 'attrs');
}
var values = source.attrs ? _.extend({}, JSON.parse(source.attrs)) : source;
var fix = function (rec) {
if (!rec) return rec;
if (_.isArray(rec)) return _.map(rec, fix);
if (rec.id > 0 && (rec.version || rec.attrs)) {
rec = _.pick(rec, 'id', 'name', 'selected');
if (!rec.selected) delete rec.selected;
}
return rec;
};
_.each(values, function (v, k) {
values[k] = fix(v);
});
// if called from form fields
if (itemScope && itemScope.updateJsonValues) {
return itemScope.updateJsonValues(values);
}
// onNew or onSave from main form
var current = target && target.attrs ? JSON.parse(target.attrs) : {};
if (source.attrs || !source.jsonModel) {
source.attrs = JSON.stringify(_.extend({}, current, values));
}
} else if (itemScope && itemScope.updateJsonValues) {
return itemScope.updateJsonValues(source);
}
function compact(value) {
if (!value) return value;
if (value.version === undefined) return value;
if (!value.id) return value;
var res = _.extend(value);
res.$version = res.version;
res.version = undefined;
return res;
}
var changed = false;
forEach(source, function(value, key) {
var dest;
var newValue = value;
var oldValue = target[key];
if (oldValue === newValue) {
return;
}
if (isArray(value)) {
dest = target[key] || [];
newValue = _.map(value, function(item) {
var found = _.find(dest, function(v){
return item.id && v.id === item.id;
});
if (_.has(item, "version") && item.id) item.$fetched = true;
if (found) {
var found_ = _.extend({}, found);
var changed_ = updateValues(item, found_);
changed = changed || changed_;
return changed_ ? found_ : found;
}
return item;
});
} else if (isObject(value) && !isDate(value)) {
dest = target[key] || {};
if (dest.id === value.id) {
if (_.isNumber(dest.version)) {
dest = _.extend({}, dest);
changed = updateValues(value, dest, itemScope, formScope) || changed;
} else {
dest.$updatedValues = value;
if (formScope) {
formScope.$broadcast('on:check-nested-values', value);
}
}
} else {
dest = compact(value);
}
newValue = dest;
}
if (!equals(oldValue, newValue)) {
changed = true;
target[key] = newValue;
}
});
if (target && changed) {
target.$dirty = true;
}
return changed;
}
function handleError(scope, item, message) {
if (!item) {
return;
}
var ctrl = item.data('$ngModelController');
if (!ctrl) {
return;
}
if (ctrl.$doReset) {
ctrl.$doReset();
}
if (!message) {
ctrl.$doReset = null;
return;
}
var e = $('<span class="error"></span>').text(message);
var p = item.parent('.form-item');
if (item.children(':first').is(':input,.input-append,.picker-input')) {
p.append(e);
} else {
p.prepend(e);
}
var clear = scope.$on('on:edit', function(){
ctrl.$doReset();
});
function cleanUp(items) {
var idx = items.indexOf(ctrl.$doReset);
if (idx > -1) {
items.splice(idx, 1);
}
}
ctrl.$doReset = function(value) {
cleanUp(ctrl.$viewChangeListeners);
cleanUp(ctrl.$formatters);
ctrl.$setValidity('invalid', true);
ctrl.$doReset = null;
e.remove();
clear();
return value;
};
if (!item.hasClass('readonly')) {
ctrl.$setValidity('invalid', false);
}
ctrl.$viewChangeListeners.push(ctrl.$doReset);
ctrl.$formatters.push(ctrl.$doReset);
}
function ActionHandler($scope, ViewService, options) {
if (!options || !options.action)
throw 'No action provided.';
this.canSave = options.canSave;
this.name = options.name;
this.prompt = options.prompt;
this.action = options.action;
this.element = options.element || $();
this.scope = $scope;
this.ws = ViewService;
this.viewType = $scope.viewType;
}
ActionHandler.prototype = {
constructor: ActionHandler,
onLoad : function() {
return this.handle();
},
onNew: function() {
return this.handle();
},
onSave: function() {
var self = this;
return this._fireBeforeSave().then(function() {
return self.handle();
});
},
onTabSelect: function(unblocked) {
return this.onSelect.apply(this, arguments);
},
onSelect: function(unblocked) {
var self = this;
var blockUI = this._blockUI;
if (unblocked) {
this._blockUI = angular.noop;
}
function reset() {
self._blockUI = blockUI;
}
var promise = this.handle();
promise.then(reset, reset);
return promise;
},
onClick: function(event) {
var self = this;
var prompt = this._getPrompt();
if (prompt) {
var deferred = this.ws.defer(),
promise = deferred.promise;
axelor.dialogs.confirm(prompt, function(confirmed){
if (confirmed) {
self._fireBeforeSave().then(function() {
self.handle().then(deferred.resolve, deferred.reject);
});
} else {
self.scope.$timeout(deferred.reject);
}
}, {
yesNo: false
});
return promise;
}
return this._fireBeforeSave().then(function() {
return self.handle();
});
},
onChange: function(event) {
return this.handle({ wait: 100 });
},
_getPrompt: function () {
var prompt = this.prompt;
var itemScope = this.element.scope();
if (_.isFunction(itemScope.attr) && !this.element.is('[ui-slick-grid]')) {
prompt = itemScope.attr('prompt') || prompt;
}
return _.isString(prompt) ? prompt : null;
},
_getContext: function() {
var scope = this.scope,
context = scope.getContext ? scope.getContext() : scope.record,
viewParams = scope._viewParams || {};
context = _.extend({}, viewParams.context, context);
if (context._model === undefined) {
context._model = scope._model;
}
// include button name as _signal (used by workflow engine)
if (this.element.is("button,a.button-item,li.action-item")) {
context._signal = this.element.attr('name') || this.element.attr('x-name');
}
return context;
},
_getRootFormElement: function () {
var formElement = $(this.element).parents('form[ui-form]:last');
if (formElement.length === 0) {
formElement = this._getFormElement();
}
return formElement;
},
_getFormElement: function () {
var elem = $(this.element);
var formElement = elem;
if (formElement.is('form')) {
return formElement;
}
formElement = elem.data('$editorForm') || elem.parents('form:first');
if (!formElement || !formElement.get(0)) { // toolbar button
formElement = this.element.parents('.form-view:first').find('form:first');
}
if (formElement.length === 0) {
formElement = this.element;
}
return formElement;
},
handle: function(options) {
var that = this;
var action = this.action.trim();
var deferred = this.ws.defer();
var all = this.scope.$actionPromises || [];
var pending = all.slice();
var opts = _.extend({}, options);
all.push(deferred.promise);
this.scope.waitForActions(function () {
var promise = that._handleAction(action);
function done() {
setTimeout(function () {
var i = all.indexOf(deferred.promise);
if (i > -1) {
all.splice(i, 1);
}
}, 10);
}
promise.then(done, done);
promise.then(deferred.resolve, deferred.reject);
}, opts.wait || 10, pending);
return deferred.promise;
},
_blockUI: function() {
// block the entire ui (auto unblocks when actions are complete)
_.delay(axelor.blockUI, 10);
},
_fireBeforeSave: function() {
var scope = this._getRootFormElement().scope();
var event = scope.$broadcast('on:before-save', scope.record);
var deferred = this.ws.defer();
if (event.defaultPrevented) {
if (event.error) {
axelor.dialogs.error(event.error);
}
setTimeout(function() {
deferred.reject(event.error);
});
} else {
scope.$timeout(function() {
scope.ajaxStop(function() {
deferred.resolve();
}, 100);
}, 50);
}
return deferred.promise;
},
_checkVersion: function() {
var self = this;
var scope = this.scope;
var deferred = this.ws.defer();
if (scope.checkVersion) {
scope.checkVersion(function (verified) {
if (verified) {
return deferred.resolve();
}
axelor.dialogs.error(
_t("The record has been updated or delete by another action."));
deferred.reject();
});
} else {
deferred.resolve();
}
return deferred.promise;
},
_handleNew: function() {
var self = this;
var scope = this.scope;
var deferred = this.ws.defer();
if (scope.onNew) {
return scope.onNew();
}
if (scope.editRecord) {
scope.editRecord(null);
deferred.resolve();
} else {
deferred.reject();
}
return deferred.promise;
},
_handleSave: function(validateOnly) {
if (validateOnly) {
return this.__handleSave(validateOnly);
}
var self = this;
var deferred = this.ws.defer();
this._checkVersion().then(function () {
self.__handleSave().then(deferred.resolve, deferred.reject);
}, deferred.reject);
return deferred.promise;
},
__handleSave: function(validateOnly) {
var self = this;
var scope = this.scope;
var id = (scope.record||{}).id;
var o2mPopup = scope._isPopup && (scope.$parent.field||{}).serverType === "one-to-many";
if (o2mPopup && !validateOnly && this.name == 'onLoad' && (!id || id < 0)) {
var deferred = this.ws.defer();
var msg = _t("The {0}={1} event can't call 'save' action on unsaved o2m item.", this.name, this.action);
deferred.reject(msg);
console.error(msg);
return deferred.promise;
}
return this._fireBeforeSave().then(function() {
return self.__doHandleSave(validateOnly);
});
},
__doHandleSave: function(validateOnly) {
this._blockUI();
// save should be done on root form scope only
var rootForm = this._getRootFormElement();
var scope = rootForm.is('[ui-view-grid]') ? this.scope : rootForm.scope();
var deferred = this.ws.defer();
if (scope.isValid && !scope.isValid()) {
if (scope.showErrorNotice) {
scope.showErrorNotice();
} else {
axelor.notify.error(_t('Please correct the invalid form values.'), {
title: _t('Validation error')
});
}
deferred.reject();
return deferred.promise;
}
if (validateOnly || (scope.isDirty && !scope.isDirty())) {
deferred.resolve();
return deferred.promise;
}
function doEdit(rec) {
var params = scope._viewParams || {};
scope.editRecord(rec);
if (params.$viewScope) {
params.$viewScope.updateRoute();
}
deferred.resolve();
}
function doSave(values) {
var ds = scope._dataSource;
ds.save(values).success(function(rec, page) {
if (scope.doRead) {
return scope.doRead(rec.id).success(doEdit);
}
return ds.read(rec.id).success(doEdit);
});
}
var values = _.extend({ _original: scope.$$original }, scope.record);
if (scope.onSave) {
scope.onSave({
values: values,
callOnSave: false,
wait: false
}).then(deferred.resolve, deferred.reject);
} else {
doSave(values);
}
this._invalidateContext = true;
return deferred.promise;
},
_closeView: function (scope) {
if (scope.onOK) {
return scope.onOK();
}
var tab = scope._viewParams || scope.selectedTab;
if (scope.closeTab) {
scope.closeTab(tab);
} else if (scope.$parent) {
this._closeView(scope.$parent);
}
},
_isSameViewType: function () {
return this.viewType === this.scope.viewType;
},
_handleAction: function(action) {
this._blockUI();
var self = this,
scope = this.scope,
context = this._getContext(),
deferred = this.ws.defer();
if (!this._isSameViewType()) {
deferred.reject();
return deferred.promise;
}
function resolveLater() {
deferred.resolve();
return deferred.promise;
}
function chain(items) {
var first = _.first(items);
if (first === undefined) {
return resolveLater();
}
return self._handleSingle(first).then(function(pending) {
if (_.isString(pending) && pending.trim().length) {
return self._handleAction(pending);
}
var _deferred = self.ws.defer();
scope.$timeout(function () {
scope.ajaxStop(function() {
_deferred.resolve();
});
});
return _deferred.promise.then(function () {
return chain(_.rest(items));
});
});
}
if (!action) {
return resolveLater();
}
action = action.replace(/(^\s*,?\s*)|(\s*,?\s*$)/, '');
var pattern = /,\s*(sync)\s*(,|$)/;
if (pattern.test(action)) {
var which = pattern.exec(action)[1];
axelor.dialogs.error(_t('Invalid use of "{0}" action, must be the first action.', which));
deferred.reject();
return deferred.promise;
}
pattern = /(^sync\s*,\s*)|(^sync$)/;
if (pattern.test(action)) {
action = action.replace(pattern, '');
return this._fireBeforeSave().then(function() {
return self._handleAction(action);
});
}
pattern = /(^|,)\s*(new)\s*,/;
if (pattern.test(action)) {
var which = pattern.exec(action)[2];
axelor.dialogs.error(_t('Invalid use of "{0}" action, must be the last action.', which));
deferred.reject();
return deferred.promise;
}
pattern = /(^|,)\s*(close)\s*,/;
if (pattern.test(action)) {
axelor.dialogs.error(_t('Invalid use of "{0}" action, must be the last action.', pattern.exec(action)[2]));
deferred.reject();
return deferred.promise;
}
if (action === 'close') {
this._closeView(scope);
deferred.resolve();
return deferred.promise;
}
if (action === 'new') {
return this._handleNew();
}
if (action === 'validate') {
return this._handleSave(true);
}
if (action === 'save') {
return this._handleSave();
}
if (this._invalidateContext) {
context = this._getContext();
this._invalidateContext = false;
}
var model = context._model || scope._model;
var data = scope.getActionData ? scope.getActionData(context) : null;
if (data && context._signal) {
data._signal = context._signal;
}
var promise = this.ws.action(action, model, context, data).then(function(response){
var resp = response.data,
data = resp.data || [];
if (resp.errors) {
data.splice(0, 0, {
errors: resp.errors
});
}
return chain(data);
});
promise.then(deferred.resolve, deferred.reject);
return deferred.promise;
},
_handleSingle: function(data) {
var deferred = this.ws.defer();
if (!data || data.length === 0) {
deferred.resolve();
return deferred.promise;
}
if (!this._isSameViewType()) {
deferred.reject();
return deferred.promise;
}
var self = this,
scope = this.scope,
formElement = this._getFormElement(),
formScope = formElement.data('$scope') || scope,
rootForm = this._getRootFormElement(),
rootScope = rootForm.is('[ui-view-grid]') ? scope : rootForm.scope();
function doReload(pending) {
self._invalidateContext = true;
var promise = _.isFunction(rootScope.reload) ? rootScope.reload() : scope.reload();
if (promise) {
promise.then(function(){
deferred.resolve(pending);
}, deferred.reject);
} else {
deferred.resolve(pending);
}
return deferred.promise;
}
if (data.exportFile) {
(function () {
var link = "ws/files/data-export/" + data.exportFile;
var frame = $('<iframe>').appendTo('body').hide();
frame.attr("src", link);
setTimeout(function(){
frame.attr("src", "");
frame.remove();
frame = null;
}, 5000);
})();
}
if (data.signal === 'refresh-app') {
if(data.flash || data.info) {
axelor.dialogs.box(data.flash || data.info, {
onClose: function () {
window.location.reload();
}
});
} else {
window.location.reload();
}
return deferred.promise;
}
if(data.flash || data.info) {
axelor.dialogs.box(data.flash || data.info, {
onClose: function () {
if (data.pending) {
scope.$applyAsync(function(){
if (data.reload) {
return doReload(data.pending);
}
deferred.resolve(data.pending);
});
}
}
});
if (data.pending) {
return deferred.promise;
}
}
if(data.notify) {
axelor.notify.info(data.notify);
}
if(data.error) {
axelor.dialogs.error(data.error, function(){
scope.$applyAsync(function(){
if (data.action) {
self._handleAction(data.action);
}
deferred.reject();
});
});
return deferred.promise;
}
if (data.alert) {
axelor.dialogs.confirm(data.alert, function(confirmed){
scope.$applyAsync(function(){
if (confirmed) {
return deferred.resolve(data.pending);
}
if (data.action) {
self._handleAction(data.action);
}
deferred.reject();
});
}, {
title: _t('Warning'),
yesNo: false
});
return deferred.promise;
}
if (!_.isEmpty(data.errors)) {
var hasError = false;
_.each(data.errors, function(v, k){
var item = (findItems(k) || $()).first();
handleError(scope, item, v);
if(v && v.length > 0) {
hasError = true;
}
});
if(hasError) {
deferred.reject();
return deferred.promise;
}
}
if (data.values) {
updateValues(data.values, scope.record, scope, formScope);
if (scope.onChangeNotify) {
scope.onChangeNotify(scope, data.values);
}
this._invalidateContext = true;
axelor.$adjustSize();
}
if (data.reload) {
return (function () {
var promise = doReload(data.pending);
if (data.view) {
promise.then(function () {
doOpenView(data.view);
});
}
return promise;
})();
}
if (data.validate || data.save) {
scope.$timeout(function () {
self._handleSave(!!data.validate).then(function(){
scope.ajaxStop(function () {
deferred.resolve(data.pending);
}, 100);
}, deferred.reject);
});
return deferred.promise;
}
if (data['new']) {
scope.$timeout(function () {
self._handleNew().then(function(){
scope.ajaxStop(function () {
deferred.resolve(data.pending);
}, 100);
}, deferred.reject);
});
return deferred.promise;
}
if (data.signal) {
formScope.$broadcast(data.signal, data['signal-data']);
}
function findItems(name) {
var items;
var toolbar;
var containers;
if (formElement.is('[ui-slick-editors]')) {
containers = formElement.parent().add(formElement);
} else if (formElement.parent().is('[ui-slick-editors],.slick-cell')) {
containers = formElement.parent().parent().add(formElement);
} else if (formElement.parent().is('[ui-panel-editor]')) {
containers = formElement.parent().add(formElement).is('.m2o-editor-form,.o2m-editor-form') ? formElement : formElement.parents('[ui-form]:first').add(formElement);
} else {
containers = formElement;
toolbar = formElement.parents('.form-view:first,.search-view:first')
.find('.record-toolbar:first,.search-view-toolbar:first');
}
var formPath = formScope.formPath;
if (formScope._model === 'com.axelor.meta.db.MetaJsonRecord') {
formPath = formPath || 'attrs';
}
items = containers.find('[x-path="' + (formPath ? formPath + '.' + name : name) + '"]');
if (items.length === 0 && formPath != 'attrs') {
items = containers.find('[x-path="attrs.' + name + '"]');
}
if (toolbar) {
return toolbar.find('[name="' + name + '"],[x-name="' + name + '"]').add(items);
}
return items;
}
function setAttrs(item, itemAttrs, itemIndex) {
var label = item.data('label'),
itemScope = item.data('$scope'),
hasValues = false,
column;
if (item.is('[ui-menu-item]')) {
itemScope = item.isolateScope();
}
// handle o2m/m2m columns
if (item.is('.slick-dummy-column')) {
column = item.data('column');
itemScope = item.parents('[x-path]:first,.portlet-grid').data('$scope');
forEach(itemAttrs, function(value, attr){
if (attr == 'hidden')
itemScope.showColumn(column.id, !value);
if (attr == 'title')
setTimeout(function(){
itemScope.setColumnTitle(column.id, value);
});
});
return;
}
//handle o2m/m2m title
if(item.is('.one2many-item') || item.is('.many2many-item')){
forEach(itemAttrs, function(value, attr){
if (attr == 'title') {
itemScope.title = value;
}
});
}
// handle notebook
if (item.is('.tab-pane')) {
forEach(itemAttrs, function(value, attr){
if (attr == 'hidden') {
itemScope.attr('hidden', value);
}
if (attr == 'title') {
itemScope.title = value;
}
});
return;
}
function isDotted() {
var name = item.attr('x-field') || '';
var dotted = name.indexOf('.') > -1;
if (dotted) {
itemAttrs.$hasDotted = true;
}
return dotted;
}
forEach(itemAttrs, function(value, attr){
if ((attr === "value" || attr.indexOf('value:') === 0)) {
hasValues = true;
if (itemScope && itemScope.$setForceWatch) {
itemScope.$setForceWatch(true);
}
if (isDotted()) return;
if (itemAttrs.$hasDotted) {
itemAttrs.$hasDotted = false;
} else if (itemIndex > 0) {
return;
}
}
switch(attr) {
case 'hidden':
if (itemScope.field && itemScope.field.hideIf === "true") return;
case 'required':
case 'readonly':
case 'collapse':
case 'precision':
case 'scale':
case 'prompt':
case 'css':
case 'icon':
case 'selection-in':
itemScope.attr(attr, value);
break;
case 'title':
(function () {
var span = $(label).add(item).children('span[ui-help-popover]:first');
if (span.length === 0) {
span = label;
}
if (span && span.length > 0) {
span.html(value);
} else if (item.is('label')) {
item.html(value);
}
})();
itemScope.attr('title', value);
break;
case 'domain':
if (itemScope.setDomain)
itemScope.setDomain(value);
break;
case 'refresh':
itemScope.waitForActions(function () {
itemScope.$broadcast('on:attrs-change:refresh');
}, 100);
break;
case 'url':
case 'url:set':
if (item.is('[ui-portlet]')) {
item.find('iframe:first').attr('src', value);
}
break;
case 'value':
case 'value:set':
if (itemScope.setValue) {
itemScope.setValue(value);
}
break;
case 'value:add':
if (itemScope.fetchData && itemScope.select) {
itemScope.fetchData(value, function(records){
itemScope.select(records);
});
}
break;
case 'value:del':
if (itemScope.removeItems) {
itemScope.removeItems(value);
}
break;
case 'selection':
//itemScope.attr(attr, value);
itemScope.field.selection = value;
itemScope.loadSelection();
break;
}
});
if (hasValues && formScope.onChangeNotify) {
formScope.onChangeNotify(formScope, formScope.record);
}
}
forEach(data.attrs, function(itemAttrs, itemName) {
var items = findItems(itemName);
if (!items || items.length === 0) {
// dashlet still not loaded ?
if (itemName.indexOf('.') > -1) {
var parentName = itemName.substring(0, itemName.indexOf('.'));
var parentElem = findItems(parentName);
if (parentElem.is('[ui-dashlet]')) {
parentElem.scope().$$pendingAttrs = parentElem.scope().$$pendingAttrs || {};
parentElem.scope().$$pendingAttrs[itemName.substring(itemName.indexOf('.')+1)] = itemAttrs;
}
}
return;
}
items.each(function(i) {
setAttrs($(this), itemAttrs, i);
});
});
if (data.report) {
return openReport(data);
}
function openReport(data) {
var record = formScope.record || {};
if (data.attached) {
record.$attachments = (record.$attachments || 0) + 1;
axelor.dialogs.confirm(_t('Report attached to current object. Would you like to download?'),
function(confirmed) {
scope.$applyAsync(function() {
if (confirmed) {
var url = "ws/rest/com.axelor.meta.db.MetaFile/" + data.attached.id + "/content/download";
ui.download(url);
return deferred.resolve();
}
deferred.reject();
});
}, {
title: _t('Download'),
yesNo: false
});
return deferred.promise;
}
var url = "ws/files/report/" + data.reportLink + "?name=" + data.reportFile;
var tab = {
title: data.reportFile,
resource: url,
viewType: 'html'
};
if (axelor.device.mobile && data.reportFormat !== "html") {
ui.download(url, data.reportFile);
} else if (['pdf', 'html'].indexOf(data.reportFormat) > -1) {
doOpenView(tab);
} else {
ui.download(url);
}
scope.$timeout(deferred.resolve);
return deferred.promise;
}
function openTab(scope, tab) {
if (scope.openTab) {
scope.openTab(tab);
} else if (scope.$parent) {
openTab(scope.$parent, tab);
}
}
function doOpenView(tab) {
tab.action = _.uniqueId('$act');
if (!tab.viewType)
tab.viewType = 'grid';
if (tab.viewType == 'grid' || tab.viewType == 'form')
tab.model = tab.model || tab.resource;
if (!tab.views) {
tab.views = [{ type: tab.viewType }];
if (tab.viewType === 'html') {
angular.extend(tab.views[0], {
resource: tab.resource,
title: tab.title
});
}
}
if (tab.viewType === "html" && (tab.params||{}).download) {
var view = _.findWhere(tab.views, { type: "html" });
if (view) {
var url = view.name || view.resource;
var fileName = tab.params.fileName || "true";
ui.download(url, fileName);
return scope.$applyAsync();
}
}
if (tab.viewType === "html" && (tab.params||{}).target === "_blank") {
var view = _.findWhere(tab.views, { type: "html" });
if (view) {
var url = view.name || view.resource;
setTimeout(function () {
window.open(url);
});
return scope.$applyAsync();
}
}
if ((tab.params && tab.params.popup) || axelor.device.mobile) {
tab.$popupParent = formScope;
}
openTab(scope, tab);
scope.$applyAsync();
}
if (data.view) {
doOpenView(data.view);
}
if (data.close || data.canClose) {
this._closeView(scope);
}
deferred.resolve();
return deferred.promise;
}
};
ui.factory('ActionService', ['ViewService', function(ViewService) {
function handler(scope, element, options) {
var opts = _.extend({}, options, { element: element });
return new ActionHandler(scope, ViewService, opts);
}
return {
handler: handler
};
}]);
var EVENTS = ['onClick', 'onChange', 'onSelect', 'onTabSelect', 'onNew', 'onLoad', 'onSave'];
ui.directive('uiActions', ['ViewService', function(ViewService) {
function link(scope, element, attrs) {
var props = _.isEmpty(scope.field) ? scope.schema : scope.field;
if (!props) {
return;
}
_.each(EVENTS, function(name){
var action = props[name];
if (!action) {
return;
}
var handler = new ActionHandler(scope, ViewService, {
name: name,
element: element,
action: action,
canSave: props.canSave,
prompt: props.prompt
});
scope.$events[name] = _.bind(handler[name], handler);
});
}
return {
link: function(scope, element, attrs) {
scope.$evalAsync(function() {
link(scope, element, attrs);
});
}
};
}]);
ui.directive('uiActionClick', ['ViewService', function(ViewService) {
return {
link: function(scope, element, attrs) {
var action = attrs.uiActionClick;
scope.$evalAsync(function() {
var handler = new ActionHandler(scope, ViewService, {
element: element,
action: action
});
element.on("click", function () {
handler.handle();
scope.$applyAsync();
});
});
}
};
}]);
})();
/*
* Axelor Business Solutions
*
* Copyright (C) 2005-2020 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var ui = angular.module('axelor.ui');
function acceptNumber(value) {
if (value === null || value === undefined) {
return value;
}
if (_.isNumber(value)) {
return +value;
}
if (/^(-)?\d+(\.\d+)?$/.test(value)) {
return +value;
}
return value;
}
function parseNumber(field, value) {
if (value === null || value === undefined) {
return value;
}
if (!field || ['integer', 'long'].indexOf(field.serverType) === -1) {
return value;
}
var num = +value;
if (isNaN(num)) {
return value;
}
return num;
}
ui.formWidget('BaseSelect', {
findInput: function(element) {
return element.find('input:first:not([ui-panel-editor] input)');
},
init: function(scope) {
scope.loadSelection = function(request, response) {
};
scope.parse = function(value) {
return value;
};
scope.format = function(value) {
return this.formatItem(value);
};
scope.formatItem = function(item) {
return item;
};
},
link_editable: function (scope, element, attrs, model) {
var input = this.findInput(element);
var showing = false;
var willShow = false;
scope.showSelection = function(delay) {
if (scope.isReadonly() || showing || willShow) {
return;
}
if (input.is('.x-focus')) {
input.removeClass('.x-focus');
return;
}
willShow = true;
input.addClass('.x-focus');
doSetup(input);
setTimeout(function () {
if (input.is(':focus')) {
input.autocomplete("search" , '');
input.removeClass('.x-focus');
}
willShow = false;
}, delay || 100);
};
scope.handleClear = function(e) {
scope.setValue(null, true);
};
scope.handleDelete = function(e) {
};
scope.handleEnter = function(e) {
};
scope.handleSelect = function(e, ui) {
};
scope.handleClose = function(e, ui) {
};
scope.handleOpen = function(e, ui) {
};
function renderItem(ul, item) {
var el = $("<li>").append( $("<a>").html(item.label)).appendTo(ul);
if (item.click) {
el.addClass("tag-select-action");
ul.addClass("tag-select-action-menu");
}
return el;
}
var doSetup = _.once(function (input) {
var loading = false;
var pending = null;
function doLoad(request, response) {
if (loading) {
return pending = _.partial(doLoad, request, response);
}
loading = true;
scope.loadSelection(request, function() {
loading = false;
response.apply(null, arguments);
if (pending) {
pending();
pending = null;
}
});
}
input.autocomplete({
minLength: 0,
position: { collision: "flip" },
source: doLoad,
focus: function(event, ui) {
return false;
},
select: function(event, ui) {
// do not select with tab key, to prevent unexpected result on editable grid
if (event.keyCode === 9) {
return false;
}
var ret = scope.handleSelect(event, ui);
if (ret !== undefined) {
return ret;
}
return false;
},
open: function(event, ui) {
showing = true;
scope.handleOpen(event, ui);
},
close: function(event, ui) {
showing = false;
scope.handleClose(event, ui);
}
});
input.data('ui-autocomplete')._renderItem = scope.renderSelectItem || renderItem;
scope.$onAdjust('size scroll', function (e) {
if (e.type === 'adjust:size' && e.target !== document) {
return;
}
if (showing) {
input.autocomplete('close');
}
});
});
input.focus(function(e) {
element.addClass('focus');
doSetup(input);
}).blur(function() {
element.removeClass('focus');
if (showing) {
input.autocomplete('close');
}
}).keyup(function(e) {
// if TAB key
if (e.which === 9) {
scope.showSelection(300);
}
}).keydown(function(e) {
var KEY = $.ui.keyCode;
switch(e.keyCode) {
case KEY.DELETE:
case KEY.BACKSPACE:
scope.handleDelete(e);
break;
case KEY.ENTER:
scope.handleEnter(e);
break;
}
}).click(function() {
scope.showSelection();
});
if (axelor.browser.mozilla) {
input.mousedown(function () {
if (!input.is(':focus')) {
scope.showSelection(300);
}
});
}
},
template_editable:
'<span class="picker-input">'+
'<input type="text" autocomplete="off">'+
'<span class="picker-icons picker-icons-2">'+
'<i class="fa fa-times" ng-show="text" ng-click="handleClear()"></i>'+
'<i class="fa fa-caret-down" ng-click="showSelection()"></i>'+
'</span>'+
'</span>'
});
function filterSelection(scope, field, selection, current) {
var selectionIn = scope.attr('selection-in') || field.selectionIn;
if (_.isEmpty(selection)) return selection;
if (_.isEmpty(selectionIn)) return selection;
var context = (scope.getContext || angular.noop)() || {};
var list = selectionIn;
if (_.isString(selectionIn)) {
var expr = selectionIn.trim();
if (expr.indexOf('[') !== 0) {
expr = '[' + expr + ']';
}
list = axelor.$eval(scope, expr, context);
}
var value = acceptNumber(current);
if (_.isEmpty(list)) {
return selection;
}
list = _.map(list, acceptNumber);
return _.filter(selection, function (item) {
var val = acceptNumber(item.value);
return val === value || list.indexOf(val) > -1;
});
}
ui.formInput('Select', 'BaseSelect', {
css: 'select-item',
cellCss: 'form-item select-item',
init: function(scope) {
this._super(scope);
var field = scope.field,
selectionList = field.selectionList || [],
selectionMap = {};
var data = _.map(selectionList, function(item) {
var value = "" + item.value;
selectionMap[value] = item.title;
return {
value: value,
label: item.title || "&nbsp;"
};
});
var dataSource = null;
function getDataSource() {
if (dataSource || !field.selection || !field.domain) {
return dataSource;
}
return dataSource = scope._dataSource._new('com.axelor.meta.db.MetaSelectItem', {
domain: "(self.select.name = :_select) AND (" + field.domain + ")",
context: {
_select: field.selection
}
});
}
scope.loadSelection = function(request, response) {
var ds = getDataSource();
function select(records) {
var items = _.filter(records, function(item) {
var label = item.label || "",
term = request.term || "";
return label.toLowerCase().indexOf(term.toLowerCase()) > -1;
});
items = filterSelection(scope, field, items);
return response(items);
}
if (ds) {
return ds.search({
fields: ['value', 'title'],
context: scope.getContext ? scope.getContext() : undefined
}).success(function (records) {
_.each(records, function (item) {
item.label = selectionMap[item.value] || item.title;
});
return select(records);
});
}
return select(data);
};
scope.formatItem = function(item) {
var key = _.isNumber(item) ? "" + item : item;
if (!key) {
return item;
}
if (_.isString(key)) {
return selectionMap[key] || "";
}
return item.label;
};
if (field.enumType) {
var __enumValues = {};
var __hasValue = false;
_.each(selectionList, function (item) {
__enumValues[item.value] = (item.data || {}).value;
__hasValue = __hasValue || __enumValues[item.value] !== undefined;
});
if (__hasValue) {
scope.$watch('record.' + field.name, function selectFieldNameWatch(value, old) {
if (value && value !== old) {
var enumValue = __enumValues[value];
if (scope.record && enumValue !== value) {
scope.record[field.name + '$value'] = enumValue;
}
}
});
}
}
},
link_editable: function(scope, element, attrs, model) {
this._super.apply(this, arguments);
var input = this.findInput(element);
function update(value) {
var val = parseNumber(scope.field, value);
scope.setValue(val, true);
scope.$applyAsync();
}
scope.handleDelete = function(e) {
if (e.keyCode === 46) { // DELETE
update(null);
}
if (e.keyCode === 8) { // BACKSPACE
var value = scope.getValue();
if (value || (e.target.value||'').length < 2) {
update(null);
}
}
};
scope.handleSelect = function(e, ui) {
update(ui.item.value);
};
scope.$render_editable = function() {
input.val(this.getText());
};
scope.$on('on:edit', function () {
// force update input text, fixes #5965
scope.$render_editable();
});
}
});
ui.formInput('Enum', 'Select');
ui.formInput('ImageSelect', 'Select', {
metaWidget: true,
BLANK: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
link: function(scope, element, attrs) {
this._super(scope, element, attrs);
var field = scope.field;
var formatItem = scope.formatItem;
var selectIcons = {};
_.each(field.selectionList, function (item) {
selectIcons[item.value] = item.icon || item.value;
});
scope.canShowText = function () {
return field.labels === undefined || field.labels;
};
scope.formatItem = function (item) {
if (scope.canShowText()) {
return formatItem(item);
}
return "";
};
scope.findImage = function (value) {
return selectIcons[value] || this.BLANK;
};
scope.$watch('getValue()', function selectFieldValueWatch(value, old) {
scope.image = scope.findImage(value);
scope.isIcon = scope.image && scope.image.indexOf('fa-') === 0;
element.toggleClass('empty', !value);
}.bind(this));
},
link_editable: function(scope, element, attrs) {
this._super(scope, element, attrs);
var input = this.findInput(element);
var selects = {};
_.each(scope.field.selectionList, function (item) {
selects[item.value] = (item.data||{}).icon || item.value;
});
scope.renderSelectItem = function(ul, item) {
var a = $("<a>");
var el = $("<li>").addClass("image-select-item").append(a).appendTo(ul);
var image = scope.findImage(item.value);
if (image && image.indexOf('fa-') === 0) {
a.append($("<i>").addClass("fa").addClass(image));
} else {
a.append($("<img>").attr("src", image));
}
if (scope.canShowText()) {
a.append($("<span></span>").html(item.label));
}
return el;
};
var $render_editable = scope.$render_editable;
scope.$render_editable = function () {
$render_editable.apply(scope, arguments);
setTimeout(function () {
element.find('input').css('padding-left', element.find('i.image,img').width());
});
};
},
template_readonly:
'<span class="image-select readonly">'+
'<i ng-if="isIcon" class="fa" ng-class="image"></i>'+
'<img ng-if="!isIcon" ng-src="{{image}}"></img> <span ng-show="canShowText()">{{text}}</span>' +
'</span>',
template_editable:
'<span class="picker-input image-select">'+
'<i ng-if="isIcon" class="fa" ng-class="image"></i>'+
'<img ng-if="!isIcon" ng-src="{{image}}"></img>' +
'<input type="text" autocomplete="off">'+
'<span class="picker-icons">'+
'<i class="fa fa-caret-down" ng-click="showSelection()"></i>'+
'</span>'+
'</span>'
});
ui.formInput('MultiSelect', 'Select', {
css: 'multi-select-item',
cellCss: 'form-item multi-select-item',
metaWidget: true,
init: function(scope) {
this._super(scope);
var __parse = scope.parse;
scope.parse = function(value) {
if (_.isArray(value)) {
return value.join(', ');
}
return __parse(value);
};
scope.format = function(value) {
var items = value,
values = [];
if (!value) {
scope.items = [];
return value;
}
if (!_.isArray(items)) items = items.split(/,\s*/);
values = _.map(items, function(item) {
return {
value: item,
title: scope.formatItem(item)
};
});
scope.items = values;
return _.pluck(values, 'title').join(', ');
};
scope.matchValues = function(a, b) {
if (a === b) return true;
if (!a) return false;
if (!b) return false;
if (_.isString(a)) return a === b;
return a.value === b.value;
};
scope.getSelection = function() {
return this.items;
};
var max = +(scope.field.max);
scope.limited = function(items) {
if (max && items && items.length > max) {
scope.more = _t("and {0} more", items.length - max);
return _.first(items, max);
}
scope.more = null;
return items;
};
},
link_editable: function(scope, element, attrs, model) {
this._super.apply(this, arguments);
var input = this.findInput(element);
input.on('input focus', function() {
scaleInput();
}).on('blur', function() {
scaleInput(50);
input.val('');
});
var placeholder = null;
if (scope.field.placeholder) {
placeholder = $('<span class="tag-select-placeholder hidden"></span>')
.text(scope.field.placeholder)
.appendTo(element)
.click(function (e) {
scope.showSelection();
});
}
function scaleInput(width) {
var elem = element.find('.tag-selector'),
pos = elem.position();
if (width) {
return input.width(width);
}
input.css('width', element.innerWidth() - pos.left - 24);
}
function update(value) {
var val = parseNumber(scope.field, value);
scope.setValue(val, true);
scope.$applyAsync();
setTimeout(function () {
scaleInput(50);
});
}
scope.removeItem = function(item) {
var items = this.getSelection(),
value = _.isString(item) ? item : (item||{}).value;
items = _.chain(items)
.pluck('value')
.filter(function(v){
return !scope.matchValues(v, value);
})
.value();
update(items);
};
scope.onShowSelection = function(e) {
if (e && $(e.target || e.srcElement).is('input,li,i,span.tag-text')) {
return;
}
input.focus();
setTimeout(function() {
scope.showSelection();
});
};
scope.handleDelete = function(e) {
if (input.val()) {
return;
}
var items = this.getSelection();
this.removeItem(_.last(items));
};
scope.handleSelect = function(e, ui) {
var items = this.getSelection(),
values = _.pluck(items, 'value');
var found = _.find(values, function(v){
return scope.matchValues(v, ui.item.value);
});
if (found) {
return false;
}
values.push(ui.item.value);
update(values);
scaleInput(50);
};
scope.handleOpen = function(e, ui) {
input.data('autocomplete')
.menu
.element
.position({
my: "left top",
at: "left bottom",
of: element
})
.width(element.width() - 4);
};
scope.$render_editable = function() {
if (placeholder) {
placeholder.toggleClass('hidden', !!scope.getValue());
}
return input.val('');
};
input.on("input blur", function () {
if (placeholder) {
placeholder.toggleClass('hidden', !!(input.val() || scope.getValue()));
}
});
scope.$watch('items.length', function selectItemsLengthWatch(value, old) {
setTimeout(function () {
scaleInput(50);
});
});
},
template_editable:
'<div class="tag-select picker-input" ng-click="onShowSelection($event)">'+
'<ul>'+
'<li class="tag-item label label-primary" ng-repeat="item in items">'+
'<span ng-class="{\'tag-link\': handleClick}" class="tag-text" ng-click="handleClick($event, item.value)">{{item.title}}</span> '+
'<i class="fa fa-times fa-small" ng-click="removeItem(item)"></i>'+
'</li>'+
'<li class="tag-selector">'+
'<input type="text" autocomplete="off">'+
'</li>'+
'</ul>'+
'<span class="picker-icons">'+
'<i class="fa fa-caret-down" ng-click="onShowSelection()"></i>'+
'</span>'+
'</div>',
template_readonly:
'<div class="tag-select">'+
'<span class="label label-primary" ng-repeat="item in limited(items)">'+
'<span ng-class="{\'tag-link\': handleClick}" class="tag-text" ng-click="handleClick($event, item.value)">{{item.title}}</span>'+
'</span>'+
'<span ng-show="more"> {{more}}</span>'+
'</div>'
});
ui.formInput('SelectQuery', 'Select', {
link_editable: function(scope, element, attrs, model) {
this._super.apply(this, arguments);
var current = {};
function update(value) {
scope.setValue(value);
scope.$applyAsync();
}
scope.format = function(value) {
if (!value) return "";
if (_.isString(value)) {
return current.label || value;
}
current = value;
return value.label;
};
scope.parse = function(value) {
if (!value || _.isString(value)) return value;
return value.value;
};
scope.handleSelect = function(e, ui) {
update(ui.item);
};
var query = scope.$eval(attrs.query);
scope.loadSelection = function(request, response) {
return query(request, response);
};
}
});
ui.formInput('RadioSelect', {
css: "radio-select",
metaWidget: true,
link: function(scope, element, attrs, model) {
var field = scope.field;
var selection = field.selectionList || [];
var dataSource = null, selectionMap = {};
function getDataSource() {
if (dataSource ) {
return dataSource;
}
return dataSource = scope._dataSource._new('com.axelor.meta.db.MetaSelectItem', {
domain: "(self.select.name = :_select)",
context: {
_select: field.selection
}
});
}
scope.loadSelection = function() {
var ds = getDataSource();
if (ds) {
return ds.search({
fields: ['value', 'title'],
context: scope.getContext ? scope.getContext() : undefined
}).success(function (records) {
_.each(records, function (item) {
item.label = selectionMap[item.value] || item.title;
});
selection = records;
scope.field.selectionList = selection;
return (records);
});
}
};
scope.formatItem = function(item) {
var key = _.isNumber(item) ? "" + item : item;
if (!key) {
return item;
}
if (_.isString(key)) {
return selectionMap[key] || "";
}
return item.label;
};
scope.getSelection = function () {
return filterSelection(scope, field, selection, scope.getValue());
};
element.on("change", ":input", function(e) {
var val = parseNumber(scope.field, $(e.target).val());
scope.setValue(val, true);
scope.$applyAsync();
});
if (field.direction === "vertical" || field.dir === "vert") {
setTimeout(function(){
element.addClass("radio-select-vertical");
});
}
},
template_editable: null,
template_readonly: null,
template:
'<ul ng-class="{ readonly: isReadonly() }">'+
'<li ng-repeat="select in getSelection()">'+
'<label class="ibox round">'+
'<input type="radio" name="radio_{{$parent.$id}}" value="{{select.value}}"'+
' ng-disabled="isReadonly()"'+
' ng-checked="getValue() == select.value">'+
'<span class="box"></span>'+
'<span class="title">{{select.title}}</span>'+
'</label>'+
'</li>'+
'</ul>'
});
ui.formInput('CheckboxSelect', {
css: "checkbox-select",
metaWidget: true,
link: function(scope, element, attrs, model) {
var field = scope.field;
var selection = field.selectionList || [];
scope.getSelection = function () {
return filterSelection(scope, field, selection, scope.getValue());
};
scope.isSelected = function (select) {
var value = scope.getValue();
var current = ("" + value).split(",").map(function (val) {
return parseNumber(scope.field, val);
});
return current.indexOf(select.value) > -1;
};
element.on("change", ":input", function(e) {
var all = element.find("input:checked");
var selected = [];
all.each(function () {
var val = parseNumber(scope.field, $(this).val());
selected.push(val);
});
var value = selected.length === 0 ? null : selected.join(",");
scope.setValue(value, true);
scope.$applyAsync();
});
if (field.direction === "vertical" || field.dir === "vert") {
setTimeout(function(){
element.addClass("checkbox-select-vertical");
});
}
},
template_editable: null,
template_readonly: null,
template:
'<ul ng-class="{ readonly: isReadonly() }">'+
'<li ng-repeat="select in getSelection()">'+
'<label class="ibox">'+
'<input type="checkbox" value="{{select.value}}"'+
' ng-disabled="isReadonly()"'+
' ng-checked="isSelected(select)">'+
'<span class="box"></span>'+
'<span class="title">{{select.title}}</span>'+
'</label>'+
'</li>'+
'</ul>'
});
ui.formInput('NavSelect', {
css: "nav-select",
metaWidget: true,
link: function(scope, element, attrs, model) {
var field = scope.field;
var selection = field.selectionList || [];
scope.getSelection = function () {
return filterSelection(scope, field, selection, scope.getValue()) || [];
};
scope.$watch('text', function navSelectTextWatch(text, old) {
adjust();
});
scope.onSelect = function(select) {
if (scope.attr('readonly')) {
return;
}
var val = parseNumber(scope.field, select.value);
this.setValue(val, true);
elemNavs.removeClass('open');
elemMenu.removeClass('open');
// if selection change is used to show/hide some elements
// the layout should be adjusted
axelor.$adjustSize();
};
scope.isSelected = function (select) {
return select && scope.getValue() == select.value;
};
var lastWidth = 0;
var lastValue = null;
var elemNavs = null;
var elemMenu = null;
var elemMenuTitle = null;
var elemMenuItems = null;
function setup() {
elemNavs = element.children('.nav-steps').children('li:not(.dropdown,.ignore)');
elemMenu = element.children('.nav-steps').children('li.dropdown');
elemMenuTitle = elemMenu.find('a.nav-label > span');
elemMenuItems = elemMenu.find('li');
adjust();
}
var setMenuTitle = (function() {
var setActive = _.debounce(function(selected) {
elemMenu.toggleClass('active', !!selected);
});
return function setMenuTitle(selected) {
elemMenu.show();
elemMenuTitle.html(selected && selected.title);
setActive(selected);
};
}());
function adjust() {
if (elemNavs === null || element.is(":hidden")) {
return;
}
var currentValue = scope.getValue();
var parentWidth = element.width() - 16;
if (parentWidth === lastWidth && currentValue === lastValue) {
return;
}
lastWidth = parentWidth;
lastValue = currentValue;
elemNavs.parent().css('visibility', 'hidden');
elemNavs.show();
elemMenu.hide();
if (elemNavs.parent().width() <= parentWidth) {
elemNavs.parent().css('visibility', '');
return;
}
var navs = scope.getSelection();
var selected = _.find(navs, scope.isSelected.bind(scope));
var selectedIndex = navs.indexOf(selected);
var elem = null;
var index = navs.length;
setMenuTitle(null);
while (elemNavs.parent().width() > parentWidth) {
elem = $(elemNavs[--index]);
elem.hide();
if (index === selectedIndex) {
setMenuTitle(selected);
}
}
elemMenuItems.hide();
while(index < navs.length) {
$(elemMenuItems[index++]).show();
}
elemNavs.parent().css('visibility', '');
}
scope.$onAdjust(adjust);
scope.$callWhen(setup, function () {
return element.is(':visible');
});
},
template_editable: null,
template_readonly: null,
template:
"<div class='nav-select'>" +
"<ul class='nav-steps' style='display: inline-flex; visibility: hidden;'>" +
"<li class='nav-step' ng-repeat='select in getSelection()' ng-class='{ active: isSelected(select), last: $last }'>" +
"<a href='' class='nav-label' ng-click='onSelect(select)' ng-bind-html='select.title'></a>" +
"</li>" +
"<li class='nav-step dropdown'>" +
"<a href='' class='nav-label dropdown-toggle' data-toggle='dropdown'><span></span></a>" +
"<ul class='dropdown-menu pull-right'>" +
"<li ng-repeat='select in getSelection()' ng-class='{active: getValue() == select.value}'>" +
"<a tabindex='-1' href='' ng-click='onSelect(select)' ng-bind-html='select.title'></a>" +
"</li>" +
"</ul>" +
"</li>" +
"</ul>"+
"</div>"
});
ui.formInput('ThemeSelect', 'Select', {
init: function (scope) {
scope.field.selectionList = _.map(axelor.config['application.themes'], function (name) {
return { value: name, title: _.titleize(name) };
});
scope.field.selectionList.unshift({
value: "default",