First commit waiting for Budget Alert
This commit is contained in:
1218
sophal/js/form/form.actions.js
Normal file
1218
sophal/js/form/form.actions.js
Normal file
File diff suppressed because it is too large
Load Diff
700
sophal/js/form/form.base.js
Normal file
700
sophal/js/form/form.base.js
Normal file
@ -0,0 +1,700 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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() {
|
||||
|
||||
/* jshint validthis: true */
|
||||
|
||||
"use strict";
|
||||
|
||||
var ui = angular.module('axelor.ui');
|
||||
var widgets = {};
|
||||
var registry = {};
|
||||
var metaWidgets = [];
|
||||
|
||||
/**
|
||||
* Perform common compile operations.
|
||||
*
|
||||
* example:
|
||||
* ui.formCompile.call(this, element, attrs)
|
||||
*/
|
||||
ui.formCompile = function(element, attrs, linkerFn) {
|
||||
|
||||
var showTitle = attrs.showTitle || this.showTitle,
|
||||
title = attrs.title || attrs.field;
|
||||
|
||||
attrs.$set('show-title', showTitle, true, 'x-show-title');
|
||||
if (title) {
|
||||
attrs.$set('title', title, true, 'x-title');
|
||||
}
|
||||
if (this.cellCss) {
|
||||
attrs.$set('x-cell-css', this.cellCss);
|
||||
}
|
||||
|
||||
function link(scope, element, attrs, controller) {
|
||||
|
||||
element.addClass(this.css).parent().addClass(this.cellCss);
|
||||
element.data('$attrs', attrs); // store the attrs object for event handlers
|
||||
|
||||
var getViewDef = this.getViewDef || scope.getViewDef || function() { return {}; };
|
||||
|
||||
var field = getViewDef.call(scope, element);
|
||||
var props = _.extend(_.pick(field, 'readonly,required,hidden,collapse,precision,scale,prompt,title,domain,css,icon,selection-in'.split(',')),
|
||||
_.pick(field.widgetAttrs || {}, 'precision,scale,domain'.split(',')));
|
||||
|
||||
var state = _.clone(props);
|
||||
|
||||
function resetAttrs() {
|
||||
var label = element.data('label');
|
||||
state = _.clone(props);
|
||||
state["force-edit"] = false;
|
||||
if (label && state.title) {
|
||||
var span = label.children('span[ui-help-popover]:first');
|
||||
if (span.length === 0) {
|
||||
span = label;
|
||||
}
|
||||
span.html(state.title);
|
||||
}
|
||||
}
|
||||
|
||||
if (field.css) {
|
||||
element.addClass(field.css);
|
||||
}
|
||||
if (field.width && field.width !== '*' && !element.is('label')) {
|
||||
element.width(field.width);
|
||||
}
|
||||
if (field.translatable) {
|
||||
element.addClass("translatable");
|
||||
}
|
||||
|
||||
scope.$events = {};
|
||||
scope.field = field || {};
|
||||
|
||||
scope.$$readonly = undefined;
|
||||
|
||||
scope.attr = function(name) {
|
||||
if (arguments.length > 1) {
|
||||
var old = state[name];
|
||||
state[name] = arguments[1];
|
||||
if (name === "highlight") {
|
||||
setHighlight(state.highlight);
|
||||
}
|
||||
if (old !== state[name]) {
|
||||
scope.$broadcast("on:attrs-changed", {
|
||||
name: name,
|
||||
value: state[name]
|
||||
});
|
||||
}
|
||||
}
|
||||
var res = state[name];
|
||||
if (res === undefined) {
|
||||
res = field[name];
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
scope.$on("on:edit", function(e, rec) {
|
||||
if (angular.equals(rec, {})) {
|
||||
resetAttrs();
|
||||
}
|
||||
scope.$$readonly = scope.$$isReadonly();
|
||||
});
|
||||
|
||||
scope.$on("on:attrs-changed", function(event, attr) {
|
||||
if (attr.name === "readonly" || attr.name === "force-edit") {
|
||||
scope.$$readonly = scope.$$isReadonly();
|
||||
}
|
||||
if (attr.name === "readonly") {
|
||||
element.attr("x-readonly", scope.$$readonly);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watch("isEditable()", function isEditableWatch(editable, old) {
|
||||
if (editable === undefined) return;
|
||||
if (editable === old) return;
|
||||
scope.$$readonly = scope.$$isReadonly();
|
||||
});
|
||||
|
||||
// js expressions should be evaluated on dummy value changes
|
||||
if (field.name && field.name[0] === '$') {
|
||||
scope.$watch('record.' + field.name, function fieldValueWatch(a, b) {
|
||||
if (a !== b) {
|
||||
scope.$broadcastRecordChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
scope.isRequired = function() {
|
||||
return this.attr("required") && this.text !== 0 && !this.text;
|
||||
};
|
||||
|
||||
scope.isReadonlyExclusive = function() {
|
||||
var parent = this.$parent || {};
|
||||
var readonly = this.attr("readonly");
|
||||
|
||||
if (scope._isPopup && !parent._isPopup) {
|
||||
return readonly || false;
|
||||
}
|
||||
if (parent.isReadonlyExclusive && parent.isReadonlyExclusive()) {
|
||||
return true;
|
||||
}
|
||||
if (readonly !== undefined) {
|
||||
return readonly || false;
|
||||
}
|
||||
|
||||
return readonly || false;
|
||||
};
|
||||
|
||||
scope.isReadonly = function() {
|
||||
if (scope.$$readonly === undefined) {
|
||||
scope.$$readonly = scope.$$isReadonly();
|
||||
}
|
||||
return scope.$$readonly;
|
||||
};
|
||||
|
||||
scope.$$isReadonly = function() {
|
||||
if ((this.hasPermission && !this.hasPermission('read')) || this.isReadonlyExclusive()) {
|
||||
return true;
|
||||
}
|
||||
if (!this.attr("readonly") && this.attr("force-edit")) {
|
||||
return false;
|
||||
}
|
||||
if (scope.isEditable && !scope.isEditable()) {
|
||||
return true;
|
||||
}
|
||||
return this.attr("readonly") || false;
|
||||
};
|
||||
|
||||
scope.isHidden = function() {
|
||||
return this.attr("hidden") || (this.$parent && this.$parent.isHidden && this.$parent.isHidden()) || false;
|
||||
};
|
||||
|
||||
scope.fireAction = function(name, success, error) {
|
||||
var handler = this.$events[name];
|
||||
if (handler) {
|
||||
return handler().then(success, error);
|
||||
}
|
||||
};
|
||||
|
||||
if (angular.isFunction(this._link_internal)) {
|
||||
this._link_internal.call(this, scope, element, attrs, controller);
|
||||
}
|
||||
if (angular.isFunction(this.init)) {
|
||||
this.init.call(this, scope);
|
||||
}
|
||||
if (angular.isFunction(this.link)) {
|
||||
this.link.call(this, scope, element, attrs, controller);
|
||||
}
|
||||
|
||||
function hideWidget(hidden) {
|
||||
var elem = element,
|
||||
parent = elem.parent('td,.form-item'),
|
||||
label = elem.data('label') || $(),
|
||||
label_parent = label.parent('td,.form-item'),
|
||||
isTable = parent.is('td');
|
||||
|
||||
// label scope should use same isHidden method (#1514)
|
||||
var lScope = label.data('$scope');
|
||||
if (lScope && lScope.isHidden !== scope.isHidden) {
|
||||
lScope.isHidden = scope.isHidden;
|
||||
}
|
||||
|
||||
elem = isTable && parent.length ? parent : elem;
|
||||
label = isTable && label_parent.length ? label_parent : label;
|
||||
|
||||
if (!isTable) {
|
||||
parent.toggleClass("form-item-hidden", hidden);
|
||||
}
|
||||
|
||||
if (hidden) {
|
||||
elem.add(label).hide();
|
||||
} else {
|
||||
elem.add(label).show().css('display', ''); //XXX: jquery may add display style
|
||||
}
|
||||
|
||||
return axelor.$adjustSize();
|
||||
}
|
||||
|
||||
var hideFn = _.contains(this.handles, 'isHidden') ? angular.noop : hideWidget;
|
||||
|
||||
var hiddenSet = false;
|
||||
scope.$watch("isHidden()", function isHiddenWatch(hidden, old) {
|
||||
if (hiddenSet && hidden === old) return;
|
||||
hiddenSet = true;
|
||||
return hideFn(hidden);
|
||||
});
|
||||
|
||||
var readonlySet = false;
|
||||
scope.$watch("isReadonly()", function isReadonlyWatch(readonly, old) {
|
||||
if (readonlySet && readonly === old) return;
|
||||
readonlySet = true;
|
||||
element.toggleClass("readonly", readonly);
|
||||
element.toggleClass("editable", !readonly);
|
||||
if (scope.canEdit) {
|
||||
element.toggleClass("no-edit", scope.canEdit() === false);
|
||||
}
|
||||
});
|
||||
|
||||
function setHighlight(args) {
|
||||
|
||||
function doHilite(params, passed) {
|
||||
var label = element.data('label') || $();
|
||||
element.toggleClass(params.css, passed);
|
||||
label.toggleClass(params.css.replace(/(hilite-[^-]+\b(?!-))/g, ''), passed);
|
||||
}
|
||||
|
||||
_.each(field.hilites, function(p) {
|
||||
if (p.css) doHilite(p, false);
|
||||
});
|
||||
|
||||
if (args && args.hilite && args.hilite.css) {
|
||||
doHilite(args.hilite, args.passed);
|
||||
}
|
||||
}
|
||||
|
||||
this.prepare(scope, element, attrs, controller);
|
||||
|
||||
scope.$evalAsync(function() {
|
||||
if (scope.isHidden()) {
|
||||
hideFn(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return angular.bind(this, link);
|
||||
};
|
||||
|
||||
ui.formDirective = function(name, object) {
|
||||
|
||||
if (object.compile === undefined) {
|
||||
object.compile = angular.bind(object, function(element, attrs){
|
||||
return ui.formCompile.apply(this, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
if (object.restrict === undefined) {
|
||||
object.restrict = 'EA';
|
||||
}
|
||||
|
||||
if (object.template && !object.replace) {
|
||||
object.replace = true;
|
||||
}
|
||||
|
||||
if (object.cellCss === undefined) {
|
||||
object.cellCss = 'form-item';
|
||||
}
|
||||
|
||||
if (object.scope === undefined) {
|
||||
object.scope = true;
|
||||
}
|
||||
|
||||
if (object.require === undefined) {
|
||||
object.require = '?ngModel';
|
||||
}
|
||||
|
||||
function prepare_templates($compile) {
|
||||
|
||||
object.prepare = angular.bind(object, function(scope, element, attrs, model) {
|
||||
|
||||
var self = this;
|
||||
|
||||
if (!this.template_editable && !this.template_readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.$elem_editable = null;
|
||||
scope.$elem_readonly = null;
|
||||
|
||||
function showEditable() {
|
||||
var template_editable = self.template_editable;
|
||||
if (scope.field && scope.field.editor) {
|
||||
template_editable = $('<div ui-panel-editor>');
|
||||
}
|
||||
if (_.isFunction(self.template_editable)) {
|
||||
template_editable = self.template_editable(scope);
|
||||
}
|
||||
if (!template_editable) {
|
||||
return false;
|
||||
}
|
||||
if (!scope.$elem_editable) {
|
||||
scope.$elem_editable = $compile(template_editable)(scope);
|
||||
if (self.link_editable) {
|
||||
self.link_editable.call(self, scope, scope.$elem_editable, attrs, model);
|
||||
}
|
||||
if (scope.validate) {
|
||||
model.$validators.valid = function(modelValue, viewValue) {
|
||||
return !!scope.validate(viewValue);
|
||||
};
|
||||
}
|
||||
// focus the first input field
|
||||
if (scope.$elem_editable.is('.input-append,.picker-input')) {
|
||||
scope.$elem_editable.on('click', '.btn, i', function(){
|
||||
if (!axelor.device.mobile) {
|
||||
scope.$elem_editable.find('input:first').focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (scope.$elem_editable.is(':input')) {
|
||||
scope.$elem_editable.attr('placeholder', scope.field.placeholder);
|
||||
}
|
||||
|
||||
if (scope.$elem_editable.is('.picker-input:not(.tag-select)')) {
|
||||
scope.$elem_editable.find(':input:first').attr('placeholder', scope.field.placeholder);
|
||||
}
|
||||
}
|
||||
if (scope.$elem_readonly) {
|
||||
scope.$elem_readonly.detach();
|
||||
}
|
||||
element.append(scope.$elem_editable);
|
||||
if (scope.$render_editable) scope.$render_editable();
|
||||
return true;
|
||||
}
|
||||
|
||||
function showReadonly() {
|
||||
var field = scope.field || {};
|
||||
var template_readonly = self.template_readonly;
|
||||
if (field.viewer) {
|
||||
template_readonly = field.viewer.template;
|
||||
scope.$moment = function(d) { return moment(d); };
|
||||
scope.$number = function(d) { return +d; };
|
||||
scope.$image = function (fieldName, imageName) { return ui.formatters.$image(this, fieldName, imageName); };
|
||||
scope.$fmt = function (fieldName, fieldValue) {
|
||||
var args = [this, fieldName];
|
||||
if (arguments.length > 1) {
|
||||
args.push(fieldValue);
|
||||
}
|
||||
return ui.formatters.$fmt.apply(null, args);
|
||||
};
|
||||
} else if (field.editor && field.editor.viewer) {
|
||||
return showEditable();
|
||||
}
|
||||
if (_.isFunction(self.template_readonly)) {
|
||||
template_readonly = self.template_readonly(scope);
|
||||
}
|
||||
if (!template_readonly) {
|
||||
return false;
|
||||
}
|
||||
if (_.isString(template_readonly)) {
|
||||
template_readonly = axelor.sanitize(template_readonly.trim());
|
||||
if (template_readonly[0] !== '<' || $(template_readonly).length > 1) {
|
||||
template_readonly = '<span>' + template_readonly + '</span>';
|
||||
}
|
||||
if (field.viewer) {
|
||||
template_readonly = template_readonly.replace(/^(\s*<\w+)/, '$1 ui-panel-viewer');
|
||||
}
|
||||
}
|
||||
if (!scope.$elem_readonly) {
|
||||
scope.$elem_readonly = $compile(template_readonly)(scope);
|
||||
if (self.link_readonly) {
|
||||
self.link_readonly.call(self, scope, scope.$elem_readonly, attrs, model);
|
||||
}
|
||||
}
|
||||
if (scope.$elem_editable) {
|
||||
scope.$elem_editable.detach();
|
||||
}
|
||||
element.append(scope.$elem_readonly);
|
||||
return true;
|
||||
}
|
||||
|
||||
scope.$watch("isReadonly()", function isReadonlyWatch(readonly) {
|
||||
if (readonly && showReadonly()) {
|
||||
return;
|
||||
}
|
||||
return showEditable();
|
||||
});
|
||||
scope.$watch("isRequired()", function isRequiredWatch(required, old) {
|
||||
if (required === old) return;
|
||||
var elem = element,
|
||||
label = elem.data('label') || $();
|
||||
if (label) {
|
||||
label.toggleClass('required', required);
|
||||
}
|
||||
attrs.$set('required', required);
|
||||
});
|
||||
|
||||
if (scope.field && scope.field.validIf) {
|
||||
scope.$watch("attr('valid')", function attrValidWatch(valid) {
|
||||
if (valid === undefined) return;
|
||||
model.$setValidity('invalid', valid);
|
||||
});
|
||||
}
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
if (scope.$elem_editable) {
|
||||
scope.$elem_editable.remove();
|
||||
scope.$elem_editable = null;
|
||||
}
|
||||
if (scope.$elem_readonly) {
|
||||
scope.$elem_readonly.remove();
|
||||
scope.$elem_readonly = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
return ui.directive(name, ['$compile', function($compile) {
|
||||
return prepare_templates($compile);
|
||||
}]);
|
||||
};
|
||||
|
||||
var FormItem = {
|
||||
|
||||
css: 'form-item',
|
||||
|
||||
cellCss: 'form-item'
|
||||
};
|
||||
|
||||
var FormInput = {
|
||||
|
||||
_link_internal: function(scope, element, attrs, model) {
|
||||
|
||||
scope.format = function(value) {
|
||||
return value;
|
||||
};
|
||||
|
||||
scope.parse = function(value) {
|
||||
return value;
|
||||
};
|
||||
|
||||
scope.validate = function(value) {
|
||||
return true;
|
||||
};
|
||||
|
||||
scope.setValue = function(value, fireOnChange) {
|
||||
|
||||
var val = this.parse(value);
|
||||
var txt = this.format(value);
|
||||
var onChange = this.$events.onChange;
|
||||
|
||||
model.$setViewValue(val);
|
||||
this.text = txt;
|
||||
|
||||
model.$render();
|
||||
if (onChange && fireOnChange) {
|
||||
onChange();
|
||||
}
|
||||
};
|
||||
|
||||
scope.getValue = function() {
|
||||
if (model) {
|
||||
return model.$viewValue;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
scope.getText = function() {
|
||||
return this.text;
|
||||
};
|
||||
|
||||
scope.initValue = function(value) {
|
||||
this.text = this.format(value);
|
||||
};
|
||||
|
||||
model.$render = function() {
|
||||
scope.initValue(scope.getValue());
|
||||
if (scope.$render_editable) {
|
||||
scope.$render_editable();
|
||||
}
|
||||
if (scope.$render_readonly) {
|
||||
scope.$render_readonly();
|
||||
}
|
||||
};
|
||||
|
||||
// Clear invalid fields (use $setPrestine of angular.js 1.1)
|
||||
scope.$on('on:new', function(e, rec) {
|
||||
if (!model.$valid && model.$viewValue) {
|
||||
model.$viewValue = undefined;
|
||||
model.$render();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
link: function(scope, element, attrs, model) {
|
||||
|
||||
},
|
||||
|
||||
link_editable: function(scope, element, attrs, model) {
|
||||
|
||||
scope.$render_editable = function() {
|
||||
var value = this.format(this.getValue());
|
||||
element.val(value);
|
||||
};
|
||||
|
||||
function bindListeners() {
|
||||
var onChange = scope.$events.onChange || angular.noop,
|
||||
onChangePending = false;
|
||||
|
||||
function listener() {
|
||||
var value = _.str.trim(element.val()) || null;
|
||||
if (value !== model.$viewValue) {
|
||||
scope.$applyAsync(function() {
|
||||
var val = scope.parse(value);
|
||||
var txt = scope.format(value);
|
||||
if (scope.$$setEditorValue && !scope.record) { // m2o editor with null value?
|
||||
scope.$$setEditorValue({}, false);
|
||||
}
|
||||
model.$setViewValue(val);
|
||||
scope.text = txt;
|
||||
});
|
||||
onChangePending = true;
|
||||
}
|
||||
}
|
||||
|
||||
var field = scope.field || {};
|
||||
if (!field.bind) {
|
||||
element.bind('input', listener);
|
||||
}
|
||||
|
||||
element.change(listener);
|
||||
|
||||
element.blur(function(e){
|
||||
if (onChangePending) {
|
||||
onChangePending = false;
|
||||
setTimeout(onChange);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (element.is(':input')) {
|
||||
setTimeout(bindListeners);
|
||||
// clear input value
|
||||
if (scope.$$setEditorValue) {
|
||||
scope.$on('on:edit', function () {
|
||||
if (model.$viewValue && !scope.record) {
|
||||
model.$setViewValue(undefined);
|
||||
scope.$render_editable();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
scope.$render_editable();
|
||||
},
|
||||
|
||||
link_readonly: function(scope, element, attrs, model) {
|
||||
|
||||
},
|
||||
|
||||
template_editable: '<input type="text">',
|
||||
|
||||
template_readonly: '<span class="display-text">{{text}}</span>',
|
||||
|
||||
template: '<span class="form-item-container"></span>'
|
||||
};
|
||||
|
||||
function inherit(array) {
|
||||
|
||||
var args = _.chain(array).rest(1).flatten(true).value();
|
||||
var last = _.last(args);
|
||||
var base = null;
|
||||
var obj = {};
|
||||
|
||||
_.chain(args).each(function(source, index) {
|
||||
if (_.isString(source)) {
|
||||
source = widgets[source];
|
||||
}
|
||||
if (index === args.length - 2) {
|
||||
base = source;
|
||||
}
|
||||
_.extend(obj, source);
|
||||
});
|
||||
|
||||
if (!base) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
function overridden(name) {
|
||||
return name !== "controller" &&
|
||||
_.isFunction(last[name]) && !last[name].$inject &&
|
||||
_.isFunction(base[name]);
|
||||
}
|
||||
|
||||
function override(name, fn){
|
||||
return function() {
|
||||
var tmp = this._super;
|
||||
this._super = base[name];
|
||||
var ret = fn.apply(this, arguments);
|
||||
this._super = tmp;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
|
||||
for(var name in last) {
|
||||
if (overridden(name)) {
|
||||
obj[name] = override(name, obj[name]);
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
ui.formWidget = function(name, object) {
|
||||
var obj = inherit(arguments);
|
||||
var widget = _.str.capitalize(name.replace(/^ui/, ''));
|
||||
var directive = "ui" + widget;
|
||||
|
||||
if (obj.metaWidget) {
|
||||
metaWidgets.push(widget);
|
||||
}
|
||||
|
||||
registry[directive] = directive;
|
||||
_.each(obj.widgets, function(alias){
|
||||
registry[alias] = directive;
|
||||
});
|
||||
delete obj.widgets;
|
||||
|
||||
widgets[widget] = _.clone(obj);
|
||||
|
||||
ui.formDirective(directive, obj);
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
ui.formItem = function(name, object) {
|
||||
return ui.formWidget(name, FormItem, _.rest(arguments, 1));
|
||||
};
|
||||
|
||||
ui.formInput = function(name, object) {
|
||||
return ui.formWidget(name, FormItem, FormInput, _.rest(arguments, 1));
|
||||
};
|
||||
|
||||
ui.getWidget = function(type) {
|
||||
var name = type,
|
||||
widget = registry["ui" + name] || registry[name];
|
||||
if (!widget) {
|
||||
name = _.str.classify(name);
|
||||
widget = registry["ui" + name] || registry[name];
|
||||
}
|
||||
if (widget) {
|
||||
widget = widget.replace(/^ui/, '');
|
||||
return _.chain(widget).underscored().dasherize().value();
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
ui.getWidgetDef = function (name) {
|
||||
return widgets[name];
|
||||
};
|
||||
|
||||
ui.getMetaWidgets = function () {
|
||||
return metaWidgets;
|
||||
};
|
||||
|
||||
})();
|
||||
134
sophal/js/form/form.code.js
Normal file
134
sophal/js/form/form.code.js
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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() {
|
||||
|
||||
/* jshint newcap: false */
|
||||
/* global CodeMirror: true */
|
||||
|
||||
"use strict";
|
||||
|
||||
var ui = angular.module('axelor.ui');
|
||||
|
||||
ui.formInput('CodeEditor', {
|
||||
|
||||
css: "code-editor",
|
||||
|
||||
metaWidget: true,
|
||||
|
||||
link: function(scope, element, attrs, model) {
|
||||
|
||||
var editor = null;
|
||||
var loading = false;
|
||||
|
||||
var field = scope.field;
|
||||
var props = {
|
||||
autofocus: true,
|
||||
lineNumbers: true,
|
||||
tabSize : 2,
|
||||
indentUnit : 2,
|
||||
indentWithTabs: false,
|
||||
theme: field.codeTheme || "default",
|
||||
extraKeys: {
|
||||
'Ctrl-F': function () {}
|
||||
}
|
||||
};
|
||||
|
||||
if (field.mode || field.codeSyntax) {
|
||||
props.mode = field.mode || field.codeSyntax;
|
||||
}
|
||||
|
||||
if (props.mode === "xml") {
|
||||
props = _.extend(props, {
|
||||
foldGutter : true,
|
||||
gutters : ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
autoCloseBrackets : true,
|
||||
autoCloseTags : true
|
||||
});
|
||||
}
|
||||
|
||||
if (field.height) {
|
||||
element.height(field.height);
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
props.readOnly = scope.$$readonly;
|
||||
editor = CodeMirror(element.get(0), props);
|
||||
model.$render();
|
||||
readonlySet(props.readOnly);
|
||||
editor.on("change", changed);
|
||||
});
|
||||
|
||||
scope.$watch('$$readonly', readonlySet);
|
||||
|
||||
model.$render = function() {
|
||||
loading = true;
|
||||
var val = model.$modelValue;
|
||||
if (editor) {
|
||||
editor.setValue(val || "");
|
||||
editor.clearHistory();
|
||||
}
|
||||
loading = false;
|
||||
};
|
||||
|
||||
model.$formatters.push(function (value) {
|
||||
return value || '';
|
||||
});
|
||||
|
||||
function readonlySet(readonly) {
|
||||
if (editor) {
|
||||
editor.setOption('readOnly', _.toBoolean(readonly));
|
||||
}
|
||||
}
|
||||
|
||||
function changed(instance, changedObj) {
|
||||
if (loading || !editor) return;
|
||||
var value = editor.getValue();
|
||||
if (value !== model.$viewValue) {
|
||||
model.$setViewValue(value);
|
||||
}
|
||||
scope.$applyAsync();
|
||||
}
|
||||
|
||||
function resize() {
|
||||
if (element[0].offsetHeight === 0) return; // is hidden?
|
||||
if (editor) {
|
||||
editor.refresh();
|
||||
}
|
||||
element.width('');
|
||||
}
|
||||
|
||||
element.resizable({
|
||||
handles: 's',
|
||||
resize: resize
|
||||
});
|
||||
|
||||
scope.$onAdjust(resize);
|
||||
},
|
||||
|
||||
replace: true,
|
||||
|
||||
transclude: true,
|
||||
|
||||
template_editable: null,
|
||||
|
||||
template_readonly: null,
|
||||
|
||||
template: '<div ng-transclude></div>'
|
||||
});
|
||||
|
||||
})();
|
||||
841
sophal/js/form/form.container.js
Normal file
841
sophal/js/form/form.container.js
Normal file
@ -0,0 +1,841 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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');
|
||||
|
||||
// this directive is used as a replacement for ng-transclude directive
|
||||
// which fails to keep scope hierarchy (see: https://github.com/angular/angular.js/issues/1809)
|
||||
ui.directive('uiTransclude', function() {
|
||||
return {
|
||||
compile: function(tElement, tAttrs, transclude) {
|
||||
return function(scope, element, attrs, ctrl) {
|
||||
transclude(scope.$new(), function(clone) {
|
||||
element.append(clone);
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* The Group widget.
|
||||
*
|
||||
*/
|
||||
ui.formWidget('Group', {
|
||||
|
||||
css: 'form-item-group',
|
||||
cellCss: 'form-item v-align-top',
|
||||
|
||||
link: function(scope, element, attrs) {
|
||||
|
||||
var props = scope.field;
|
||||
|
||||
scope.collapsed = false;
|
||||
|
||||
scope.canCollapse = function() {
|
||||
return props.canCollapse || props.collapseIf;
|
||||
};
|
||||
|
||||
scope.setCollapsed = function(collapsed) {
|
||||
scope.collapsed = collapsed;
|
||||
element.children('legend').nextAll(':not(br)')[collapsed ? 'hide' : 'show']();
|
||||
axelor.$adjustSize();
|
||||
};
|
||||
|
||||
scope.toggle = function() {
|
||||
scope.collapsed = !scope.collapsed;
|
||||
scope.setCollapsed(scope.collapsed);
|
||||
};
|
||||
|
||||
scope.$watch("attr('collapse')", function groupCollapseWatch(collapsed) {
|
||||
scope.setCollapsed(collapsed);
|
||||
});
|
||||
|
||||
// if auto title, then don't show it
|
||||
if (attrs.title === attrs.field) {
|
||||
attrs.$set('title', '');
|
||||
}
|
||||
|
||||
if (props.showTitle !== false) {
|
||||
scope.$watch('attr("title")', function groupTitleWatch(value){
|
||||
scope.title = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
transclude: true,
|
||||
template:
|
||||
'<fieldset ng-class="{\'bordered-box\': title, \'has-title\': title}" x-layout-selector="> div:first">'+
|
||||
'<legend ng-show="title">'+
|
||||
'<i ng-show="canCollapse()" ng-click="toggle()" ng-class="{\'fa fa-plus\': collapsed, \'fa fa-minus\': !collapsed}"></i>'+
|
||||
'<span ng-bind-html="title"></span></legend>'+
|
||||
'<div ui-transclude></div>'+
|
||||
'</fieldset>'
|
||||
});
|
||||
|
||||
ui.formWidget('Portlet', {
|
||||
|
||||
css: 'form-item-portlet',
|
||||
cellCss: 'form-item v-align-top',
|
||||
|
||||
showTitle: false,
|
||||
|
||||
link: function(scope, element, attrs) {
|
||||
|
||||
var field = scope.field;
|
||||
|
||||
scope.canSearch = field.canSearch !== "false";
|
||||
scope.actionName = field.action;
|
||||
|
||||
if (field.name) {
|
||||
scope.formPath = field.name;
|
||||
}
|
||||
|
||||
if (field.height) {
|
||||
element.height(field.height);
|
||||
}
|
||||
|
||||
element.resizable({
|
||||
handles: 's',
|
||||
resize: _.debounce(function() {
|
||||
axelor.$adjustSize();
|
||||
element.width('auto');
|
||||
}, 100)
|
||||
});
|
||||
},
|
||||
|
||||
template:
|
||||
'<div>'+
|
||||
'<div ui-view-portlet x-action="{{actionName}}" x-can-search="{{canSearch}}"></div>'+
|
||||
'</div>'
|
||||
});
|
||||
|
||||
ui.formWidget('Dashlet', {
|
||||
|
||||
css: 'dashboard',
|
||||
|
||||
showTitle: false,
|
||||
|
||||
link: function(scope, element, attrs) {
|
||||
|
||||
var field = scope.field;
|
||||
var dashlet = _.extend({}, scope.field);
|
||||
|
||||
scope.dashlet = dashlet;
|
||||
scope.formPath = field.name || field.action;
|
||||
|
||||
scope.$watch('attr("title")', function dashletTitleWatch(title, old) {
|
||||
if (title === old) {
|
||||
return;
|
||||
}
|
||||
var dashletScope = element.children('[ui-view-dashlet]').scope();
|
||||
if (dashletScope) {
|
||||
dashletScope.title = title;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
template:
|
||||
'<div>'+
|
||||
'<div ui-view-dashlet></div>'+
|
||||
'</div>'
|
||||
});
|
||||
|
||||
/**
|
||||
* The Tabs widget (notebook).
|
||||
*/
|
||||
ui.formWidget('Tabs', {
|
||||
|
||||
cellCss: 'form-item v-align-top',
|
||||
|
||||
widgets: ['Notebook'],
|
||||
|
||||
controller: ['$scope', '$element', function($scope, $element) {
|
||||
|
||||
var tabs = $scope.tabs = [],
|
||||
selected = -1;
|
||||
|
||||
var doOnSelectPending = false;
|
||||
var doOnSelect = _.debounce(function () {
|
||||
var select = tabs[selected];
|
||||
if (doOnSelectPending || !select) {
|
||||
return;
|
||||
}
|
||||
doOnSelectPending = true;
|
||||
$scope.waitForActions(function () {
|
||||
if (select.handleSelect) {
|
||||
select.handleSelect();
|
||||
}
|
||||
$scope.waitForActions(function () {
|
||||
doOnSelectPending = false;
|
||||
});
|
||||
});
|
||||
}, 100);
|
||||
|
||||
$scope.select = function(tab) {
|
||||
|
||||
var current = selected;
|
||||
|
||||
angular.forEach(tabs, function(tab, i){
|
||||
tab.tabSelected = false;
|
||||
});
|
||||
|
||||
tab.tabSelected = true;
|
||||
selected = _.indexOf(tabs, tab);
|
||||
|
||||
if (current === selected) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
if ($scope.$tabs) {
|
||||
$scope.$tabs.trigger('adjust:tabs');
|
||||
}
|
||||
axelor.$adjustSize();
|
||||
if(current != selected){
|
||||
doOnSelect();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$on('on:edit', function (e, record) {
|
||||
if ($scope.record === record) {
|
||||
doOnSelect();
|
||||
}
|
||||
});
|
||||
|
||||
this.addTab = function(tab) {
|
||||
if (tabs.length === 0) $scope.select(tab);
|
||||
tab.index = tabs.length;
|
||||
tabs.push(tab);
|
||||
};
|
||||
|
||||
function inRange(index) {
|
||||
return index > -1 && index < tabs.length;
|
||||
}
|
||||
|
||||
function findItem(index) {
|
||||
return $element.find('ul.nav-tabs:first > li:nth-child(' + (index+1) + ')');
|
||||
}
|
||||
|
||||
this.showTab = function(index) {
|
||||
|
||||
if (!inRange(index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tab = tabs[index];
|
||||
var item = findItem(index);
|
||||
|
||||
tab.hidden = false;
|
||||
item.show();
|
||||
|
||||
if (selected == -1 || selected === index) {
|
||||
return $scope.select(tabs[index]);
|
||||
}
|
||||
|
||||
axelor.$adjustSize();
|
||||
};
|
||||
|
||||
this.hideTab = function(index) {
|
||||
|
||||
if (!inRange(index))
|
||||
return;
|
||||
|
||||
var item = findItem(index),
|
||||
tab = tabs[index];
|
||||
|
||||
var wasHidden = item.is(":hidden");
|
||||
|
||||
item.hide();
|
||||
item.removeClass('active');
|
||||
|
||||
tab.hidden = true;
|
||||
tab.tabSelected = false;
|
||||
|
||||
if (!wasHidden && selected > -1 && selected !== index)
|
||||
return axelor.$adjustSize();
|
||||
|
||||
for(var i = 0 ; i < tabs.length ; i++) {
|
||||
tab = tabs[i];
|
||||
if (!tab.hidden) {
|
||||
return $scope.select(tabs[i]);
|
||||
}
|
||||
}
|
||||
selected = -1;
|
||||
};
|
||||
|
||||
$scope.setTitle = function(value,index){
|
||||
var item = findItem(index),
|
||||
pageScope = item.first().data('$scope');
|
||||
|
||||
pageScope.tab.title = value;
|
||||
};
|
||||
}],
|
||||
|
||||
link: function(scope, elem, attrs) {
|
||||
|
||||
var props = scope.field;
|
||||
|
||||
scope.$tabs = $(elem).bsTabs({
|
||||
closable: false
|
||||
});
|
||||
|
||||
elem.on('click', '.dropdown-toggle', function(e){
|
||||
axelor.$adjustSize();
|
||||
});
|
||||
|
||||
// set height (#1011)
|
||||
if (props.height) {
|
||||
elem.children('.tab-content:first').height(props.height);
|
||||
}
|
||||
},
|
||||
transclude: true,
|
||||
template:
|
||||
'<div class="tabbable-tabs">' +
|
||||
'<div class="nav-tabs-wrap">' +
|
||||
'<div class="nav-tabs-scroll-l"><a tabindex="-1" href="#"><i class="fa fa-chevron-left"></i></a></div>' +
|
||||
'<div class="nav-tabs-scroll-r"><a tabindex="-1" href="#"><i class="fa fa-chevron-right"></i></a></div>' +
|
||||
'<div class="nav-tabs-strip">' +
|
||||
'<ul class="nav nav-tabs">' +
|
||||
'<li tabindex="-1" ng-repeat="tab in tabs" ng-class="{active:tab.tabSelected}">'+
|
||||
'<a tabindex="-1" href="" ng-click="select(tab)">'+
|
||||
'<img class="prefix-icon" ng-show="tab.icon" ng-src="{{tab.icon}}">'+
|
||||
'<span ng-bind-html="tab.title"></span>'+
|
||||
'</a>' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'</div>' +
|
||||
'<div class="nav-tabs-menu">'+
|
||||
'<div class="dropdown pull-right">'+
|
||||
'<a class="dropdown-toggle" data-toggle="dropdown" href="#"><i class="caret"></i></a>'+
|
||||
'<ul class="dropdown-menu" role="menu">'+
|
||||
'<li ng-repeat="tab in tabs">'+
|
||||
'<a tabindex="-1" href="javascript: void(0)" ng-click="select(tab)" ng-bind-html="tab.title"></a>'+
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'</a>'+
|
||||
'</div>'+
|
||||
'</div>'+
|
||||
'</div>' +
|
||||
'<div class="tab-content" ui-transclude></div>' +
|
||||
'</div>'
|
||||
});
|
||||
|
||||
/**
|
||||
* The Tab widget (notebook page).
|
||||
*/
|
||||
ui.formWidget('Tab', {
|
||||
|
||||
require: '^uiTabs',
|
||||
|
||||
widgets: ['Page'],
|
||||
|
||||
handles: ['isHidden'],
|
||||
|
||||
link: function(scope, elem, attrs, tabs) {
|
||||
|
||||
scope.tabSelected = false;
|
||||
scope.icon = scope.field && scope.field.icon;
|
||||
|
||||
tabs.addTab(scope);
|
||||
|
||||
scope.$watch('attr("title")', function tabTitleWatch(value){
|
||||
scope.title = value;
|
||||
});
|
||||
|
||||
scope.$watch("isHidden()", function tabHiddenWatch(hidden, old) {
|
||||
if (hidden) {
|
||||
return tabs.hideTab(scope.index);
|
||||
}
|
||||
return tabs.showTab(scope.index);
|
||||
});
|
||||
|
||||
scope.handleSelect = function () {
|
||||
var onSelect = scope.$events.onSelect;
|
||||
if (onSelect && !elem.is(":hidden")) {
|
||||
onSelect();
|
||||
}
|
||||
};
|
||||
},
|
||||
cellCss: 'form-item v-align-top',
|
||||
transclude: true,
|
||||
template: '<div ui-actions class="tab-pane" ng-class="{active: tabSelected}" x-layout-selector="> div:first">'+
|
||||
'<div ui-transclude></div>'+
|
||||
'</div>'
|
||||
});
|
||||
|
||||
ui.formWidget('ButtonGroup', {
|
||||
|
||||
link: function (scope, element, attrs) {
|
||||
function adjustButtons() {
|
||||
var visible = element.children('.btn:visible').length;
|
||||
if (visible) {
|
||||
element.children('.btn:visible')
|
||||
.css('max-width', (100.00/visible) + '%')
|
||||
.css('width', (100.00/visible) + '%');
|
||||
}
|
||||
}
|
||||
scope.$watch(adjustButtons);
|
||||
scope.$callWhen(function () {
|
||||
return element.is(':visible');
|
||||
}, adjustButtons);
|
||||
},
|
||||
transclude: true,
|
||||
template_editable: null,
|
||||
template_readonly: null,
|
||||
template:
|
||||
"<div class='btn-group' ui-transclude></div>"
|
||||
});
|
||||
|
||||
ui.formWidget('Panel', {
|
||||
|
||||
showTitle: false,
|
||||
|
||||
link: function (scope, element, attrs) {
|
||||
|
||||
var field = scope.field || {};
|
||||
var body = element.children(".panel-body");
|
||||
|
||||
element.addClass(field.serverType);
|
||||
if (field.sidebar && !attrs.itemSpan) {
|
||||
attrs.$set('itemSpan', 12, true, 'x-item-span');
|
||||
}
|
||||
|
||||
scope.menus = null;
|
||||
if (field.menu) {
|
||||
scope.menus = [field.menu];
|
||||
}
|
||||
|
||||
scope.canCollapse = function() {
|
||||
return field.canCollapse || field.collapseIf;
|
||||
};
|
||||
|
||||
scope.setCollapsed = function(collapsed) {
|
||||
var old = scope.collapsed;
|
||||
var action = collapsed ? "hide" : "show";
|
||||
|
||||
scope.collapsed = collapsed;
|
||||
scope.collapsedIcon = collapsed ? 'fa-chevron-down' : 'fa-chevron-up';
|
||||
|
||||
if (collapsed === old) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.removeClass("collapsed");
|
||||
body[action]("blind", 200, function () {
|
||||
element.toggleClass("collapsed", !!collapsed);
|
||||
if (body.css('display') !== 'none' && action === 'hide') {
|
||||
body.hide();
|
||||
}
|
||||
axelor.$adjustSize();
|
||||
});
|
||||
};
|
||||
|
||||
scope.toggle = function() {
|
||||
if (scope.canCollapse()) {
|
||||
scope.setCollapsed(!scope.collapsed);
|
||||
}
|
||||
};
|
||||
|
||||
scope.$watch("attr('collapse')", function panelCollapseWatch(collapsed) {
|
||||
scope.setCollapsed(collapsed);
|
||||
});
|
||||
|
||||
var nested = element.parents('.panel:first').length > 0;
|
||||
if (nested) {
|
||||
element.addClass("panel-nested");
|
||||
}
|
||||
if (field.showFrame === false) {
|
||||
element.addClass('noframe');
|
||||
}
|
||||
scope.notitle = field.showFrame === false || field.showTitle === false;
|
||||
scope.title = field.title;
|
||||
scope.$watch('attr("title")', function panelTitleWatch(title, old) {
|
||||
if (title === undefined || title === old) return;
|
||||
scope.title = title;
|
||||
});
|
||||
|
||||
var icon = field.icon;
|
||||
var iconBg = field.iconBackground;
|
||||
|
||||
if (icon && icon.indexOf('fa-') === 0) {
|
||||
scope.icon = icon;
|
||||
} else if (icon) {
|
||||
scope.image = icon;
|
||||
}
|
||||
|
||||
if (scope.icon && iconBg) {
|
||||
setTimeout(function() {
|
||||
var iconElem = element.children('.panel-header').children('.panel-icon');
|
||||
if (iconBg.indexOf("#") === 0) {
|
||||
iconElem.css('background-color', iconBg);
|
||||
} else {
|
||||
iconElem.addClass('bg-' + iconBg);
|
||||
}
|
||||
iconElem.addClass('has-bg');
|
||||
iconElem.find('i').addClass('fg-white');
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
var nestedJson = element.parents('.panel-json:first').length > 0;
|
||||
if (nestedJson) {
|
||||
element.removeClass("panel-nested");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
transclude: true,
|
||||
template:
|
||||
"<div class='panel panel-default'>" +
|
||||
"<div class='panel-header' ng-click='toggle()' ng-if='!notitle && field.title' ng-class=\"{'clickable-header' : canCollapse()}\" tabindex='-1'>" +
|
||||
"<div class='panel-icon' ng-if='icon'><i class='fa' ng-class='icon'></i></div>" +
|
||||
"<img class='panel-image' ng-if='image' ng-src='{{image}}'>" +
|
||||
"<div class='panel-title'>{{title}}</div>" +
|
||||
"<div ng-if='menus' ui-menu-bar menus='menus' handler='this'></div>" +
|
||||
"<div ng-show='canCollapse()' class='panel-icons'>" +
|
||||
"<a href=''><i class='fa' ng-class='collapsedIcon'></i></a>" +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
"<div class='panel-body' ui-transclude></div>" +
|
||||
"</div>"
|
||||
});
|
||||
|
||||
ui.formWidget('PanelStack', {
|
||||
transclude: true,
|
||||
template: "<div class='panel-stack'>" +
|
||||
"<span ui-transclude></span>" +
|
||||
"</div>"
|
||||
});
|
||||
|
||||
ui.formWidget('PanelTabs', {
|
||||
|
||||
link: function (scope, element, attrs) {
|
||||
|
||||
scope.tabs = [];
|
||||
scope.more = null;
|
||||
|
||||
element.find('> .tab-content > div').each(function (index) {
|
||||
var elem = $(this);
|
||||
var tab = {
|
||||
title: elem.attr('x-title'),
|
||||
selected: false,
|
||||
hidden: false,
|
||||
elem: elem,
|
||||
tabItem: $(),
|
||||
menuItem: $()
|
||||
};
|
||||
scope.tabs.push(tab);
|
||||
});
|
||||
|
||||
var selected = null;
|
||||
var adjustPending = false;
|
||||
|
||||
function findTab(tab) {
|
||||
var found = scope.tabs[tab] || tab;
|
||||
if (!found || _.isNumber(found)) {
|
||||
return null;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
var doOnSelectPending = false;
|
||||
var doOnSelect = _.debounce(function () {
|
||||
if (doOnSelectPending || !selected || !selected.elem) {
|
||||
return;
|
||||
}
|
||||
doOnSelectPending = true;
|
||||
scope.waitForActions(function () {
|
||||
var elemScope = selected.elem.scope();
|
||||
if (elemScope.handleSelect) {
|
||||
elemScope.handleSelect();
|
||||
}
|
||||
doOnSelectPending = false;
|
||||
});
|
||||
}, 100);
|
||||
|
||||
scope.selectTab = function(tab) {
|
||||
var current = selected;
|
||||
var found = findTab(tab);
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
scope.tabs.forEach(function (current) {
|
||||
current.selected = false;
|
||||
current.elem.hide();
|
||||
});
|
||||
|
||||
selected = found;
|
||||
|
||||
found.selected = true;
|
||||
found.elem.show();
|
||||
|
||||
found.elem
|
||||
.add(found.tabItem)
|
||||
.add(found.menuItem)
|
||||
.addClass('active');
|
||||
|
||||
setTimeout(function () {
|
||||
scope.$broadcast('tab:select');
|
||||
elemTabs.removeClass('open');
|
||||
elemMenu.removeClass('open');
|
||||
axelor.$adjustSize();
|
||||
if (current != selected) {
|
||||
doOnSelect();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
scope.showTab = function (tab) {
|
||||
var found = findTab(tab);
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
|
||||
adjustPending = true;
|
||||
|
||||
found.hidden = false;
|
||||
found.tabItem.show();
|
||||
|
||||
if (!selected || selected === found) {
|
||||
return scope.selectTab(found);
|
||||
}
|
||||
|
||||
found.elem.hide();
|
||||
|
||||
axelor.$adjustSize();
|
||||
};
|
||||
|
||||
scope.hideTab = function (tab) {
|
||||
var found = findTab(tab);
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
|
||||
adjustPending = true;
|
||||
|
||||
var wasHidden = found.hidden;
|
||||
|
||||
found.hidden = true;
|
||||
found.selected = false;
|
||||
found.elem.hide();
|
||||
|
||||
found.tabItem.add(found.menuItem).hide().removeClass('active');
|
||||
|
||||
if (!wasHidden && selected && selected !== found) {
|
||||
return axelor.$adjustSize();
|
||||
}
|
||||
|
||||
var tabs = scope.tabs;
|
||||
for(var i = 0 ; i < tabs.length ; i++) {
|
||||
tab = tabs[i];
|
||||
if (!tab.hidden) {
|
||||
return scope.selectTab(tabs[i]);
|
||||
}
|
||||
}
|
||||
selected = null;
|
||||
};
|
||||
|
||||
var lastWidth = 0;
|
||||
var lastTab = null;
|
||||
|
||||
var elemTabs = $();
|
||||
var elemMenu = $();
|
||||
var elemMenuTitle = $();
|
||||
var elemMenuItems = $();
|
||||
|
||||
function setup() {
|
||||
elemTabs = element.children('.nav-tabs').children('li:not(.dropdown)');
|
||||
elemMenu = element.children('.nav-tabs').children('li.dropdown');
|
||||
elemMenuTitle = elemMenu.children('a:first').children('span');
|
||||
elemMenuItems = elemMenu.find('li');
|
||||
|
||||
_.each(scope.tabs, function (tab, index) {
|
||||
tab.tabItem = $(elemTabs[index]);
|
||||
tab.menuItem = $(elemMenuItems[index]);
|
||||
});
|
||||
}
|
||||
|
||||
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 (elemTabs === null || !scope.tabs || element.is(":hidden")) {
|
||||
return;
|
||||
}
|
||||
|
||||
var parentWidth = element.width() - 2;
|
||||
if (parentWidth === lastWidth && lastTab === selected && !adjustPending) {
|
||||
return;
|
||||
}
|
||||
lastWidth = parentWidth;
|
||||
lastTab = selected;
|
||||
|
||||
elemTabs.parent().css('visibility', 'hidden');
|
||||
elemMenu.hide();
|
||||
|
||||
// show visible tabs
|
||||
scope.tabs.forEach(function (tab, i) {
|
||||
if (tab.hidden) {
|
||||
$(elemTabs[i]).hide();
|
||||
} else {
|
||||
$(elemTabs[i]).show();
|
||||
}
|
||||
});
|
||||
|
||||
if (elemTabs.parent().width() <= parentWidth) {
|
||||
elemTabs.parent().css('visibility', '');
|
||||
return;
|
||||
}
|
||||
|
||||
setMenuTitle(null);
|
||||
|
||||
var elem = null;
|
||||
var index = elemTabs.length;
|
||||
var selectedIndex = scope.tabs.indexOf(selected);
|
||||
|
||||
while (elemTabs.parent().width() > parentWidth) {
|
||||
elem = $(elemTabs[--index]);
|
||||
elem.hide();
|
||||
if (index === selectedIndex) {
|
||||
setMenuTitle(selected);
|
||||
}
|
||||
}
|
||||
|
||||
elemMenuItems.hide();
|
||||
var tab = null;
|
||||
while(index < scope.tabs.length) {
|
||||
tab = scope.tabs[index++];
|
||||
if (!tab.hidden) {
|
||||
tab.menuItem.show();
|
||||
}
|
||||
}
|
||||
|
||||
elemTabs.parent().css('visibility', '');
|
||||
}
|
||||
|
||||
var adjusting = false;
|
||||
scope.$onAdjust(function() {
|
||||
if (adjusting) { return; }
|
||||
try {
|
||||
adjusting = true;
|
||||
adjust();
|
||||
} finally {
|
||||
adjusting = false;
|
||||
adjustPending = false;
|
||||
}
|
||||
}, 10);
|
||||
|
||||
scope.$timeout(function() {
|
||||
setup();
|
||||
var first = _.find(scope.tabs, function (tab) {
|
||||
return !tab.hidden;
|
||||
});
|
||||
if (first) {
|
||||
scope.selectTab(first);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$on('on:edit', function (e, record) {
|
||||
if (scope.record === record && !doOnSelectPending) {
|
||||
scope.ajaxStop(doOnSelect, 100);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watch(function tabsWatch() {
|
||||
var hidden = scope.attr('hidden');
|
||||
if (hidden) return;
|
||||
// show selected tab only
|
||||
scope.tabs.forEach(function (tab) {
|
||||
if (!tab.selected) tab.elem.hide();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
transclude: true,
|
||||
template:
|
||||
"<div class='panel-tabs tabbable-tabs'>" +
|
||||
"<ul class='nav nav-tabs nav-tabs-responsive'>" +
|
||||
"<li ng-repeat='tab in tabs' ng-class='{active: tab.selected}'>" +
|
||||
"<a tabindex='-1' href='' ng-click='selectTab(tab)' ng-bind-html='tab.title'></a>" +
|
||||
"</li>" +
|
||||
"<li class='dropdown' style='display: none'>" +
|
||||
"<a tabindex='-1' href='' class='dropdown-toggle' data-toggle='dropdown'><span></span><b class='caret'></b></a>" +
|
||||
"<ul class='dropdown-menu pull-right'>" +
|
||||
"<li ng-repeat='tab in tabs' ng-class='{active: tab.selected}'>" +
|
||||
"<a tabindex='-1' href='' ng-click='selectTab(tab)' ng-bind-html='tab.title'></a>" +
|
||||
"</li>" +
|
||||
"</ul>" +
|
||||
"</li>" +
|
||||
"</ul>" +
|
||||
"<div class='tab-content' ui-transclude></div>" +
|
||||
"</div>"
|
||||
});
|
||||
|
||||
ui.formWidget('PanelTab', {
|
||||
|
||||
link: function (scope, element, attrs) {
|
||||
|
||||
var index = element.parent().children().index(element);
|
||||
var tab = null;
|
||||
var isHidden = scope.isHidden;
|
||||
|
||||
function findTab() {
|
||||
return tab || (tab = (scope.tabs||[])[index]) || {};
|
||||
}
|
||||
|
||||
scope.handleSelect = function () {
|
||||
var onTabSelect = scope.$events.onTabSelect;
|
||||
if (onTabSelect && !element.is(":hidden")) {
|
||||
onTabSelect();
|
||||
}
|
||||
};
|
||||
|
||||
scope.isHidden = function () {
|
||||
var tab = findTab();
|
||||
return !tab.selected || isHidden.call(scope);
|
||||
};
|
||||
|
||||
scope.$watch("attr('title')", function tabTitleWatch(value) {
|
||||
var tab = findTab();
|
||||
tab.title = value;
|
||||
});
|
||||
|
||||
scope.$watch("attr('hidden')", function tabHiddenWatch(hidden, old) {
|
||||
scope.$evalAsync(function () {
|
||||
if (hidden) {
|
||||
return scope.hideTab(index);
|
||||
}
|
||||
return scope.showTab(index);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
234
sophal/js/form/form.converters.js
Normal file
234
sophal/js/form/form.converters.js
Normal file
@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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 currencySymbols = {
|
||||
en: '\u0024',
|
||||
fr: '\u20AC'
|
||||
};
|
||||
|
||||
var thousandSeparator = {
|
||||
en: ',',
|
||||
fr: ' '
|
||||
};
|
||||
|
||||
function addCurrency(value, symbol) {
|
||||
if (value && symbol) {
|
||||
var val = '' + value;
|
||||
if (axelor.config['user.lang'] === 'fr' ) {
|
||||
return val.endsWith(symbol) ? val : val + ' ' + symbol;
|
||||
}
|
||||
return val.startsWith(symbol) ? val : symbol + val;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function canSetNested(record, name) {
|
||||
if (record && name && name in record) {
|
||||
return true;
|
||||
}
|
||||
if (name) {
|
||||
var path = name.split('.');
|
||||
var val = record || {};
|
||||
var idx = 0;
|
||||
while (idx < path.length - 1) {
|
||||
val = val[path[idx++]];
|
||||
if (!val) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function findNested(record, name) {
|
||||
if (record && name && name in record) {
|
||||
return record[name] === undefined ? null : record[name];
|
||||
}
|
||||
if (name) {
|
||||
var path = name.split('.');
|
||||
var val = record || {};
|
||||
var idx = 0;
|
||||
while (val && idx < path.length) {
|
||||
val = val[path[idx++]];
|
||||
}
|
||||
if (idx === path.length) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function setNested(record, name, value) {
|
||||
if (!record || !name) return record;
|
||||
var path = name.split('.');
|
||||
var nested = record;
|
||||
var idx = -1;
|
||||
while (++idx < path.length) {
|
||||
var key = path[idx];
|
||||
if (idx !== path.length - 1) {
|
||||
nested = nested[key] || (nested[key] = {});
|
||||
} else {
|
||||
nested[key] = value;
|
||||
}
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
// override angular.js currency filter
|
||||
ui.filter('currency', function () {
|
||||
return addCurrency;
|
||||
});
|
||||
|
||||
function formatNumber(field, value, scale) {
|
||||
var num = +(value);
|
||||
if ((value === null || value === undefined) && !field.defaultValue) {
|
||||
return value;
|
||||
}
|
||||
if (num === 0 || num) {
|
||||
var lang = axelor.config['user.lang'];
|
||||
var tsep = thousandSeparator[lang] || thousandSeparator.en;
|
||||
return _.numberFormat(num, scale, '.', tsep);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
ui.findNested = findNested;
|
||||
ui.setNested = setNested;
|
||||
ui.canSetNested = canSetNested;
|
||||
|
||||
ui.formatters = {
|
||||
|
||||
"string": function(field, value, context) {
|
||||
if (field.translatable && value && context) {
|
||||
var key = '$t:' + field.name;
|
||||
return context[key] || value;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
"integer": function(field, value) {
|
||||
return formatNumber(field, value);
|
||||
},
|
||||
|
||||
"decimal": function(field, value, context) {
|
||||
var scale = (field.widgetAttrs||{}).scale || field.scale || 2;
|
||||
var currency = (field.widgetAttrs||{}).currency || field.currency;
|
||||
|
||||
var text = formatNumber(field, value, scale);
|
||||
if (text && currency) {
|
||||
text = addCurrency(text, findNested(context, currency));
|
||||
}
|
||||
return text;
|
||||
},
|
||||
|
||||
"boolean": function(field, value) {
|
||||
return value;
|
||||
},
|
||||
|
||||
"duration": function(field, value) {
|
||||
return ui.formatDuration(field, value);
|
||||
},
|
||||
|
||||
"date": function(field, value) {
|
||||
return value ? moment(value).format('DD/MM/YYYY') : "";
|
||||
},
|
||||
|
||||
"time": function(field, value) {
|
||||
return value ? value : "";
|
||||
},
|
||||
|
||||
"datetime": function(field, value) {
|
||||
return value ? moment(value).format('DD/MM/YYYY HH:mm') : "";
|
||||
},
|
||||
|
||||
"many-to-one": function(field, value) {
|
||||
return value
|
||||
? (field.targetName ? value[field.targetName] : (value.name || value.code || value.id || ""))
|
||||
: "";
|
||||
},
|
||||
|
||||
"one-to-many": function(field, value) {
|
||||
return value ? '(' + value.length + ')' : "";
|
||||
},
|
||||
|
||||
"many-to-many": function(field, value) {
|
||||
return value ? '(' + value.length + ')' : "";
|
||||
},
|
||||
|
||||
"selection": function(field, value) {
|
||||
var cmp = field.type === "integer" ? function(a, b) { return a == b ; } : _.isEqual;
|
||||
var res = _.find(field.selectionList, function(item){
|
||||
return cmp(item.value, value);
|
||||
}) || {};
|
||||
return res.title;
|
||||
}
|
||||
};
|
||||
|
||||
ui.formatters["enum"] = ui.formatters.selection;
|
||||
|
||||
function findField(scope, name) {
|
||||
if (scope.field && scope.field.target) {
|
||||
return ((scope.field.viewer||{}).fields||{})[name]
|
||||
|| ((scope.field.editor||{}).fields||{})[name];
|
||||
}
|
||||
return (scope.viewItems || scope.fields || {})[name];
|
||||
}
|
||||
|
||||
ui.formatters.$image = function (scope, fieldName, imageName) {
|
||||
var record = scope.record || {};
|
||||
var model = scope._model;
|
||||
|
||||
if (fieldName) {
|
||||
var field = (scope.fields||{})[fieldName];
|
||||
if (field && field.target) {
|
||||
record = record[fieldName] || {};
|
||||
model = field.target;
|
||||
}
|
||||
}
|
||||
|
||||
var v = record.version || record.$version || 0;
|
||||
var n = record.id;
|
||||
if (n > 0) {
|
||||
return "ws/rest/" + model + "/" + n + "/" + imageName + "/download?image=true&v=" + v
|
||||
+ "&parentId=" + scope.record.id + "&parentModel=" + scope._model;
|
||||
}
|
||||
return "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
||||
};
|
||||
|
||||
ui.formatters.$fmt = function (scope, fieldName, fieldValue, record) {
|
||||
var context = record || scope.record || {};
|
||||
var value = arguments.length === 2 ? context[fieldName] : fieldValue;
|
||||
if (value === undefined || value === null) {
|
||||
return "";
|
||||
}
|
||||
var field = findField(scope, fieldName);
|
||||
if (!field) {
|
||||
return value;
|
||||
}
|
||||
var type = field.selection ? "selection" : field.type;
|
||||
var formatter = ui.formatters[type];
|
||||
if (formatter) {
|
||||
return formatter(field, value, context);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
})();
|
||||
447
sophal/js/form/form.input.binary.js
Normal file
447
sophal/js/form/form.input.binary.js
Normal file
@ -0,0 +1,447 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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 BLANK = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
||||
var META_FILE = "com.axelor.meta.db.MetaFile";
|
||||
var META_JSON_RECORD = "com.axelor.meta.db.MetaJsonRecord";
|
||||
|
||||
function makeURL(model, field, recordOrId, version, scope) {
|
||||
var value = recordOrId;
|
||||
if (!value) return null;
|
||||
var id = value.id ? value.id : value;
|
||||
var ver = version;
|
||||
if (ver === undefined || ver === null) ver = value.version;
|
||||
if (ver === undefined || ver === null) ver = value.$version;
|
||||
if (ver === undefined || ver === null) ver = (new Date()).getTime();
|
||||
if (!id || id <= 0) return null;
|
||||
var url = "ws/rest/" + model + "/" + id + "/" + field + "/download?v=" + ver;
|
||||
if (scope && scope.record) {
|
||||
var parentId = scope.record.id;
|
||||
if (!parentId && scope.field && scope._jsonContext && scope._jsonContext.$record) {
|
||||
parentId = scope._jsonContext.$record.id;
|
||||
}
|
||||
url += "&parentId=" + parentId + "&parentModel=" + scope._model;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
ui.makeImageURL = makeURL;
|
||||
|
||||
ui.formInput('ImageLink', {
|
||||
css: 'image-item',
|
||||
cssClass: 'from-item image-item',
|
||||
metaWidget: true,
|
||||
controller: ['$scope', '$element', '$interpolate', function($scope, $element, $interpolate) {
|
||||
|
||||
$scope.parseText = function(text) {
|
||||
if (!text) return BLANK;
|
||||
if (!text.match(/{{.*?}}/)) {
|
||||
return text;
|
||||
}
|
||||
return $interpolate(text)($scope.record);
|
||||
};
|
||||
}],
|
||||
|
||||
init: function(scope) {
|
||||
var field = scope.field;
|
||||
|
||||
var width = field.width || 140;
|
||||
var height = field.height || '100%';
|
||||
|
||||
scope.styles = [{
|
||||
'width': width,
|
||||
'max-width': '100%',
|
||||
'max-height': '100%'
|
||||
}, {
|
||||
'width': width,
|
||||
'height': height,
|
||||
'max-width': '100%',
|
||||
'max-height': '100%'
|
||||
}];
|
||||
|
||||
if (field.noframe) {
|
||||
_.extend(scope.styles[1], {
|
||||
border: 0,
|
||||
padding: 0,
|
||||
background: 'none',
|
||||
boxShadow: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
link_readonly: function(scope, element, attrs, model) {
|
||||
|
||||
var image = element.children('img:first');
|
||||
var update = scope.$render_readonly = function () {
|
||||
if (scope.isReadonly()) {
|
||||
image.get(0).src = scope.parseText(model.$viewValue) || BLANK;
|
||||
}
|
||||
};
|
||||
|
||||
scope.$watch("record.id", update);
|
||||
scope.$watch("record.version", update);
|
||||
scope.$watch("isReadonly()", update);
|
||||
},
|
||||
template_editable: '<input type="text">',
|
||||
template_readonly:
|
||||
'<div ng-style="styles[0]">'+
|
||||
'<img class="img-polaroid" ng-style="styles[1]">'+
|
||||
'</div>'
|
||||
});
|
||||
|
||||
ui.formInput('Image', 'ImageLink', {
|
||||
|
||||
init: function(scope) {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
var field = scope.field;
|
||||
var isBinary = field.serverType === 'binary';
|
||||
|
||||
if (!isBinary && field.target !== META_FILE) {
|
||||
throw new Error("Invalid field type for Image widget.");
|
||||
}
|
||||
|
||||
scope.parseText = function (value) {
|
||||
return scope.getLink(value);
|
||||
};
|
||||
|
||||
scope.getLink = function (value) {
|
||||
var record = scope.record || {};
|
||||
var model = scope._model;
|
||||
if (value === null) return BLANK;
|
||||
if (isBinary) {
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
if (record.id) {
|
||||
return makeURL(model, field.name, record, undefined, scope) + "&image=true";
|
||||
}
|
||||
return BLANK;
|
||||
}
|
||||
return value ? makeURL(META_FILE, "content", (value.id || value), value.version || value.$version, scope) : BLANK;
|
||||
};
|
||||
},
|
||||
|
||||
link_editable: function(scope, element, attrs, model) {
|
||||
|
||||
var field = scope.field;
|
||||
var input = element.children('input:first');
|
||||
var image = element.children('img:first');
|
||||
var buttons = element.children('.btn-group');
|
||||
|
||||
var isBinary = field.serverType === 'binary';
|
||||
var timer = null;
|
||||
|
||||
input.add(buttons).hide();
|
||||
element.on('mouseenter', function (e) {
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(function () {
|
||||
buttons.slideDown();
|
||||
}, 500);
|
||||
});
|
||||
element.on('mouseleave', function (e) {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}
|
||||
buttons.slideUp();
|
||||
});
|
||||
|
||||
scope.doSelect = function() {
|
||||
input.click();
|
||||
};
|
||||
|
||||
scope.doSave = function() {
|
||||
var content = image.get(0).src;
|
||||
if (content) {
|
||||
window.open(content);
|
||||
}
|
||||
};
|
||||
|
||||
scope.doRemove = function() {
|
||||
image.get(0).src = "";
|
||||
input.val(null);
|
||||
update(null);
|
||||
};
|
||||
|
||||
input.change(function(e, ui) {
|
||||
var file = input.get(0).files[0];
|
||||
var uploadSize = +(axelor.config["file.upload.size"]) || 0;
|
||||
|
||||
// reset file selection
|
||||
input.get(0).value = null;
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(uploadSize > 0 && file.size > 1048576 * uploadSize) {
|
||||
return axelor.dialogs.say(_t("You are not allow to upload a file bigger than") + ' ' + uploadSize + 'MB');
|
||||
}
|
||||
|
||||
if (!isBinary) {
|
||||
return doUpload(file);
|
||||
}
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
update(e.target.result, file.name);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
function doUpload(file) {
|
||||
var ds = scope._dataSource._new(META_FILE);
|
||||
var value = field.target === META_FILE ? (scope.getValue()||{}) : {};
|
||||
var record = {
|
||||
fileName: file.name,
|
||||
fileType: file.type,
|
||||
fileSize: file.size,
|
||||
id: value.id,
|
||||
version: value.version || value.$version
|
||||
};
|
||||
|
||||
record.$upload = {
|
||||
file: file
|
||||
};
|
||||
|
||||
ds.save(record).success(function (saved) {
|
||||
update(saved);
|
||||
});
|
||||
}
|
||||
|
||||
function doUpdate(value) {
|
||||
image.get(0).src = scope.getLink(value);
|
||||
model.$setViewValue(getData(value));
|
||||
}
|
||||
|
||||
function update(value) {
|
||||
scope.$applyAsync(function() {
|
||||
doUpdate(value);
|
||||
});
|
||||
}
|
||||
|
||||
function getData(value) {
|
||||
if (!value || isBinary) {
|
||||
return value;
|
||||
}
|
||||
return {
|
||||
id: value.id
|
||||
};
|
||||
}
|
||||
|
||||
var updateLink = scope.$render_editable = function() {
|
||||
image.get(0).src = scope.getLink(model.$viewValue);
|
||||
};
|
||||
|
||||
scope.$watch("record.id", updateLink);
|
||||
scope.$watch("record.version", updateLink);
|
||||
|
||||
if (field.accept) {
|
||||
input.attr('accept', field.accept);
|
||||
}
|
||||
},
|
||||
template_editable:
|
||||
'<div ng-style="styles[0]" class="image-wrapper">' +
|
||||
'<input type="file" accept="image/*">' +
|
||||
'<img class="img-polaroid" ng-style="styles[1]" style="display: inline-block;">' +
|
||||
'<div class="btn-group">' +
|
||||
'<button ng-click="doSelect()" class="btn" type="button"><i class="fa fa-arrow-circle-up"></i></button>' +
|
||||
'<button ng-click="doRemove()" class="btn" type="button"><i class="fa fa-times"></i></button>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
});
|
||||
|
||||
ui.formInput('Binary', {
|
||||
|
||||
css: 'file-item',
|
||||
cellCss: 'form-item file-item',
|
||||
|
||||
link: function(scope, element, attrs, model) {
|
||||
|
||||
var field = scope.field;
|
||||
var input = element.children('input:first').hide();
|
||||
|
||||
scope.doSelect = function() {
|
||||
input.click();
|
||||
};
|
||||
|
||||
scope.doSave = function() {
|
||||
var record = scope.record;
|
||||
var model = scope._model;
|
||||
var url = makeURL(model, field.name, record, undefined, scope);
|
||||
ui.download(url, record.fileName || field.name);
|
||||
};
|
||||
|
||||
scope.doRemove = function() {
|
||||
var record = scope.record;
|
||||
input.val(null);
|
||||
model.$setViewValue(null);
|
||||
record.$upload = null;
|
||||
if(scope._model === META_FILE) {
|
||||
record.fileName = null;
|
||||
record.fileType = null;
|
||||
}
|
||||
record.fileSize = null;
|
||||
};
|
||||
|
||||
scope.canDownload = function() {
|
||||
var record = scope.record || {};
|
||||
if (!record.id) return false;
|
||||
if (scope._model === META_FILE) {
|
||||
return !!record.fileName;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
input.change(function(e) {
|
||||
var file = input.get(0).files[0];
|
||||
var record = scope.record;
|
||||
|
||||
// reset file selection
|
||||
input.get(0).value = null;
|
||||
|
||||
if (file) {
|
||||
record.$upload = {
|
||||
field: field.name,
|
||||
file: file
|
||||
};
|
||||
if(scope._model === META_FILE) {
|
||||
record.fileName = file.name;
|
||||
}
|
||||
scope.$applyAsync(function() {
|
||||
record.fileType = file.type;
|
||||
record.fileSize = file.size;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (field.accept) {
|
||||
input.attr('accept', field.accept);
|
||||
}
|
||||
},
|
||||
template_readonly: null,
|
||||
template_editable: null,
|
||||
template:
|
||||
'<div>' +
|
||||
'<input type="file">' +
|
||||
'<div class="btn-group">' +
|
||||
'<button ng-click="doSelect()" ng-show="!isReadonly()" class="btn" type="button"><i class="fa fa-arrow-circle-up"></i></button>' +
|
||||
'<button ng-click="doSave()" ng-show="canDownload()" class="btn" type="button"><i class="fa fa-arrow-circle-down"></i></button>' +
|
||||
'<button ng-click="doRemove()" ng-show="!isReadonly()" class="btn" type="button"><i class="fa fa-times"></i></button>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
});
|
||||
|
||||
ui.formInput('BinaryLink', {
|
||||
|
||||
css: 'file-item',
|
||||
cellCss: 'form-item file-item',
|
||||
metaWidget: true,
|
||||
|
||||
link: function(scope, element, attrs, model) {
|
||||
|
||||
var field = scope.field;
|
||||
var input = element.children('input:first').hide();
|
||||
|
||||
if (field.target !== META_FILE) {
|
||||
throw new Error("BinaryLink widget can be used with MetaFile field only.");
|
||||
}
|
||||
|
||||
scope.doSelect = function() {
|
||||
input.click();
|
||||
};
|
||||
|
||||
scope.doRemove = function() {
|
||||
input.val(null);
|
||||
scope.setValue(null, true);
|
||||
};
|
||||
|
||||
scope.canDownload = function() {
|
||||
var value = model.$viewValue;
|
||||
return value && value.id > 0;
|
||||
};
|
||||
|
||||
scope.format = function (value) {
|
||||
if (value) {
|
||||
return value.fileName;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
scope.doSave = function() {
|
||||
var value = model.$viewValue;
|
||||
var version = value ? (value.version || value.$version) : undefined;
|
||||
var url = makeURL(META_FILE, "content", value, version, scope);
|
||||
ui.download(url, scope.text);
|
||||
};
|
||||
|
||||
input.change(function(e) {
|
||||
var file = input.get(0).files[0];
|
||||
|
||||
// reset file selection
|
||||
input.get(0).value = null;
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
var ds = scope._dataSource._new(META_FILE);
|
||||
var value = scope.getValue() || {};
|
||||
var record = _.extend({
|
||||
fileName: file.name,
|
||||
fileType: file.type,
|
||||
fileSize: file.size
|
||||
}, {
|
||||
id: value.id,
|
||||
version: value.version || value.$version
|
||||
});
|
||||
|
||||
record.$upload = {
|
||||
file: file
|
||||
};
|
||||
|
||||
ds.save(record).success(function (rec) {
|
||||
scope.setValue(rec, true);
|
||||
});
|
||||
});
|
||||
|
||||
if (field.accept) {
|
||||
input.attr('accept', field.accept);
|
||||
}
|
||||
},
|
||||
template_readonly: null,
|
||||
template_editable: null,
|
||||
template:
|
||||
'<div>' +
|
||||
'<input type="file">' +
|
||||
'<div class="btn-group">' +
|
||||
'<button ng-click="doSelect()" ng-show="!isReadonly()" class="btn" type="button"><i class="fa fa-arrow-circle-up"></i></button>' +
|
||||
'<button ng-click="doRemove()" ng-show="canDownload() && !isReadonly()" class="btn" type="button"><i class="fa fa-times"></i></button>' +
|
||||
'</div> ' +
|
||||
'<a ng-show="text" href="javascript:" ng-click="doSave()">{{text}}</a>' +
|
||||
'</div>'
|
||||
});
|
||||
|
||||
})();
|
||||
228
sophal/js/form/form.input.boolean.js
Normal file
228
sophal/js/form/form.input.boolean.js
Normal file
@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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');
|
||||
|
||||
/**
|
||||
* The Boolean input widget.
|
||||
*/
|
||||
ui.formInput('Boolean', {
|
||||
|
||||
css: 'boolean-item',
|
||||
|
||||
cellCss: 'form-item boolean-item',
|
||||
|
||||
link: function (scope, element, attrs, model) {
|
||||
|
||||
element.on('click', 'input:not(.no-toggle)', function (e) {
|
||||
var value = $(e.target).data('value');
|
||||
var checked = value === undefined ? e.target.checked : value;
|
||||
scope.setValue(checked, true);
|
||||
});
|
||||
|
||||
Object.defineProperty(scope, '$value', {
|
||||
get: function () {
|
||||
return model.$viewValue;
|
||||
},
|
||||
set: function(value) {
|
||||
model.$setViewValue(value);
|
||||
}
|
||||
});
|
||||
},
|
||||
template_readonly: null,
|
||||
template_editable:
|
||||
"<label class='ibox'>" +
|
||||
"<input type='checkbox' ng-model='$value' ng-disabled='isReadonly()'>" +
|
||||
"<span class='box'></span>" +
|
||||
"</label>"
|
||||
});
|
||||
|
||||
/**
|
||||
* The Boolean widget with label on right.
|
||||
*/
|
||||
ui.formInput('InlineCheckbox', 'Boolean', {
|
||||
css: 'checkbox-inline',
|
||||
metaWidget: true,
|
||||
showTitle: false,
|
||||
link: function (scope, element, attrs, model) {
|
||||
this._super.apply(this, arguments);
|
||||
scope.$watch('attr("title")', function booleanTitleWatch(title) {
|
||||
scope.label = title;
|
||||
});
|
||||
},
|
||||
template_readonly: null,
|
||||
template_editable:
|
||||
"<label class='ibox'>" +
|
||||
"<input type='checkbox' ng-model='$value' ng-disabled='isReadonly()'>" +
|
||||
"<div class='box'></div>" +
|
||||
"<span class='title' ui-help-popover>{{label}}</span>" +
|
||||
"</label>"
|
||||
});
|
||||
|
||||
ui.formInput('Toggle', 'Boolean', {
|
||||
cellCss: 'form-item toggle-item',
|
||||
metaWidget: true,
|
||||
link: function (scope, element, attrs, model) {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
var field = scope.field;
|
||||
var icon = element.find('i');
|
||||
|
||||
scope.icon = function () {
|
||||
return model.$viewValue && field.iconActive ? field.iconActive : field.icon;
|
||||
};
|
||||
|
||||
scope.toggle = function () {
|
||||
var value = !model.$viewValue;
|
||||
if (scope.setExclusive && field.exclusive) {
|
||||
scope.setExclusive(field.name, scope.record);
|
||||
}
|
||||
scope.setValue(value, true);
|
||||
};
|
||||
|
||||
if (field.help || field.title) {
|
||||
element.attr('title', field.help || field.title);
|
||||
}
|
||||
},
|
||||
template_readonly: null,
|
||||
template_editable:
|
||||
"<button tabindex='-1' class='btn btn-default' ng-class='{active: $value}' ng-click='toggle()'>" +
|
||||
"<i class='fa {{icon()}}'></i>" +
|
||||
"</button>"
|
||||
});
|
||||
|
||||
ui.formInput('BooleanSelect', 'Boolean', {
|
||||
css: 'form-item boolean-select-item',
|
||||
metaWidget: true,
|
||||
init: function (scope) {
|
||||
var field = scope.field;
|
||||
var trueText = _t((field.widgetAttrs||{}).trueText) || _t('Yes');
|
||||
var falseText = _t((field.widgetAttrs||{}).falseText) || _t('No');
|
||||
|
||||
scope.$items = [trueText, falseText];
|
||||
scope.$selection = [{ value: trueText, val: true}, { value: falseText, val: false }];
|
||||
if (field.nullable) {
|
||||
scope.$selection.unshift({ value: '', val: null });
|
||||
}
|
||||
scope.format = function (value) {
|
||||
if (field.nullable && (value === null || value === undefined)) {
|
||||
return "";
|
||||
}
|
||||
return value ? scope.$items[0] : scope.$items[1];
|
||||
};
|
||||
},
|
||||
link_editable: function (scope, element, attrs, model) {
|
||||
var input = element.find('input');
|
||||
var items = scope.$items;
|
||||
|
||||
input.autocomplete({
|
||||
minLength: 0,
|
||||
source: scope.$selection,
|
||||
select: function (e, u) {
|
||||
scope.setValue(u.item.val, true);
|
||||
scope.$applyAsync();
|
||||
}
|
||||
}).click(function (e) {
|
||||
input.autocomplete("search" , '');
|
||||
});
|
||||
|
||||
scope.doShowSelect = function () {
|
||||
input.autocomplete("search" , '');
|
||||
};
|
||||
|
||||
scope.$render_editable = function () {
|
||||
var value = model.$viewValue;
|
||||
var text = scope.format(value);
|
||||
input.val(text);
|
||||
};
|
||||
|
||||
scope.$watch('isReadonly()', function booleanReadonlyWatch(readonly) {
|
||||
input.autocomplete(readonly ? "disable" : "enable");
|
||||
input.toggleClass('not-readonly', !readonly);
|
||||
});
|
||||
},
|
||||
template: "<span class='form-item-container'></span>",
|
||||
template_readonly: '<span>{{text}}</span>',
|
||||
template_editable: "<span class='picker-input'>" +
|
||||
"<input type='text' readonly='readonly' class='no-toggle'>" +
|
||||
"<span class='picker-icons picker-icons-1'>" +
|
||||
"<i class='fa fa-caret-down' ng-click='doShowSelect()'></i>" +
|
||||
"</span>" +
|
||||
"</span>"
|
||||
});
|
||||
|
||||
ui.formInput('BooleanRadio', 'BooleanSelect', {
|
||||
css: 'form-item boolean-radio-item',
|
||||
metaWidget: true,
|
||||
link_editable: function (scope, element, attrs, model) {
|
||||
var field = scope.field;
|
||||
|
||||
if (field.direction === "vertical") {
|
||||
element.addClass("boolean-radio-vertical");
|
||||
}
|
||||
|
||||
var inputName = _.uniqueId('boolean-radio');
|
||||
var trueInput = $('<input type="radio" data-value="true" name="' + inputName + '">');
|
||||
var falseInput = $('<input type="radio" data-value="false" name="' + inputName + '">');
|
||||
|
||||
var items = scope.$items;
|
||||
|
||||
$('<li>').append(
|
||||
$('<label class="ibox round">')
|
||||
.append(trueInput)
|
||||
.append($('<i class="box">'))
|
||||
.append($('<span class="title">').text(items[0]))
|
||||
).appendTo(element);
|
||||
|
||||
$('<li>').append(
|
||||
$('<label class="ibox round">')
|
||||
.append(falseInput)
|
||||
.append($('<i class="box">'))
|
||||
.append($('<span class="title">').text(items[1]))
|
||||
).appendTo(element);
|
||||
|
||||
scope.$render_editable = function () {
|
||||
var value = model.$viewValue || false;
|
||||
var input = value ? trueInput : falseInput;
|
||||
input.prop('checked', true);
|
||||
};
|
||||
|
||||
element.on('change', 'input', function (e) {
|
||||
var value = $(this).data('value') === true;
|
||||
scope.setValue(value, true);
|
||||
scope.$applyAsync();
|
||||
});
|
||||
},
|
||||
template_editable: "<ul class='boolean-radio'></ul>"
|
||||
});
|
||||
|
||||
ui.formInput('BooleanSwitch', 'Boolean', {
|
||||
css: 'form-item',
|
||||
metaWidget: true,
|
||||
template_readonly: null,
|
||||
template_editable:
|
||||
"<label class='iswitch'>" +
|
||||
"<input type='checkbox' ng-model='$value' ng-disabled='isReadonly()'>" +
|
||||
"<span class='box'></span>" +
|
||||
"</label>"
|
||||
});
|
||||
|
||||
})();
|
||||
635
sophal/js/form/form.input.datetime.js
Normal file
635
sophal/js/form/form.input.datetime.js
Normal file
@ -0,0 +1,635 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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 regional = {
|
||||
monthNames: [
|
||||
_t('January'),
|
||||
_t('February'),
|
||||
_t('March'),
|
||||
_t('April'),
|
||||
_t('May'),
|
||||
_t('June'),
|
||||
_t('July'),
|
||||
_t('August'),
|
||||
_t('September'),
|
||||
_t('October'),
|
||||
_t('November'),
|
||||
_t('December')],
|
||||
|
||||
monthNamesShort: [
|
||||
_t('Jan'),
|
||||
_t('Feb'),
|
||||
_t('Mar'),
|
||||
_t('Apr'),
|
||||
_t('May'),
|
||||
_t('Jun'),
|
||||
_t('Jul'),
|
||||
_t('Aug'),
|
||||
_t('Sep'),
|
||||
_t('Oct'),
|
||||
_t('Nov'),
|
||||
_t('Dec')],
|
||||
|
||||
dayNames: [
|
||||
_t('Sunday'),
|
||||
_t('Monday'),
|
||||
_t('Tuesday'),
|
||||
_t('Wednesday'),
|
||||
_t('Thursday'),
|
||||
_t('Friday'),
|
||||
_t('Saturday')],
|
||||
|
||||
dayNamesShort : [_t('Sun'), _t('Mon'), _t('Tue'), _t('Wed'), _t('Thu'), _t('Fri'), _t('Sat')],
|
||||
dayNamesMin : [_t('Su'), _t('Mo'), _t('Tu'), _t('We'), _t('Th'), _t('Fr'), _t('Sa')],
|
||||
weekHeader : _t('Wk'),
|
||||
hourText : _t('Hour'),
|
||||
minuteText : _t('Minute'),
|
||||
secondText : _t('Second'),
|
||||
currentText : _t('Now'),
|
||||
closeText : _t('Done'),
|
||||
prevText : _t('Prev'),
|
||||
nextText : _t('Next'),
|
||||
firstDay: 1
|
||||
};
|
||||
|
||||
// configure datepicker
|
||||
$.timepicker.setDefaults(regional);
|
||||
$.datepicker.setDefaults(regional);
|
||||
|
||||
// configure moment.js
|
||||
moment.locale('en', {
|
||||
months: regional.monthNames,
|
||||
monthsShort: regional.monthNamesShort,
|
||||
weekdays: regional.dayNames,
|
||||
weekdaysShort: regional.dayNamesShort,
|
||||
weekdaysMin: regional.dayNamesMin,
|
||||
calendar : {
|
||||
sameDay : _t('[Today at] LT'),
|
||||
nextDay : _t('[Tomorrow at] LT'),
|
||||
nextWeek : _t('dddd [at] LT'),
|
||||
lastDay : _t('[Yesterday at] LT'),
|
||||
lastWeek : _t('[Last] dddd [at] LT'),
|
||||
sameElse : _t('L')
|
||||
},
|
||||
relativeTime : {
|
||||
future : _t("in %s"),
|
||||
past : _t("%s ago"),
|
||||
s : _t("a few seconds"),
|
||||
m : _t("a minute"),
|
||||
mm : _t("%d minutes"),
|
||||
h : _t("an hour"),
|
||||
hh : _t("%d hours"),
|
||||
d : _t("a day"),
|
||||
dd : _t("%d days"),
|
||||
M : _t("a month"),
|
||||
MM : _t("%d months"),
|
||||
y : _t("a year"),
|
||||
yy : _t("%d years")
|
||||
}
|
||||
});
|
||||
|
||||
// configure ui.mask
|
||||
function createTwoDigitDefinition( maximum ) {
|
||||
return function( value ) {
|
||||
var number = parseInt( value, 10 );
|
||||
|
||||
if (value === "" || /\D/.test(value) || _.isNaN(number)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// pad to 2 characters
|
||||
number = ( number < 10 ? "0" : "" ) + number;
|
||||
if ( number <= maximum ) {
|
||||
return number;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function yearsDefinition( value ) {
|
||||
var temp;
|
||||
|
||||
// if the value is empty, or contains a non-digit, it is invalid
|
||||
if ( value === "" || /\D/.test( value ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
temp = parseInt( value, 10 );
|
||||
|
||||
// convert 2 digit years to 4 digits, this allows us to type 80 <right>
|
||||
if ( value.length <= 2 ) {
|
||||
// before "32" we assume 2000's otherwise 1900's
|
||||
if ( temp < 10 ) {
|
||||
return "200" + temp;
|
||||
} else if ( temp < 32 ) {
|
||||
return "20" + temp;
|
||||
} else {
|
||||
return "19" + temp;
|
||||
}
|
||||
}
|
||||
if ( temp === 0 ) {
|
||||
return "2000";
|
||||
}
|
||||
if ( value.length === 3 ) {
|
||||
return "0"+value;
|
||||
}
|
||||
if ( value.length === 4 ) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
$.extend($.ui.mask.prototype.options.definitions, {
|
||||
"MM": createTwoDigitDefinition( 12 ),
|
||||
"DD": createTwoDigitDefinition( 31 ),
|
||||
"YYYY": yearsDefinition,
|
||||
"HH": createTwoDigitDefinition( 23 ),
|
||||
"mm": createTwoDigitDefinition( 59 )
|
||||
});
|
||||
|
||||
// datepicker keyboad navigation hack
|
||||
var _doKeyDown = $.datepicker._doKeyDown;
|
||||
$.extend($.datepicker, {
|
||||
_doKeyDown: function(event) {
|
||||
var inst = $.datepicker._getInst(event.target),
|
||||
handled = false;
|
||||
inst._keyEvent = true;
|
||||
if ($.datepicker._datepickerShowing) {
|
||||
switch (event.keyCode) {
|
||||
case 36: // home
|
||||
$.datepicker._gotoToday(event.target);
|
||||
handled = true;
|
||||
break;
|
||||
case 37: // left
|
||||
$.datepicker._adjustDate(event.target, -1, "D");
|
||||
handled = true;
|
||||
break;
|
||||
case 38: // up
|
||||
$.datepicker._adjustDate(event.target, -7, "D");
|
||||
handled = true;
|
||||
break;
|
||||
case 39: // right
|
||||
$.datepicker._adjustDate(event.target, +1, "D");
|
||||
handled = true;
|
||||
break;
|
||||
case 40: // down
|
||||
$.datepicker._adjustDate(event.target, +7, "D");
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
if (handled) {
|
||||
event.ctrlKey = true;
|
||||
}
|
||||
} else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home
|
||||
$.datepicker._showDatepicker(this);
|
||||
handled = true;
|
||||
}
|
||||
if (handled) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
} else {
|
||||
_doKeyDown(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var _updateDatepicker = $.datepicker._updateDatepicker;
|
||||
$.datepicker._updateDatepicker = function(inst) {
|
||||
if (!$.datepicker._noUpdate) {
|
||||
return _updateDatepicker.call($.datepicker, inst);
|
||||
}
|
||||
};
|
||||
|
||||
var _parseDate = $.datepicker.parseDate;
|
||||
$.datepicker.parseDate = function (format, value, settings) {
|
||||
if (_.isString(value) && value.indexOf('_') > -1) {
|
||||
return;
|
||||
}
|
||||
return _parseDate.call($.datepicker, format, value, settings);
|
||||
};
|
||||
|
||||
// suppress annoying logs
|
||||
$.timepicker.log = function () {};
|
||||
|
||||
/**
|
||||
* The DateTime input widget.
|
||||
*/
|
||||
ui.formInput('DateTime', {
|
||||
|
||||
css : 'datetime-item',
|
||||
|
||||
format: 'DD/MM/YYYY HH:mm',
|
||||
mask: 'DD/MM/YYYY HH:mm',
|
||||
|
||||
widgets: ['Datetime'],
|
||||
|
||||
init: function(scope) {
|
||||
|
||||
var isDate = this.isDate,
|
||||
format = this.format;
|
||||
|
||||
scope.parse = function(value) {
|
||||
if (angular.isDate(value)) {
|
||||
return isDate ? moment(value).startOf('day').format('YYYY-MM-DD') : value.toISOString();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
scope.format = function(value) {
|
||||
if (value) {
|
||||
return moment(value).format(format);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
},
|
||||
|
||||
link_editable: function(scope, element, attrs, model) {
|
||||
var input = element.children('input:first');
|
||||
var button = element.find('i:first');
|
||||
var onChange = scope.$events.onChange;
|
||||
var props = scope.field;
|
||||
var isDate = this.isDate;
|
||||
var isShowing = false;
|
||||
var lastValue = null;
|
||||
|
||||
var options = {
|
||||
dateFormat: 'dd/mm/yy',
|
||||
showButtonsPanel: false,
|
||||
showTime: false,
|
||||
showOn: null,
|
||||
beforeShow: function (e, ui) {
|
||||
lastValue = input.mask("value") || '';
|
||||
isShowing = true;
|
||||
},
|
||||
onClose: function (e, ui) {
|
||||
lastValue = null;
|
||||
isShowing = false;
|
||||
},
|
||||
onSelect: function(dateText, inst) {
|
||||
input.mask('value', dateText);
|
||||
updateModel();
|
||||
if (!inst.timeDefined) {
|
||||
input.datetimepicker('hide');
|
||||
setTimeout(function(){
|
||||
input.focus().select();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (this.isDate) {
|
||||
options.showTimepicker = false;
|
||||
}
|
||||
|
||||
input.datetimepicker(options);
|
||||
input.mask({
|
||||
mask: this.mask
|
||||
});
|
||||
|
||||
var changed = false;
|
||||
var rendering = false;
|
||||
|
||||
input.on('change', function(e, ui){
|
||||
if (changed) return;
|
||||
if (isShowing) {
|
||||
changed = lastValue !== (input.mask("value") || '');
|
||||
} else {
|
||||
changed = !rendering;
|
||||
}
|
||||
});
|
||||
input.on('blur', function() {
|
||||
if (changed) {
|
||||
changed = false;
|
||||
updateModel();
|
||||
}
|
||||
});
|
||||
input.on('grid:check', function () {
|
||||
updateModel();
|
||||
});
|
||||
input.on('keydown', function(e){
|
||||
|
||||
if (e.isDefaultPrevented()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.keyCode === $.ui.keyCode.DOWN) {
|
||||
input.datetimepicker('show');
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
if (e.keyCode === $.ui.keyCode.ENTER && $(this).datepicker("widget").is(':visible')) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
if (e.keyCode === $.ui.keyCode.ENTER || e.keyCode === $.ui.keyCode.TAB) {
|
||||
if (changed) updateModel();
|
||||
}
|
||||
});
|
||||
button.click(function(e, ui){
|
||||
if (scope.isReadonly()) {
|
||||
return;
|
||||
}
|
||||
var mobile = axelor.device.mobile;
|
||||
if (mobile) {
|
||||
input.attr('disabled', 'disabled')
|
||||
.addClass('mobile-disabled-input');
|
||||
}
|
||||
input.datetimepicker('show');
|
||||
if (mobile) {
|
||||
input.removeAttr('disabled')
|
||||
.removeClass('mobile-disabled-input');
|
||||
}
|
||||
});
|
||||
|
||||
scope.$onAdjust('size scroll', function () {
|
||||
if (isShowing) {
|
||||
input.datepicker('widget').hide();
|
||||
input.datetimepicker('hide');
|
||||
}
|
||||
});
|
||||
|
||||
function updateModel() {
|
||||
var masked = input.mask("value") || '',
|
||||
value = input.datetimepicker('getDate') || null,
|
||||
oldValue = scope.getValue() || null;
|
||||
|
||||
if (_.isEmpty(masked)) {
|
||||
value = null;
|
||||
}
|
||||
if (!input.mask("valid")) {
|
||||
model.$setViewValue(value); // force validation
|
||||
model.$render();
|
||||
scope.$applyAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
value = scope.parse(value);
|
||||
|
||||
if (angular.equals(value, oldValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.setValue(value, true);
|
||||
scope.$applyAsync();
|
||||
}
|
||||
|
||||
scope.validate = function(value) {
|
||||
var minSize = props.minSize === 'now' ? moment() : props.minSize,
|
||||
maxSize = props.maxSize,
|
||||
val = moment(value),
|
||||
valid = true;
|
||||
|
||||
var masked = input.mask('value');
|
||||
if (masked && !input.mask("valid")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(isDate) {
|
||||
if(minSize) minSize = moment(minSize).startOf('day');
|
||||
if(maxSize) maxSize = moment(maxSize).startOf('day');
|
||||
}
|
||||
else {
|
||||
if(minSize) minSize = moment(minSize);
|
||||
if(maxSize) maxSize = moment(maxSize);
|
||||
}
|
||||
|
||||
if(minSize) {
|
||||
if(!val) return false;
|
||||
valid = !val.isBefore(minSize) ;
|
||||
}
|
||||
if(valid && maxSize) {
|
||||
if(!val) return true;
|
||||
valid = !val.isAfter(maxSize) ;
|
||||
}
|
||||
|
||||
return valid;
|
||||
};
|
||||
|
||||
scope.$render_editable = function() {
|
||||
rendering = true;
|
||||
try {
|
||||
var value = scope.getText();
|
||||
if (value) {
|
||||
input.mask('value', value);
|
||||
try {
|
||||
$.datepicker._noUpdate = true;
|
||||
$.datepicker._setDateDatepicker(input[0], moment(scope.getValue()).toDate());
|
||||
input.mask('refresh');
|
||||
} finally {
|
||||
$.datepicker._noUpdate = false;
|
||||
}
|
||||
} else {
|
||||
input.mask('value', '');
|
||||
}
|
||||
} finally {
|
||||
rendering = false;
|
||||
}
|
||||
};
|
||||
},
|
||||
template_editable:
|
||||
'<span class="picker-input">'+
|
||||
'<input type="text" autocomplete="off">'+
|
||||
'<span class="picker-icons">'+
|
||||
'<i class="fa fa-calendar"></i>'+
|
||||
'</span>'+
|
||||
'</span>'
|
||||
});
|
||||
|
||||
ui.formInput('Date', 'DateTime', {
|
||||
format: 'DD/MM/YYYY',
|
||||
mask: 'DD/MM/YYYY',
|
||||
isDate: true
|
||||
});
|
||||
|
||||
ui.formInput('Time', 'DateTime', {
|
||||
|
||||
css: 'time-item',
|
||||
mask: 'HH:mm',
|
||||
|
||||
init: function(scope) {
|
||||
this._super(scope);
|
||||
|
||||
scope.parse = function(value) {
|
||||
return value;
|
||||
};
|
||||
|
||||
scope.format = function(value) {
|
||||
return value;
|
||||
};
|
||||
},
|
||||
|
||||
link_editable: function(scope, element, attrs, model) {
|
||||
|
||||
element.mask({
|
||||
mask: this.mask
|
||||
});
|
||||
|
||||
element.change(function(e, ui) {
|
||||
updateModel();
|
||||
});
|
||||
|
||||
element.on('keydown', function(e){
|
||||
if (e.isDefaultPrevented()) {
|
||||
return;
|
||||
}
|
||||
if (e.keyCode === $.ui.keyCode.ENTER || e.keyCode === $.ui.keyCode.TAB) {
|
||||
updateModel();
|
||||
}
|
||||
});
|
||||
|
||||
element.on('grid:check', function () {
|
||||
updateModel();
|
||||
});
|
||||
|
||||
scope.validate = function(value) {
|
||||
return !value || /^(\d+:\d+)$/.test(value);
|
||||
};
|
||||
|
||||
function updateModel() {
|
||||
var masked = element.mask("value") || '',
|
||||
value = element.val() || '',
|
||||
oldValue = scope.getValue() || '';
|
||||
|
||||
if (value && !element.mask("valid")) {
|
||||
return model.$setViewValue(value); // force validation
|
||||
}
|
||||
if (_.isEmpty(masked)) {
|
||||
value = null;
|
||||
}
|
||||
|
||||
value = scope.parse(value);
|
||||
|
||||
if (angular.equals(value, oldValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.setValue(value, true);
|
||||
scope.$applyAsync();
|
||||
}
|
||||
|
||||
scope.$render_editable = function() {
|
||||
var value = scope.getText();
|
||||
if (value) {
|
||||
element.mask('value', value);
|
||||
} else {
|
||||
element.mask('value', '');
|
||||
}
|
||||
};
|
||||
},
|
||||
template_editable: '<input type="text">'
|
||||
});
|
||||
|
||||
ui.formInput('RelativeTime', 'DateTime', {
|
||||
metaWidget: true,
|
||||
init: function(scope) {
|
||||
this._super(scope);
|
||||
|
||||
scope.isReadonly = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
scope.format = function(value) {
|
||||
if (value) {
|
||||
return moment(value).fromNow();
|
||||
}
|
||||
return "";
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
function formatDuration(field, value) {
|
||||
if (!value || !_.isNumber(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
var h = Math.floor(value / 3600);
|
||||
var m = Math.floor((value % 3600) / 60);
|
||||
var s = Math.floor((value % 3600) % 60);
|
||||
|
||||
h = _.str.pad(h, field.big ? 3 : 2, '0');
|
||||
m = _.str.pad(m, 2, '0');
|
||||
s = _.str.pad(s, 2, '0');
|
||||
|
||||
var text = h + ':' + m;
|
||||
|
||||
if (field.seconds) {
|
||||
text = text + ':' + s;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
ui.formatDuration = formatDuration;
|
||||
|
||||
ui.formInput('Duration', 'Time', {
|
||||
metaWidget: true,
|
||||
mask: '99:mm',
|
||||
|
||||
init: function(scope) {
|
||||
this._super(scope);
|
||||
|
||||
var field = scope.field;
|
||||
var pattern = /^\d+:\d+(:\d+)?$/;
|
||||
|
||||
scope.format = function(value) {
|
||||
return formatDuration(field, value);
|
||||
};
|
||||
|
||||
scope.parse = function(value) {
|
||||
if (!value || !_.isString(value)) {
|
||||
return value;
|
||||
}
|
||||
if (!pattern.test(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var parts = value.split(':'),
|
||||
first = +(parts[0]),
|
||||
next = +(parts[1]),
|
||||
last = +(parts[2] || 0);
|
||||
|
||||
return (first * 60 * 60) + (next * 60) + last;
|
||||
};
|
||||
},
|
||||
|
||||
link_editable: function(scope, element, attrs, model) {
|
||||
var field = scope.field || {},
|
||||
mask = this.mask;
|
||||
|
||||
if (field.big) {
|
||||
mask = "999:mm";
|
||||
}
|
||||
if (field.seconds) {
|
||||
mask = mask + ":mm";
|
||||
}
|
||||
|
||||
this.mask = mask;
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
scope.validate = function(value) {
|
||||
return !value || _.isNumber(value);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
499
sophal/js/form/form.input.html.js
Normal file
499
sophal/js/form/form.input.html.js
Normal file
@ -0,0 +1,499 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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 getStylePopup(element, styles) {
|
||||
|
||||
return function($popup, $button) {
|
||||
|
||||
var $list = $('<div/>').addClass('wysiwyg-plugin-list').attr('unselectable', 'on');
|
||||
|
||||
$.each(styles, function(format, name) {
|
||||
var $link = $('<a/>').attr('href', '#').html(name).click(
|
||||
function(event) {
|
||||
$(element).wysiwyg('shell').format(format).closePopup();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
});
|
||||
$list.append($link);
|
||||
});
|
||||
|
||||
$popup.append($list);
|
||||
};
|
||||
}
|
||||
|
||||
function getFontNamePopup(element, fonts) {
|
||||
|
||||
return function($popup, $button) {
|
||||
|
||||
var $list = $('<div/>').addClass('wysiwyg-plugin-list').attr('unselectable', 'on');
|
||||
|
||||
$.each(fonts, function(font, name) {
|
||||
var $link = $('<a/>').attr('href', '#').html(name).click(
|
||||
function(event) {
|
||||
try {
|
||||
document.execCommand('styleWithCSS', false, false);
|
||||
$(element).wysiwyg('shell').fontName(font).closePopup();
|
||||
} finally {
|
||||
document.execCommand('styleWithCSS', false, true);
|
||||
}
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
});
|
||||
$list.append($link);
|
||||
});
|
||||
|
||||
$popup.append($list);
|
||||
};
|
||||
}
|
||||
|
||||
function getFontSizePopup(element, sizes) {
|
||||
|
||||
return function($popup, $button) {
|
||||
|
||||
var $list = $('<div/>').addClass('wysiwyg-plugin-list').attr('unselectable', 'on');
|
||||
|
||||
$.each(sizes, function(size, name) {
|
||||
var $link = $('<a/>').attr('href', '#').html(name).click(
|
||||
function(event) {
|
||||
$(element).focus();
|
||||
try {
|
||||
document.execCommand('styleWithCSS', false, false);
|
||||
$(element).wysiwyg('shell').fontSize(size).closePopup();
|
||||
} finally {
|
||||
document.execCommand('styleWithCSS', false, true);
|
||||
}
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
});
|
||||
$list.append($link);
|
||||
});
|
||||
|
||||
$popup.append($list);
|
||||
};
|
||||
}
|
||||
|
||||
function getButtons(scope, element) {
|
||||
|
||||
var lite = scope.field.lite;
|
||||
|
||||
return {
|
||||
style: lite ? false : {
|
||||
title: _t('Style'),
|
||||
image: '\uf1dd',
|
||||
popup: getStylePopup(element, {
|
||||
'<p>' : _t('Normal'),
|
||||
'<pre>' : '<pre>' + _t('Formatted') + '</pre>',
|
||||
'<blockquote>' : '<blockquote>' + _t('Blockquote') + '</blockquote>',
|
||||
'<h1>': '<h1>' + _t('Header 1') + '</h1>',
|
||||
'<h2>': '<h2>' + _t('Header 2') + '</h2>',
|
||||
'<h3>': '<h3>' + _t('Header 3') + '</h3>',
|
||||
'<h4>': '<h4>' + _t('Header 4') + '</h4>',
|
||||
'<h5>': '<h5>' + _t('Header 5') + '</h5>',
|
||||
'<h6>': '<h6>' + _t('Header 6') + '</h6>'
|
||||
})
|
||||
},
|
||||
fontName: lite ? false : {
|
||||
title: _t('Font'),
|
||||
image: '\uf031',
|
||||
popup: getFontNamePopup(element, {
|
||||
'"Times New Roman", Times, serif': '<span style="font-family: \"Times New Roman\", Times, serif">Times New Roman</span>',
|
||||
'Arial, Helvetica, sans-serif': '<span style="font-family: Arial, Helvetica, sans-serif">Arial</span>',
|
||||
'"Courier New", Courier, monospace': '<span style="font-family: \"Courier New\", Courier, monospace">Courier New</span>',
|
||||
'Comic Sans, Comic Sans MS, cursive': '<span style="font-family: Comic Sans, Comic Sans MS, cursive">Comic Sans</span>',
|
||||
'Impact, fantasy': '<span style="font-family: Impact, fantasy">Impact</span>'
|
||||
})
|
||||
},
|
||||
fontSize: lite ? false : {
|
||||
title: _t('Font size'),
|
||||
image: '\uf035',
|
||||
popup: getFontSizePopup(element, {
|
||||
'1': '<span style="font-size: x-small">' + _t('Smaller') + '</span>',
|
||||
'2': '<span style="font-size: small">' + _t('Small') + '</span>',
|
||||
'3': '<span style="font-size: medium">' + _t('Medium') + '</span>',
|
||||
'4': '<span style="font-size: large">' + _t('Large') + '</span>',
|
||||
'5': '<span style="font-size: x-large">' + _t('Larger') + '</span>'
|
||||
})
|
||||
},
|
||||
d1: lite ? false : {
|
||||
html: $('<span class="wysiwyg-toolbar-divider"></span>')
|
||||
},
|
||||
bold: {
|
||||
title: _t('Bold (Ctrl+B)'),
|
||||
image: '\uf032'
|
||||
},
|
||||
italic: {
|
||||
title: _t('Italic (Ctrl+I)'),
|
||||
image: '\uf033'
|
||||
},
|
||||
underline: {
|
||||
title: _t('Underline (Ctrl+U)'),
|
||||
image: '\uf0cd'
|
||||
},
|
||||
strikethrough: {
|
||||
title: _t('Strikethrough (Ctrl+S)'),
|
||||
image: '\uf0cc'
|
||||
},
|
||||
removeformat: {
|
||||
title: _t('Remove format'),
|
||||
image: '\uf12d'
|
||||
},
|
||||
d2: {
|
||||
html: $('<span class="wysiwyg-toolbar-divider"></span>')
|
||||
},
|
||||
forecolor: lite ? false : {
|
||||
title: _t('Text color'),
|
||||
image: '\uf1fc'
|
||||
},
|
||||
highlight: lite ? false : {
|
||||
title: _t('Background color'),
|
||||
image: '\uf043'
|
||||
},
|
||||
d3: lite ? false : {
|
||||
html: $('<span class="wysiwyg-toolbar-divider"></span>')
|
||||
},
|
||||
alignleft: {
|
||||
title: _t('Left'),
|
||||
image: '\uf036'
|
||||
},
|
||||
aligncenter: {
|
||||
title: _t('Center'),
|
||||
image: '\uf037'
|
||||
},
|
||||
alignright: {
|
||||
title: _t('Right'),
|
||||
image: '\uf038'
|
||||
},
|
||||
alignjustify: {
|
||||
title: _t('Justify'),
|
||||
image: '\uf039'
|
||||
},
|
||||
d4: {
|
||||
html: $('<span class="wysiwyg-toolbar-divider"></span>')
|
||||
},
|
||||
orderedList: {
|
||||
title: _t('Ordered list'),
|
||||
image: '\uf0cb'
|
||||
},
|
||||
unorderedList: {
|
||||
title: _t('Unordered list'),
|
||||
image: '\uf0ca'
|
||||
},
|
||||
d6: lite ? false : {
|
||||
html: $('<span class="wysiwyg-toolbar-divider"></span>')
|
||||
},
|
||||
indent: lite ? false : {
|
||||
title: _t('Indent'),
|
||||
image: '\uf03c'
|
||||
},
|
||||
outdent: lite ? false : {
|
||||
title: _t('Outdent'),
|
||||
image: '\uf03b'
|
||||
},
|
||||
d7: lite ? false : {
|
||||
html: $('<span class="wysiwyg-toolbar-divider"></span>')
|
||||
},
|
||||
insertimage: lite ? false : {
|
||||
title: _t('Insert image'),
|
||||
image: '\uf030'
|
||||
},
|
||||
insertlink: lite ? false : {
|
||||
title: _t('Insert link'),
|
||||
image: '\uf08e'
|
||||
},
|
||||
d8: lite ? false : {
|
||||
html: $('<span class="wysiwyg-toolbar-divider"></span>')
|
||||
},
|
||||
normalize: lite ? false : {
|
||||
title: _t('Normalize'),
|
||||
image: '\uf0d0',
|
||||
click: function () {
|
||||
scope.normalize();
|
||||
}
|
||||
},
|
||||
showCode: lite ? false : {
|
||||
title: _t('Code'),
|
||||
image: '\uf121',
|
||||
click: function () {
|
||||
scope.toggleCode();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ui.formInput('Html', {
|
||||
|
||||
css: "html-item",
|
||||
metaWidget: true,
|
||||
|
||||
init: function(scope) {
|
||||
|
||||
scope.parse = function(value) {
|
||||
return value ? value : null;
|
||||
};
|
||||
},
|
||||
|
||||
link_editable: function(scope, element, attrs, model) {
|
||||
this._super(scope, element, attrs, model);
|
||||
var textElement = element.find('textarea');
|
||||
var buttons = getButtons(scope, textElement);
|
||||
|
||||
var props = scope.field,
|
||||
minSize = +props.minSize,
|
||||
maxSize = +props.maxSize,
|
||||
height = +(scope.field.height) || null;
|
||||
|
||||
if (height) {
|
||||
height = Math.max(100, height);
|
||||
}
|
||||
|
||||
function isParagraph(node) {
|
||||
return node && /^DIV|^P|^LI|^H[1-7]/.test(node.nodeName.toUpperCase());
|
||||
}
|
||||
|
||||
function findParagraph(node) {
|
||||
if (!node) return null;
|
||||
if (node.classList && node.classList.contains('wysiwyg-editor')) return null;
|
||||
if (isParagraph(node)) return node;
|
||||
return findParagraph(node.parentNode);
|
||||
}
|
||||
|
||||
textElement.wysiwyg({
|
||||
toolbar: 'top',
|
||||
buttons: buttons,
|
||||
submit: {
|
||||
title: _t('Submit'),
|
||||
image: '\uf00c'
|
||||
},
|
||||
selectImage: _t('Click or drop image'),
|
||||
placeholderUrl: 'www.example.com',
|
||||
maxImageSize: [600, 200],
|
||||
hijackContextmenu: false,
|
||||
onKeyPress: function(key, character, shiftKey, altKey, ctrlKey, metaKey) {
|
||||
if (key !== 13 || shiftKey) {
|
||||
return;
|
||||
}
|
||||
var parent = findParagraph(document.getSelection().anchorNode);
|
||||
if (!parent) {
|
||||
document.execCommand('formatBlock', false, '<p>');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var shell = textElement.wysiwyg('shell');
|
||||
var shellElement = $(shell.getElement());
|
||||
var shellActive = true;
|
||||
|
||||
shellElement.addClass('html-content');
|
||||
|
||||
if (scope.field.height) {
|
||||
shellElement.parent().height(height).resizable({
|
||||
handles: 's',
|
||||
resize: function () {
|
||||
shellElement.parent().width('');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onChange(e) {
|
||||
|
||||
var value = shellActive ? shell.getHTML() : textElement.val();
|
||||
var text = (value || '').trim()
|
||||
|
||||
if (text === '<p><br></p>' || text === '<br>') {
|
||||
value = null;
|
||||
}
|
||||
|
||||
var old = scope.getValue() || null;
|
||||
var txt = scope.parse(value) || null;
|
||||
|
||||
if (old === txt) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.setValue(value, true);
|
||||
scope.$applyAsync();
|
||||
}
|
||||
|
||||
scope.$render_editable = function () {
|
||||
var value = scope.getValue() || "";
|
||||
scope.text = scope.format(value);
|
||||
|
||||
var current = shellActive ? shell : textElement;
|
||||
var getter = shellActive ? 'getHTML' : 'val';
|
||||
var setter = shellActive ? 'setHTML' : 'val';
|
||||
|
||||
var html = current[getter]();
|
||||
if (value !== html) {
|
||||
current[setter](value);
|
||||
}
|
||||
};
|
||||
|
||||
textElement.on('input paste change blur', _.debounce(onChange, 100));
|
||||
|
||||
textElement.on("focus", _.once(function (e) {
|
||||
|
||||
// Chrome and Edge supports this
|
||||
document.execCommand('defaultParagraphSeparator', false, 'p');
|
||||
|
||||
// firefox uses attributes for some commands
|
||||
document.execCommand('styleWithCSS', false, true);
|
||||
document.execCommand('insertBrOnReturn', false, false);
|
||||
}));
|
||||
|
||||
scope.toggleCode = function () {
|
||||
|
||||
shellActive = !shellActive;
|
||||
|
||||
element.parent().find('.wysiwyg-toolbar-icon')
|
||||
.toggleClass('disabled', !shellActive)
|
||||
.last().removeClass('disabled');
|
||||
|
||||
if (shellActive) {
|
||||
textElement.hide();
|
||||
shellElement.show();
|
||||
shell.setHTML(textElement.val());
|
||||
} else {
|
||||
var height = Math.max(100, shellElement.outerHeight());
|
||||
shellElement.hide();
|
||||
textElement.show();
|
||||
|
||||
if (!scope.field.height) {
|
||||
textElement.height(height);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
scope.normalize = function () {
|
||||
|
||||
var html = shell.getHTML();
|
||||
var div = $('<div>').html(html);
|
||||
|
||||
div.find('p').css({
|
||||
'margin-top': 0,
|
||||
'margin-bottom': '1em'});
|
||||
|
||||
div.find('ol,ul').each(function() {
|
||||
var el = $(this);
|
||||
if (el.parents('ol,ul').length) return;
|
||||
el.css({
|
||||
'margin-top': 0,
|
||||
'margin-bottom': '1em'});
|
||||
});
|
||||
|
||||
div.find('blockquote').each(function() {
|
||||
var el = $(this);
|
||||
el.css({
|
||||
'margin': el.parents('blockquote').length ? '0 0 0 2em' : '0 0 1em 2em',
|
||||
'border': 'none',
|
||||
'padding': 0
|
||||
});
|
||||
});
|
||||
|
||||
shellElement.focus();
|
||||
shell.setHTML(div[0].innerHTML);
|
||||
|
||||
div.remove();
|
||||
};
|
||||
|
||||
scope.validate = function(value) {
|
||||
if (_.isEmpty(value)) {
|
||||
return true;
|
||||
}
|
||||
var length = value.length,
|
||||
valid = true;
|
||||
|
||||
if (minSize) {
|
||||
valid = length >= minSize;
|
||||
}
|
||||
if(valid && maxSize) {
|
||||
valid = length <= maxSize;
|
||||
}
|
||||
|
||||
return valid;
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
template_readonly:
|
||||
'<div class="form-item-container">'+
|
||||
'<div class="html-viewer html-content" ui-bind-template x-text="text" x-locals="record" x-live="field.live"></div>'+
|
||||
'</div>',
|
||||
template_editable:
|
||||
'<div class="form-item-container">'+
|
||||
'<textarea class="html-editor html-content"></textarea>'+
|
||||
'</div>'
|
||||
|
||||
});
|
||||
|
||||
ui.directive('uiBindTemplate', ['$interpolate', function($interpolate){
|
||||
|
||||
function expand(scope, template) {
|
||||
if (!template || !template.match(/{{.*?}}/)) {
|
||||
return template;
|
||||
}
|
||||
return $interpolate(template)(scope.locals());
|
||||
}
|
||||
|
||||
return {
|
||||
terminal: true,
|
||||
scope: {
|
||||
locals: "&",
|
||||
text: "=text",
|
||||
live: "&"
|
||||
},
|
||||
link: function(scope, element, attrs) {
|
||||
|
||||
var template = null;
|
||||
|
||||
function update() {
|
||||
var output = expand(scope, template) || "";
|
||||
element.html(output);
|
||||
}
|
||||
|
||||
scope.$watch("text", function(text, old) {
|
||||
|
||||
if (text === template) {
|
||||
return;
|
||||
}
|
||||
template = text;
|
||||
update();
|
||||
});
|
||||
|
||||
var live = false;
|
||||
scope.$watch("live()", function(value) {
|
||||
if (live || !value) {
|
||||
return;
|
||||
}
|
||||
live = true;
|
||||
scope.$watch("locals()", function(value) {
|
||||
update();
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
})();
|
||||
431
sophal/js/form/form.input.json.js
Normal file
431
sophal/js/form/form.input.json.js
Normal file
@ -0,0 +1,431 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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');
|
||||
|
||||
ui.formWidget('PanelJson', {
|
||||
showTitle: false,
|
||||
transclude: true,
|
||||
template: "<div class='panel-json' ui-transclude></div>"
|
||||
});
|
||||
|
||||
ui.formInput('JsonField', 'String', {
|
||||
showTitle: false,
|
||||
link: function (scope, element, attrs, model) {
|
||||
var field = scope.field;
|
||||
var jsonFields = field.jsonFields || [];
|
||||
var jsonNames = _.pluck(jsonFields, 'name');
|
||||
var jsonFix = {};
|
||||
|
||||
jsonFields.forEach(function (item) {
|
||||
if (item.widget && item.showTitle === undefined) {
|
||||
var widget = ui.getWidgetDef(item.widget);
|
||||
if (widget) {
|
||||
item.showTitle = widget.showTitle;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var defaultValues = {};
|
||||
var parentUnwatch = null;
|
||||
var selfUnwatch = null;
|
||||
|
||||
scope.formPath = scope.formPath ? scope.formPath + "." + field.name : field.name;
|
||||
scope.record = {};
|
||||
|
||||
jsonFields.forEach(function (item) {
|
||||
if (item.target === 'com.axelor.meta.db.MetaJsonRecord' &&
|
||||
item.targetName && item.targetName.indexOf('attrs.') === 0) {
|
||||
jsonFix[item.name] = function (v) {
|
||||
if (v) {
|
||||
v[item.targetName.substring(6)] = v[item.targetName];
|
||||
}
|
||||
return v;
|
||||
};
|
||||
}
|
||||
if (item.contextField && item.contextFieldValue) {
|
||||
if (item.showIf === undefined && item.hideIf === undefined && item.hidden) {
|
||||
return;
|
||||
}
|
||||
var condition = "($record." + item.contextField + ".id === " + item.contextFieldValue + ")";
|
||||
if (item.showIf) condition += " && (" + item.showIf + ")";
|
||||
if (item.hideIf) condition += " && !(" + item.hideIf + ")";
|
||||
item.showIf = condition;
|
||||
item.hideIf = null;
|
||||
}
|
||||
});
|
||||
|
||||
function getDefaultValues() {
|
||||
jsonFields.forEach(function (item) {
|
||||
if (item.defaultValue === undefined) return;
|
||||
var value = item.defaultValue;
|
||||
switch(item.type) {
|
||||
case 'integer':
|
||||
value = +(value);
|
||||
break;
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
value = value === 'now' ? new Date() : moment(value).toDate();
|
||||
break;
|
||||
}
|
||||
defaultValues[item.name] = value;
|
||||
});
|
||||
return angular.copy(defaultValues);
|
||||
}
|
||||
|
||||
function unwatchParent() {
|
||||
if (parentUnwatch) {
|
||||
parentUnwatch();
|
||||
parentUnwatch = null;
|
||||
}
|
||||
}
|
||||
|
||||
function unwatchSelf() {
|
||||
if (selfUnwatch) {
|
||||
selfUnwatch();
|
||||
selfUnwatch = null;
|
||||
}
|
||||
}
|
||||
|
||||
function watchParent() {
|
||||
unwatchParent();
|
||||
parentUnwatch = scope.$watch('$parent.record.' + field.name, function jsonParentWatch(value, old) {
|
||||
if (value === old) return;
|
||||
onRender();
|
||||
});
|
||||
}
|
||||
|
||||
function watchSelf() {
|
||||
unwatchSelf();
|
||||
selfUnwatch = scope.$watch('record', function jsonRecordWatch(record, old) {
|
||||
if (record !== old) {
|
||||
onUpdate();
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
function format(name, value) {
|
||||
var func = jsonFix[name];
|
||||
return func ? func(value) : value;
|
||||
}
|
||||
|
||||
function onUpdate() {
|
||||
var rec = null;
|
||||
_.each(scope.record, function (v, k) {
|
||||
if (k.indexOf('$') === 0 || v === null || v === undefined) return;
|
||||
if (_.isArray(v)) {
|
||||
if (v.length == 0) return;
|
||||
v = v.map(function (x) {
|
||||
return x.id ? { id: x.id } : x;
|
||||
});
|
||||
}
|
||||
if (rec === null) {
|
||||
rec = {};
|
||||
}
|
||||
rec[k] = format(k, v);
|
||||
});
|
||||
unwatchParent();
|
||||
if (scope.$parent.record[field.name] || rec) {
|
||||
scope.$parent.record[field.name] = rec ? angular.toJson(rec) : rec;
|
||||
}
|
||||
watchParent();
|
||||
}
|
||||
|
||||
function onRender() {
|
||||
var record = scope.$parent.record || {};
|
||||
var value = record[field.name];
|
||||
unwatchSelf();
|
||||
if (value) {
|
||||
scope.record = angular.fromJson(value);
|
||||
} else {
|
||||
scope.record = getDefaultValues();
|
||||
if (!_.isEmpty(scope.record)) {
|
||||
record[field.name] = angular.toJson(scope.record);
|
||||
}
|
||||
onUpdate();
|
||||
}
|
||||
scope._jsonContext = { '$record': record };
|
||||
record['$' + field.name] = scope.record;
|
||||
watchSelf();
|
||||
}
|
||||
|
||||
scope.$on('on:new', onRender);
|
||||
scope.$on('on:edit', function () {
|
||||
if (scope.viewType === 'form' || (!scope.viewType && scope._isPopup)) onRender();
|
||||
});
|
||||
|
||||
scope.updateJsonValues = function (values) {
|
||||
var rec = null;
|
||||
_.each(values, function (v, k) {
|
||||
if (jsonNames.indexOf(k) === -1 && scope.fields[k]) {
|
||||
scope.$parent.record[k] = v;
|
||||
} else {
|
||||
if (rec === null) {
|
||||
rec = {};
|
||||
}
|
||||
rec[k] = v;
|
||||
}
|
||||
});
|
||||
if (rec) {
|
||||
scope.record = _.extend({}, scope.record, rec);
|
||||
}
|
||||
};
|
||||
|
||||
watchParent();
|
||||
|
||||
// hide parent panel if no jsonFields defined
|
||||
scope.$evalAsync(function () {
|
||||
var parent = scope.$parent.field || {};
|
||||
if (parent.type === 'panel' && _.size(parent.items) === 1 && _.isEmpty(field.jsonFields)) {
|
||||
element.parents('.panel:first').addClass('hide').hide();
|
||||
}
|
||||
});
|
||||
|
||||
scope.$on('on:update-context', function (e, context) {
|
||||
if (context && !context[field.name]) {
|
||||
context[field.name] = angular.toJson(scope.record || {});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ui.formInput('JsonRaw', 'String', {
|
||||
showTitle: false,
|
||||
link: function (scope, element, attrs, model) {
|
||||
|
||||
scope.placeHolderKey = _t('name');
|
||||
scope.placeHolderVal = _t('value');
|
||||
|
||||
scope.items = [];
|
||||
|
||||
scope.onAdd = function () {
|
||||
var last = _.last(scope.items);
|
||||
if (last && !(_.trim(last.name) && _.trim(last.value))) return;
|
||||
scope.items.push({});
|
||||
};
|
||||
|
||||
scope.onRemove = function (index) {
|
||||
if (scope.items.length > 0) {
|
||||
scope.items.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
var unwatch = null;
|
||||
|
||||
function doWatch () {
|
||||
if (unwatch) {
|
||||
unwatch();
|
||||
}
|
||||
unwatch = scope.$watch('items', function jsonItemsWatch(items, old) {
|
||||
if (items === old) return;
|
||||
var record = null;
|
||||
_.each(items, function (item) {
|
||||
if (!_.trim(item.name) || !_.trim(item.value)) return;
|
||||
if (record === null) {
|
||||
record = {};
|
||||
}
|
||||
record[item.name] = item.value;
|
||||
});
|
||||
model.$setViewValue(record ? angular.toJson(record) : null);
|
||||
}, true);
|
||||
}
|
||||
|
||||
model.$render = function () {
|
||||
var value = model.$viewValue;
|
||||
if (value) {
|
||||
value = angular.fromJson(value);
|
||||
} else {
|
||||
value = {};
|
||||
}
|
||||
scope.items = _.map(_.keys(value), function (name) {
|
||||
return { name: name, value: value[name] || '' };
|
||||
});
|
||||
doWatch();
|
||||
};
|
||||
},
|
||||
template_readonly:
|
||||
"<div class='json-editor'>" +
|
||||
"<table class='form-layout'>" +
|
||||
"<tr ng-repeat='(i, item) in items'>" +
|
||||
"<td class='form-label'>" +
|
||||
"<strong class='display-text'>{{item.name}}</strong>:" +
|
||||
"</td>" +
|
||||
"<td class='form-item'>" +
|
||||
"<span class='display-text'>{{item.value}}</span>" +
|
||||
"</td>" +
|
||||
"</tr>" +
|
||||
"</table>" +
|
||||
"</div>",
|
||||
template_editable:
|
||||
"<div class='json-editor'>" +
|
||||
"<table class='form-layout'>" +
|
||||
"<tr ng-repeat='(i, item) in items'>" +
|
||||
"<td class='form-item'><span class='form-item-container'>" +
|
||||
"<input type='text' placeholder='{{placeHolderKey}}' ng-model='item.name'></span>" +
|
||||
"</td>" +
|
||||
"<td class='form-item'><span class='form-item-container'>" +
|
||||
"<input type='text' placeholder='{{placeHolderVal}}' ng-model='item.value'></span>" +
|
||||
"</td>" +
|
||||
"<td><a href='' ng-click='onRemove(i)'><i class='fa fa-minus'></i></a></td>" +
|
||||
"</tr>" +
|
||||
"</table>" +
|
||||
"<a href='' ng-click='onAdd()'><i class='fa fa-plus'></i></a>" +
|
||||
"</div>"
|
||||
});
|
||||
|
||||
ui.formInput('JsonRefSelect', {
|
||||
|
||||
css: 'multi-object-select',
|
||||
|
||||
controller: ['$scope', 'ViewService', function($scope, ViewService) {
|
||||
|
||||
$scope.createElement = function(id, name, selectionList) {
|
||||
|
||||
var elemGroup = $('<div ui-group ui-table-layout cols="2" x-widths="150,*"></div>');
|
||||
var elemSelect = $('<input ui-select showTitle="false">')
|
||||
.attr("name", name + "$model")
|
||||
.attr("x-for-widget", id)
|
||||
.attr("ng-model", "record." + name + ".model");
|
||||
|
||||
var elemSelects = $('<div></div>').attr('ng-switch', "record." + name + ".model");
|
||||
var elemItems = _.map(selectionList, function(s) {
|
||||
return $('<input ui-json-ref-item ng-switch-when="' + s.value +'">')
|
||||
.attr('ng-model', 'record.' + name)
|
||||
.attr('name', name)
|
||||
.attr('x-target', s.value);
|
||||
});
|
||||
|
||||
elemGroup
|
||||
.append($('<div></div>').append(elemSelect))
|
||||
.append(elemSelects.append(elemItems));
|
||||
|
||||
return ViewService.compile(elemGroup)($scope);
|
||||
};
|
||||
}],
|
||||
|
||||
link: function(scope, element, attrs, model) {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
var name = scope.field.name;
|
||||
var selectionList = scope.field.selectionList;
|
||||
|
||||
scope.fieldsCache = {};
|
||||
|
||||
scope.refFireEvent = function (name) {
|
||||
var handler = scope.$events[name];
|
||||
if (handler) {
|
||||
return handler();
|
||||
}
|
||||
};
|
||||
|
||||
var elem = scope.createElement(element.attr('id'), name, selectionList);
|
||||
setTimeout(function() {
|
||||
element.append(elem);
|
||||
});
|
||||
|
||||
scope.$watch("record." + name + ".model", function jsonModelWatch(value, old) {
|
||||
if (value === old || old === undefined) return;
|
||||
if (scope.record && scope.record[name]) {
|
||||
scope.record[name] = _.pick(scope.record[name], 'model');
|
||||
if (!scope.record[name].model) {
|
||||
delete scope.record[name];
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
template_editable: null,
|
||||
template_readonly: null
|
||||
});
|
||||
|
||||
ui.formInput('JsonRefItem', 'ManyToOne', {
|
||||
|
||||
showTitle: false,
|
||||
|
||||
link: function(scope, element, attrs, model) {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
if (scope.field.targetName) {
|
||||
return this._link.apply(this, arguments);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var target = element.attr('x-target');
|
||||
var data = (_.findWhere(scope.$parent.field.selectionList, {value: target})||{}).data || {};
|
||||
|
||||
function doLink(fields) {
|
||||
var name = false,
|
||||
search = [];
|
||||
|
||||
_.each(fields, function(f) {
|
||||
if (f.nameColumn) name = f.name;
|
||||
if (f.name === "name") search.push("name");
|
||||
if (f.name === "code") search.push("code");
|
||||
});
|
||||
|
||||
if (!name && _.contains(search, "name")) {
|
||||
name = "name";
|
||||
}
|
||||
|
||||
_.extend(scope.field, {
|
||||
target: scope._model,
|
||||
targetName: name,
|
||||
targetSearch: search,
|
||||
domain: data.domain
|
||||
});
|
||||
|
||||
self._link(scope, element, attrs, model);
|
||||
}
|
||||
|
||||
if (scope.fieldsCache[scope._model]) {
|
||||
doLink(scope.fieldsCache[scope._model]);
|
||||
} else {
|
||||
scope.loadFields().success(function (fields) {
|
||||
scope.fieldsCache[scope._model] = fields;
|
||||
doLink(fields);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_link: function(scope, element, attrs, model) {
|
||||
var name = element.attr('name');
|
||||
|
||||
scope.getValue = function () {
|
||||
return scope.record[name];
|
||||
};
|
||||
|
||||
var __setValue = scope.setValue;
|
||||
|
||||
scope.setValue = function (value) {
|
||||
var val = _.pick(scope.record[name], 'model');
|
||||
val = _.extend(val, value);
|
||||
__setValue.call(scope, val);
|
||||
};
|
||||
|
||||
function doSelect() {
|
||||
var value = (scope.record || {})[name];
|
||||
scope.select(value);
|
||||
}
|
||||
|
||||
scope.$watch("record", doSelect);
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
252
sophal/js/form/form.input.number.js
Normal file
252
sophal/js/form/form.input.number.js
Normal file
@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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() {
|
||||
|
||||
/* jshint validthis: true */
|
||||
|
||||
"use strict";
|
||||
|
||||
var ui = angular.module('axelor.ui');
|
||||
|
||||
/**
|
||||
* The Numeric input widget.
|
||||
*/
|
||||
ui.formInput('Number', {
|
||||
|
||||
css: 'integer-item',
|
||||
|
||||
widgets: ['Integer', 'Long', 'Decimal'],
|
||||
|
||||
template_readonly: '<span class="display-text">{{localeValue()}}</span>',
|
||||
|
||||
link: function(scope, element, attrs, model) {
|
||||
|
||||
var props = scope.field,
|
||||
minSize = +props.minSize,
|
||||
maxSize = +props.maxSize;
|
||||
|
||||
var isDecimal = props.serverType === "decimal" || props.widget === "decimal",
|
||||
pattern = isDecimal ? /^(-)?\d+(\.\d+)?$/ : /^\s*-?[0-9]*\s*$/;
|
||||
|
||||
function scale() {
|
||||
var value = scope.attr('scale');
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
if ((props.widgetAttrs||{}).scale) {
|
||||
return props.widgetAttrs.scale;
|
||||
}
|
||||
return props.scale || 2;
|
||||
}
|
||||
|
||||
function precision() {
|
||||
var value = scope.attr('precision');
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
if ((props.widgetAttrs||{}).precision) {
|
||||
return props.widgetAttrs.precision;
|
||||
}
|
||||
return props.precision || 18;
|
||||
}
|
||||
|
||||
scope.isNumber = function(value) {
|
||||
return _.isEmpty(value) || _.isNumber(value) || pattern.test(value);
|
||||
};
|
||||
|
||||
scope.validate = scope.isValid = function(value) {
|
||||
var valid = scope.isNumber(value);
|
||||
if (valid && isDecimal && _.isString(value)) {
|
||||
value = scope.format(value);
|
||||
valid = _.string.trim(value, '-').length - 1 <= precision();
|
||||
value = +value;
|
||||
}
|
||||
|
||||
if (valid && (minSize || minSize === 0)) {
|
||||
valid = value >= minSize;
|
||||
}
|
||||
if (valid && (maxSize || maxSize === 0)) {
|
||||
valid = value <= maxSize;
|
||||
}
|
||||
|
||||
return valid;
|
||||
};
|
||||
|
||||
scope.localeValue = function localeValue() {
|
||||
var value = scope.getValue();
|
||||
var field = isDecimal ? _.extend({}, scope.field, {
|
||||
scale: scale(),
|
||||
precision: precision()
|
||||
}) : scope.field;
|
||||
return isDecimal
|
||||
? ui.formatters.decimal(field, value)
|
||||
: ui.formatters.integer(field, value);
|
||||
};
|
||||
|
||||
scope.format = function format(value) {
|
||||
if (isDecimal && _.isString(value) && value.trim().length > 0) {
|
||||
return parseFloat(value).toFixed(scale());
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
scope.parse = function(value) {
|
||||
if (isDecimal) return value;
|
||||
if (value && _.isString(value)) return +value;
|
||||
return value;
|
||||
};
|
||||
|
||||
scope.$on("on:attrs-changed", function (e, attr) {
|
||||
if (attr.name === 'scale' || attr.name === 'precision') {
|
||||
model.$render();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
link_editable: function(scope, element, attrs, model) {
|
||||
|
||||
var props = scope.field;
|
||||
|
||||
var options = {
|
||||
step: 1
|
||||
};
|
||||
|
||||
element.on("spin", onSpin);
|
||||
element.on("spinchange", function(e, row) {
|
||||
updateModel(element.val());
|
||||
});
|
||||
element.on("grid:check", function(e, row) {
|
||||
updateModel(element.val());
|
||||
});
|
||||
|
||||
var pendingChange = false;
|
||||
|
||||
function handleChange(changed) {
|
||||
var onChange = scope.$events.onChange;
|
||||
if (onChange && (changed || pendingChange)) {
|
||||
pendingChange = false;
|
||||
setTimeout(onChange);
|
||||
}
|
||||
}
|
||||
|
||||
function equals(a, b) {
|
||||
if (a === b) return true;
|
||||
if (angular.equals(a, b)) return true;
|
||||
if (a === "" && b === undefined) return true;
|
||||
if (b === "" && a === undefined) return true;
|
||||
if (a === undefined || b === undefined) return false;
|
||||
if (a === null || b === null) return false;
|
||||
if (!scope.isNumber(a) || !scope.isNumber(b)) return false;
|
||||
a = a === "" ? a : ((+a) || 0);
|
||||
b = b === "" ? b : ((+b) || 0);
|
||||
return a === b;
|
||||
}
|
||||
|
||||
function updateModel(value, handle) {
|
||||
if (!scope.isNumber(value)) {
|
||||
return model.$setViewValue(value); // force validation
|
||||
}
|
||||
var val = scope.parse(value);
|
||||
var old = scope.getValue();
|
||||
var text = scope.format(value);
|
||||
|
||||
element.val(text);
|
||||
|
||||
if (equals(val, old)) {
|
||||
return handleChange();
|
||||
}
|
||||
|
||||
scope.setValue(val);
|
||||
scope.$applyAsync();
|
||||
|
||||
pendingChange = true;
|
||||
|
||||
if (handle !== false) {
|
||||
handleChange();
|
||||
}
|
||||
}
|
||||
|
||||
function onSpin(event, ui) {
|
||||
|
||||
var text = this.value,
|
||||
value = ui.value,
|
||||
orig = element.spinner('value'),
|
||||
parts, integer, decimal, min, max, dir = 0;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (!scope.isNumber(text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value < orig)
|
||||
dir = -1;
|
||||
if (value > orig)
|
||||
dir = 1;
|
||||
|
||||
parts = text.split(/\./);
|
||||
integer = +parts[0];
|
||||
decimal = parts[1];
|
||||
|
||||
integer += dir;
|
||||
if (parts.length > 1) {
|
||||
value = integer + '.' + decimal;
|
||||
}
|
||||
|
||||
min = options.min;
|
||||
max = options.max;
|
||||
|
||||
if (_.isNumber(min) && value < min)
|
||||
value = min;
|
||||
if (_.isNumber(max) && value > max)
|
||||
value = max;
|
||||
|
||||
updateModel(value, false);
|
||||
}
|
||||
|
||||
if (props.minSize !== undefined)
|
||||
options.min = +props.minSize;
|
||||
if (props.maxSize !== undefined)
|
||||
options.max = +props.maxSize;
|
||||
|
||||
setTimeout(function(){
|
||||
element.spinner(options);
|
||||
scope.$elem_editable = element.parent();
|
||||
model.$render = function() {
|
||||
var value = model.$viewValue;
|
||||
if (value) {
|
||||
value = scope.format(value);
|
||||
}
|
||||
element.val(value);
|
||||
scope.initValue(value);
|
||||
};
|
||||
model.$render();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// fix spinner repeat issue
|
||||
var oldRepeat = $.ui.spinner.prototype._repeat;
|
||||
$.ui.spinner.prototype._repeat = function () {
|
||||
if (this.element.scope().isReadonly()) {
|
||||
return this._stop();
|
||||
}
|
||||
return oldRepeat.apply(this, arguments);
|
||||
};
|
||||
|
||||
})();
|
||||
115
sophal/js/form/form.input.progress.js
Normal file
115
sophal/js/form/form.input.progress.js
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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');
|
||||
|
||||
ui.ProgressMixin = {
|
||||
|
||||
css: 'progress-item',
|
||||
cellCss: 'form-item progress-item',
|
||||
metaWidget: true,
|
||||
|
||||
link_readonly: function(scope, element, attrs, model) {
|
||||
|
||||
var field = scope.field || {},
|
||||
that = this;
|
||||
|
||||
scope.$watch("getValue()", function progressValueWatch(value, old) {
|
||||
var props = that.compute(field, value);
|
||||
scope.cssClasses = 'progress ' + props.css;
|
||||
scope.styles = {
|
||||
width: props.width + '%'
|
||||
};
|
||||
scope.css = props.css;
|
||||
scope.width = props.width;
|
||||
});
|
||||
},
|
||||
|
||||
compute: function(field, value) {
|
||||
|
||||
var max = +(field.max) || 100,
|
||||
min = +(field.min) || 0;
|
||||
|
||||
var colors = [
|
||||
["r", 24], // 00 - 24 (red)
|
||||
["y", 49], // 25 - 49 (yellow)
|
||||
["b", 74], // 50 - 74 (blue)
|
||||
["g", 100] // 75 - 100 (green)
|
||||
];
|
||||
|
||||
if (field.colors) {
|
||||
colors = _.chain(field.colors.split(/,/)).invoke('split', /:/).value() || [];
|
||||
}
|
||||
|
||||
colors.reverse();
|
||||
|
||||
var styles = {
|
||||
"r": "progress-danger",
|
||||
"y": "progress-warning",
|
||||
"b": "progress-primary",
|
||||
"g": "progress-success"
|
||||
};
|
||||
|
||||
var width = +(value) || 0;
|
||||
var css = "progress-striped";
|
||||
|
||||
width = (width * 100) / (max - min);
|
||||
width = Math.min(Math.round(width), 100);
|
||||
|
||||
var color = "";
|
||||
for(var i = 0 ; i < colors.length; i++) {
|
||||
var c = colors[i][0];
|
||||
var v = +colors[i][1];
|
||||
if (width <= v) {
|
||||
color = styles[c] || "";
|
||||
}
|
||||
}
|
||||
|
||||
css += " " + color;
|
||||
if (width < 100) {
|
||||
css += " " + "active";
|
||||
}
|
||||
|
||||
return {
|
||||
css: css,
|
||||
width: width
|
||||
};
|
||||
},
|
||||
|
||||
template_readonly:
|
||||
'<div ng-class="cssClasses">'+
|
||||
'<div class="bar" ng-style="styles"></div>'+
|
||||
'</div>'
|
||||
};
|
||||
|
||||
/**
|
||||
* The Progress widget with integer/decimal input.
|
||||
*
|
||||
*/
|
||||
ui.formInput('Progress', 'Integer', _.extend({}, ui.ProgressMixin));
|
||||
|
||||
/**
|
||||
* The Progress widget with selection input.
|
||||
*
|
||||
*/
|
||||
ui.formInput('SelectProgress', 'Select', _.extend({}, ui.ProgressMixin));
|
||||
|
||||
})();
|
||||
1011
sophal/js/form/form.input.select.js
Normal file
1011
sophal/js/form/form.input.select.js
Normal file
File diff suppressed because it is too large
Load Diff
165
sophal/js/form/form.input.spreadsheet.js
Normal file
165
sophal/js/form/form.input.spreadsheet.js
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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() {
|
||||
|
||||
/* global Handsontable: true */
|
||||
|
||||
"use strict";
|
||||
|
||||
var ui = angular.module('axelor.ui');
|
||||
|
||||
ui.formInput('Spreadsheet', {
|
||||
|
||||
css: "spreadsheet-item",
|
||||
|
||||
link: function (scope, element, attrs, model) {
|
||||
|
||||
var field = scope.field;
|
||||
var height = field.height || 580;
|
||||
|
||||
var inst;
|
||||
|
||||
scope.$timeout(function () {
|
||||
|
||||
element.height(height).css({
|
||||
"position": "relative",
|
||||
"overflow": "hidden"
|
||||
});
|
||||
|
||||
inst = new Handsontable(element[0], {
|
||||
colWidths: 60,
|
||||
rowHeaders: true,
|
||||
colHeaders: true,
|
||||
contextMenu: true,
|
||||
manualColumnResize: true,
|
||||
manualRowResize: true,
|
||||
afterChange: function (change, source) {
|
||||
if (source !== 'loadData') {
|
||||
update();
|
||||
}
|
||||
},
|
||||
afterCreateCol: update,
|
||||
afterCreateRow: update,
|
||||
afterRemoveCol: update,
|
||||
afterRemoveRow: update
|
||||
});
|
||||
model.$render();
|
||||
});
|
||||
|
||||
element.resizable({
|
||||
handles: 's',
|
||||
resize: function () {
|
||||
if (inst) {
|
||||
inst.render();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function update() {
|
||||
if (!inst) { return; }
|
||||
var current = model.$viewValue;
|
||||
var value = compact(inst.getData());
|
||||
|
||||
value = value ? JSON.stringify(value) : value;
|
||||
if (value === current) {
|
||||
return;
|
||||
}
|
||||
scope.setValue(value, true);
|
||||
scope.$applyAsync();
|
||||
}
|
||||
|
||||
function compact(items) {
|
||||
var res = [];
|
||||
var i;
|
||||
|
||||
for (i = 0; i < items.length; i++) {
|
||||
var item = items[i];
|
||||
if (Array.isArray(item)) {
|
||||
item = compact(item);
|
||||
}
|
||||
if (item === "" || item === null || item === undefined || item.length === 0) {
|
||||
continue;
|
||||
}
|
||||
res[i] = item;
|
||||
}
|
||||
|
||||
var n = res.length;
|
||||
for (i = n - 1; i >= 0; i--) {
|
||||
if (res[i] !== null) {
|
||||
n = i+1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
res = res.slice(0, n);
|
||||
return res.length ? res : null;
|
||||
}
|
||||
|
||||
function fill(data) {
|
||||
var cols = 0;
|
||||
var rows = data.length;
|
||||
var i, row;
|
||||
|
||||
for(i = 0; i < data.length; i++) {
|
||||
row = data[i] || (data[i] = []);
|
||||
cols = Math.max(row.length, cols);
|
||||
}
|
||||
|
||||
cols = Math.max(50, cols);
|
||||
rows = Math.max(100, rows);
|
||||
|
||||
for(i = 0; i < rows; i++) {
|
||||
row = data[i] || (data[i] = []);
|
||||
for (var j = 0; j < cols + 1; j++) {
|
||||
if (row[j] === undefined) {
|
||||
row[j] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
model.$render = function () {
|
||||
var value = null;
|
||||
try {
|
||||
value = JSON.parse(model.$viewValue) || null;
|
||||
} catch (e) {
|
||||
}
|
||||
if (inst) {
|
||||
value = fill(value || []);
|
||||
inst.loadData(value || null);
|
||||
setTimeout(function () {
|
||||
inst.render();
|
||||
}, 300);
|
||||
}
|
||||
};
|
||||
|
||||
scope.$on("$destroy", function () {
|
||||
if (inst) {
|
||||
inst.destroy();
|
||||
inst = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
template_editable: null,
|
||||
template_readonly: null,
|
||||
template:
|
||||
"<div></div>"
|
||||
});
|
||||
|
||||
})();
|
||||
849
sophal/js/form/form.input.static.js
Normal file
849
sophal/js/form/form.input.static.js
Normal file
@ -0,0 +1,849 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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 popoverElem = null;
|
||||
var popoverTimer = null;
|
||||
|
||||
function canDisplayPopover(scope, details) {
|
||||
if (axelor.device.mobile) {
|
||||
return false;
|
||||
}
|
||||
if(!axelor.config['user.technical']) {
|
||||
return details ? false : scope.field && scope.field.help;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function makePopover(scope, element, callback, placement) {
|
||||
|
||||
var mode = axelor.config['application.mode'];
|
||||
var tech = axelor.config['user.technical'];
|
||||
var doc = $(document);
|
||||
|
||||
var table = null;
|
||||
|
||||
function addRow(label, text, klass) {
|
||||
if (table === null) {
|
||||
table = $('<table class="field-details"></table>');
|
||||
}
|
||||
|
||||
var tr = $('<tr></tr>').appendTo(table);
|
||||
if (label) {
|
||||
$('<th></th>').text(label).appendTo(tr);
|
||||
}
|
||||
if (klass == null) {
|
||||
text = '<code>' + text + '</code>';
|
||||
}
|
||||
var td = $('<td></td>').html(text).addClass(klass).appendTo(tr);
|
||||
if (!label) {
|
||||
td.attr('colspan', 2);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
element.popover({
|
||||
html: true,
|
||||
delay: { show: 1000, hide: 100 },
|
||||
animate: true,
|
||||
placement: function() {
|
||||
if (placement) return placement;
|
||||
var coord = $(element.get(0)).offset(),
|
||||
viewport = { height: window.innerHeight, width: window.innerWidth };
|
||||
if(viewport.height < (coord.top + 100))
|
||||
return 'top';
|
||||
if(coord.left > (viewport.width / 2))
|
||||
return 'left';
|
||||
return 'right';
|
||||
},
|
||||
trigger: 'manual',
|
||||
container: 'body',
|
||||
title: function() {
|
||||
return element.text();
|
||||
},
|
||||
content: function() {
|
||||
if (table) {
|
||||
table.remove();
|
||||
table = null;
|
||||
}
|
||||
callback(scope, addRow);
|
||||
if (table) return table;
|
||||
return "";
|
||||
}
|
||||
});
|
||||
|
||||
element.on('mouseenter.popover', enter);
|
||||
element.on('mouseleave.popover', leave);
|
||||
|
||||
function selectText(elem) {
|
||||
var el = $(elem).get(0);
|
||||
if (document.selection) {
|
||||
var range = document.body.createTextRange();
|
||||
range.moveToElementText(el);
|
||||
range.select();
|
||||
} else if (window.getSelection) {
|
||||
var range = document.createRange();
|
||||
range.selectNodeContents(el);
|
||||
window.getSelection().removeAllRanges();
|
||||
window.getSelection().addRange(range);
|
||||
}
|
||||
}
|
||||
|
||||
function enter(e, show) {
|
||||
if (popoverTimer) {
|
||||
clearTimeout(popoverTimer);
|
||||
}
|
||||
popoverTimer = setTimeout(function () {
|
||||
if (popoverElem === null) {
|
||||
popoverElem = element;
|
||||
popoverElem.popover('show');
|
||||
if (e.ctrlKey) {
|
||||
selectText(table.find('.field-name,.model-name').get(0));
|
||||
}
|
||||
}
|
||||
var tip = element.data('popover').$tip;
|
||||
if (tip) {
|
||||
tip.attr('tabIndex', 0);
|
||||
tip.css('outline', 'none');
|
||||
}
|
||||
}, (e.ctrlKey || show) ? 0 : 1000);
|
||||
}
|
||||
|
||||
function leave(e) {
|
||||
|
||||
if (e.ctrlKey) {
|
||||
doc.off('mousemove.popover');
|
||||
doc.on('mousemove.popover', leave);
|
||||
return;
|
||||
}
|
||||
|
||||
if (popoverTimer) {
|
||||
clearTimeout(popoverTimer);
|
||||
popoverTimer = null;
|
||||
}
|
||||
if (popoverElem) {
|
||||
popoverElem.popover('hide');
|
||||
popoverElem = null;
|
||||
doc.off('mousemove.popover');
|
||||
}
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
if (popoverTimer) {
|
||||
clearTimeout(popoverTimer);
|
||||
popoverTimer = null;
|
||||
}
|
||||
if (element) {
|
||||
element.off('mouseenter.popover');
|
||||
element.off('mouseleave.popover');
|
||||
element.popover('destroy');
|
||||
element = null;
|
||||
}
|
||||
if (table) {
|
||||
table.remove();
|
||||
table = null;
|
||||
}
|
||||
doc.off('mousemove.popover');
|
||||
}
|
||||
|
||||
element.on('$destroy', destroy);
|
||||
scope.$on('$destroy', destroy);
|
||||
}
|
||||
|
||||
function setupPopover(scope, element, getHelp, placement) {
|
||||
|
||||
if (!canDisplayPopover(scope, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var timer = null;
|
||||
element.on('mouseenter.help.setup', function (e) {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
timer = setTimeout(function () {
|
||||
element.off('mouseenter.help.setup');
|
||||
element.off('mouseleave.help.setup');
|
||||
makePopover(scope, element, getHelp, placement);
|
||||
element.trigger('mouseenter.popover', true);
|
||||
}, e.ctrlKey ? 0 : 1000);
|
||||
});
|
||||
element.on('mouseleave.help.setup $destroy', function () {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ui.directive('uiTabPopover', function() {
|
||||
|
||||
function getHelp(scope, addRow) {
|
||||
var tab = scope.tab || {};
|
||||
var type = tab.viewType;
|
||||
var view = _.findWhere(tab.views, {type: type});
|
||||
|
||||
var viewScope = tab.$viewScope;
|
||||
if (viewScope && viewScope.schema) {
|
||||
view = viewScope.schema;
|
||||
}
|
||||
|
||||
if (tab.action) {
|
||||
addRow(_t('Action'), tab.action);
|
||||
}
|
||||
if (tab.model) {
|
||||
addRow(_t('Object'), '<code>' + tab.model + '</code>', 'model-name');
|
||||
}
|
||||
if (tab.domain) {
|
||||
addRow(_t('Domain'), tab.domain);
|
||||
}
|
||||
if (view && view.name) {
|
||||
addRow(_t('View'), view.name);
|
||||
}
|
||||
}
|
||||
|
||||
return function(scope, element, attrs) {
|
||||
setupPopover(scope, element, getHelp, 'bottom');
|
||||
};
|
||||
});
|
||||
|
||||
ui.directive('uiHelpPopover', function() {
|
||||
|
||||
function getHelp(scope, addRow) {
|
||||
|
||||
var field = scope.field;
|
||||
var text = field.help;
|
||||
if (text) {
|
||||
text = text.replace(/\\n/g, '<br>');
|
||||
addRow(null, text, 'help-text');
|
||||
}
|
||||
|
||||
if(!canDisplayPopover(scope, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (text) {
|
||||
addRow(null, '<hr noshade>', 'help-text');
|
||||
}
|
||||
|
||||
var model = scope._model;
|
||||
if (model === field.target) {
|
||||
model = scope._parentModel || scope.$parent._model;
|
||||
}
|
||||
|
||||
addRow(_t('Object'), model);
|
||||
addRow(_t('Field Name'), '<code>' + field.name + '</code>', 'field-name');
|
||||
addRow(_t('Field Type'), field.serverType);
|
||||
|
||||
if (field.type === 'text') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (field.domain) {
|
||||
addRow(_t('Filter'), field.domain);
|
||||
}
|
||||
|
||||
if (field.target) {
|
||||
addRow(_t('Reference'), field.target);
|
||||
}
|
||||
|
||||
var value = scope.$eval('$$original.' + field.name);
|
||||
var length;
|
||||
|
||||
if (value && /-one$/.test(field.serverType)) {
|
||||
value = _.compact([value.id, value[field.targetName]]).join(',');
|
||||
value = '(' + value + ')';
|
||||
}
|
||||
if (value && field.type === "password") {
|
||||
value = _.str.repeat('*', value.length);
|
||||
}
|
||||
if (value && /^(string|image|binary)$/.test(field.type)) {
|
||||
length = value.length;
|
||||
value = _.first(value, 50);
|
||||
if (length > 50) {
|
||||
value.push('...');
|
||||
}
|
||||
value = value.join('');
|
||||
}
|
||||
if (value && /(panel-related|one-to-many|many-to-many)/.test(field.serverType)) {
|
||||
length = value.length;
|
||||
value = _.first(value, 5);
|
||||
value = _.map(value, function(v){
|
||||
return v.id;
|
||||
});
|
||||
if (length > 5) {
|
||||
value.push('...');
|
||||
}
|
||||
value = value.join(', ');
|
||||
}
|
||||
|
||||
addRow(_t('Orig. Value'), value);
|
||||
}
|
||||
|
||||
function doLink(scope, element, attrs) {
|
||||
var field = scope.field;
|
||||
if (field == null) {
|
||||
return;
|
||||
}
|
||||
if (field.help && axelor.config['user.noHelp'] !== true) {
|
||||
if (element.parent('label').length) {
|
||||
element.parent('label').addClass('has-help');
|
||||
} else {
|
||||
element.addClass('has-help');
|
||||
}
|
||||
}
|
||||
setupPopover(scope, element, getHelp);
|
||||
}
|
||||
|
||||
return function(scope, element, attrs) {
|
||||
var field = scope.field;
|
||||
if (!_.isEmpty(field)) {
|
||||
return doLink(scope, element, attrs);
|
||||
}
|
||||
var unwatch = scope.$watch('field', function popoverFieldWatch(field, old) {
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
unwatch();
|
||||
doLink(scope, element, attrs);
|
||||
}, true);
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* The Label widget.
|
||||
*
|
||||
*/
|
||||
ui.formItem('Label', {
|
||||
|
||||
css: 'label-item',
|
||||
cellCss: 'form-label',
|
||||
|
||||
transclude: true,
|
||||
|
||||
link: function(scope, element, attrs) {
|
||||
var field = scope.field;
|
||||
if (field && field.required) {
|
||||
element.addClass('required');
|
||||
}
|
||||
},
|
||||
|
||||
template:
|
||||
"<label><span ui-help-popover ng-transclude></span></label>"
|
||||
});
|
||||
|
||||
ui.directive('uiTranslateIcon', ['$q', function ($q) {
|
||||
return {
|
||||
link: function (scope, element) {
|
||||
var icon = $("<i class='fa fa-flag translate-icon'></i>").attr('title', _t('Show translations.')).appendTo(element);
|
||||
var toggle = function () {
|
||||
icon.toggle(!scope.$$readonlyOrig);
|
||||
};
|
||||
|
||||
scope.$watch("$$readonlyOrig", toggle);
|
||||
scope.$on("on:new", toggle);
|
||||
scope.$on("on:edit", toggle);
|
||||
|
||||
var myDs = scope._dataSource;
|
||||
var trDs = scope._dataSource._new("com.axelor.meta.db.MetaTranslation");
|
||||
trDs._sortBy = ["id"];
|
||||
|
||||
function saveData(value, data, orig, callback) {
|
||||
var changed = [];
|
||||
var removed = [];
|
||||
|
||||
data.forEach(function (item) {
|
||||
var found = _.findWhere(orig, { id: item.id });
|
||||
if (!angular.equals(found, item)) {
|
||||
changed.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
orig.forEach(function (item) {
|
||||
var found = _.findWhere(data, { id: item.id });
|
||||
if (!found) {
|
||||
removed.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
function saveTranslations() {
|
||||
var all = [];
|
||||
|
||||
if (removed.length) {
|
||||
all.push(trDs.removeAll(removed));
|
||||
}
|
||||
if (changed.length) {
|
||||
all.push(trDs.saveAll(changed));
|
||||
}
|
||||
|
||||
if (all.length) {
|
||||
$q.all(all).then(function () {
|
||||
var lang = axelor.config['user.lang'] || en;
|
||||
var key = 'value:' + scope.getValue();
|
||||
var trKey = '$t:' + scope.field.name;
|
||||
return trDs.search({
|
||||
domain: "self.key = :key and self.language = :lang",
|
||||
context: { key: key, lang: lang },
|
||||
limit: 1
|
||||
}).success(function (records) {
|
||||
var record = _.first(records);
|
||||
if (scope.record) {
|
||||
scope.record[trKey] = (record||{}).message;
|
||||
scope.$parent.$parent.text = scope.format(scope.getValue());
|
||||
var rec = scope._dataSource.get(scope.record.id);
|
||||
if (rec) {
|
||||
rec[trKey] = scope.record[trKey];
|
||||
}
|
||||
}
|
||||
});
|
||||
}).then(callback, callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
if (value !== scope.getValue()) {
|
||||
scope.$parent.$parent.setValue(value, true);
|
||||
scope.waitForActions(function () {
|
||||
scope.$parent.$parent.onSave().then(saveTranslations, callback);
|
||||
});
|
||||
} else {
|
||||
saveTranslations();
|
||||
}
|
||||
}
|
||||
|
||||
function showPopup(data) {
|
||||
|
||||
if (!data || data.length == 0) {
|
||||
data = [];
|
||||
}
|
||||
|
||||
var value = scope.getValue();
|
||||
|
||||
var orig = angular.copy(data);
|
||||
var form = $("<form>");
|
||||
|
||||
var valueInput = (scope.field.multiline
|
||||
? $("<textarea class='span12'>")
|
||||
: $("<input type='text' class='span12'>"))
|
||||
.prop('name', scope.field.name)
|
||||
.prop('required', true)
|
||||
.val(value)
|
||||
.on('input', function () {
|
||||
value = this.value;
|
||||
data.forEach(function (item) {
|
||||
item.key = 'value:' + value;
|
||||
});
|
||||
});
|
||||
|
||||
// add value fields
|
||||
$("<div class='row-fluid'>")
|
||||
.append($("<label class='span12'>").text(_t("Value")))
|
||||
.appendTo(form);
|
||||
$("<div class='row-fluid'>")
|
||||
.append(valueInput)
|
||||
.appendTo(form);
|
||||
|
||||
form.append('<hr>');
|
||||
|
||||
// add translation fields
|
||||
$("<div class='row-fluid'>")
|
||||
.append($("<label class='span8'>").text(_t("Translation")))
|
||||
.append($("<label class='span4'>").text(_t("Language")))
|
||||
.appendTo(form);
|
||||
|
||||
function addRow(item) {
|
||||
var onchange = function () {
|
||||
var v = item[this.name];
|
||||
if (v !== this.value) {
|
||||
item[this.name] = this.value;
|
||||
}
|
||||
};
|
||||
|
||||
item.key = item.key || ('value:' + value);
|
||||
|
||||
var input1 = (scope.field.multiline
|
||||
? $("<textarea class='span8'>")
|
||||
: $("<input type='text' class='span8'>"))
|
||||
.prop("name", "message")
|
||||
.prop("required", true)
|
||||
.val(item.message)
|
||||
.on("input", onchange);
|
||||
var input2 = $("<input type='text' class='span4'>")
|
||||
.prop("name", "language")
|
||||
.prop("required", true)
|
||||
.val(item.language)
|
||||
.on("input", onchange);
|
||||
var row = $("<div class='row-fluid'>")
|
||||
.append(input1)
|
||||
.append(input2)
|
||||
.appendTo(form);
|
||||
|
||||
if (dialog) {
|
||||
input1.focus();
|
||||
}
|
||||
|
||||
// remove icon
|
||||
$("<i class='fa fa-times'>")
|
||||
.add('help', _t('Remove'))
|
||||
.appendTo(row)
|
||||
.click(function () {
|
||||
var i = data.indexOf(item);
|
||||
data.splice(i, 1);
|
||||
row.remove();
|
||||
});
|
||||
}
|
||||
|
||||
function addNew() {
|
||||
var item = {};
|
||||
data.push(item);
|
||||
addRow(item);
|
||||
}
|
||||
|
||||
var dialog;
|
||||
|
||||
function validate() {
|
||||
var empty = html.find('input:text[value=""]');
|
||||
if (empty.length) {
|
||||
empty.first().focus();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
var html = $("<div>").append(form);
|
||||
|
||||
// add icon
|
||||
$("<i class='fa fa-plus'>")
|
||||
.attr('help', _t('Add'))
|
||||
.appendTo(html)
|
||||
.click(function () {
|
||||
if (validate()) {
|
||||
addNew();
|
||||
}
|
||||
});
|
||||
|
||||
data.forEach(addRow);
|
||||
|
||||
if (data.length === 0) {
|
||||
addNew();
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (dialog) {
|
||||
dialog.dialog('close');
|
||||
}
|
||||
}
|
||||
|
||||
dialog = axelor.dialogs.box(html, {
|
||||
title: _t('Translations'),
|
||||
buttons: [{
|
||||
'text' : _t('Cancel'),
|
||||
'class' : 'btn',
|
||||
'click' : close
|
||||
}, {
|
||||
'text' : _t('OK'),
|
||||
'class' : 'btn btn-primary',
|
||||
'click' : function() {
|
||||
if (validate()) {
|
||||
saveData(value, data, orig, close);
|
||||
}
|
||||
}
|
||||
}]
|
||||
}).addClass('translation-form');
|
||||
}
|
||||
|
||||
icon.click(function (e) {
|
||||
var value = scope.getValue();
|
||||
if (value) {
|
||||
trDs.search({
|
||||
domain: "self.key = :key",
|
||||
context: { key: 'value:' + value }
|
||||
}).success(showPopup);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
/**
|
||||
* The Spacer widget.
|
||||
*
|
||||
*/
|
||||
ui.formItem('Spacer', {
|
||||
css: 'spacer-item',
|
||||
template: '<div> </div>'
|
||||
});
|
||||
|
||||
/**
|
||||
* The Separator widget.
|
||||
*
|
||||
*/
|
||||
ui.formItem('Separator', {
|
||||
css: 'separator-item',
|
||||
showTitle: false,
|
||||
template: '<div>{{field.title}}</div>'
|
||||
});
|
||||
|
||||
/**
|
||||
* The Static Text widget.
|
||||
*
|
||||
*/
|
||||
ui.formItem('Static', {
|
||||
css: 'static-item',
|
||||
link: function (scope, element, attrs, ctrl) {
|
||||
var field = scope.field;
|
||||
element.html(field.text);
|
||||
},
|
||||
template: '<div></div>'
|
||||
});
|
||||
|
||||
/**
|
||||
* The Static Label widget.
|
||||
*
|
||||
*/
|
||||
ui.formItem('StaticLabel', {
|
||||
css: 'static-item',
|
||||
transclude: true,
|
||||
template: '<label ng-transclude></label>'
|
||||
});
|
||||
|
||||
/**
|
||||
* The Help Text widget.
|
||||
*
|
||||
*/
|
||||
ui.formItem('Help', {
|
||||
css: 'help-item',
|
||||
link: function (scope, element, attrs, ctrl) {
|
||||
var field = scope.field;
|
||||
var css = "alert alert-info";
|
||||
if (field.css && field.css.indexOf('alert-') > -1) {
|
||||
css = "alert";
|
||||
}
|
||||
element.addClass(css).html(field.text);
|
||||
},
|
||||
template: '<div></div>'
|
||||
});
|
||||
|
||||
/**
|
||||
* The button widget.
|
||||
*/
|
||||
ui.formItem('Button', {
|
||||
css: 'button-item',
|
||||
transclude: true,
|
||||
link: function(scope, element, attrs, model) {
|
||||
var field = scope.field || {};
|
||||
|
||||
var icon = field.icon || "";
|
||||
var iconHover = field.iconHover || "";
|
||||
|
||||
var isIcon = icon.indexOf('fa-') === 0;
|
||||
|
||||
if (isIcon || icon) {
|
||||
element.prepend(' ');
|
||||
}
|
||||
|
||||
var css = field.css || '';
|
||||
if (css.indexOf('btn-') > -1 && css.indexOf('btn-primary') === -1) {
|
||||
element.removeClass('btn-primary');
|
||||
}
|
||||
|
||||
if (field && field.help && axelor.config['user.noHelp'] !== true) {
|
||||
element.addClass('has-help');
|
||||
}
|
||||
|
||||
if (isIcon) {
|
||||
var e = $('<i>').addClass('fa').addClass(icon).prependTo(element);
|
||||
if (iconHover) {
|
||||
e.hover(function() {
|
||||
$(this).removeClass(icon).addClass(iconHover);
|
||||
}, function() {
|
||||
$(this).removeClass(iconHover).addClass(icon);
|
||||
});
|
||||
}
|
||||
} else if (icon) {
|
||||
$('<img>').attr('src', icon).prependTo(element);
|
||||
}
|
||||
|
||||
if (!field.title) {
|
||||
element.addClass("button-icon");
|
||||
}
|
||||
|
||||
if (_.isString(field.link)) {
|
||||
element.removeClass('btn btn-primary').addClass('btn-link');
|
||||
element.attr("href", field.link);
|
||||
}
|
||||
|
||||
element.one('mouseover', function () {
|
||||
element.tooltip({
|
||||
html: true,
|
||||
title: function() {
|
||||
if (field.help) {
|
||||
return field.help;
|
||||
}
|
||||
if (element.innerWidth() < element[0].scrollWidth) {
|
||||
return field.title;
|
||||
}
|
||||
},
|
||||
delay: { show: 1000, hide: 100 },
|
||||
container: 'body'
|
||||
});
|
||||
|
||||
element.on("$destroy", function () {
|
||||
var t = element.data('tooltip');
|
||||
if (t) {
|
||||
t.destroy();
|
||||
t = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
element.on("click", function(e) {
|
||||
|
||||
if (scope.isReadonlyExclusive() || element.hasClass('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
function enable() {
|
||||
scope.ajaxStop(function () {
|
||||
setDisabled(scope.isReadonlyExclusive());
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function setEnable(p) {
|
||||
if (p && p.then) {
|
||||
p.then(enable, enable);
|
||||
} else {
|
||||
scope.ajaxStop(enable, 500);
|
||||
}
|
||||
}
|
||||
|
||||
function doClick() {
|
||||
setEnable(scope.fireAction("onClick"));
|
||||
}
|
||||
|
||||
setDisabled(true);
|
||||
|
||||
if (scope.waitForActions) {
|
||||
return scope.waitForActions(doClick);
|
||||
}
|
||||
return doClick();
|
||||
});
|
||||
|
||||
function setDisabled(disabled) {
|
||||
if (disabled || disabled === undefined) {
|
||||
return element.addClass("disabled").attr('tabindex', -1);
|
||||
}
|
||||
return element.removeClass("disabled").removeAttr('tabindex');
|
||||
}
|
||||
|
||||
var readonlySet = false;
|
||||
scope.$watch('isReadonlyExclusive()', function buttonReadonlyWatch(readonly, old) {
|
||||
if (readonly === old && readonlySet) return;
|
||||
readonlySet = true;
|
||||
return setDisabled(readonly);
|
||||
});
|
||||
|
||||
scope.$watch('attr("title")', function buttonTitleWatch(title, old) {
|
||||
if (!title || title === old) return;
|
||||
if (element.is('button')) {
|
||||
return element.html(title);
|
||||
}
|
||||
element.children('.btn-text').html(title);
|
||||
});
|
||||
|
||||
scope.$watch('attr("css")', function buttonCssWatch(css, old) {
|
||||
var curr = css || field.css || 'btn-success';
|
||||
var prev = old || field.css || 'btn-success';
|
||||
if (curr !== prev) {
|
||||
element.removeClass(prev || '').addClass(curr);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watch('attr("icon")', function buttonIconWatch(icon, old) {
|
||||
if (icon === old || (icon && icon.indexOf('fa-') !== 0)) return;
|
||||
var iconElem = element.find('i.fa:first');
|
||||
if (iconElem.length == 0) {
|
||||
iconElem = $('<i>').addClass('fa').prependTo(element.prepend(' '));
|
||||
}
|
||||
iconElem.removeClass(old || '').addClass(icon || field.icon || '');
|
||||
});
|
||||
},
|
||||
template: '<a href="" class="btn btn-primary">'+
|
||||
'<span class="btn-text" ng-transclude></span>'+
|
||||
'</a>'
|
||||
});
|
||||
|
||||
ui.formItem('InfoButton', 'Button', {
|
||||
link: function (scope, element, attrs) {
|
||||
this._super.apply(this, arguments);
|
||||
var field = scope.field || {};
|
||||
scope.title = field.title;
|
||||
scope.$watch('attr("title")', function infoButtonTitleWatch(title, old) {
|
||||
if (!title || title === old) return;
|
||||
scope.title = title;
|
||||
});
|
||||
Object.defineProperty(scope, 'value', {
|
||||
get: function () {
|
||||
return field.currency
|
||||
? ui.formatters.decimal(field, (scope.record || {})[field.name], scope.record)
|
||||
: ui.formatters.$fmt(scope, field.name);
|
||||
}
|
||||
});
|
||||
},
|
||||
replace: true,
|
||||
template:
|
||||
"<div class='btn info-button'>" +
|
||||
"<div class='info-button-data'>" +
|
||||
"<span class='info-button-value'>{{value}}</span>" +
|
||||
"<small class='info-button-title'>{{title}}</small>" +
|
||||
"</div>" +
|
||||
"</div>"
|
||||
});
|
||||
|
||||
ui.formItem('ToolButton', 'Button', {
|
||||
|
||||
getViewDef: function(element) {
|
||||
return this.btn;
|
||||
},
|
||||
|
||||
link: function(scope, element, attrs) {
|
||||
this._super.apply(this, arguments);
|
||||
var field = scope.field;
|
||||
if (field == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.title = field.title;
|
||||
scope.showTitle = field.showTitle !== false;
|
||||
|
||||
scope.btn.isHidden = function() {
|
||||
return scope.isHidden();
|
||||
};
|
||||
},
|
||||
|
||||
template:
|
||||
'<button class="btn" ui-show="!isHidden()" name="{{btn.name}}" ui-actions ui-widget-states>' +
|
||||
'<span ng-show="showTitle">{{title}}</span>' +
|
||||
'</button>'
|
||||
});
|
||||
|
||||
})();
|
||||
206
sophal/js/form/form.input.text.js
Normal file
206
sophal/js/form/form.input.text.js
Normal file
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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');
|
||||
|
||||
/**
|
||||
* The String widget.
|
||||
*/
|
||||
ui.formInput('String', {
|
||||
css: 'string-item',
|
||||
|
||||
init: function(scope) {
|
||||
var field = scope.field;
|
||||
var isReadonly = scope.isReadonly;
|
||||
var trKey = "$t:" + field.name;
|
||||
|
||||
scope.isReadonly = function () {
|
||||
scope.$$readonlyOrig = isReadonly.apply(this, arguments);
|
||||
return (scope.record && scope.record[trKey]) || scope.$$readonlyOrig;
|
||||
};
|
||||
|
||||
scope.format = function (value) {
|
||||
if ((scope.record && scope.record[trKey])) {
|
||||
return scope.record[trKey];
|
||||
}
|
||||
return value;
|
||||
};
|
||||
},
|
||||
|
||||
link_editable: function(scope, element, attrs, model) {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
var field = scope.field,
|
||||
regex = field.pattern ? new RegExp(field.pattern, 'i') : null,
|
||||
minSize = +(field.minSize),
|
||||
maxSize = +(field.maxSize);
|
||||
|
||||
scope.validate = function(value) {
|
||||
if (_.isEmpty(value)) {
|
||||
return true;
|
||||
}
|
||||
var length = value.length,
|
||||
valid = true;
|
||||
|
||||
if (minSize) {
|
||||
valid = length >= minSize;
|
||||
}
|
||||
if(valid && maxSize) {
|
||||
valid = length <= maxSize;
|
||||
}
|
||||
if (valid && regex) {
|
||||
valid = regex.test(value);
|
||||
}
|
||||
|
||||
return valid;
|
||||
};
|
||||
},
|
||||
|
||||
template_readonly: '<input type="text" ng-show="text" tabindex="-1" readonly="readonly" class="display-text" value="{{text}}">'
|
||||
});
|
||||
|
||||
/**
|
||||
* The Email input widget.
|
||||
*/
|
||||
ui.formInput('Email', {
|
||||
|
||||
css: 'email-item',
|
||||
|
||||
metaWidget: true,
|
||||
|
||||
pattern: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
|
||||
|
||||
link: function(scope, element, attrs, model) {
|
||||
|
||||
var pattern = this.pattern;
|
||||
|
||||
scope.validate = function(value) {
|
||||
if(_.isEmpty(value)) {
|
||||
return true;
|
||||
}
|
||||
return pattern.test(value);
|
||||
};
|
||||
},
|
||||
|
||||
template_editable: '<input type="email">',
|
||||
template_readonly: '<a target="_blank" ng-show="text" href="mailto:{{text}}">{{text}}</a>'
|
||||
});
|
||||
|
||||
/**
|
||||
* The URL input widget.
|
||||
*/
|
||||
ui.formInput('Url', {
|
||||
css: 'url-item',
|
||||
metaWidget: true,
|
||||
template_editable: '<input type="url">',
|
||||
template_readonly: '<a target="_blank" ng-show="text" href="{{text}}">{{text}}</a>'
|
||||
});
|
||||
|
||||
/**
|
||||
* The Phone input widget.
|
||||
*/
|
||||
ui.formInput('Phone', 'String', {
|
||||
css: 'phone-item',
|
||||
template_editable: '<input type="tel">'
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* The Text input widget.
|
||||
*/
|
||||
ui.formInput('Text', {
|
||||
css: 'text-item',
|
||||
link_editable: function(scope, element, attrs, model) {
|
||||
this._super.apply(this, arguments);
|
||||
var field = scope.field,
|
||||
textarea = element.get(0);
|
||||
|
||||
textarea.rows = parseInt(field.height) || 8;
|
||||
|
||||
//Firefox add one more line
|
||||
if (axelor.browser.mozilla) {
|
||||
textarea.rows -= 1;
|
||||
}
|
||||
|
||||
var field = scope.field,
|
||||
regex = field.pattern ? new RegExp(field.pattern, 'i') : null,
|
||||
minSize = +(field.minSize),
|
||||
maxSize = +(field.maxSize);
|
||||
|
||||
scope.validate = function(value) {
|
||||
if (_.isEmpty(value)) {
|
||||
return true;
|
||||
}
|
||||
var length = value.length,
|
||||
valid = true;
|
||||
|
||||
if (minSize) {
|
||||
valid = length >= minSize;
|
||||
}
|
||||
if(valid && maxSize) {
|
||||
valid = length <= maxSize;
|
||||
}
|
||||
if (valid && regex) {
|
||||
valid = regex.test(value);
|
||||
}
|
||||
|
||||
return valid;
|
||||
};
|
||||
|
||||
},
|
||||
template_editable: '<textarea></textarea >',
|
||||
template_readonly: '<pre ng-show="text">{{text}}</pre>'
|
||||
});
|
||||
|
||||
ui.formInput('Password', 'String', {
|
||||
|
||||
css: 'password-item',
|
||||
|
||||
metaWidget: true,
|
||||
|
||||
init: function(scope) {
|
||||
|
||||
scope.password = function() {
|
||||
var value = this.getValue() || "";
|
||||
return _.str.repeat('*', value.length);
|
||||
};
|
||||
},
|
||||
template_readonly: '<input type="password" ng-show="text" tabindex="-1" readonly="readonly" class="display-text" value="{{password()}}"></input>',
|
||||
template_editable: '<input type="password" autocomplete="new-password">'
|
||||
});
|
||||
|
||||
ui.directive('uiTextareaAutoSize', function () {
|
||||
|
||||
return function (scope, element, attrs) {
|
||||
|
||||
if (!element.is('textarea')) return;
|
||||
|
||||
function resize() {
|
||||
var diff = element.outerHeight() - element.innerHeight();
|
||||
element.css('height', 'auto').css('height', element[0].scrollHeight + diff);
|
||||
}
|
||||
|
||||
element.on('focus keyup input', resize);
|
||||
setTimeout(resize);
|
||||
};
|
||||
});
|
||||
|
||||
})();
|
||||
602
sophal/js/form/form.layout.js
Normal file
602
sophal/js/form/form.layout.js
Normal file
@ -0,0 +1,602 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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() {
|
||||
|
||||
/* jshint newcap: false */
|
||||
|
||||
"use strict";
|
||||
|
||||
var ui = angular.module('axelor.ui');
|
||||
|
||||
function TableLayout(items, attrs, $scope, $compile) {
|
||||
|
||||
var colWidths = attrs.widths,
|
||||
numCols = +attrs.cols || 4,
|
||||
curCol = 0,
|
||||
layout = [[]];
|
||||
|
||||
function add(item, label) {
|
||||
|
||||
if (item.is('br')) {
|
||||
curCol = 0;
|
||||
item.hide();
|
||||
return layout.push([]);
|
||||
}
|
||||
|
||||
var row = _.last(layout),
|
||||
cell = null,
|
||||
colspan = +item.attr('x-colspan') || 1,
|
||||
rowspan = +item.attr('x-rowspan') || 1;
|
||||
|
||||
if (curCol + colspan >= numCols + 1) {
|
||||
curCol = 0, row = [];
|
||||
layout.push(row);
|
||||
}
|
||||
|
||||
if (label) {
|
||||
cell = {};
|
||||
cell.elem = label;
|
||||
cell.css = label.attr('x-cell-css');
|
||||
row.push(cell);
|
||||
if (rowspan > 1) cell.rowspan = rowspan;
|
||||
if (colspan > 1) colspan -= 1;
|
||||
curCol += 1;
|
||||
}
|
||||
|
||||
cell = {};
|
||||
cell.elem = item;
|
||||
cell.css = item.attr('x-cell-css');
|
||||
if (colspan > 1) cell.colspan = colspan;
|
||||
if (rowspan > 1) cell.rowspan = rowspan;
|
||||
|
||||
row.push(cell);
|
||||
curCol += colspan;
|
||||
}
|
||||
|
||||
if (colWidths && angular.isString(colWidths)) {
|
||||
colWidths = colWidths.trim().split(/\s*,\s*/);
|
||||
for(var i = 0 ; i < colWidths.length; i++) {
|
||||
var width = colWidths[i];
|
||||
if (/^(\d+)$/.test(width)) width = width + 'px';
|
||||
if (width == '*') width = 'auto';
|
||||
colWidths[i] = width;
|
||||
}
|
||||
}
|
||||
|
||||
items.each(function(){
|
||||
var el = $(this),
|
||||
title = el.attr('x-title'),
|
||||
noTitle = el.attr('x-show-title') == 'false';
|
||||
|
||||
var labelScope = el.data('$scope');
|
||||
if (labelScope) {
|
||||
labelScope = labelScope.$new();
|
||||
}
|
||||
|
||||
if (numCols > 1 && !noTitle && title) {
|
||||
var label = $('<label ui-label></label>').html(title).attr('x-for-widget', el.attr('id')),
|
||||
labelElem = $compile(label)(labelScope || $scope);
|
||||
el.data('label', labelElem);
|
||||
return add(el, labelElem);
|
||||
}
|
||||
add(el);
|
||||
});
|
||||
|
||||
var table = $('<table class="form-layout"></table>');
|
||||
|
||||
function isLabel(cell) {
|
||||
return cell.css === "form-label" || (cell.elem && cell.elem.is('label,.spacer-item'));
|
||||
}
|
||||
|
||||
function computeWidths(row) {
|
||||
if (row.length === 1) return null;
|
||||
var widths = [],
|
||||
labelCols = 0,
|
||||
itemCols = 0,
|
||||
emptyCols = 0;
|
||||
|
||||
_.each(row, function(cell) {
|
||||
if (isLabel(cell)) {
|
||||
labelCols += (cell.colspan || 1);
|
||||
} else {
|
||||
itemCols += (cell.colspan || 1);
|
||||
}
|
||||
});
|
||||
|
||||
emptyCols = numCols - (labelCols + itemCols);
|
||||
|
||||
labelCols += (emptyCols / 2);
|
||||
itemCols += (emptyCols / 2) + (emptyCols % 2);
|
||||
|
||||
var labelWidth = labelCols ? Math.min(50, (12 * labelCols)) / labelCols : 0;
|
||||
var itemWidth = (100 - (labelWidth * labelCols)) / itemCols;
|
||||
|
||||
_.each(row, function(cell, i) {
|
||||
var width = ((isLabel(cell) ? labelWidth : itemWidth) * (cell.colspan || 1));
|
||||
widths[i] = width + "%";
|
||||
});
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
_.each(layout, function(row){
|
||||
var tr = $('<tr></tr>'),
|
||||
numCells = 0,
|
||||
widths = colWidths || computeWidths(row);
|
||||
|
||||
_.each(row, function(cell, i) {
|
||||
var el = $('<td></td>')
|
||||
.addClass(cell.css)
|
||||
.attr('colspan', cell.colspan)
|
||||
.attr('rowspan', cell.rowspan)
|
||||
.append(cell.elem)
|
||||
.appendTo(tr);
|
||||
if (_.isArray(widths) && widths[i]) {
|
||||
el.width(widths[i]);
|
||||
}
|
||||
numCells += cell.colspan || 1;
|
||||
});
|
||||
|
||||
// append remaining cells
|
||||
for (var i = numCells ; i < numCols ; i++) {
|
||||
$('<td></td>').appendTo(tr).width((widths||[])[i]);
|
||||
}
|
||||
|
||||
tr.appendTo(table);
|
||||
});
|
||||
|
||||
return table;
|
||||
} //- TableLayout
|
||||
|
||||
|
||||
ui.directive('uiTableLayout', ['$compile', function($compile) {
|
||||
|
||||
return function(scope, element, attrs) {
|
||||
var elem = attrs.layoutSelector ? element.find(attrs.layoutSelector) : element;
|
||||
var items = elem.children();
|
||||
|
||||
var layout = TableLayout(items, attrs, scope, $compile);
|
||||
var brTags = element.children('br:hidden'); // detach all the <br> tags
|
||||
|
||||
scope.$on('$destroy', function(){
|
||||
brTags.remove();
|
||||
});
|
||||
|
||||
elem.append(layout);
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
function PanelLayout(items, attrs, $scope, $compile) {
|
||||
|
||||
var stacked = attrs.stacked || false,
|
||||
flexbox = attrs.flexbox || false,
|
||||
numCols = 12,
|
||||
numSpan = +(attrs.itemSpan) || 6,
|
||||
curCol = 0,
|
||||
canAddRow = !stacked && !flexbox,
|
||||
rowClass = flexbox ? 'panel-flex' : 'row-fluid',
|
||||
cellClass = flexbox ? 'flex' : 'span',
|
||||
layout = [$('<div>').addClass(rowClass)];
|
||||
|
||||
function add(item, label) {
|
||||
var row = _.last(layout),
|
||||
cell = $('<div>'),
|
||||
span = +item.attr('x-colspan') || numSpan,
|
||||
offset = +item.attr('x-coloffset') || 0;
|
||||
|
||||
span = Math.min(span, numCols);
|
||||
if (stacked) {
|
||||
span = 0;
|
||||
}
|
||||
|
||||
if (curCol + (span + offset) >= numCols + 1 && canAddRow) {
|
||||
curCol = 0, row = $('<div>').addClass(rowClass);
|
||||
layout.push(row);
|
||||
}
|
||||
if (label) {
|
||||
label.appendTo(cell);
|
||||
row.addClass('has-labels');
|
||||
}
|
||||
|
||||
cell.addClass(item.attr('x-cell-css'));
|
||||
|
||||
if (span) {
|
||||
cell.addClass(cellClass + span);
|
||||
}
|
||||
if (offset) {
|
||||
cell.addClass('offset' + offset);
|
||||
}
|
||||
|
||||
cell.append(item);
|
||||
cell.appendTo(row);
|
||||
|
||||
curCol += (span + offset);
|
||||
}
|
||||
|
||||
items.each(function (item, i) {
|
||||
var el = $(this),
|
||||
title = el.attr('x-title'),
|
||||
noTitle = el.attr('x-show-title') == 'false';
|
||||
|
||||
var labelScope = el.data('$scope');
|
||||
if (labelScope) {
|
||||
labelScope = labelScope.$new();
|
||||
}
|
||||
|
||||
if (!noTitle && title) {
|
||||
var label = $('<label ui-label></label>').html(title).attr('x-for-widget', el.attr('id')),
|
||||
labelElem = $compile(label)(labelScope || $scope);
|
||||
el.data('label', labelElem);
|
||||
return add(el, labelElem);
|
||||
}
|
||||
add(el);
|
||||
});
|
||||
|
||||
var container = $('<div class="panel-layout"></div>').append(layout);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
ui.directive('uiPanelLayout', ['$compile', function($compile) {
|
||||
|
||||
return {
|
||||
priority: 1000,
|
||||
link: function(scope, element, attrs) {
|
||||
var elem = element.children('[ui-transclude]:first');
|
||||
var items = elem.children();
|
||||
var layout = PanelLayout(items, attrs, scope, $compile);
|
||||
elem.append(layout);
|
||||
}
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
function BarLayout(items, attrs, $scope, $compile) {
|
||||
|
||||
var main = $('<div class="bar-main">');
|
||||
var side = $('<div class="bar-side">');
|
||||
var wrap = $('<div class="bar-wrap">').appendTo(main);
|
||||
|
||||
|
||||
items.each(function(item, i) {
|
||||
var elem = $(this);
|
||||
var prop = elem.scope().field || {};
|
||||
if (elem.attr('x-sidebar')) {
|
||||
elem.appendTo(side);
|
||||
} else {
|
||||
elem.appendTo(wrap);
|
||||
}
|
||||
if (prop.attached) {
|
||||
elem.addClass("attached");
|
||||
}
|
||||
});
|
||||
|
||||
var row = $('<div class="bar-container">').append(main);
|
||||
|
||||
if (side && axelor.device.small) {
|
||||
side.children().first().prependTo(wrap);
|
||||
side.children().appendTo(wrap);
|
||||
}
|
||||
|
||||
wrap.children('[ui-panel-mail]').appendTo(main);
|
||||
|
||||
if (side.children().length > 0) {
|
||||
side.appendTo(row.addClass('has-side'));
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
ui.directive('uiBarLayout', ['$compile', function($compile) {
|
||||
|
||||
return function(scope, element, attrs) {
|
||||
var items = element.children();
|
||||
var layout = BarLayout(items, attrs, scope, $compile);
|
||||
var schema = scope.schema || {};
|
||||
var css = null;
|
||||
|
||||
scope._isPanelForm = true;
|
||||
|
||||
element.append(layout);
|
||||
element.addClass('bar-layout');
|
||||
|
||||
if (element.has('[x-sidebar]').length === 0) {
|
||||
css = "mid";
|
||||
}
|
||||
if (element.is('form') && ["mini", "mid", "large"].indexOf(schema.width) > -1) {
|
||||
css = scope.schema.width;
|
||||
}
|
||||
if (css) {
|
||||
element.addClass(css + '-form');
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
ui.directive('uiPanelViewer', function () {
|
||||
return {
|
||||
scope: true,
|
||||
link: function (scope, element, attrs) {
|
||||
var field = scope.field;
|
||||
var isRelational = /-to-one$/.test(field.type);
|
||||
if (isRelational) {
|
||||
Object.defineProperty(scope, 'record', {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return (scope.$parent.record||{})[field.name];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
ui.directive('uiPanelEditor', ['$compile', 'ActionService', function($compile, ActionService) {
|
||||
|
||||
return {
|
||||
scope: true,
|
||||
link: function(scope, element, attrs) {
|
||||
var field = scope.field;
|
||||
var editor = field.editor;
|
||||
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
function applyAttrs(item, level) {
|
||||
if (item.showTitle === undefined && !item.items) {
|
||||
item.showTitle = (editor.widgetAttrs||{}).showTitles !== "false";
|
||||
}
|
||||
if (!item.showTitle && !item.items) {
|
||||
var itemField = (editor.fields||scope.fields||{})[item.name] || {};
|
||||
item.placeholder = item.placeholder || itemField.placeholder || item.title || itemField.title || item.autoTitle;
|
||||
}
|
||||
if (editor.itemSpan && !item.colSpan && !level) {
|
||||
item.colSpan = editor.itemSpan;
|
||||
}
|
||||
if (item.items) {
|
||||
_.map(item.items, function (x) {
|
||||
applyAttrs(x, (level||0) + 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var items = editor.items || [];
|
||||
var hasColSpan = false;
|
||||
var widths = _.map(items, function (item) {
|
||||
applyAttrs(item);
|
||||
if (item.colSpan) {
|
||||
hasColSpan = true;
|
||||
}
|
||||
var width = item.width || (item.widgetAttrs||{}).width;
|
||||
return width ? width : (item.widget === 'toggle' ? 24 : '*');
|
||||
});
|
||||
|
||||
var schema = hasColSpan ? {
|
||||
cols: 12,
|
||||
items: items
|
||||
} : {
|
||||
cols: items.length,
|
||||
colWidths: widths,
|
||||
items: items
|
||||
};
|
||||
|
||||
if (editor.layout !== 'table') {
|
||||
schema = {
|
||||
items: [{
|
||||
type: 'panel',
|
||||
items: items,
|
||||
flexbox: editor.flexbox
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
scope.fields = editor.fields || scope.fields;
|
||||
|
||||
var form = ui.formBuild(scope, schema, scope.fields);
|
||||
var isRelational = /-to-one$/.test(field.type);
|
||||
|
||||
if (isRelational) {
|
||||
Object.defineProperty(scope, 'record', {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return (scope.$parent.record||{})[field.name];
|
||||
},
|
||||
set: function (value) {
|
||||
scope.setValue(value, true);
|
||||
}
|
||||
});
|
||||
Object.defineProperty(scope, '$$original', {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return (scope.$parent.$$original||{})[field.name];
|
||||
},
|
||||
set: function (value) {}
|
||||
});
|
||||
scope.$$setEditorValue = function (value, fireOnChange) {
|
||||
scope.setValue(value, fireOnChange === undefined ? true: fireOnChange);
|
||||
};
|
||||
}
|
||||
|
||||
if (field.target) {
|
||||
scope.getDummyValues = function() {
|
||||
if (!scope.record) return {};
|
||||
var fields = _.keys(scope.fields);
|
||||
var extra = _.chain(scope.fields_view)
|
||||
.filter(function(f) { return f.name && f.name[0] === '$' && !_.contains(fields, f.name); })
|
||||
.filter(function(f) { return ['$changed', '$editorModel', '$version', '$fetched', '$fetchedRelated'].indexOf(f) === -1; })
|
||||
.pluck('name')
|
||||
.compact()
|
||||
.value();
|
||||
|
||||
if (scope._model === 'com.axelor.auth.db.User') {
|
||||
extra = extra.filter(function (n) {
|
||||
return ['change', 'oldPassword', 'newPassword', 'chkPassword'].indexOf(n) === -1;
|
||||
});
|
||||
}
|
||||
|
||||
return _.pick(scope.record, extra);
|
||||
};
|
||||
|
||||
scope.getContext = function () {
|
||||
var context = _.extend({}, scope.record);
|
||||
var dummy = scope.getDummyValues();
|
||||
context._model = scope._model;
|
||||
context._parent = scope.$parent.getContext();
|
||||
return ui.prepareContext(scope._model, context, dummy);
|
||||
};
|
||||
|
||||
scope.$broadcastRecordChange = function () {
|
||||
scope.$broadcast("on:record-change", scope.record);
|
||||
};
|
||||
|
||||
scope.$on('on:before-save', function watchParentRecord() {
|
||||
var dummyValues = scope.getDummyValues();
|
||||
var watcher = scope.$watch('$parent.record', function (record, old) {
|
||||
if (record === old) return;
|
||||
var value = (record||{})[field.name];
|
||||
if (value && dummyValues) {
|
||||
value = _.extend(value, dummyValues);
|
||||
}
|
||||
dummyValues = null;
|
||||
watcher();
|
||||
});
|
||||
});
|
||||
|
||||
scope.$watch('record', function (record, old) {
|
||||
if (record && !record.$editorModel) {
|
||||
record.$editorModel = scope._model;
|
||||
}
|
||||
});
|
||||
|
||||
// make sure to fetch missing values
|
||||
var fetchMissing = function (value) {
|
||||
var ds = scope._dataSource;
|
||||
var record = scope.record;
|
||||
if (value <= 0 || !value || record.$fetched || record.$fetchedRelated) {
|
||||
return;
|
||||
}
|
||||
var missing = _.filter(_.keys(editor.fields), function (name) {
|
||||
if (!record) return false;
|
||||
if (name.indexOf('.') === -1) {
|
||||
return !record.hasOwnProperty(name);
|
||||
}
|
||||
var path = name.split('.');
|
||||
var nested = record;
|
||||
for (var i = 0; i < path.length - 1; i++) {
|
||||
nested = nested[path[i]];
|
||||
if (!nested) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !nested.hasOwnProperty(path[path.length - 1]);
|
||||
});
|
||||
if (missing.length === 0) {
|
||||
return;
|
||||
}
|
||||
record.$fetchedRelated = true;
|
||||
return ds.read(value, {fields: missing}).success(function(rec) {
|
||||
var values = _.pick(rec, missing);
|
||||
record = _.extend(record, values);
|
||||
});
|
||||
};
|
||||
// make sure to trigger record-change with proper record data
|
||||
var watchRun = function (value, old) {
|
||||
if (value && value !== old) {
|
||||
value.$changed = true;
|
||||
value.version = _.isNumber(value.version) ? value.version : value.$version;
|
||||
}
|
||||
if (value) {
|
||||
// parent form's getContext will check this to prepare context for editor
|
||||
// to have proper selection flags in nest o2m/m2m
|
||||
value.$editorModel = scope._model;
|
||||
fetchMissing(value.id);
|
||||
}
|
||||
scope.$applyAsync(function () {
|
||||
scope.$broadcast("on:record-change", value || {}, true);
|
||||
});
|
||||
// if it's an o2m editor, make sure to update values
|
||||
if (scope.$itemsChanged) {
|
||||
scope.$itemsChanged();
|
||||
}
|
||||
};
|
||||
scope.$watch('record', _.debounce(watchRun, 100), true);
|
||||
scope.$timeout(function () {
|
||||
scope.$broadcast("on:record-change", scope.record || {}, true);
|
||||
});
|
||||
}
|
||||
|
||||
form = $compile(form)(scope);
|
||||
form.removeClass('mid-form mini-form').children('div.row').removeClass('row').addClass('row-fluid');
|
||||
element.append(form);
|
||||
|
||||
if (field.target) {
|
||||
var handler = null;
|
||||
if (editor.onNew) {
|
||||
schema.onNew = editor.onNew;
|
||||
form.data('$editorForm', form);
|
||||
handler = ActionService.handler(scope, form, {
|
||||
action: editor.onNew
|
||||
});
|
||||
}
|
||||
scope.$watch('record.id', function editorRecordIdWatch(value, old) {
|
||||
if (!value && handler) {
|
||||
handler.onNew();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
scope.isValid = function () {
|
||||
return scope.form && scope.form.$valid;
|
||||
};
|
||||
|
||||
function isEmpty(record) {
|
||||
if (!record || _.isEmpty(record)) return true;
|
||||
var values = _.filter(record, function (value, name) {
|
||||
return !(/[\$_]/.test(name) || value === null || value === undefined);
|
||||
});
|
||||
return values.length === 0;
|
||||
}
|
||||
|
||||
scope.$watch(function editorValidWatch() {
|
||||
if (isRelational && editor.showOnNew === false && !scope.canShowEditor()) {
|
||||
return;
|
||||
}
|
||||
var valid = scope.isValid();
|
||||
if (!valid && !field.jsonFields && !scope.$parent.isRequired() && isEmpty(scope.record)) {
|
||||
var errors = (scope.form || {}).$error || {};
|
||||
valid = !errors.valid;
|
||||
}
|
||||
if (scope.setValidity) {
|
||||
scope.setValidity('valid', valid, scope.record);
|
||||
element.toggleClass('nested-not-required', valid);
|
||||
} else {
|
||||
scope.$parent.form.$setValidity('valid', valid, scope.form);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function () {
|
||||
if (scope.setValidity) {
|
||||
scope.setValidity('valid', true);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
})();
|
||||
1209
sophal/js/form/form.mail.js
Normal file
1209
sophal/js/form/form.mail.js
Normal file
File diff suppressed because it is too large
Load Diff
561
sophal/js/form/form.relational.base.js
Normal file
561
sophal/js/form/form.relational.base.js
Normal file
@ -0,0 +1,561 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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');
|
||||
|
||||
ui.RefFieldCtrl = RefFieldCtrl;
|
||||
|
||||
function RefFieldCtrl($scope, $element, DataSource, ViewService, initCallback) {
|
||||
|
||||
var field = $scope.getViewDef($element),
|
||||
params = {
|
||||
model: field.target || $element.attr('x-target'),
|
||||
views: field.views || {},
|
||||
domain: field.domain,
|
||||
context: field.context
|
||||
},
|
||||
views = {};
|
||||
|
||||
if (field.jsonTarget) {
|
||||
params.context = _.extend({}, params.context, { jsonModel: field.jsonTarget });
|
||||
}
|
||||
|
||||
if (!$element.is('fieldset')) {
|
||||
|
||||
_.each(field.views, function(view){
|
||||
views[view.type] = view;
|
||||
});
|
||||
|
||||
var formView = null,
|
||||
gridView = null,
|
||||
summaryView = null;
|
||||
|
||||
if (field.summaryView === "" || field.summaryView === "true") {
|
||||
summaryView = views.form;
|
||||
}
|
||||
|
||||
if (field.gridView) {
|
||||
gridView = {
|
||||
type: 'grid',
|
||||
name: field.gridView
|
||||
};
|
||||
}
|
||||
if (field.formView) {
|
||||
formView = {
|
||||
type: 'form',
|
||||
name: field.formView
|
||||
};
|
||||
}
|
||||
|
||||
if (field.summaryView === "" || field.summaryView === "true") {
|
||||
summaryView = views.form || formView || { type: 'form' };
|
||||
} else if (field.summaryView) {
|
||||
summaryView = {
|
||||
type: "form",
|
||||
name: field.summaryView
|
||||
};
|
||||
}
|
||||
|
||||
views.form = formView || views.form;
|
||||
views.grid = gridView || views.grid;
|
||||
params.summaryView = angular.copy(summaryView);
|
||||
params.summaryViewDefault = params.summaryView || views.form;
|
||||
|
||||
params.views = _.compact([views.grid, views.form]);
|
||||
$scope._viewParams = params;
|
||||
}
|
||||
|
||||
ui.ViewCtrl($scope, DataSource, ViewService);
|
||||
|
||||
$scope.ngModel = null;
|
||||
$scope.editorCanSave = true;
|
||||
$scope.editorCanReload = field.canReload;
|
||||
|
||||
if (initCallback) {
|
||||
initCallback.call(this);
|
||||
}
|
||||
|
||||
var editor = null;
|
||||
var selector = null;
|
||||
var embedded = null;
|
||||
|
||||
$scope.createNestedEditor = function() {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Show/Hide the nested editor according to the show parameter, if
|
||||
* undefined then toggle.
|
||||
*
|
||||
*/
|
||||
$scope.showNestedEditor = function showNestedEditor(show) {
|
||||
if (!params.summaryView) {
|
||||
return;
|
||||
}
|
||||
if (embedded === null) {
|
||||
embedded = $scope.createNestedEditor();
|
||||
}
|
||||
var es = embedded.data('$scope');
|
||||
if (es !== null) {
|
||||
es.visible = (show === undefined ? !es.visible : show);
|
||||
embedded.toggle(es.visible);
|
||||
}
|
||||
return embedded;
|
||||
};
|
||||
|
||||
$scope.showPopupEditor = function(record) {
|
||||
if (!record && this.isReadonly()) {
|
||||
return;
|
||||
}
|
||||
if (editor == null) {
|
||||
editor = ViewService.compile('<div ui-editor-popup></div>')($scope);
|
||||
editor.data('$target', $element);
|
||||
}
|
||||
|
||||
var popup = editor.isolateScope();
|
||||
popup.show(record);
|
||||
popup._afterPopupShow = function() {
|
||||
if (record == null) {
|
||||
popup.$broadcast("on:new");
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function _showEditor(record) {
|
||||
|
||||
if (!$scope._isPopup && field.editWindow === "blank" && record && record.id > 0) {
|
||||
var checkVersion = "" + axelor.config["view.form.check-version"];
|
||||
var context = ($scope.selectedTab || {}).context || {};
|
||||
if (context.__check_version !== undefined) {
|
||||
checkVersion = "" + context.__check_version;
|
||||
}
|
||||
var tab = {
|
||||
action: _.uniqueId('$act'),
|
||||
title: field.title,
|
||||
model: field.target,
|
||||
recordId: record.id,
|
||||
views: [{
|
||||
type: 'form',
|
||||
name: field.formView
|
||||
}, {
|
||||
type: 'grid',
|
||||
name: field.gridView
|
||||
}]
|
||||
};
|
||||
if (checkVersion) {
|
||||
tab.context = { __check_version: checkVersion };
|
||||
}
|
||||
|
||||
return $scope.$root.openTab(tab);
|
||||
}
|
||||
|
||||
if ($scope.editorCanReload && record && record.id) {
|
||||
var parent = $scope.$parent;
|
||||
if (parent && parent.canSave()) {
|
||||
var opts = {
|
||||
callOnSave: field.callOnSave
|
||||
};
|
||||
return parent.onSave(opts).then(function(){
|
||||
$scope.showPopupEditor(record);
|
||||
});
|
||||
}
|
||||
}
|
||||
return $scope.showPopupEditor(record);
|
||||
}
|
||||
|
||||
$scope.showEditor = function(record) {
|
||||
var perm = record ? "read" : "create";
|
||||
var id = (record||{}).id;
|
||||
|
||||
if (perm === 'read' && (!id || id < 0)) {
|
||||
return _showEditor(record);
|
||||
}
|
||||
return $scope.isPermitted(perm, record, function(){
|
||||
_showEditor(record);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.parentReload = function() {
|
||||
var parent = $scope.$parent;
|
||||
if (parent) {
|
||||
parent.reload();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.showSelector = function() {
|
||||
if (this.isReadonly()) {
|
||||
return;
|
||||
}
|
||||
function doShow() {
|
||||
if (selector == null) {
|
||||
selector = $('<div ui-selector-popup></div>').attr('x-select-mode', $scope.selectMode || "multi");
|
||||
selector = ViewService.compile(selector)($scope);
|
||||
selector.data('$target', $element);
|
||||
}
|
||||
var popup = selector.isolateScope();
|
||||
popup._domain = $scope._domain; // make sure that popup uses my domain (#1233)
|
||||
popup.show();
|
||||
}
|
||||
|
||||
var onSelect = this.$events.onSelect;
|
||||
if (onSelect) {
|
||||
onSelect().then(function(){
|
||||
doShow();
|
||||
});
|
||||
} else {
|
||||
doShow();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$on("on:edit", function(record){
|
||||
var domain = ($scope.field||field).domain;
|
||||
var context = ($scope.field||field).context;
|
||||
if (domain !== undefined) $scope._domain = domain;
|
||||
if (context !== undefined) $scope._context = context;
|
||||
});
|
||||
|
||||
$scope.setDomain = function(domain, context) {
|
||||
if (domain !== undefined) $scope._domain = domain;
|
||||
if (context !== undefined) $scope._context = context;
|
||||
};
|
||||
|
||||
$scope.getDomain = function() {
|
||||
return {
|
||||
_domain: $scope._domain,
|
||||
_context: $scope._context
|
||||
};
|
||||
};
|
||||
|
||||
var fetchDS = (function () {
|
||||
var fds = null;
|
||||
return function () {
|
||||
if (fds) return fds;
|
||||
var ds = $scope._dataSource;
|
||||
return fds = DataSource.create(ds._model, {
|
||||
domain: ds._domain,
|
||||
context: ds._context
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
||||
$scope.fetchData = function(value, success) {
|
||||
|
||||
var records = $.makeArray(value),
|
||||
ids = [];
|
||||
|
||||
_.each(records, function(item) {
|
||||
if (_.isNumber(item)) {
|
||||
return ids.push(item);
|
||||
}
|
||||
if (_.isNumber(item.id) && item.id > 0 &&
|
||||
_.isUndefined(item.version) &&
|
||||
_.isUndefined(item.$fetched)) {
|
||||
return ids.push(item.id);
|
||||
}
|
||||
});
|
||||
|
||||
if (ids.length === 0) {
|
||||
return success(value);
|
||||
}
|
||||
|
||||
var fields = $scope.selectFields();
|
||||
|
||||
function doFetch(view) {
|
||||
var domain = "self.id in (:_field_ids)";
|
||||
var context = _.pick($scope.getContext(), ['id', '_model']);
|
||||
|
||||
var sortBy = view.sortBy || view.orderBy;
|
||||
if (sortBy) {
|
||||
sortBy = sortBy.split(",");
|
||||
}
|
||||
if (view.canMove && fields.indexOf('sequence') === -1) {
|
||||
fields.push('sequence');
|
||||
}
|
||||
|
||||
context._field = field.name;
|
||||
context._field_ids = ids;
|
||||
|
||||
return fetchDS().search({
|
||||
fields: fields,
|
||||
sortBy: fetchDS()._sortBy || sortBy,
|
||||
archived: true,
|
||||
limit: -1,
|
||||
domain: domain,
|
||||
context: context
|
||||
}).success(function(records, page){
|
||||
// only edited records should have version property
|
||||
var items = _.map(records, function(item){
|
||||
item.$version = item.version;
|
||||
item.$fetched = false;
|
||||
delete item.version;
|
||||
return item;
|
||||
});
|
||||
success(items, page);
|
||||
});
|
||||
}
|
||||
|
||||
if ($scope.isHidden()) {
|
||||
return doFetch($scope.view || {});
|
||||
}
|
||||
|
||||
return $scope._viewPromise.then(function(view) {
|
||||
return doFetch(view || {});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.fetchSelection = function(request, response) {
|
||||
var fn = fetchSelection.bind(this);
|
||||
var onSelect = this.$events.onSelect;
|
||||
if (onSelect) {
|
||||
return onSelect(true).then(function() {
|
||||
return fn(request, response);
|
||||
});
|
||||
}
|
||||
return fn(request, response);
|
||||
};
|
||||
|
||||
function fetchSelection(request, response) {
|
||||
|
||||
/* jshint validthis: true */
|
||||
|
||||
var field = this.field;
|
||||
var nameField = field.targetName || 'id',
|
||||
fields = field.targetSearch || [],
|
||||
filter = {},
|
||||
limit = field.limit || (axelor.device.small ? 6 : 10),
|
||||
sortBy = field.orderBy;
|
||||
|
||||
fields = ["id", nameField].concat(fields);
|
||||
fields = _.chain(fields).compact().unique().value();
|
||||
|
||||
_.each(fields, function(name){
|
||||
if (name !== "id" && request.term) {
|
||||
filter[name] = request.term;
|
||||
}
|
||||
});
|
||||
|
||||
var domain = this._domain,
|
||||
context = this._context;
|
||||
|
||||
if (domain !== undefined && this.getContext) {
|
||||
context = _.extend({}, context, this.getContext());
|
||||
}
|
||||
|
||||
if (sortBy) {
|
||||
sortBy = sortBy.split(",");
|
||||
}
|
||||
|
||||
var params = {
|
||||
filter: filter,
|
||||
fields: fields,
|
||||
sortBy: sortBy,
|
||||
limit: limit
|
||||
};
|
||||
|
||||
if (domain !== undefined) {
|
||||
params.domain = domain;
|
||||
params.context = context;
|
||||
}
|
||||
|
||||
fetchDS().search(params).success(function(records, page){
|
||||
var trKey = '$t:' + nameField;
|
||||
var items = _.map(records, function(record) {
|
||||
return {
|
||||
label: record[trKey] || record[nameField],
|
||||
value: record
|
||||
};
|
||||
});
|
||||
response(items, page);
|
||||
});
|
||||
}
|
||||
|
||||
$scope.createOnTheFly = function (term, popup, onSaveCallback) {
|
||||
|
||||
var field = $scope.field;
|
||||
var targetFields = null;
|
||||
var requiredFields = (field.create||"").split(/,\s*/);
|
||||
|
||||
function createItem(fields, term, popup) {
|
||||
var ds = $scope._dataSource,
|
||||
data = { $forceDirty: true }, missing = false;
|
||||
|
||||
_.each(fields, function(field) {
|
||||
if (field.name === "name") return data["name"] = term;
|
||||
if (field.name === "code") return data["code"] = term;
|
||||
if (field.nameColumn) return data[field.name] = term;
|
||||
if (requiredFields.indexOf(field.name) > -1) {
|
||||
return data[field.name] = term;
|
||||
}
|
||||
if (field.required) {
|
||||
missing = true;
|
||||
}
|
||||
});
|
||||
if (popup || missing || _.isEmpty(data)) {
|
||||
return $scope.showPopupEditor(data);
|
||||
}
|
||||
return ds.save(data).success(onSaveCallback);
|
||||
}
|
||||
|
||||
|
||||
if (targetFields) {
|
||||
return createItem(targetFields, term, popup);
|
||||
}
|
||||
|
||||
return $scope.loadView("form").success(function(fields, view){
|
||||
targetFields = fields;
|
||||
return createItem(fields, term, popup);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.attachTagEditor = function attachTagEditor(scope, element, attrs) {
|
||||
|
||||
var field = scope.field;
|
||||
var input = null;
|
||||
|
||||
if (!field.target) {
|
||||
return;
|
||||
}
|
||||
|
||||
function onTagEdit(e, item) {
|
||||
|
||||
var elem = $(e.target);
|
||||
var field = scope.field;
|
||||
var value = item[field.targetName];
|
||||
|
||||
function onKeyDown(e) {
|
||||
|
||||
// enter key
|
||||
if (e.keyCode === 13) {
|
||||
item[field.targetName] = input.val();
|
||||
saveAndSelect(item);
|
||||
hideEditor();
|
||||
}
|
||||
|
||||
// escape
|
||||
if (e.keyCode === 27) {
|
||||
hideEditor();
|
||||
}
|
||||
}
|
||||
|
||||
function hideEditor(forceSave) {
|
||||
|
||||
$(document).off('mousedown.tag-editor');
|
||||
$(input).off('keydown.tag-editor').hide();
|
||||
|
||||
if (forceSave && value !== input.val()) {
|
||||
item[field.targetName] = input.val();
|
||||
saveAndSelect(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (input === null) {
|
||||
input = $('<input class="tag-editor" type="text">').appendTo(element);
|
||||
}
|
||||
|
||||
input.val(value)
|
||||
.width(element.width() - 6)
|
||||
.show().focus()
|
||||
.position({
|
||||
my: 'left top',
|
||||
at: 'left+3 top+3',
|
||||
of: element
|
||||
});
|
||||
|
||||
$(input).on('keydown.tag-editor', onKeyDown);
|
||||
$(document).on('mousedown.tag-editor', function (e) {
|
||||
if (!input.is(e.target)) {
|
||||
hideEditor(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveAndSelect(record) {
|
||||
var ds = scope._dataSource;
|
||||
var data = _.extend({}, record, {
|
||||
version: record.version || record.$version
|
||||
});
|
||||
ds.save(data).success(function (rec) {
|
||||
scope.select(rec);
|
||||
});
|
||||
}
|
||||
|
||||
scope.onTagEdit = onTagEdit;
|
||||
};
|
||||
|
||||
$scope.canSelect = function() {
|
||||
var canSelect = $scope.attr('canSelect');
|
||||
if (canSelect !== undefined) return canSelect;
|
||||
if ($scope.selectEnable !== undefined) return $scope.selectEnable;
|
||||
return true;
|
||||
};
|
||||
|
||||
$scope.canNew = function() {
|
||||
return $scope.attr('canNew') !== false;
|
||||
};
|
||||
|
||||
$scope.canEdit = function() {
|
||||
return !$scope.isReadonly();
|
||||
};
|
||||
|
||||
$scope.canView = function() {
|
||||
return $scope.attr('canView') !== false;
|
||||
};
|
||||
|
||||
$scope.canRemove = function() {
|
||||
return $scope.attr('canRemove') !== false;
|
||||
};
|
||||
|
||||
$scope.select = function(value) {
|
||||
|
||||
};
|
||||
|
||||
$scope.onNew = function() {
|
||||
$scope.showEditor(null);
|
||||
};
|
||||
|
||||
$scope.onEdit = function() {
|
||||
|
||||
};
|
||||
|
||||
$scope.onSelect = function() {
|
||||
$scope.showSelector();
|
||||
};
|
||||
|
||||
$scope.onRemove = function() {
|
||||
|
||||
};
|
||||
|
||||
var hasPermission = $scope.hasPermission;
|
||||
$scope.hasPermission = function(perm) {
|
||||
if (hasPermission && !hasPermission.apply($scope, arguments)) {
|
||||
return false;
|
||||
}
|
||||
if (!field.perms) return true;
|
||||
var perms = field.perms;
|
||||
var permitted = perms[perm];
|
||||
if (!permitted) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
})();
|
||||
1323
sophal/js/form/form.relational.multiple.js
Normal file
1323
sophal/js/form/form.relational.multiple.js
Normal file
File diff suppressed because it is too large
Load Diff
400
sophal/js/form/form.relational.nested.js
Normal file
400
sophal/js/form/form.relational.nested.js
Normal file
@ -0,0 +1,400 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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 NestedForm = {
|
||||
scope: true,
|
||||
controller: [ '$scope', '$element', function($scope, $element) {
|
||||
|
||||
ui.FormViewCtrl.call(this, $scope, $element);
|
||||
|
||||
$scope.onShow = function(viewPromise) {
|
||||
|
||||
};
|
||||
|
||||
$scope.$$forceWatch = false;
|
||||
$scope.$$forceCounter = false;
|
||||
|
||||
$scope.$setForceWatch = function () {
|
||||
$scope.$$forceWatch = true;
|
||||
$scope.$$forceCounter = true;
|
||||
};
|
||||
|
||||
$scope.registerNested($scope);
|
||||
$scope.show();
|
||||
}],
|
||||
link: function(scope, element, attrs, ctrl) {
|
||||
|
||||
},
|
||||
template: '<div ui-view-form x-handler="this"></div>'
|
||||
};
|
||||
|
||||
ui.EmbeddedEditorCtrl = EmbeddedEditorCtrl;
|
||||
ui.EmbeddedEditorCtrl.$inject = ['$scope', '$element', 'DataSource', 'ViewService'];
|
||||
|
||||
function EmbeddedEditorCtrl($scope, $element, DataSource, ViewService) {
|
||||
|
||||
var params = angular.copy($scope._viewParams);
|
||||
|
||||
params.views = _.compact([params.summaryView || params.summaryViewDefault]);
|
||||
$scope._viewParams = params;
|
||||
|
||||
ui.ViewCtrl($scope, DataSource, ViewService);
|
||||
ui.FormViewCtrl.call(this, $scope, $element);
|
||||
|
||||
$scope.visible = false;
|
||||
$scope.onShow = function() {
|
||||
|
||||
};
|
||||
|
||||
var originalEdit = $scope.edit;
|
||||
|
||||
function doEdit(record) {
|
||||
if (record && record.id > 0 && !record.$fetched) {
|
||||
$scope.doRead(record.id).success(function(record){
|
||||
originalEdit(record);
|
||||
});
|
||||
} else {
|
||||
originalEdit(record);
|
||||
}
|
||||
}
|
||||
|
||||
function doClose() {
|
||||
if ($scope.isDetailView) {
|
||||
$scope.edit($scope.getSelectedRecord());
|
||||
return;
|
||||
}
|
||||
$scope.edit(null);
|
||||
$scope.waitForActions(function () {
|
||||
$scope.visible = false;
|
||||
$element.hide();
|
||||
$element.data('$rel').show();
|
||||
});
|
||||
}
|
||||
|
||||
$scope.edit = function(record) {
|
||||
doEdit(record);
|
||||
$scope.setEditable(!$scope.$parent.$$readonly);
|
||||
};
|
||||
|
||||
$scope.onClose = function() {
|
||||
$scope.onClear();
|
||||
doClose();
|
||||
};
|
||||
|
||||
$scope.onOK = function() {
|
||||
if (!$scope.isValid()) {
|
||||
return;
|
||||
}
|
||||
var record = $scope.record;
|
||||
if (record) record.$fetched = true;
|
||||
|
||||
var event = $scope.$broadcast('on:before-save', record);
|
||||
if (event.defaultPrevented) {
|
||||
if (event.error) {
|
||||
return axelor.dialogs.error(event.error);
|
||||
}
|
||||
}
|
||||
$scope.waitForActions(function () {
|
||||
$scope.select($scope.record);
|
||||
$scope.waitForActions(doClose);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.onAdd = function() {
|
||||
if (!$scope.isValid() || !$scope.record) {
|
||||
return;
|
||||
}
|
||||
|
||||
var record = $scope.record;
|
||||
record.id = null;
|
||||
record.version = null;
|
||||
record.$version = null;
|
||||
|
||||
$scope.onClear();
|
||||
|
||||
function doSelect(rec) {
|
||||
if (rec) {
|
||||
$scope.select(rec);
|
||||
}
|
||||
return doEdit(rec);
|
||||
}
|
||||
|
||||
if (!$scope.editorCanSave) {
|
||||
return doSelect(record);
|
||||
}
|
||||
|
||||
$scope.onSave().then(function (rec) {
|
||||
doSelect(rec);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.onClear = function() {
|
||||
if ($scope.$parent.selection) {
|
||||
$scope.$parent.selection.length = 0;
|
||||
}
|
||||
doEdit(null);
|
||||
};
|
||||
|
||||
$scope.canUpdate = function () {
|
||||
return $scope.record && $scope.record.id;
|
||||
};
|
||||
|
||||
function loadSelected() {
|
||||
var record = $scope.getSelectedRecord();
|
||||
if ($scope.isDetailView) {
|
||||
$scope.edit(record);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$on('grid:changed', function(event) {
|
||||
loadSelected();
|
||||
});
|
||||
|
||||
$scope.$on('on:edit', function(event, record) {
|
||||
if ($scope.$parent.record === record) {
|
||||
$scope.waitForActions(loadSelected);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$parent.$watch('isReadonly()', function nestedReadonlyWatch(readonly, old) {
|
||||
if (readonly === old) return;
|
||||
$scope.setEditable(!readonly);
|
||||
});
|
||||
|
||||
$scope.show();
|
||||
}
|
||||
|
||||
var EmbeddedEditor = {
|
||||
restrict: 'EA',
|
||||
css: 'nested-editor',
|
||||
scope: true,
|
||||
controller: EmbeddedEditorCtrl,
|
||||
link: function (scope, element, attrs) {
|
||||
setTimeout(function () {
|
||||
var prev = element.prev();
|
||||
if (prev.is("[ui-slick-grid]")) {
|
||||
element.zIndex(prev.zIndex() + 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
template:
|
||||
'<fieldset class="form-item-group bordered-box" ui-show="visible">'+
|
||||
'<div ui-view-form x-handler="this"></div>'+
|
||||
'<div class="btn-toolbar pull-right">'+
|
||||
'<button type="button" class="btn btn btn-info" ng-click="onClose()" ng-show="isReadonly()"><span x-translate>Back</span></button> '+
|
||||
'<button type="button" class="btn btn-primary" ng-click="onOK()" ng-show="!isReadonly() && canUpdate()"><span x-translate>OK</span></button>'+
|
||||
'<button type="button" class="btn btn-primary" ng-click="onAdd()" ng-show="!isReadonly() && !canUpdate()"><span x-translate>Add</span></button> '+
|
||||
'<button type="button" class="btn btn-danger" ng-click="onClose()" ng-show="!isReadonly()"><span x-translate>Cancel</span></button> '+
|
||||
'</div>'+
|
||||
'</fieldset>'
|
||||
};
|
||||
|
||||
ui.NestedEditorCtrl = NestedEditorCtrl;
|
||||
ui.NestedEditorCtrl.$inject = ['$scope', '$element', 'DataSource', 'ViewService'];
|
||||
|
||||
function NestedEditorCtrl($scope, $element, DataSource, ViewService) {
|
||||
|
||||
var params = angular.copy($scope._viewParams);
|
||||
|
||||
params.views = _.compact([params.summaryView || params.summaryViewDefault]);
|
||||
$scope._viewParams = params;
|
||||
|
||||
ui.ManyToOneCtrl.call(this, $scope, $element, DataSource, ViewService);
|
||||
|
||||
$scope.nested = null;
|
||||
$scope.registerNested = function(scope) {
|
||||
$scope.nested = scope;
|
||||
|
||||
$scope.$watch("isReadonly()", function nestedReadonlyWatch(readonly) {
|
||||
scope.setEditable(!readonly);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
var NestedEditor = {
|
||||
restrict: 'EA',
|
||||
css: 'nested-editor',
|
||||
require: '?ngModel',
|
||||
scope: true,
|
||||
controller: NestedEditorCtrl,
|
||||
link: function(scope, element, attrs, model) {
|
||||
|
||||
function setValidity(nested, valid) {
|
||||
model.$setValidity('valid', nested.isValid());
|
||||
if (scope.setValidity) {
|
||||
scope.setValidity('valid', nested.isValid());
|
||||
}
|
||||
}
|
||||
|
||||
var configure = _.once(function (nested) {
|
||||
|
||||
//FIX: select on M2O doesn't apply to nested editor
|
||||
var unwatchId = scope.$watch(attrs.ngModel + '.id', function nestedRecordIdWatch(id, old){
|
||||
if (id === old) {
|
||||
return;
|
||||
}
|
||||
unwatchId();
|
||||
unwatchId = null;
|
||||
scope.$applyAsync();
|
||||
});
|
||||
|
||||
var unwatchValid = nested.$watch('form.$valid', function nestedValidWatch(valid, old){
|
||||
if (valid === old) {
|
||||
return;
|
||||
}
|
||||
unwatchValid();
|
||||
unwatchValid = null;
|
||||
setValidity(nested, valid);
|
||||
});
|
||||
|
||||
scope.$on("on:check-nested-values", function (e, value) {
|
||||
if (nested && value) {
|
||||
var val = scope.getValue() || {};
|
||||
if (val.$updatedValues === value) {
|
||||
_.extend(nested.record, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var parentAttrs = scope.$parent.field || {};
|
||||
if (parentAttrs.forceWatch) {
|
||||
nested.$$forceWatch = true;
|
||||
}
|
||||
});
|
||||
|
||||
var unwatch = null;
|
||||
var original = null;
|
||||
|
||||
function nestedEdit(record, fireOnLoad) {
|
||||
|
||||
var nested = scope.nested;
|
||||
var counter = 0;
|
||||
|
||||
if (!nested) return;
|
||||
if (unwatch) unwatch();
|
||||
|
||||
original = angular.copy(record);
|
||||
|
||||
unwatch = nested.$watch('record', function nestedRecordWatch(rec, old) {
|
||||
|
||||
if (counter++ === 0 && !nested.$$forceCounter) {
|
||||
return;
|
||||
}
|
||||
|
||||
var ds = nested._dataSource;
|
||||
var name = scope.field.name;
|
||||
|
||||
// don't process default values
|
||||
if (ds.equals(rec, nested.defaultValues)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_.isEmpty(rec)) rec = null;
|
||||
if (_.isEmpty(old)) old = null;
|
||||
if (rec == old) {
|
||||
return;
|
||||
}
|
||||
if (rec) {
|
||||
rec.$dirty = !(rec.id > 0 && ds.equals(rec, original));
|
||||
}
|
||||
|
||||
model.$setViewValue(rec);
|
||||
setValidity(nested, nested.isValid());
|
||||
}, true);
|
||||
|
||||
return nested.edit(record, fireOnLoad);
|
||||
}
|
||||
|
||||
scope.ngModel = model;
|
||||
scope.visible = false;
|
||||
|
||||
scope.onClear = function() {
|
||||
scope.$parent.setValue(null, true);
|
||||
scope.$parent.$broadcast('on:new');
|
||||
};
|
||||
|
||||
scope.onClose = function() {
|
||||
scope.$parent._isNestedOpen = false;
|
||||
scope.visible = false;
|
||||
element.hide();
|
||||
};
|
||||
|
||||
scope.canClose = function() {
|
||||
return scope.canToggle() && scope.canSelect();
|
||||
};
|
||||
|
||||
attrs.$observe('title', function(title){
|
||||
scope.title = title;
|
||||
});
|
||||
|
||||
model.$render = function() {
|
||||
var nested = scope.nested,
|
||||
promise = nested._viewPromise,
|
||||
oldValue = model.$viewValue;
|
||||
|
||||
function doRender() {
|
||||
var value = model.$viewValue;
|
||||
if (oldValue !== value) { // prevent unnecessary onLoad
|
||||
return;
|
||||
}
|
||||
if (!value || !value.id || value.$dirty) {
|
||||
return nestedEdit(value, false);
|
||||
}
|
||||
if (value.$fetched && (nested.record||{}).$fetched) return;
|
||||
return nested.doRead(value.id).success(function(record){
|
||||
record.$fetched = true;
|
||||
value.$fetched = true;
|
||||
return nestedEdit(_.extend({}, value, record));
|
||||
});
|
||||
}
|
||||
|
||||
if (nested == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
promise.then(function() {
|
||||
configure(nested);
|
||||
nestedEdit(model.$viewValue, false);
|
||||
scope.waitForActions(doRender, 100);
|
||||
});
|
||||
};
|
||||
},
|
||||
template:
|
||||
'<fieldset class="form-item-group bordered-box" ui-show="visible">'+
|
||||
'<legend>'+
|
||||
'<span ng-bind-html="title"></span> '+
|
||||
'<span class="legend-toolbar" style="display: none;" ng-show="!isReadonly()">'+
|
||||
'<a href="" tabindex="-1" ng-click="onClear()" title="{{\'Clear\' | t}}" ng-show="canShowIcon(\'clear\')"><i class="fa fa-ban"></i></a> '+
|
||||
'<a href="" tabindex="-1" ng-click="onSelect()" title="{{\'Select\' | t}}" ng-show="canShowIcon(\'select\')"><i class="fa fa-search"></i></a> '+
|
||||
'<a href="" tabindex="-1" ng-click="onClose()" title="{{\'Close\' | t}}" ng-show="canClose()"><i class="fa fa-times-circle"></i></a>'+
|
||||
'</span>'+
|
||||
'</legend>'+
|
||||
'<div ui-nested-form></div>'+
|
||||
'</fieldset>'
|
||||
};
|
||||
|
||||
ui.formDirective('uiNestedEditor', NestedEditor);
|
||||
ui.formDirective('uiEmbeddedEditor', EmbeddedEditor);
|
||||
ui.formDirective('uiNestedForm', NestedForm);
|
||||
|
||||
})();
|
||||
1212
sophal/js/form/form.relational.single.js
Normal file
1212
sophal/js/form/form.relational.single.js
Normal file
File diff suppressed because it is too large
Load Diff
483
sophal/js/form/form.widget.js
Normal file
483
sophal/js/form/form.widget.js
Normal file
@ -0,0 +1,483 @@
|
||||
/*
|
||||
* Axelor Business Solutions
|
||||
*
|
||||
* Copyright (C) 2005-2019 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');
|
||||
|
||||
/**
|
||||
* The Form widget.
|
||||
*
|
||||
*/
|
||||
ui.formWidget('Form', {
|
||||
|
||||
priority: 100,
|
||||
|
||||
css: "dynamic-form",
|
||||
|
||||
scope: false,
|
||||
|
||||
compile: function(element, attrs) {
|
||||
|
||||
element.hide();
|
||||
element.find('[x-field],[data-field]').each(function(){
|
||||
|
||||
var elem = $(this),
|
||||
name = elem.attr('x-field') || elem.attr('data-field');
|
||||
|
||||
if (name && elem.attr('ui-button') === undefined) {
|
||||
if (!elem.attr('ng-model')) {
|
||||
elem.attr('ng-model', 'record.' + name);
|
||||
}
|
||||
if (!elem.attr('ng-required')) {
|
||||
// always attache a required validator to make
|
||||
// dynamic `required` attribute change effective
|
||||
elem.attr('ng-required', false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return ui.formCompile.apply(this, arguments);
|
||||
},
|
||||
|
||||
link: function(scope, element, attrs, controller) {
|
||||
|
||||
element.on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
scope.$watch('record', function formRecordWatch(rec, old) {
|
||||
if (element.is(':visible')) {
|
||||
return;
|
||||
}
|
||||
scope.ajaxStop(function() {
|
||||
element.show();
|
||||
axelor.$adjustSize();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* This directive is used filter $watch on scopes of inactive tabs.
|
||||
*
|
||||
*/
|
||||
ui.directive('uiTabGate', function() {
|
||||
|
||||
return {
|
||||
|
||||
compile: function compile(tElement, tAttrs) {
|
||||
|
||||
return {
|
||||
pre: function preLink(scope, element, attrs) {
|
||||
scope.$watchChecker(function(current) {
|
||||
if (current.$$popupStack.length) return true;
|
||||
if (current.tabSelected === undefined) {
|
||||
return !scope.tab || scope.tab.selected === undefined || scope.tab.selected;
|
||||
}
|
||||
return current.tabSelected;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* This directive is used to filter $watch on scopes of hidden forms.
|
||||
*
|
||||
*/
|
||||
ui.directive('uiFormGate', function() {
|
||||
|
||||
return {
|
||||
compile: function compile(tElement, tAttrs) {
|
||||
|
||||
return {
|
||||
pre: function preLink(scope, element, attrs) {
|
||||
var parent = null;
|
||||
scope.$watchChecker(function(current) {
|
||||
if (scope.tabSelected === false) {
|
||||
return false;
|
||||
}
|
||||
if (parent === null) {
|
||||
parent = element.parents('[ui-show]:first');
|
||||
}
|
||||
// hack for hidden nested editors (#2173)
|
||||
if (scope.$$forceWatch) {
|
||||
return true;
|
||||
}
|
||||
return !(parent.hasClass('ui-hide') || parent.hasClass('ui-hide'));
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* This directive is used to filter $watch on scopes based on some condition.
|
||||
*
|
||||
*/
|
||||
ui.directive('uiWatchIf', ['$parse', function($parse) {
|
||||
|
||||
return {
|
||||
compile: function compile(tElement, tAttrs) {
|
||||
return {
|
||||
pre: function preLink(scope, element, attrs) {
|
||||
var value = false,
|
||||
expression = $parse(attrs.uiWatchIf);
|
||||
|
||||
scope.$watchChecker(function (current) {
|
||||
if (current === scope) {
|
||||
return value = expression(scope);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
function toBoolean(value) {
|
||||
if (value && value.length !== 0) {
|
||||
var v = angular.lowercase("" + value);
|
||||
value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
|
||||
} else {
|
||||
value = false;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This directive is used to speedup uiFormGate.
|
||||
*/
|
||||
ui.directive('uiShow', function() {
|
||||
|
||||
return {
|
||||
scope: true, // create new scope to always watch the expression
|
||||
link: function link(scope, element, attrs) {
|
||||
scope.$$shouldWatch = true;
|
||||
scope.$watch(attrs.uiShow, function uiShowWatchAction(value){
|
||||
var val = toBoolean(value);
|
||||
element.css({ display: val ? '' : 'none', opacity: 0 }).toggleClass('ui-hide', !val);
|
||||
if (val) {
|
||||
element.animate({ opacity: 1 }, 300);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* This directive is used by view-pane to attach/detach element from DOM tree
|
||||
*/
|
||||
ui.directive('uiAttach', function () {
|
||||
return function (scope, element, attrs) {
|
||||
var parent = null;
|
||||
var detachTimer = null;
|
||||
var uiAttachWatch = function uiAttachWatch(attach) {
|
||||
var result = toBoolean(attach);
|
||||
if (result) {
|
||||
if (parent) {
|
||||
if (detachTimer) {
|
||||
clearTimeout(detachTimer);
|
||||
detachTimer = null;
|
||||
} else {
|
||||
element.appendTo(parent);
|
||||
}
|
||||
parent = null;
|
||||
scope.$broadcast('dom:attach');
|
||||
}
|
||||
} else {
|
||||
parent = element.parent();
|
||||
scope.$broadcast('dom:detach');
|
||||
detachTimer = setTimeout(function () {
|
||||
detachTimer = null;
|
||||
element.detach();
|
||||
}, 200);
|
||||
}
|
||||
};
|
||||
|
||||
uiAttachWatch.uiAttachWatch = true;
|
||||
|
||||
scope.$watch(attrs.uiAttach, uiAttachWatch, true);
|
||||
scope.$on('$destroy', function () {
|
||||
if (detachTimer) {
|
||||
clearTimeout(detachTimer);
|
||||
detachTimer = null;
|
||||
}
|
||||
if (parent) {
|
||||
parent = null;
|
||||
element.remove();
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* This directive can be used by widget to restore scroll when element is re-attached to DOM tree.
|
||||
*/
|
||||
ui.directive('uiAttachScroll', function () {
|
||||
return function (scope, element, attrs) {
|
||||
setTimeout(function () {
|
||||
var elem = element;
|
||||
var scrollTop = 0;
|
||||
|
||||
if (attrs.uiAttachScroll) {
|
||||
elem = element.find(attrs.uiAttachScroll);
|
||||
}
|
||||
|
||||
elem.on('scroll', function () {
|
||||
scrollTop = this.scrollTop;
|
||||
});
|
||||
|
||||
function resetScroll() {
|
||||
elem.scrollTop(scrollTop);
|
||||
}
|
||||
|
||||
scope.$on('dom:attach', resetScroll);
|
||||
scope.$on('tab:select', resetScroll);
|
||||
}, 300);
|
||||
};
|
||||
});
|
||||
|
||||
ui.directive('uiWidgetStates', ['$parse', '$interpolate', function($parse, $interpolate) {
|
||||
|
||||
function isValid(scope, name) {
|
||||
if (!name) return scope.isValid();
|
||||
var ctrl = scope.form;
|
||||
if (ctrl) {
|
||||
ctrl = ctrl[name];
|
||||
}
|
||||
if (ctrl) {
|
||||
return ctrl.$valid;
|
||||
}
|
||||
}
|
||||
|
||||
function withContext(scope, record) {
|
||||
var values = _.extend({}, scope._context, scope._jsonContext, record);
|
||||
return _.extend(values, {
|
||||
$user: axelor.config['user.login'],
|
||||
$group: axelor.config['user.group'],
|
||||
$userId: axelor.config['user.id'],
|
||||
});
|
||||
}
|
||||
|
||||
function handleCondition(scope, field, attr, condition, negative) {
|
||||
|
||||
if (!condition || _.isBoolean(condition)) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.$on("on:record-change", function(e, rec, force) {
|
||||
if (field && field.jsonField) {
|
||||
handle(scope.record);
|
||||
} else if (rec === scope.record || force) {
|
||||
handle(rec);
|
||||
}
|
||||
});
|
||||
scope.$on("on:grid-selection-change", function(e, context) {
|
||||
if (field && field.jsonField) return;
|
||||
if (!scope._isDetailsForm) {
|
||||
handle(context);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watch("isReadonly()", watcher);
|
||||
scope.$watch("isRequired()", watcher);
|
||||
scope.$watch("isValid()", watcher);
|
||||
|
||||
var expr = $parse(condition);
|
||||
|
||||
function watcher(current, old) {
|
||||
var rec = scope.record;
|
||||
if (rec === undefined && current === old) return;
|
||||
if (rec === undefined && scope.getContext) {
|
||||
rec = scope.getContext();
|
||||
}
|
||||
handle(rec);
|
||||
}
|
||||
|
||||
function handle(rec) {
|
||||
var value;
|
||||
try {
|
||||
value = !!axelor.$eval(scope, expr, withContext(scope, rec));
|
||||
} catch (e) {
|
||||
console.error('FAILED:', condition, e);
|
||||
}
|
||||
// defer attr change to allow field init, see RM-14998
|
||||
scope.$applyAsync(function () {
|
||||
scope.attr(attr, negative ? !value : value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleHilites(scope, field) {
|
||||
if (!field || _.isEmpty(field.hilites)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var hilites = field.hilites || [];
|
||||
var exprs = _.map(_.pluck(hilites, 'condition'), function (s) { return $parse(s); });
|
||||
|
||||
function handle(rec) {
|
||||
for (var i = 0; i < hilites.length; i++) {
|
||||
var hilite = hilites[i];
|
||||
var expr = exprs[i];
|
||||
var value = false;
|
||||
try {
|
||||
value = axelor.$eval(scope, expr, withContext(scope, rec));
|
||||
} catch (e) {
|
||||
console.error('FAILED:', hilite, e);
|
||||
}
|
||||
if (value) {
|
||||
return scope.attr('highlight', {
|
||||
hilite: hilite,
|
||||
passed: value
|
||||
});
|
||||
}
|
||||
}
|
||||
return scope.attr('highlight', {});
|
||||
}
|
||||
|
||||
scope.$on("on:record-change", function(e, rec) {
|
||||
if (rec === scope.record) {
|
||||
handle(rec);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleBind(scope, field) {
|
||||
|
||||
if (!field.bind || !field.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
var expr = $interpolate(field.bind);
|
||||
var last = null;
|
||||
|
||||
function handle(rec) {
|
||||
var value;
|
||||
try {
|
||||
value = expr(withContext(scope, rec));
|
||||
if (value.length === 0) {
|
||||
value = null;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('FAILED:', field.bind, e);
|
||||
}
|
||||
|
||||
if (scope.setValue && scope.record && last !== value) {
|
||||
scope.setValue(last = value);
|
||||
}
|
||||
}
|
||||
|
||||
scope.$on("on:record-change", function(e, rec) {
|
||||
if (field && field.jsonField) {
|
||||
handle(scope.record);
|
||||
} else if (rec && rec === scope.record) {
|
||||
handle(rec);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleValueExpr(scope, field) {
|
||||
|
||||
if (!field.valueExpr || !field.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
var expr = $parse(field.valueExpr);
|
||||
|
||||
function handle(rec) {
|
||||
var value;
|
||||
try {
|
||||
value = axelor.$eval(scope, expr, withContext(scope, rec));
|
||||
if (value && value.length === 0) {
|
||||
value = null;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('FAILED:', field.valueExpr, e);
|
||||
}
|
||||
|
||||
if (scope.setValue && scope.record) {
|
||||
scope.setValue(value, false);
|
||||
}
|
||||
}
|
||||
|
||||
scope.$on("on:record-change", function(e, rec) {
|
||||
scope.$timeout(function () {
|
||||
if (field && field.jsonField) {
|
||||
handle(scope.record);
|
||||
} else if (rec && rec === scope.record) {
|
||||
handle(rec);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleFor(scope, field, attr, conditional, negative) {
|
||||
if (field[conditional]) {
|
||||
handleCondition(scope, field, attr, field[conditional], negative);
|
||||
}
|
||||
}
|
||||
|
||||
function handleForField(scope) {
|
||||
var field = scope.field;
|
||||
if (!field) return;
|
||||
handleFor(scope, field, "valid", "validIf");
|
||||
handleFor(scope, field, "hidden", "hideIf");
|
||||
handleFor(scope, field, "hidden", "showIf", true);
|
||||
handleFor(scope, field, "readonly", "readonlyIf");
|
||||
handleFor(scope, field, "required", "requiredIf");
|
||||
handleFor(scope, field, "collapse", "collapseIf");
|
||||
handleFor(scope, field, "canNew", "canNew");
|
||||
handleFor(scope, field, "canView", "canView");
|
||||
handleFor(scope, field, "canEdit", "canEdit");
|
||||
handleFor(scope, field, "canRemove", "canRemove");
|
||||
handleFor(scope, field, "canSelect", "canSelect");
|
||||
handleHilites(scope, field);
|
||||
handleBind(scope, field);
|
||||
handleValueExpr(scope, field);
|
||||
}
|
||||
|
||||
function handleForView(scope) {
|
||||
var field = scope.schema;
|
||||
if (!field) return;
|
||||
handleFor(scope, field, "canNew", "canNew");
|
||||
handleFor(scope, field, "canEdit", "canEdit");
|
||||
handleFor(scope, field, "canSave", "canSave");
|
||||
handleFor(scope, field, "canCopy", "canCopy");
|
||||
handleFor(scope, field, "canDelete", "canDelete");
|
||||
handleFor(scope, field, "canArchive", "canArchive");
|
||||
handleFor(scope, field, "canAttach", "canAttach");
|
||||
}
|
||||
|
||||
return function(scope, element, attrs) {
|
||||
scope.$evalAsync(function() {
|
||||
if (element.is('[ui-form]')) {
|
||||
return handleForView(scope);
|
||||
}
|
||||
handleForField(scope);
|
||||
});
|
||||
};
|
||||
}]);
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user