2914 lines
74 KiB
JavaScript
2914 lines
74 KiB
JavaScript
/*
|
|
* 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 Slick: true */
|
|
|
|
"use strict";
|
|
|
|
var ui = angular.module('axelor.ui');
|
|
|
|
//used to keep track of columns by x-path
|
|
function setDummyCols(element, cols) {
|
|
var e = $('<div>').appendTo(element).hide();
|
|
_.each(cols, function(col, i) {
|
|
$('<span class="slick-dummy-column">')
|
|
.data('column', col)
|
|
.attr('x-path', col.xpath)
|
|
.appendTo(e);
|
|
});
|
|
}
|
|
|
|
function makeFilterCombo(input, selection, callback) {
|
|
|
|
var data = _.map(selection, function(item){
|
|
return {
|
|
key: item.value,
|
|
value: item.title
|
|
};
|
|
});
|
|
|
|
function update(item) {
|
|
var filter = {};
|
|
item = item || {};
|
|
input.val(item.value || '');
|
|
filter[input.data('columnId')] = item.key || '';
|
|
callback(filter);
|
|
}
|
|
|
|
input.autocomplete({
|
|
minLength: 0,
|
|
source: data,
|
|
focus: function(event, ui) {
|
|
return false;
|
|
},
|
|
select: function(event, ui) {
|
|
update(ui.item);
|
|
return false;
|
|
}
|
|
}).keyup(function(e){
|
|
return false;
|
|
}).keydown(function(e){
|
|
switch(e.keyCode) {
|
|
case 8: // backspace
|
|
case 46: // delete
|
|
update(null);
|
|
}
|
|
}).click(function(){
|
|
input.autocomplete("search", "");
|
|
});
|
|
|
|
$("<i class='fa fa-caret-down combo-icon'></i>").appendTo(input.parent()).click(function () {
|
|
input.focus();
|
|
input.autocomplete("search", "");
|
|
});
|
|
}
|
|
|
|
var Editor = function(args) {
|
|
|
|
var element;
|
|
var column = args.column;
|
|
var scope;
|
|
var external;
|
|
|
|
var form = $(args.container)
|
|
.parents('[ui-slick-grid]:first')
|
|
.find('[ui-slick-editors]:first');
|
|
|
|
element = form.find('[x-field="'+ column.field +'"]');
|
|
scope = form.data('$scope');
|
|
|
|
if (!element.parent().is('td.form-item'))
|
|
element = element.parent();
|
|
element.data('$parent', element.parent());
|
|
element.data('$editorForm', form);
|
|
|
|
external = element.is('.text-item,.html-item');
|
|
|
|
this.init = function() {
|
|
|
|
var container = $(args.container);
|
|
if (external) {
|
|
container = container.parents('.ui-dialog-content:first,.view-container:first').first();
|
|
$(document).on('mousedown.slick-external', function (e) {
|
|
if (element.is(e.target) || element.find(e.target).length > 0) {
|
|
return;
|
|
}
|
|
args.grid.getEditorLock().commitCurrentEdit();
|
|
});
|
|
}
|
|
|
|
element.css('display', 'inline-block')
|
|
.addClass('slick-external-editor')
|
|
.appendTo(container)
|
|
.hide();
|
|
|
|
if (!element.data('keydown.nav')) {
|
|
element.data('keydown.nav', true);
|
|
element.bind("keydown.nav", function (e) {
|
|
switch (e.keyCode) {
|
|
case 37: // LEFT
|
|
case 39: // RIGHT
|
|
case 38: // UP
|
|
case 40: // DOWN
|
|
e.stopImmediatePropagation();
|
|
break;
|
|
case 9: // TAB
|
|
if (external) {
|
|
args.grid.onKeyDown.notify(args.grid.getActiveCell(), e);
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
|
|
element.bind('close:slick-editor', function(e) {
|
|
var grid = args.grid;
|
|
var lock = grid.getEditorLock();
|
|
if (lock.isActive()) {
|
|
lock.commitCurrentEdit();
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
this.shouldWait = function () {
|
|
var es = element.scope();
|
|
if (es && es.field && es.field.onChange) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
this.destroy = function() {
|
|
scope.$lastEditor = this;
|
|
element.appendTo(element.data('$parent') || form)
|
|
.removeData('$parent')
|
|
.removeData('$editorForm');
|
|
element.trigger("hide:slick-editor");
|
|
element.parent().zIndex('');
|
|
$(document).off('mousedown.slick-external');
|
|
};
|
|
|
|
this.position = function(pos) {
|
|
//XXX: ui-dialog issue
|
|
var zIndex = element.parents('.slickgrid:first').zIndex();
|
|
if (zIndex) {
|
|
element.parent().zIndex(zIndex);
|
|
}
|
|
if (external) {
|
|
setTimeout(adjustExternal);
|
|
}
|
|
};
|
|
|
|
function adjustExternal() {
|
|
|
|
var container = $(args.container);
|
|
var parent = element.data('$parent') || element;
|
|
var zIndex = (parent.parents('.slickgrid:first').zIndex() || 0) + container.zIndex();
|
|
element.css({
|
|
border: 0,
|
|
width: container.width(),
|
|
zIndex: zIndex + 1
|
|
});
|
|
element.css('position', 'absolute');
|
|
element.position({
|
|
my: 'left top',
|
|
at: 'left top',
|
|
of: args.container
|
|
});
|
|
|
|
// focus html editor
|
|
if (element.is('.html-item')) {
|
|
element.find('[contenteditable]').focus();
|
|
}
|
|
}
|
|
|
|
function focus() {
|
|
// Firefox throws exception if element is hidden
|
|
if (element.is(':hidden')) return;
|
|
if (element.is(':input')) {
|
|
element.focus().select();
|
|
} else {
|
|
element.find(':input:first').focus().select();
|
|
}
|
|
if (element.is('[x-cell-css*=select-item]') && element.scope().showSelection) {
|
|
element.scope().showSelection(300);
|
|
}
|
|
}
|
|
|
|
this.focus = function() {
|
|
_.delay(focus);
|
|
};
|
|
|
|
this.loadValue = function(item) {
|
|
var that = this,
|
|
record = scope.record || {},
|
|
current = item || { id: 0 },
|
|
updated = false;
|
|
|
|
if ((!current.id || current.id < 0) && (current[column.field] === undefined)) {
|
|
var defaults = scope.$events.onNew ? scope.record : scope.defaultValues;
|
|
current[column.field] = (defaults||{})[column.field] || null;
|
|
}
|
|
|
|
var changed = (record.id !== item.id || record.version !== current.version);
|
|
if (changed) {
|
|
scope.editRecord(current);
|
|
} else {
|
|
record[column.field] = current[column.field];
|
|
updated = true;
|
|
}
|
|
setTimeout(function(){
|
|
if (updated) {
|
|
scope.$applyAsync();
|
|
}
|
|
element.show();
|
|
that.focus();
|
|
});
|
|
};
|
|
|
|
this.serializeValue = function() {
|
|
var record = scope.record || {};
|
|
var value = record[column.field];
|
|
return value === undefined ? "" : value;
|
|
};
|
|
|
|
this.applyValue = function(item, state) {
|
|
if (item.id === undefined) {
|
|
item = _.extend(item, scope.record);
|
|
}
|
|
item[column.field] = state;
|
|
item.$dirty = true;
|
|
if (item.id === undefined) {
|
|
args.grid.onCellChange.notify(args.grid.getActiveCell());
|
|
}
|
|
};
|
|
|
|
this.isValueChanged = function() {
|
|
|
|
// force change event on spinner widget
|
|
element.find('.ui-spinner-input').trigger('grid:check', args.item);
|
|
element.find('.ui-mask').trigger('grid:check');
|
|
|
|
var record = scope.record || {};
|
|
var current = args.item || { id: 0 };
|
|
|
|
var v1 = record[column.field];
|
|
var v2 = current[column.field];
|
|
|
|
var changed = !angular.equals(v1, v2);
|
|
if (changed && element.scope().isRequired() && !v1) {
|
|
current[column.field] = v1;
|
|
}
|
|
|
|
return changed;
|
|
};
|
|
|
|
this.validate = function() {
|
|
return {
|
|
valid: !element.hasClass('ng-invalid'),
|
|
msg: null
|
|
};
|
|
};
|
|
|
|
this.init();
|
|
};
|
|
|
|
var Formatters = {
|
|
|
|
"string": function(field, value, context) {
|
|
return _.escapeHTML(ui.formatters.string(field, value, context));
|
|
},
|
|
|
|
"integer": function(field, value) {
|
|
return ui.formatters.integer(field, value);
|
|
},
|
|
|
|
"decimal": function(field, value, context) {
|
|
return ui.formatters.decimal(field, value, context);
|
|
},
|
|
|
|
"boolean": function(field, value) {
|
|
return value ? '<i class="fa fa-check"></i>' : "<i class='fa'></i>";
|
|
},
|
|
|
|
"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') : "";
|
|
},
|
|
|
|
"one-to-one": function(field, value) {
|
|
var text = (value||{})[field.targetName];
|
|
return text ? _.escapeHTML(text) : "";
|
|
},
|
|
|
|
"many-to-one": function(field, value) {
|
|
var text = (value||{})[field.targetName];
|
|
return text ? _.escapeHTML(text) : "";
|
|
},
|
|
|
|
"one-to-many": function(field, value) {
|
|
return value ? '(' + value.map(i => s.concat(i["fullName"].split("-")[0])) + ')' : "";
|
|
},
|
|
|
|
"many-to-many": function(field, value) {
|
|
return value ? '(' + value.length + ')' : "";
|
|
},
|
|
|
|
"button": function(field, value, context, grid) {
|
|
var elem;
|
|
var icon = field.icon || (field.widgetAttrs || {}).icon;
|
|
var isIcon = icon && icon.indexOf('fa-') === 0;
|
|
var css = isIcon ? "slick-icon-button fa " + icon : "slick-img-button";
|
|
var help = field.help || field.title;
|
|
var handler = grid.scope.handler;
|
|
|
|
var rec = context;
|
|
if (field.jsonField && rec) {
|
|
rec = angular.fromJson(rec[field.jsonField]);
|
|
}
|
|
if (field.readonlyIf && axelor.$eval(grid.scope, field.readonlyIf, _.extend({}, handler._context, rec))) {
|
|
css += " readonly disabled";
|
|
}
|
|
|
|
if(isIcon) {
|
|
elem = '<a href="javascript: void(0)" tabindex="-1"';
|
|
if (help) {
|
|
elem += ' title="' + _.escapeHTML(help) + '"';
|
|
}
|
|
elem += '><i class="' + css + '"></i></a>';
|
|
} else if (icon) {
|
|
elem = '<img class="' + css + '" src="' + icon + '"';
|
|
if (help) {
|
|
elem += ' title="' + _.escapeHTML(help) + '"';
|
|
}
|
|
elem += '>';
|
|
} else {
|
|
return "";
|
|
}
|
|
|
|
return elem;
|
|
},
|
|
|
|
"progress": function(field, value) {
|
|
var props = ui.ProgressMixin.compute(field, value);
|
|
return '<div class="progress ' + props.css + '" style="height: 18px; margin: 0; margin-top: 1px;">'+
|
|
'<div class="bar" style="width: ' + props.width +'%;"></div>'+
|
|
'</div>';
|
|
},
|
|
|
|
"selection": function(field, value) {
|
|
var cmp = field.type === "integer" ? function(a, b) { return a == b ; } : _.isEqual;
|
|
var findSelect = function (v) {
|
|
var val = field.type === "integer" ? v : _.unescapeHTML(v);
|
|
var res = _.extend({}, _.find(field.selectionList, function(item) {
|
|
return cmp(item.value, val);
|
|
}));
|
|
if (_.isString(res.title)) {
|
|
res.title = _.escapeHTML(res.title);
|
|
}
|
|
return res;
|
|
};
|
|
|
|
if (value && field.widget === 'MultiSelect') {
|
|
var items = value.split(/\s*,\s*/).map(findSelect).map(function (res) {
|
|
return '<span class="label label-primary"><span class="tag-text">'+res.title+'</span></span>';
|
|
});
|
|
return '<span class="tag-select">' + items.join(' ') + '</span>';
|
|
}
|
|
|
|
var res = findSelect(value);
|
|
var text = res.title;
|
|
if (field.widget === 'ImageSelect' && res.icon) {
|
|
var image = "<img style='max-height: 24px;' src='" + (res.icon || res.value) + "'>";
|
|
return field.labels === false ? image : image + " " + text;
|
|
}
|
|
return text;
|
|
},
|
|
|
|
"url": function(field, value) {
|
|
return '<a target="_blank" ng-show="text" href="' + _.escapeHTML(value) + '">' + _.escapeHTML(value) + '</a>';
|
|
},
|
|
|
|
"icon": function(field, value, dataContext, grid) {
|
|
if (value && value.indexOf("fa-") > -1) {
|
|
return '<i class="slick-icon ' + value + '"></i>';
|
|
}
|
|
return Formatters.button(field, value, dataContext, grid);
|
|
},
|
|
|
|
"jsonRef": function (field, value) {
|
|
var val = _.extend({}, value);
|
|
var id = val.id;
|
|
if (!id || id < 0) return '';
|
|
delete val.model;
|
|
delete val.version;
|
|
delete val.id;
|
|
var vals = _.flatten([id, _.values(val)]);
|
|
return '[' + vals.join(', ') + ']';
|
|
},
|
|
|
|
"json": function(field, value) {
|
|
if (!value || !field.jsonFields || field.jsonFields.length === 0) return "";
|
|
var that = this;
|
|
var items = [];
|
|
var json = angular.fromJson(value);
|
|
field.jsonFields.forEach(function (item) {
|
|
if (json[item.name] === undefined || json[item.name] === null) return;
|
|
var value = json[item.name];
|
|
var type = item.selection ? 'selection' : item.type;
|
|
if (item.widget === 'json-ref-select') type = 'jsonRef';
|
|
var func = that[type];
|
|
if (func) {
|
|
value = func(item, value);
|
|
}
|
|
items.push('<strong>' + item.title + '</strong>: ' + value);
|
|
});
|
|
|
|
return items.join(' • ');
|
|
}
|
|
};
|
|
|
|
Formatters.text = Formatters.string;
|
|
|
|
function totalsFormatter(totals, columnDef) {
|
|
|
|
var field = columnDef.descriptor;
|
|
if (["integer", "long", "decimal"].indexOf(field.type) === -1) {
|
|
return "";
|
|
}
|
|
|
|
var vals = totals[field.aggregate || 'sum'] || {};
|
|
var val = vals[field.name];
|
|
|
|
var formatter = Formatters[field.type];
|
|
if (formatter) {
|
|
return formatter(field, val);
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
function Factory(grid) {
|
|
this.grid = grid;
|
|
}
|
|
|
|
_.extend(Factory.prototype, {
|
|
|
|
getEditor : function(col) {
|
|
var field = col.descriptor;
|
|
if (!field || field.readonly || col.forEdit === false) {
|
|
return null;
|
|
}
|
|
if (field.type == 'binary') {
|
|
return null;
|
|
}
|
|
if (!col.editor) {
|
|
col.editor = Editor;
|
|
}
|
|
return col.editor;
|
|
},
|
|
|
|
getFormatter: function(col) {
|
|
return _.bind(this.formatter, this);
|
|
},
|
|
|
|
formatter: function(row, cell, value, columnDef, dataContext) {
|
|
|
|
var field = columnDef.descriptor || {},
|
|
attrs = _.extend({}, field, field.widgetAttrs),
|
|
widget = attrs.widget || "",
|
|
type = attrs.type;
|
|
|
|
if (widget === 'json-field' || attrs.json) {
|
|
return Formatters.json(field, value);
|
|
}
|
|
|
|
if (attrs.jsonPath && attrs.jsonField) {
|
|
var jsonValue = dataContext[attrs.name];
|
|
if (jsonValue === undefined) {
|
|
jsonValue = dataContext[attrs.jsonField];
|
|
if (jsonValue) {
|
|
jsonValue = angular.fromJson(jsonValue);
|
|
value = jsonValue[attrs.jsonPath];
|
|
}
|
|
} else if (jsonValue && attrs.target) { // relational field value
|
|
value = angular.fromJson(jsonValue);
|
|
}
|
|
}
|
|
|
|
if (widget === "Progress" || widget === "progress" || widget === "SelectProgress") {
|
|
type = "progress";
|
|
}
|
|
if (_.isArray(field.selectionList) && widget !== "SelectProgress") {
|
|
type = "selection";
|
|
}
|
|
|
|
if (typeof value === 'string') {
|
|
value = axelor.sanitize(value).replace('<', '<').replace('>', '>').replace('&', '&');
|
|
}
|
|
|
|
if (type === "button" || type === "progress") {
|
|
return Formatters[type](field, value, dataContext, this.grid);
|
|
}
|
|
|
|
if (["Url", "url", "duration"].indexOf(widget) > 0) {
|
|
type = widget.toLowerCase();
|
|
}
|
|
|
|
if (widget.toLowerCase() === "image" || widget.toLowerCase() === "binary-link" || (type === "binary" && field.name === "image")) {
|
|
var url = null;
|
|
if (field.target === "com.axelor.meta.db.MetaFile") {
|
|
if (value) {
|
|
url = ui.makeImageURL("com.axelor.meta.db.MetaFile", "content", (value.id || value), undefined, this.grid.handler);
|
|
}
|
|
if (url && widget.toLowerCase() === "binary-link") {
|
|
return '<a href="' + url + '" download="' + value.fileName + '">' + value.fileName + '</a>';
|
|
}
|
|
} else {
|
|
url = ui.makeImageURL(this.grid.handler._model, field.name, dataContext, undefined, this.grid.handler) + "&image=true";
|
|
}
|
|
return url ? '<img src="' + url + '" style="height: 21px;margin-top: -2px;">' : '';
|
|
}
|
|
|
|
if (widget.toLowerCase() === "html") {
|
|
return value ? '<span>' + value + '</span>' : '';
|
|
}
|
|
|
|
// try to get dotted field value from related object
|
|
if ((value === null || value === undefined) && field.name && field.name.indexOf('.') > -1) {
|
|
var path = field.name.split('.');
|
|
var val = dataContext || {};
|
|
var idx = 0;
|
|
while (val && idx < path.length) {
|
|
val = val[path[idx++]];
|
|
}
|
|
if (idx === path.length) {
|
|
value = val;
|
|
}
|
|
}
|
|
|
|
var fn = Formatters[type];
|
|
if (fn) {
|
|
value = fn(field, value, dataContext, this.grid);
|
|
} else if (_.isString(value)) {
|
|
value = _.escapeHTML(value);
|
|
}
|
|
if (value === null || value === undefined || (_.isObject(value) && _.isEmpty(value))) {
|
|
return "";
|
|
}
|
|
return value;
|
|
},
|
|
|
|
formatProgress: function(field, value) {
|
|
|
|
var props = ui.ProgressMixin.compute(field, value);
|
|
|
|
return '<div class="progress ' + props.css + '" style="height: 18px; margin: 0; margin-top: 1px;">'+
|
|
'<div class="bar" style="width: ' + props.width +'%;"></div>'+
|
|
'</div>';
|
|
},
|
|
|
|
formatDecimal: function(field, value) {
|
|
var scale = field.scale || 2,
|
|
num = +(value);
|
|
if (num) {
|
|
return num.toFixed(scale);
|
|
}
|
|
return value;
|
|
},
|
|
|
|
formatButton: function(field, value, columnDef) {
|
|
return '<img class="slick-img-button" src="' + field.icon + '">';
|
|
}
|
|
});
|
|
|
|
var Grid = function(scope, element, attrs, ViewService, ActionService) {
|
|
|
|
var noFilter = scope.$eval('noFilter');
|
|
if (_.isString(noFilter)) {
|
|
noFilter = noFilter === 'true';
|
|
}
|
|
|
|
this.compile = function(template) {
|
|
return ViewService.compile(template)(scope.$new());
|
|
};
|
|
|
|
this.newActionHandler = function(scope, element, options) {
|
|
return ActionService.handler(scope, element, options);
|
|
};
|
|
|
|
this.scope = scope;
|
|
this.element = element;
|
|
this.attrs = attrs;
|
|
this.handler = scope.handler;
|
|
this.showFilters = !noFilter;
|
|
this.$oldValues = null;
|
|
this.grid = this.parse(scope.view);
|
|
};
|
|
|
|
function buttonScope(scope) {
|
|
var btnScope = scope.$new();
|
|
var handler = scope.handler;
|
|
|
|
btnScope._dataSource = handler._dataSource;
|
|
btnScope.editRecord = function (record) {};
|
|
btnScope.reload = function () {
|
|
if ((handler.field||{}).target) {
|
|
handler.$parent.reload();
|
|
}
|
|
return handler.onRefresh();
|
|
};
|
|
if ((handler.field||{}).target) {
|
|
btnScope.onSave = function () {
|
|
return handler.$parent.onSave.call(handler.$parent, {
|
|
callOnSave: false,
|
|
wait: false
|
|
});
|
|
};
|
|
}
|
|
|
|
return btnScope;
|
|
}
|
|
|
|
Grid.prototype.parse = function(view) {
|
|
|
|
var that = this,
|
|
scope = this.scope,
|
|
handler = scope.handler,
|
|
dataView = scope.dataView,
|
|
element = this.element;
|
|
|
|
scope.fields_view = {};
|
|
|
|
var cols = [];
|
|
var allColsHasWidth = true;
|
|
|
|
_.each(view.items, function(item) {
|
|
var field = handler.fields[item.name] || {},
|
|
path = handler.formPath, type;
|
|
|
|
type = (item.widgetAttrs||{}).type || field.type || item.serverType || item.type || 'string';
|
|
|
|
field = _.extend({}, field, item, {type: type});
|
|
scope.fields_view[item.name] = field;
|
|
path = path ? path + '.' + item.name : item.name;
|
|
|
|
if (type === 'field' || field.selection) {
|
|
type = 'string';
|
|
}
|
|
|
|
if (!item.width) {
|
|
allColsHasWidth = false;
|
|
}
|
|
|
|
var sortable = view.sortable !== undefined && field.sortable === undefined
|
|
? view.sortable !== false
|
|
: field.sortable !== false;
|
|
|
|
switch (field.type) {
|
|
case 'field': // dummy field
|
|
case 'icon':
|
|
case 'button':
|
|
case 'one-to-many':
|
|
case 'many-to-many':
|
|
sortable = false;
|
|
break;
|
|
}
|
|
|
|
if (field.transient || field.json || field.encrypted) {
|
|
sortable = false;
|
|
}
|
|
|
|
if (field.type == "button") {
|
|
if (scope.selector) return;
|
|
field.image = field.title;
|
|
field.handler = that.newActionHandler(buttonScope(scope), element, {
|
|
action: field.onClick
|
|
});
|
|
}
|
|
|
|
if (field.type == "button" || field.type == "icon") {
|
|
item.title = " ";
|
|
item.width = field.width || 32;
|
|
}
|
|
|
|
var column = {
|
|
name: item.title || field.title || item.autoTitle || _.chain(item.name).humanize().titleize().value(),
|
|
id: item.name,
|
|
field: item.name,
|
|
toolTip: item.help,
|
|
forEdit: item.forEdit,
|
|
descriptor: field,
|
|
sortable: sortable,
|
|
width: parseInt(item.width) || null,
|
|
hasWidth: item.width ? true : false,
|
|
cssClass: type,
|
|
headerCssClass: type,
|
|
xpath: path
|
|
};
|
|
|
|
var minWidth = view.colWidth || 100;
|
|
|
|
column.minWidth = Math.min(minWidth, column.width || minWidth);
|
|
column._title = column.name;
|
|
|
|
var css = [type];
|
|
if (item.forEdit !== false) {
|
|
if (!field.readonly) {
|
|
css.push('slick-cell-editable');
|
|
}
|
|
if (field.required) {
|
|
css.push('slick-cell-required');
|
|
}
|
|
}
|
|
column.cssClass = css.join(' ');
|
|
|
|
cols.push(column);
|
|
|
|
if (field.aggregate) {
|
|
column.groupTotalsFormatter = totalsFormatter;
|
|
}
|
|
|
|
if (field.type === "button" || field.type === "boolean" || field.type === "icon") {
|
|
return;
|
|
}
|
|
|
|
var menus = [sortable ? {
|
|
iconImage: "lib/slickgrid/images/sort-asc.gif",
|
|
title: _t("Sort Ascending"),
|
|
command: "sort-asc"
|
|
} : null, sortable ? {
|
|
iconImage: "lib/slickgrid/images/sort-desc.gif",
|
|
title: _t("Sort Descending"),
|
|
command: "sort-desc"
|
|
} : null, sortable ? {
|
|
separator: true
|
|
} : null, view.editable ? null : {
|
|
title: _t("Group by") + " <i>" + column.name + "</i>",
|
|
command: "group-by"
|
|
}, view.editable ? null : {
|
|
title: _t("Ungroup"),
|
|
command: "ungroup"
|
|
}, view.editable ? null : {
|
|
separator: true
|
|
}, {
|
|
title: _t("Hide") + " <i>" + column.name + "</i>",
|
|
command: "hide"
|
|
}];
|
|
|
|
menus = _.compact(menus);
|
|
|
|
column.header = {
|
|
menu: {
|
|
items: menus,
|
|
position: function($menu, $button) {
|
|
$menu.css('top', 0)
|
|
.css('left', 0);
|
|
$menu.position({
|
|
my: 'left top',
|
|
at: 'left bottom',
|
|
of: $button
|
|
});
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
// if all columns are fixed width, add a dummy column
|
|
if (allColsHasWidth) {
|
|
cols.push({
|
|
name: " "
|
|
});
|
|
}
|
|
|
|
// create edit column
|
|
var editColumn = null;
|
|
if (view.editIcon && (!scope.selector || scope.selector === "checkbox") && (!handler.hasPermission || handler.hasPermission('write'))) {
|
|
editColumn = new EditIconColumn({
|
|
onClick: function (e, args) {
|
|
if (e.isDefaultPrevented()) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
var elem = $(e.target);
|
|
if (elem.is('.fa-minus') && handler) {
|
|
return handler.dataView.deleteItem(0);
|
|
}
|
|
if (handler && handler.onEdit) {
|
|
handler.waitForActions(function () {
|
|
args.grid.setActiveCell(args.row, args.cell);
|
|
handler.$applyAsync(function () {
|
|
handler.onEdit(true);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
});
|
|
cols.unshift(editColumn.getColumnDefinition());
|
|
}
|
|
|
|
// create checkbox column
|
|
var selectColumn = null;
|
|
if (scope.selector) {
|
|
selectColumn = new Slick.CheckboxSelectColumn({
|
|
cssClass: "slick-cell-checkboxsel",
|
|
multiSelect: scope.selector !== "single"
|
|
});
|
|
|
|
cols.unshift(_.extend(selectColumn.getColumnDefinition(), {
|
|
headerCssClass: "slick-cell-checkboxsel"
|
|
}));
|
|
}
|
|
|
|
// add column for re-ordering rows
|
|
this._canMove = view.canMove && view.orderBy === "sequence" && !view.groupBy;
|
|
if (this._canMove) {
|
|
cols.push({
|
|
id: "_move_column",
|
|
name: "",
|
|
width: 32,
|
|
behavior: "selectAndMove",
|
|
selectable: false,
|
|
resizable: false,
|
|
cssClass: "fa fa-bars move-icon",
|
|
canMove: function () {
|
|
if (handler.isReadonly && handler.isReadonly()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
var factory = new Factory(this);
|
|
|
|
var options = {
|
|
rowHeight: Math.max(view.rowHeight || 26, 26),
|
|
editable: view.editable && !axelor.device.mobile,
|
|
editorFactory: factory,
|
|
formatterFactory: factory,
|
|
enableCellNavigation: true,
|
|
enableColumnReorder: false,
|
|
fullWidthRows: true,
|
|
multiColumnSort: true,
|
|
showHeaderRow: this.showFilters,
|
|
multiSelect: scope.selector !== "single",
|
|
explicitInitialization: true
|
|
};
|
|
|
|
var grid = new Slick.Grid(element, dataView, cols, options);
|
|
|
|
this.cols = cols;
|
|
this.grid = grid;
|
|
|
|
this._selectColumn = selectColumn;
|
|
this._editColumn = editColumn;
|
|
|
|
element.show();
|
|
element.data('grid', grid);
|
|
|
|
// delegate some methods to handler scope
|
|
handler.showColumn = _.bind(this.showColumn, this);
|
|
handler.resetColumns = _.bind(this.resetColumns, this);
|
|
handler.setColumnTitle = _.bind(this.setColumnTitle, this);
|
|
handler.getVisibleCols = _.bind(this.getVisibleCols, this);
|
|
|
|
// set dummy columns to apply attrs if grid is not initialized yet
|
|
setDummyCols(element, this.cols);
|
|
|
|
function adjustSize(event, force) {
|
|
scope.ajaxStop(function () {
|
|
var forceAdjust = force || handler._isPopup;
|
|
if (forceAdjust || element.is(':visible')) {
|
|
that.adjustSize(forceAdjust);
|
|
if (forceAdjust) {
|
|
setTimeout(grid.autosizeColumns, 100);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
scope.$on('grid:adjust-size', function (e, viewScope) {
|
|
adjustSize(e, viewScope === handler);
|
|
});
|
|
|
|
scope.$onAdjust(adjustSize, 100); // handle global events
|
|
|
|
scope.$callWhen(function () {
|
|
return element.is(':visible');
|
|
}, adjustSize, 100);
|
|
|
|
element.addClass('slickgrid-empty');
|
|
this.doInit = _.once(function doInit() {
|
|
this._doInit(view);
|
|
element.removeClass('slickgrid-empty');
|
|
}.bind(this));
|
|
|
|
handler._dataSource.on('change', function (e, records, page) {
|
|
element.toggleClass('slickgrid-empty-message', page && page.size === 0);
|
|
});
|
|
|
|
var emptyMessage = handler.$emptyMessage || _t("No records found.");
|
|
element.append($("<div class='slickgrid-empty-text'>").hide().text(emptyMessage));
|
|
|
|
return grid;
|
|
};
|
|
|
|
Grid.prototype._doInit = function(view) {
|
|
|
|
var that = this,
|
|
grid = this.grid,
|
|
scope = this.scope,
|
|
handler = this.scope.handler,
|
|
dataView = this.scope.dataView,
|
|
element = this.element;
|
|
|
|
var headerMenu = new Slick.Plugins.HeaderMenu({
|
|
buttonImage: "lib/slickgrid/images/down.gif"
|
|
});
|
|
|
|
var rowMoveManager = new Slick.RowMoveManager({
|
|
cancelEditOnDrag: true
|
|
});
|
|
|
|
grid.setSelectionModel(new Slick.RowSelectionModel());
|
|
grid.registerPlugin(new Slick.Data.GroupItemMetadataProvider());
|
|
grid.registerPlugin(headerMenu);
|
|
if (this._selectColumn) {
|
|
grid.registerPlugin(this._selectColumn);
|
|
}
|
|
if (this._editColumn) {
|
|
grid.registerPlugin(this._editColumn);
|
|
}
|
|
if (this._canMove) {
|
|
grid.registerPlugin(rowMoveManager);
|
|
}
|
|
|
|
// performance tweaks
|
|
var _containerH = 0;
|
|
var _containerW = 0;
|
|
var _resizeCanvas = grid.resizeCanvas;
|
|
grid.resizeCanvas = _.debounce(function() {
|
|
var w = element.width(),
|
|
h = element.height();
|
|
if (element.is(':hidden') || (w === _containerW && h === _containerH)) {
|
|
return;
|
|
}
|
|
_containerW = w;
|
|
_containerH = h;
|
|
_resizeCanvas.call(grid);
|
|
}, 100);
|
|
|
|
grid.init();
|
|
this.$$initialized = true;
|
|
|
|
// end performance tweaks
|
|
|
|
dataView.$syncSelection = function(old, oldIds, focus) {
|
|
var selection = dataView.mapIdsToRows(oldIds || []);
|
|
// if saving o2m items, we may get negative oldIds, consider reselecting old selection
|
|
if (old && oldIds && old.length === 1 && oldIds.length === 1 && oldIds[0] < 0) {
|
|
dataView.getItem(old[0]).selected = true;
|
|
}
|
|
if (!focus) {
|
|
_.each(dataView.getItems(), function (item, i) {
|
|
if (item.selected) {
|
|
selection.push(i);
|
|
}
|
|
});
|
|
}
|
|
selection = _.unique(selection);
|
|
grid.setSelectedRows(selection);
|
|
if (selection.length === 0 && !grid.getEditorLock().isActive()) {
|
|
grid.setActiveCell(null);
|
|
} else if (focus) {
|
|
grid.setActiveCell(_.first(selection), 1);
|
|
grid.focus();
|
|
}
|
|
};
|
|
dataView.$setSelection = function(selection, focus) {
|
|
var rows = selection || [];
|
|
grid.setSelectedRows(rows);
|
|
if (selection.length === 0 && !grid.getEditorLock().isActive()) {
|
|
grid.setActiveCell(null);
|
|
} else if (focus) {
|
|
grid.setActiveCell(_.first(selection), 1);
|
|
grid.focus();
|
|
}
|
|
};
|
|
|
|
if (this._canMove) {
|
|
dataView.$resequence = _.bind(this._resequence, this);
|
|
}
|
|
|
|
// register grid event handlers
|
|
this.subscribe(grid.onSort, this.onSort);
|
|
this.subscribe(grid.onSelectedRowsChanged, this.onSelectionChanged);
|
|
this.subscribe(grid.onClick, this.onItemClick);
|
|
this.subscribe(grid.onDblClick, this.onItemDblClick);
|
|
|
|
this.subscribe(grid.onKeyDown, this.onKeyDown);
|
|
this.subscribe(grid.onCellChange, this.onCellChange);
|
|
this.subscribe(grid.onBeforeEditCell, this.onBeforeEditCell);
|
|
|
|
// register dataView event handlers
|
|
this.subscribe(dataView.onRowCountChanged, this.onRowCountChanged);
|
|
this.subscribe(dataView.onRowsChanged, this.onRowsChanged);
|
|
|
|
// register header menu event handlers
|
|
this.subscribe(headerMenu.onBeforeMenuShow, this.onBeforeMenuShow);
|
|
this.subscribe(headerMenu.onCommand, this.onMenuCommand);
|
|
|
|
// register row move handlers
|
|
this.subscribe(rowMoveManager.onBeforeMoveRows, this.onBeforeMoveRows);
|
|
this.subscribe(rowMoveManager.onMoveRows, this.onMoveRows);
|
|
|
|
// hilite support
|
|
var getItemMetadata = dataView.getItemMetadata;
|
|
dataView.getItemMetadata = function (row) {
|
|
var item = grid.getDataItem(row);
|
|
if (item && !item.$style) {
|
|
that.hilite(row);
|
|
}
|
|
var meta = getItemMetadata.apply(dataView, arguments);
|
|
var my = that.getItemMetadata(row);
|
|
if (my && meta && meta.cssClasses) {
|
|
my.cssClasses += " " + meta.cssClasses;
|
|
}
|
|
return meta || my;
|
|
};
|
|
this.subscribe(grid.onCellChange, function (e, args) {
|
|
that.hilite(args.row);
|
|
grid.invalidateRow(args.row);
|
|
grid.render();
|
|
});
|
|
|
|
function setFilterCols() {
|
|
|
|
if (!that.showFilters) {
|
|
return;
|
|
}
|
|
|
|
var filters = {};
|
|
var filtersRow = $(grid.getHeaderRow());
|
|
|
|
function updateFilters(event) {
|
|
/* jshint validthis: true */
|
|
var elem = $(this);
|
|
if (elem.is('.ui-autocomplete-input')) {
|
|
return;
|
|
}
|
|
filters[$(this).data('columnId')] = $(this).val().trim();
|
|
handler._simpleFilters = filters;
|
|
}
|
|
|
|
function clearFilters() {
|
|
filters = {};
|
|
filtersRow.find(":input").val("");
|
|
handler._simpleFilters = null;
|
|
}
|
|
|
|
handler.clearFilters = clearFilters;
|
|
|
|
filtersRow.on('keyup', ':input', updateFilters);
|
|
filtersRow.on('keypress', ':input', function(event){
|
|
if (event.keyCode === 13) {
|
|
updateFilters.call(this, event);
|
|
scope.handler.filter(filters, that.advanceFilter);
|
|
}
|
|
});
|
|
|
|
function _setInputs(cols) {
|
|
_.each(cols, function(col){
|
|
if (!col.xpath || col.descriptor.type === 'button' || col.descriptor.json || col.descriptor.encrypted) return;
|
|
var header = grid.getHeaderRowColumn(col.id),
|
|
input = $('<input type="text">').data("columnId", col.id).val(filters[col.id]).appendTo(header),
|
|
field = col.descriptor || {};
|
|
input.on("change", function () {
|
|
input.attr('placeholder', input.is(':focus') ? _t('Search...') : null);
|
|
});
|
|
input.on("focus", function () {
|
|
input.attr('placeholder', _t('Search...'));
|
|
});
|
|
input.on("blur", function () {
|
|
input.attr('placeholder', '');
|
|
});
|
|
if (_.isArray(field.selectionList)) {
|
|
makeFilterCombo(input, field.selectionList, function(filter){
|
|
_.extend(filters, filter);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
var _setColumns = grid.setColumns;
|
|
grid.setColumns = function(columns) {
|
|
_setColumns.apply(grid, arguments);
|
|
_setInputs(columns);
|
|
};
|
|
|
|
_setInputs(that.cols);
|
|
}
|
|
|
|
setFilterCols();
|
|
|
|
var onInit = scope.onInit();
|
|
if (_.isFunction(onInit)) {
|
|
onInit(grid, this);
|
|
}
|
|
|
|
if (view.groupBy) {
|
|
this.groupBy(view.groupBy);
|
|
}
|
|
|
|
setTimeout(function () {
|
|
grid.setColumns(that.getVisibleCols());
|
|
});
|
|
|
|
if (scope.$parent._viewResolver) {
|
|
scope.$parent._viewResolver.resolve(view, element);
|
|
}
|
|
|
|
scope.$on("cancel:grid-edit", function(e) {
|
|
|
|
if (that.$oldValues && that.canSave()){
|
|
|
|
dataView.beginUpdate();
|
|
dataView.setItems(that.$oldValues);
|
|
dataView.endUpdate();
|
|
|
|
that.$oldValues = null;
|
|
|
|
that.clearDirty();
|
|
|
|
grid.invalidateAllRows();
|
|
grid.render();
|
|
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
|
|
scope.$on("on:new", function(e) {
|
|
that.$oldValues = null;
|
|
that.clearDirty();
|
|
that.resetColumns();
|
|
});
|
|
|
|
scope.$on("on:edit", function(e, record) {
|
|
if (record && record.id > 0) {
|
|
that.$oldValues = null;
|
|
that.clearDirty();
|
|
that.resetColumns();
|
|
}
|
|
});
|
|
|
|
scope.$on("on:advance-filter", function (e, criteria) {
|
|
if (e.targetScope === handler) {
|
|
that.advanceFilter = criteria;
|
|
}
|
|
});
|
|
|
|
scope.$on("on:context-field-change", function (e, data) {
|
|
that.contextField = data && data.field ? data.field.name : null;
|
|
that.contextValue = data.value;
|
|
that.resetColumns();
|
|
});
|
|
|
|
scope.$on("on:before-save", function(e) {
|
|
|
|
// only for editable grid
|
|
if (!that.editable) {
|
|
return;
|
|
}
|
|
|
|
var row = null;
|
|
var lock = grid.getEditorLock();
|
|
if (lock.isActive()) {
|
|
lock.commitCurrentEdit();
|
|
row = grid.getDataItem(grid.getDataLength() - 1); // to check if adding new row
|
|
}
|
|
if (grid.getActiveCell() && that.focusInvalidCell(grid.getActiveCell())) {
|
|
e.preventDefault();
|
|
showErrorNotice();
|
|
return false;
|
|
}
|
|
|
|
var beforeSavePending = that.__beforeSavePending || (row && row.id === 0);
|
|
|
|
that.__beforeSavePending = false;
|
|
|
|
function showErrorNotice() {
|
|
|
|
var args = that.grid.getActiveCell() || {};
|
|
var col = that.getColumn(args.cell);
|
|
|
|
if (!col || !col.xpath) {
|
|
return;
|
|
}
|
|
|
|
var name = col.name;
|
|
if (that.handler.field &&
|
|
that.handler.field.title) {
|
|
name = that.handler.field.title + "[" + args.row +"] / " + name;
|
|
}
|
|
|
|
var items = "<ul><li>" + name + "</li></ul>";
|
|
axelor.notify.error(items, {
|
|
title: _t("The following fields are invalid:")
|
|
});
|
|
}
|
|
|
|
var node = that.element.find('.slick-cell-required:empty,.slick-cell > .ng-invalid').first();
|
|
if (node.parent().is('.slick-cell')) {
|
|
node = node.parent();
|
|
}
|
|
if (node.length) {
|
|
that.grid.setActiveNode(node[0]);
|
|
that.grid.editActiveCell();
|
|
e.preventDefault();
|
|
showErrorNotice();
|
|
return false;
|
|
}
|
|
|
|
if (!that.isDirty() || !beforeSavePending || that.saveChanges()) {
|
|
return;
|
|
}
|
|
if (!that.editorScope || that.editorScope.isValid()) {
|
|
return;
|
|
}
|
|
if (that.editorForm && that.editorForm.is(":hidden")) {
|
|
return;
|
|
}
|
|
|
|
var args = that.grid.getActiveCell();
|
|
if (args) {
|
|
that.focusInvalidCell(args);
|
|
showErrorNotice();
|
|
} else {
|
|
var item = that.editorScope.record;
|
|
if (item && item.id === 0) {
|
|
// new row was canceled
|
|
return;
|
|
}
|
|
axelor.dialogs.error(_t('There are some invalid rows.'));
|
|
}
|
|
|
|
e.preventDefault();
|
|
return false;
|
|
});
|
|
|
|
|
|
scope.$timeout(function () {
|
|
that.zIndexFix();
|
|
grid.invalidate();
|
|
grid.autosizeColumns();
|
|
// focus first filter input
|
|
if (!axelor.device.mobile && !that.element.parent().is('.portlet-grid')) {
|
|
that.element
|
|
.find('.slick-headerrow:first input[type=text]:first')
|
|
.focus()
|
|
.select();
|
|
}
|
|
});
|
|
|
|
var scrollTop;
|
|
this.subscribe(grid.onScroll, function (e, args) {
|
|
scrollTop = args.scrollTop;
|
|
});
|
|
$(document).on("click",".fa-check", function () {
|
|
grid.re
|
|
$(this).parent().parent().parent().removeClass("hilite-info-text");
|
|
$(this).parent().parent().parent().removeClass("hilite-warning-text");
|
|
$(this).parent().parent().parent().addClass("hilite-success-text");
|
|
$(this).parent().parent().siblings(".l11").text("Accepté")
|
|
});
|
|
function resetScroll() {
|
|
if (scrollTop) {
|
|
setTimeout(function () {
|
|
that.element.children('.slick-viewport').scrollTop(scrollTop);
|
|
setTimeout(function() {
|
|
grid.invalidateAllRows();
|
|
grid.render();
|
|
}, 100);
|
|
});
|
|
}
|
|
}
|
|
scope.$on('dom:attach', resetScroll);
|
|
scope.$on('tab:select', resetScroll);
|
|
|
|
var onColumnsResized = false;
|
|
this.subscribe(grid.onColumnsResized, function (e, args) {
|
|
onColumnsResized = true;
|
|
});
|
|
|
|
scope.$on('grid:adjust-columns', function () {
|
|
if (!onColumnsResized) {
|
|
grid.autosizeColumns();
|
|
}
|
|
});
|
|
|
|
scope.$on('$destroy', function () {
|
|
if (that.editorForm) {
|
|
that.editorForm.remove();
|
|
}
|
|
if (that.editorScope) {
|
|
that.editorScope.$destroy();
|
|
}
|
|
grid.destroy();
|
|
});
|
|
};
|
|
|
|
Grid.prototype.subscribe = function(event, handler) {
|
|
event.subscribe(_.bind(handler, this));
|
|
};
|
|
|
|
Grid.prototype.zIndexFix = function() {
|
|
//XXX: ui-dialog issue (filter row)
|
|
var zIndex = this.element.parents('.ui-dialog:first').zIndex();
|
|
if (zIndex) {
|
|
this.element.find('.slick-headerrow-column').zIndex(zIndex);
|
|
}
|
|
};
|
|
|
|
Grid.prototype.adjustSize = function(force) {
|
|
if (!this.grid || (!force && this.element.is(':hidden')) || this.grid.getEditorLock().isActive()) {
|
|
return;
|
|
}
|
|
this.doInit();
|
|
if (this.grid.getCanvasNode() && !this.grid.getCanvasNode().hasChildNodes()) {
|
|
this.grid.invalidate();
|
|
}
|
|
this.adjustToScreen();
|
|
this.grid.resizeCanvas();
|
|
this.zIndexFix();
|
|
};
|
|
|
|
Grid.prototype.adjustToScreen = function() {
|
|
var compact = this.__compact;
|
|
var mobile = axelor.device.small;
|
|
|
|
if (!compact && !mobile) {
|
|
return;
|
|
}
|
|
if (mobile && compact) {
|
|
return;
|
|
}
|
|
|
|
this.__compact = mobile;
|
|
|
|
_.each(this.cols, function (col, i) {
|
|
var field = col.descriptor || {};
|
|
if (field.hidden) {
|
|
return;
|
|
}
|
|
}, this);
|
|
};
|
|
|
|
Grid.prototype.getColumn = function(indexOrName) {
|
|
var cols = this.grid.getColumns(),
|
|
index = indexOrName;
|
|
|
|
if (_.isString(index)) {
|
|
index = this.grid.getColumnIndex(index);
|
|
}
|
|
return cols[index];
|
|
};
|
|
|
|
Grid.prototype.showColumn = function(name, show) {
|
|
|
|
var that = this,
|
|
grid = this.grid,
|
|
cols = this.cols;
|
|
|
|
this.visibleCols = this.visibleCols || _.pluck(this.getVisibleCols(), 'id');
|
|
|
|
show = _.isUndefined(show) ? true : show;
|
|
|
|
var visible = [],
|
|
current = [];
|
|
|
|
_.each(cols, function(col){
|
|
if (col.id != name && _.contains(that.visibleCols, col.id))
|
|
return visible.push(col.id);
|
|
if (col.id == name && show)
|
|
return visible.push(name);
|
|
});
|
|
|
|
this.visibleCols = visible;
|
|
|
|
if (!this.$$initialized) {
|
|
return;
|
|
}
|
|
|
|
current = _.filter(cols, function(col) {
|
|
return _.contains(visible, col.id);
|
|
});
|
|
|
|
grid.setColumns(current);
|
|
grid.getViewport().rightPx = 0;
|
|
grid.resizeCanvas();
|
|
grid.autosizeColumns();
|
|
|
|
this.zIndexFix();
|
|
};
|
|
|
|
Grid.prototype.getVisibleCols = function(reset) {
|
|
var visible = reset ? [] : (this.visibleCols || []);
|
|
if (visible.length === 0) {
|
|
var contextField = this.contextField;
|
|
var contextValue = this.contextValue;
|
|
return this.cols.filter(function (col) {
|
|
var desc = col.descriptor||{};
|
|
if (desc.contextField) {
|
|
return !desc.hidden
|
|
&& desc.contextField === contextField
|
|
&& desc.contextFieldValue === contextValue;
|
|
}
|
|
return !desc.hidden;
|
|
});
|
|
}
|
|
return this.cols.filter(function (col) {
|
|
return visible.length ? _.contains(visible, col.id) : true;
|
|
});
|
|
};
|
|
|
|
Grid.prototype.resetColumns = function() {
|
|
var grid = this.grid,
|
|
cols = this.getVisibleCols(true);
|
|
|
|
this.visibleCols = _.pluck(cols, 'id');
|
|
|
|
grid.setColumns(cols);
|
|
grid.getViewport().rightPx = 0;
|
|
grid.resizeCanvas();
|
|
grid.autosizeColumns();
|
|
};
|
|
|
|
Grid.prototype.setColumnTitle = function(name, title) {
|
|
if (this.$$initialized) {
|
|
return this.grid.updateColumnHeader(name, title);
|
|
}
|
|
var col = this.getColumn(name);
|
|
if (col && title) {
|
|
col.name = title;
|
|
}
|
|
};
|
|
|
|
Grid.prototype.getItemMetadata = function(row) {
|
|
var item = this.grid.getDataItem(row);
|
|
if (item && item.$style) {
|
|
return {
|
|
cssClasses: item.$style
|
|
};
|
|
}
|
|
return null;
|
|
};
|
|
|
|
Grid.prototype.hilite = function (row, field) {
|
|
var view = this.scope.view,
|
|
record = this.grid.getDataItem(row),
|
|
params = null;
|
|
|
|
if (!view || !record || record.__group || record.__groupTotals) {
|
|
return null;
|
|
}
|
|
|
|
if (!field) {
|
|
_.each(this.scope.fields_view, function (item) {
|
|
if (item.hilites) this.hilite(row, item);
|
|
}, this);
|
|
}
|
|
|
|
var hilites = field ? field.hilites : view.hilites;
|
|
if (!hilites || hilites.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
record.$style = null;
|
|
|
|
var ctx = record || {};
|
|
if (this.handler._context) {
|
|
ctx = _.extend({}, this.handler._context, ctx);
|
|
}
|
|
|
|
for (var i = 0; i < hilites.length; i++) {
|
|
params = hilites[i];
|
|
var condition = params.condition,
|
|
styles = null,
|
|
pass = false;
|
|
|
|
try {
|
|
pass = axelor.$eval(this.scope, condition, ctx);
|
|
} catch (e) {
|
|
}
|
|
if (!pass && field) {
|
|
styles = record.$styles || (record.$styles = {});
|
|
styles[field.name] = null;
|
|
}
|
|
if (!pass) {
|
|
continue;
|
|
}
|
|
if (field) {
|
|
styles = record.$styles || (record.$styles = {});
|
|
styles[field.name] = params.css;
|
|
} else {
|
|
record.$style = params.css;
|
|
}
|
|
break;
|
|
}
|
|
};
|
|
|
|
Grid.prototype.onBeforeMenuShow = function(event, args) {
|
|
|
|
var menu = args.menu;
|
|
if (!menu || !menu.items || !this.visibleCols) {
|
|
return;
|
|
}
|
|
|
|
menu.items = _.filter(menu.items, function(item) {
|
|
return item.command !== 'show';
|
|
});
|
|
|
|
_.each(this.cols, function(col) {
|
|
if (_.contains(this.visibleCols, col.id)) return;
|
|
menu.items.push({
|
|
title: _t('Show') + " <i>" + col.name + "</i>",
|
|
command: 'show',
|
|
field: col.field
|
|
});
|
|
}, this);
|
|
};
|
|
|
|
Grid.prototype.onMenuCommand = function(event, args) {
|
|
|
|
var grid = this.grid;
|
|
|
|
if (args.command === 'sort-asc' ||
|
|
args.command == 'sort-desc') {
|
|
|
|
var opts = {
|
|
grid: grid,
|
|
multiColumnSort: true,
|
|
sortCols: [{
|
|
sortCol: args.column,
|
|
sortAsc: args.command === 'sort-asc'
|
|
}]
|
|
};
|
|
return grid.onSort.notify(opts, event, grid);
|
|
}
|
|
|
|
var groups, index;
|
|
|
|
if (args.command === 'group-by') {
|
|
groups = this._groups || [];
|
|
index = groups.indexOf(args.column.field);
|
|
if (index === -1) {
|
|
groups.push(args.column.field);
|
|
}
|
|
return this.groupBy(groups);
|
|
}
|
|
|
|
if (args.command === 'ungroup') {
|
|
groups = this._groups || [];
|
|
index = groups.indexOf(args.column.field);
|
|
if (index > -1) {
|
|
groups.splice(index, 1);
|
|
}
|
|
return this.groupBy(groups);
|
|
}
|
|
|
|
if (args.command === 'hide') {
|
|
return this.showColumn(args.column.field, false);
|
|
}
|
|
|
|
if (args.command === 'show') {
|
|
return this.showColumn(args.item.field, true);
|
|
}
|
|
};
|
|
|
|
Grid.prototype.onBeforeEditCell = function(event, args) {
|
|
if (this.$oldValues === null) {
|
|
this.$oldValues = [];
|
|
var n = 0;
|
|
while (n < this.grid.getDataLength()) {
|
|
var item = this.grid.getDataItem(n++);
|
|
if (item && item.id) {
|
|
this.$oldValues.push(_.clone(item));
|
|
}
|
|
}
|
|
}
|
|
if (args.item && args.item._original === undefined) {
|
|
args.item._original = _.clone(args.item);
|
|
}
|
|
};
|
|
|
|
Grid.prototype.onKeyDown = function(e, args) {
|
|
var that = this,
|
|
grid = this.grid,
|
|
lock = grid.getEditorLock();
|
|
|
|
if (e.which === $.ui.keyCode.ENTER && $(e.target).is('textarea,[contenteditable]')) {
|
|
return;
|
|
}
|
|
|
|
if (e.isDefaultPrevented()){
|
|
e.stopImmediatePropagation();
|
|
return false;
|
|
}
|
|
|
|
if (!e.isBlocked && (blocked() || !lock.isActive())) {
|
|
return false;
|
|
}
|
|
|
|
function blockCallback(blocked) {
|
|
if (blocked && e.which === $.ui.keyCode.TAB) {
|
|
setTimeout(function(){
|
|
var cell = e.shiftKey ? that.findPrevEditable(args.row, args.cell)
|
|
: that.findNextEditable(args.row, args.cell);
|
|
if (cell) {
|
|
grid.setActiveCell(cell.row, cell.cell);
|
|
grid.editActiveCell();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function blocked() {
|
|
if (that.isDirty() && axelor.blockUI(blockCallback)) {
|
|
grid.focus();
|
|
e.stopImmediatePropagation();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function commitChanges() {
|
|
if (lock.commitCurrentEdit() && !blocked()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function focusCell(row, cell) {
|
|
grid.setActiveCell(row, cell);
|
|
// make sure cell has focus RM-3938
|
|
setTimeout(function () {
|
|
grid.editActiveCell();
|
|
});
|
|
}
|
|
|
|
// firefox & IE fails to trigger onChange
|
|
if ((axelor.browser.mozilla || axelor.browser.msie) &&
|
|
(e.which === $.ui.keyCode.TAB || e.which === $.ui.keyCode.ENTER)) {
|
|
var editor = grid.getCellEditor(),
|
|
target = $(e.target);
|
|
if (editor.isValueChanged()) {
|
|
target.change();
|
|
}
|
|
setTimeout(function(){
|
|
target.blur();
|
|
});
|
|
}
|
|
var handled = false;
|
|
if (e.which === $.ui.keyCode.TAB) {
|
|
var cell = e.shiftKey ? this.findPrevEditable(args.row, args.cell) :
|
|
this.findNextEditable(args.row, args.cell);
|
|
|
|
if (commitChanges() && cell && cell.row > args.row && this.isDirty()) {
|
|
args.item = null;
|
|
this.scope.waitForActions(function () {
|
|
that.scope.waitForActions(function () {
|
|
that.addNewRow(args);
|
|
});
|
|
});
|
|
} else if (cell) {
|
|
focusCell(cell.row, cell.cell);
|
|
}
|
|
|
|
handled = true;
|
|
}
|
|
|
|
if (e.which === $.ui.keyCode.ENTER) {
|
|
if (e.ctrlKey) {
|
|
if (!this.saveChanges(args)) {
|
|
this.focusInvalidCell(args);
|
|
}
|
|
} else {
|
|
if (!lock.commitCurrentEdit()) {
|
|
this.focusInvalidCell(args);
|
|
}
|
|
grid.focus();
|
|
}
|
|
grid.focus();
|
|
handled = true;
|
|
}
|
|
|
|
if (e.which === $.ui.keyCode.ESCAPE) {
|
|
grid.focus();
|
|
}
|
|
|
|
if (handled) {
|
|
e.stopImmediatePropagation();
|
|
return false;
|
|
}
|
|
};
|
|
|
|
Grid.prototype.isCellEditable = function(row, cell) {
|
|
var cols = this.grid.getColumns(),
|
|
col = cols[cell];
|
|
if (!col || col.id === "_edit_column" || col.id === "_move_column" || col.id === "_checkbox_selector") {
|
|
return false;
|
|
}
|
|
var field = col.descriptor || {};
|
|
var form = this.editorForm;
|
|
|
|
if (field.type === 'button' || (field.name && field.name.indexOf('.') > -1)) {
|
|
return false;
|
|
}
|
|
if (!form) {
|
|
return !field.readonly;
|
|
}
|
|
|
|
var current = this.grid.getActiveCell();
|
|
if (current && current.row === row) {
|
|
var item = this.element.find('[x-field=' + field.name + ']:first');
|
|
if (item.length) {
|
|
return !item.scope().isReadonly();
|
|
}
|
|
}
|
|
|
|
return !field.readonly;
|
|
};
|
|
|
|
Grid.prototype.findNextEditable = function(posY, posX) {
|
|
var grid = this.grid,
|
|
cols = grid.getColumns(),
|
|
args = {row: posY, cell: posX + 1};
|
|
while (args.cell < cols.length) {
|
|
if (this.isCellEditable(args.row, args.cell)) {
|
|
return args;
|
|
}
|
|
args.cell += 1;
|
|
}
|
|
if (grid.getDataItem(args.row)) {
|
|
args.row += 1;
|
|
}
|
|
args.cell = 0;
|
|
while (args.cell <= posX) {
|
|
if (this.isCellEditable(args.row, args.cell)) {
|
|
return args;
|
|
}
|
|
args.cell += 1;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
Grid.prototype.findPrevEditable = function(posY, posX) {
|
|
var grid = this.grid,
|
|
cols = grid.getColumns(),
|
|
args = {row: posY, cell: posX - 1};
|
|
while (args.cell > -1) {
|
|
if (this.isCellEditable(args.row, args.cell)) {
|
|
return args;
|
|
}
|
|
args.cell -= 1;
|
|
}
|
|
if (args.row > 0) {
|
|
args.row -= 1;
|
|
}
|
|
args.cell = cols.length - 1;
|
|
while (args.cell >= posX) {
|
|
if (this.isCellEditable(args.row, args.cell)) {
|
|
return args;
|
|
}
|
|
args.cell -= 1;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
Grid.prototype.saveChanges = function(args, callback) {
|
|
|
|
// onBeforeSave may cause recursion
|
|
if (this._saveChangesRunning) {
|
|
return;
|
|
}
|
|
|
|
var that = this;
|
|
var grid = this.grid;
|
|
var lock = grid.getEditorLock();
|
|
var force = arguments[2];
|
|
|
|
if (!force &&
|
|
((lock.isActive() && !lock.commitCurrentEdit()) ||
|
|
(this.editorScope && !this.editorScope.isValid()))) {
|
|
return false;
|
|
}
|
|
|
|
this._saveChangesRunning = true;
|
|
if (this.editorScope) {
|
|
this.editorScope.$emit("on:before-save", this.editorScope.record);
|
|
}
|
|
var params = arguments;
|
|
this.scope.waitForActions(function () {
|
|
that.__saveChanges.apply(that, params);
|
|
that._saveChangesRunning = false;
|
|
}, 100);
|
|
|
|
return true;
|
|
};
|
|
|
|
Grid.prototype.__saveChanges = function(args, callback) {
|
|
|
|
var that = this;
|
|
var grid = this.grid;
|
|
|
|
if (!args) {
|
|
args = _.extend({ row: 0, cell: 0 }, grid.getActiveCell());
|
|
args.item = grid.getDataItem(args.row);
|
|
}
|
|
|
|
var ds = this.handler._dataSource;
|
|
var data = this.scope.dataView;
|
|
var records = [];
|
|
|
|
records = _.map(data.getItems(), function(rec) {
|
|
var res = {};
|
|
for(var key in rec) {
|
|
var val = rec[key];
|
|
if (_.isString(val) && val.trim() === "")
|
|
val = null;
|
|
res[key] = val;
|
|
}
|
|
if (res.id === 0) {
|
|
res.id = null;
|
|
}
|
|
if (res.$dirty && _.isUndefined(res.version)) {
|
|
res.version = res.$version;
|
|
}
|
|
return res;
|
|
});
|
|
|
|
function focus() {
|
|
grid.setActiveCell(args.row, args.cell);
|
|
grid.focus();
|
|
if (callback) {
|
|
that.handler.waitForActions(callback);
|
|
}
|
|
}
|
|
|
|
var onBeforeSave = this.scope.onBeforeSave(),
|
|
onAfterSave = this.scope.onAfterSave();
|
|
|
|
if (onBeforeSave && onBeforeSave(records) === false) {
|
|
return setTimeout(focus, 200);
|
|
}
|
|
|
|
// prevent cache
|
|
var saveDS = ds;
|
|
var handler = this.handler || {};
|
|
if (handler.field && handler.field.target) {
|
|
saveDS = ds._new(ds._model, {
|
|
domain: ds._domain,
|
|
context: ds._context
|
|
});
|
|
}
|
|
|
|
records = _.where(records, { $dirty: true });
|
|
if (records.length === 0) {
|
|
return setTimeout(focus, 200);
|
|
}
|
|
|
|
var fields = handler.selectFields ? handler.selectFields() : undefined;
|
|
|
|
return saveDS.saveAll(records, fields).success(function(records, page) {
|
|
if (data.getItemById(0)) {
|
|
data.deleteItem(0);
|
|
}
|
|
if (onAfterSave) {
|
|
onAfterSave(records, page);
|
|
}
|
|
setTimeout(focus);
|
|
});
|
|
};
|
|
|
|
Grid.prototype.canSave = function() {
|
|
return this.editorScope && this.editorScope.isValid() && this.isDirty();
|
|
};
|
|
|
|
Grid.prototype.isDirty = function(row) {
|
|
var grid = this.grid;
|
|
var item;
|
|
|
|
if (row === null || row === undefined) {
|
|
var n = 0;
|
|
while (n < grid.getDataLength()) {
|
|
item = grid.getDataItem(n);
|
|
if (item && item.$dirty) {
|
|
return true;
|
|
}
|
|
n ++;
|
|
}
|
|
} else {
|
|
item = grid.getDataItem(row);
|
|
if (item && item.$dirty) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
Grid.prototype.__markHandlerDirty = function () {
|
|
if (this.scope.handler && this.scope.handler.$dirtyGrid) {
|
|
this.$gid = this.$gid || _.uniqueId('grid');
|
|
this.scope.handler.$dirtyGrid(this.$gid, this.isDirty());
|
|
}
|
|
};
|
|
|
|
Grid.prototype.markDirty = function(row, field) {
|
|
|
|
var grid = this.grid,
|
|
dataView = this.scope.dataView,
|
|
hash = grid.getCellCssStyles("highlight") || {},
|
|
items = hash[row] || {};
|
|
|
|
items[field] = "dirty";
|
|
hash[row] = items;
|
|
|
|
var record = dataView.getItem(row);
|
|
if (this.handler.$$ensureIds && record && !record.id) {
|
|
this.handler.$$ensureIds([record]);
|
|
}
|
|
|
|
grid.setCellCssStyles("highlight", hash);
|
|
grid.invalidateAllRows();
|
|
grid.render();
|
|
this.__markHandlerDirty();
|
|
this.__beforeSavePending = true;
|
|
};
|
|
|
|
Grid.prototype.clearDirty = function(row) {
|
|
var grid = this.grid,
|
|
hash = grid.getCellCssStyles("highlight") || {};
|
|
|
|
if (row === null || row === undefined) {
|
|
hash = {};
|
|
} else {
|
|
delete hash[row];
|
|
}
|
|
|
|
grid.setCellCssStyles("highlight", hash);
|
|
grid.invalidateAllRows();
|
|
grid.render();
|
|
|
|
this.__markHandlerDirty();
|
|
this.__beforeSavePending = false;
|
|
};
|
|
|
|
Grid.prototype.focusInvalidCell = function(args) {
|
|
var grid = this.grid,
|
|
formCtrl = this.editorForm.children('form').data('$formController'),
|
|
error = formCtrl.$error || {};
|
|
|
|
if (this.editorForm.is(':hidden') && _.isEmpty(this.editorScope.record)) {
|
|
return false;
|
|
}
|
|
|
|
for(var name in error) {
|
|
var errors = error[name] || [];
|
|
if (errors.length) {
|
|
name = errors[0].$name;
|
|
var cell = grid.getColumnIndex(name);
|
|
if (cell > -1) {
|
|
grid.setActiveCell(args.row, cell);
|
|
grid.editActiveCell();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
Grid.prototype.addNewRow = function (args) {
|
|
var self = this,
|
|
scope = this.scope,
|
|
grid = this.grid,
|
|
dataView = scope.dataView,
|
|
lock = grid.getEditorLock();
|
|
|
|
if (lock.isActive()) {
|
|
lock.commitCurrentEdit();
|
|
}
|
|
|
|
args.row = Math.max(0, args.row);
|
|
args.cell = Math.max(0, args.cell);
|
|
|
|
var cell = self.findNextEditable(args.row, 0);
|
|
|
|
function addRow(defaults) {
|
|
var args = { row: grid.getDataLength(), cell: 0 };
|
|
var item = _.extend({ id: 0 }, defaults);
|
|
|
|
grid.invalidateRow(dataView.length);
|
|
dataView.addItem(item);
|
|
|
|
self.scope.waitForActions(function () {
|
|
cell = self.findNextEditable(args.row, args.cell);
|
|
if (cell) {
|
|
grid.focus();
|
|
grid.setActiveCell(cell.row, cell.cell);
|
|
setTimeout(function () {
|
|
grid.editActiveCell();
|
|
}, 200);
|
|
}
|
|
}, 100);
|
|
}
|
|
|
|
function focus() {
|
|
grid.focus();
|
|
grid.setActiveCell(cell.row, cell.cell);
|
|
|
|
if (grid.getDataLength() > cell.row) {
|
|
return grid.editActiveCell();
|
|
}
|
|
if (!self.canAdd()) {
|
|
return;
|
|
}
|
|
|
|
self.editorScope.doOnNew();
|
|
self.scope.waitForActions(function () {
|
|
self.scope.waitForActions(function () {
|
|
addRow(self.editorScope.record);
|
|
});
|
|
}, 100);
|
|
}
|
|
|
|
if (args.item || grid.getDataLength() === 0) {
|
|
return focus();
|
|
}
|
|
var saved = self.saveChanges(args, function () {
|
|
cell.row += 1;
|
|
focus();
|
|
});
|
|
if (!saved) {
|
|
self.focusInvalidCell(args);
|
|
}
|
|
};
|
|
|
|
Grid.prototype.canEdit = function () {
|
|
var handler = this.handler || {};
|
|
if (!this.editable) return false;
|
|
if (handler.canEdit && !handler.canEdit()) return false;
|
|
if (handler.isReadonly && handler.isReadonly()) return false;
|
|
return true;
|
|
};
|
|
|
|
Grid.prototype.canAdd = function () {
|
|
var handler = this.handler || {};
|
|
if (!this.editable) return false;
|
|
if (handler.isReadonly && handler.isReadonly()) return false;
|
|
return handler.canNew && handler.canNew();
|
|
};
|
|
|
|
Grid.prototype.setEditors = function(form, formScope, forEdit) {
|
|
var grid = this.grid,
|
|
data = this.scope.dataView,
|
|
element = this.element;
|
|
|
|
this.editable = forEdit = forEdit === undefined ? true : forEdit;
|
|
|
|
grid.setOptions({
|
|
editable: !axelor.device.mobile,
|
|
asyncEditorLoading: false,
|
|
editorLock: new Slick.EditorLock()
|
|
});
|
|
|
|
form.prependTo(element).hide();
|
|
formScope.onChangeNotify = function(scope, values) {
|
|
var item, editor, cell = grid.getActiveCell();
|
|
if (!cell || formScope.record !== scope.record) {
|
|
return;
|
|
}
|
|
item = grid.getDataItem(cell.row);
|
|
if (item) {
|
|
editor = grid.getCellEditor();
|
|
if (grid.getEditorLock().isActive()) {
|
|
grid.getEditorLock().commitCurrentEdit();
|
|
}
|
|
item = _.extend(item, values);
|
|
|
|
// update dotted fields
|
|
_.filter(grid.getColumns(), function (col) {
|
|
return col.field && col.field.indexOf('.') > -1;
|
|
}).forEach(function (col) {
|
|
var path = col.field.split('.');
|
|
var val = item || {};
|
|
var idx = 0;
|
|
while (val && idx < path.length) {
|
|
val = val[path[idx++]];
|
|
}
|
|
if (idx === path.length && val !== undefined) {
|
|
item[col.field] = val;
|
|
}
|
|
});
|
|
|
|
grid.updateRowCount();
|
|
grid.invalidateRow(cell.row);
|
|
grid.render();
|
|
|
|
grid.setActiveCell(cell.row, cell.cell);
|
|
|
|
if (editor) {
|
|
grid.focus();
|
|
}
|
|
}
|
|
};
|
|
|
|
formScope.onNewHandler = function (event) {
|
|
|
|
};
|
|
|
|
formScope.doOnNew = function () {
|
|
|
|
if (formScope.defaultValues === null) {
|
|
formScope.defaultValues = {};
|
|
_.each(formScope.fields, function (field, name) {
|
|
if (field.defaultValue !== undefined) {
|
|
formScope.defaultValues[name] = field.defaultValue;
|
|
}
|
|
});
|
|
}
|
|
|
|
var values = angular.copy(formScope.defaultValues);
|
|
var args = grid.getActiveCell();
|
|
|
|
formScope.editRecord(values);
|
|
formScope.$applyAsync();
|
|
|
|
if (!formScope.$events.onNew) {
|
|
return;
|
|
}
|
|
|
|
var handler = formScope.$events.onNew;
|
|
var lock = grid.getEditorLock();
|
|
if (lock.isActive()) {
|
|
lock.commitCurrentEdit();
|
|
}
|
|
|
|
var promise = handler();
|
|
promise.then(function () {
|
|
grid.focus();
|
|
grid.editActiveCell();
|
|
});
|
|
};
|
|
|
|
// delegate isDirty to the dataView
|
|
data.canSave = _.bind(this.canSave, this);
|
|
data.saveChanges = _.bind(this.saveChanges, this);
|
|
|
|
var that = this;
|
|
var onNew = this.handler.onNew;
|
|
if (onNew) {
|
|
this.handler.onNew = function () {
|
|
var lock = that.grid.getEditorLock();
|
|
|
|
if (that.editable) {
|
|
if (lock.isActive()) {
|
|
lock.commitCurrentEdit();
|
|
}
|
|
|
|
var cell = that.findNextEditable(that.grid.getDataLength() - 1, 0) || { row: that.grid.getDataLength(), cell: 0 };
|
|
that.grid.focus();
|
|
that.grid.setActiveCell(cell.row, cell.cell);
|
|
that.grid.editActiveCell();
|
|
return that.scope.$timeout(function () {
|
|
return that.addNewRow(cell);
|
|
});
|
|
}
|
|
return onNew.apply(that.handler, arguments);
|
|
};
|
|
}
|
|
|
|
if (!forEdit) {
|
|
formScope.setEditable(false);
|
|
}
|
|
|
|
this.editorForm = form;
|
|
this.editorScope = formScope;
|
|
this.editorForEdit = forEdit;
|
|
};
|
|
|
|
Grid.prototype.onSelectionChanged = function(event, args) {
|
|
var grid = this.grid;
|
|
var activeCell = grid.getActiveCell();
|
|
var selectedRows = args.rows || [];
|
|
|
|
if (activeCell && selectedRows.indexOf(activeCell.row) === -1) {
|
|
grid.resetActiveCell();
|
|
}
|
|
|
|
if (this.handler.onSelectionChanged) {
|
|
this.handler.onSelectionChanged(event, args);
|
|
}
|
|
this.element.find(' > .slick-viewport > .grid-canvas > .slick-row')
|
|
.removeClass('selected')
|
|
.find(' > .slick-cell.selected')
|
|
.parent()
|
|
.each(function () {
|
|
$(this).addClass('selected');
|
|
});
|
|
};
|
|
|
|
Grid.prototype.onCellChange = function(event, args) {
|
|
var grid = this.grid,
|
|
cols = grid.getColumns(),
|
|
name = cols[args.cell].field;
|
|
|
|
var es = this.editorScope;
|
|
if (es.record && es.record.version === undefined) {
|
|
es.record.version = es.record.$version;
|
|
}
|
|
if (es.isDirty()) {
|
|
this.markDirty(args.row, name);
|
|
}
|
|
};
|
|
|
|
Grid.prototype.onSort = function(event, args) {
|
|
if (this.canSave())
|
|
return;
|
|
if (this.handler.onSort)
|
|
this.handler.onSort(event, args);
|
|
};
|
|
|
|
Grid.prototype.onBeforeMoveRows = function (event, args) {
|
|
var data = this.scope.dataView;
|
|
for (var i = 0; i < args.rows.length; i++) {
|
|
// no point in moving before or after itself
|
|
if (args.rows[i] == args.insertBefore || args.rows[i] == args.insertBefore - 1) {
|
|
event.stopPropagation();
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
Grid.prototype._resequence = function (items) {
|
|
var min = _.min(_.map(items, function (item) {
|
|
return item.sequence || 0;
|
|
}));
|
|
for (var i = 0; i < items.length; i++) {
|
|
var last = items[i].sequence;
|
|
var next = min++;
|
|
if (items[i].sequence !== next) {
|
|
items[i].sequence = next;
|
|
items[i].$dirty = true;
|
|
}
|
|
}
|
|
return items;
|
|
};
|
|
|
|
Grid.prototype.onMoveRows = function (event, args) {
|
|
var grid = this.grid;
|
|
var dataView = this.scope.dataView;
|
|
var rows = args.rows;
|
|
var items = dataView.getItems();
|
|
var insertBefore = args.insertBefore;
|
|
|
|
var left = items.slice(0, insertBefore);
|
|
var right = items.slice(insertBefore, items.length);
|
|
var extractedRows = [];
|
|
|
|
rows.sort(function(a, b) { return a - b; });
|
|
|
|
var i;
|
|
|
|
for (i = 0; i < rows.length; i++) {
|
|
extractedRows.push(items[rows[i]]);
|
|
}
|
|
|
|
rows.reverse();
|
|
|
|
for (i = 0; i < rows.length; i++) {
|
|
var row = rows[i];
|
|
if (row < insertBefore) {
|
|
left.splice(row, 1);
|
|
} else {
|
|
right.splice(row - insertBefore, 1);
|
|
}
|
|
}
|
|
|
|
items = left.concat(extractedRows.concat(right));
|
|
|
|
var selectedRows = [];
|
|
for (i = 0; i < rows.length; i++) {
|
|
selectedRows.push(left.length + i);
|
|
}
|
|
|
|
// resequence
|
|
this._resequence(items);
|
|
|
|
function resetSelection() {
|
|
grid.setActiveCell(selectedRows[0], 0);
|
|
grid.setSelectedRows(selectedRows);
|
|
}
|
|
|
|
dataView.beginUpdate();
|
|
dataView.setItems(items);
|
|
dataView.endUpdate();
|
|
resetSelection();
|
|
grid.render();
|
|
|
|
var that = this;
|
|
this.scope.$timeout(function () {
|
|
dataView.$isResequencing = true;
|
|
var saved = that.saveChanges(null, function() {
|
|
delete dataView.$isResequencing;
|
|
resetSelection();
|
|
}, true);
|
|
if (saved === false) {
|
|
delete dataView.$isResequencing;
|
|
}
|
|
});
|
|
};
|
|
|
|
Grid.prototype.onButtonClick = function(event, args) {
|
|
if ($(event.srcElement || event.target).is('.readonly') || this._buttonClickRunning) {
|
|
event.stopImmediatePropagation();
|
|
return false;
|
|
}
|
|
|
|
var grid = this.grid;
|
|
var data = this.scope.dataView;
|
|
var cols = this.getColumn(args.cell);
|
|
var field = (cols || {}).descriptor || {};
|
|
|
|
// set selection
|
|
grid.setSelectedRows([args.row]);
|
|
grid.setActiveCell(args.row, args.cell);
|
|
|
|
if (field.handler) {
|
|
this._buttonClickRunning = true;
|
|
|
|
var handlerScope = this.scope.handler;
|
|
var model = handlerScope._model;
|
|
var record = data.getItem(args.row) || {};
|
|
var that = this;
|
|
|
|
// defer record access so that any pending changes are applied
|
|
Object.defineProperty(field.handler.scope, 'record', {
|
|
enumerable: true,
|
|
configurable: true,
|
|
get: function () {
|
|
return data.getItem(args.row) || {};
|
|
}
|
|
});
|
|
|
|
if(field.prompt) {
|
|
field.handler.prompt = field.prompt;
|
|
}
|
|
field.handler.scope.getContext = function() {
|
|
var context = _.extend({
|
|
_model: model
|
|
}, record);
|
|
var current = handlerScope.dataView.getItem(_.first(handlerScope.selection));
|
|
if (handlerScope.field && handlerScope.field.target) {
|
|
context._parent = handlerScope.getContext();
|
|
}
|
|
if (context.id === 0) {
|
|
context.id = null;
|
|
}
|
|
if (current && current.id > 0 && (!context.id || context.id < 0)) {
|
|
context.id = current.id;
|
|
}
|
|
return context;
|
|
};
|
|
|
|
var old = _.extend({}, record);
|
|
|
|
field.handler.onClick().then(function(res){
|
|
delete that._buttonClickRunning;
|
|
|
|
var current = field.handler.scope.record;
|
|
if (handlerScope.setValue && !handlerScope._dataSource.equals(old, current)) {
|
|
current.version = current.version === undefined ? current.$version : current.version;
|
|
handlerScope.setValue(data.getItems(), true);
|
|
}
|
|
|
|
grid.invalidateRows([args.row]);
|
|
grid.render();
|
|
let active = $(".ui-widget-content.active");
|
|
|
|
if(field.handler.action == "action-purchase-order-supplier-line-method-refuse"){
|
|
active.removeClass("hilite-info-text hilite-success-text");
|
|
active.addClass("hilite-warning-text");
|
|
active.children(".l11").text("Non retenu")
|
|
active.children(".l13").text(new Date().toLocaleDateString("fr-FR"))
|
|
}else if(field.handler.action == "action-purchase-order-supplier-line-method-accept"){
|
|
active.removeClass("hilite-info-text hilite-warning-text");
|
|
active.addClass("hilite-success-text");
|
|
active.children(".l11").text("Accepté")
|
|
active.children(".l13").text(new Date().toLocaleDateString("fr-FR"))
|
|
}
|
|
|
|
|
|
}, function () {
|
|
delete that._buttonClickRunning;
|
|
});
|
|
}
|
|
};
|
|
|
|
Grid.prototype.onItemClick = function(event, args) {
|
|
|
|
var that = this;
|
|
var waitCallback = function (done) {
|
|
setTimeout(function () {
|
|
that.handler.waitForActions(function () {
|
|
that.__onItemClick(event, args);
|
|
if (done) {
|
|
done();
|
|
}
|
|
}, 100);
|
|
}, 100);
|
|
};
|
|
|
|
var lock = this.grid.getEditorLock();
|
|
if (lock.isActive()) {
|
|
lock.commitCurrentEdit();
|
|
if (this.editorScope &&
|
|
this.editorScope.$lastEditor &&
|
|
this.editorScope.$lastEditor.shouldWait()) {
|
|
return waitCallback;
|
|
}
|
|
}
|
|
|
|
// prevent edit if some action is still in progress
|
|
if (this.isDirty() && axelor.blockUI()) {
|
|
return waitCallback;
|
|
}
|
|
|
|
return this.__onItemClick(event, args);
|
|
};
|
|
|
|
Grid.prototype.__onItemClick = function(event, args) {
|
|
|
|
// prevent edit if some action is still in progress
|
|
if (this.isDirty() && axelor.blockUI()) {
|
|
return;
|
|
}
|
|
|
|
var source = $(event.target);
|
|
if (source.is('img.slick-img-button,i.slick-icon-button')) {
|
|
return this.onButtonClick(event, args);
|
|
}
|
|
|
|
// checkbox column
|
|
if (this.scope.selector && args.cell === 0) {
|
|
return false;
|
|
}
|
|
|
|
//XXX: hack to show popup grid (selector and editable in conflict?)
|
|
if (this.scope.selector && this.editable) {
|
|
var col = this.grid.getColumns()[args.cell] || {},
|
|
field = col.descriptor || {};
|
|
if (col.forEdit !== false &&
|
|
(field.type === 'one-to-many' || field.type === 'many-to-many' || this.scope.selector === 'checkbox')) {
|
|
this.grid.setActiveCell(args.row, args.cell);
|
|
this.grid.editActiveCell();
|
|
event.preventDefault();
|
|
event.stopImmediatePropagation();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!this.scope.selector && this.canEdit()) {
|
|
return this.grid.setActiveCell();
|
|
}
|
|
if (this.handler.onItemClick) {
|
|
this.handler.onItemClick(event, args);
|
|
}
|
|
};
|
|
|
|
Grid.prototype.onItemDblClick = function(event, args) {
|
|
|
|
if ($(event.srcElement || event.target).is('img.slick-img-button,i.slick-icon-button')) {
|
|
return this.onButtonClick(event, args);
|
|
}
|
|
|
|
var col = this.grid.getColumns()[args.cell];
|
|
if (col.id === '_edit_column') return;
|
|
var item = this.grid.getDataItem(args.row) || {};
|
|
if (item.__group || item.__groupTotals) {
|
|
return;
|
|
}
|
|
if (!this.handler.field && this.canSave())
|
|
return;
|
|
|
|
var selected = this.grid.getSelectedRows() || [];
|
|
if (selected.length === 0) {
|
|
this.grid.setSelectedRows([args.row]);
|
|
}
|
|
|
|
if (this.handler.onItemDblClick)
|
|
this.handler.onItemDblClick(event, args);
|
|
event.stopImmediatePropagation();
|
|
};
|
|
|
|
Grid.prototype.onRowCountChanged = function(event, args) {
|
|
this.grid.updateRowCount();
|
|
this.grid.render();
|
|
};
|
|
|
|
Grid.prototype.onRowsChanged = function(event, args) {
|
|
var grid = this.grid,
|
|
data = this.scope.dataView,
|
|
forEdit = this.editorForEdit;
|
|
|
|
if (!this.isDirty()) {
|
|
this.clearDirty();
|
|
}
|
|
grid.invalidateRows(args.rows);
|
|
grid.render();
|
|
};
|
|
|
|
Grid.prototype.groupBy = function(names) {
|
|
|
|
var grid = this.grid,
|
|
data = this.scope.dataView,
|
|
cols = this.grid.getColumns();
|
|
|
|
var aggregators = _.map(cols, function(col) {
|
|
var field = col.descriptor;
|
|
if (!field) return null;
|
|
if (field.aggregate === "sum") {
|
|
return new Slick.Data.Aggregators.Sum(field.name);
|
|
}
|
|
if (field.aggregate === "avg") {
|
|
return new Slick.Data.Aggregators.Avg(field.name);
|
|
}
|
|
if (field.aggregate === "min") {
|
|
return new Slick.Data.Aggregators.Min(field.name);
|
|
}
|
|
if (field.aggregate === "max") {
|
|
return new Slick.Data.Aggregators.Max(field.name);
|
|
}
|
|
});
|
|
|
|
aggregators = _.compact(aggregators);
|
|
|
|
var all = names;
|
|
|
|
if (_.isString(all)) {
|
|
all = all.split(/\s*,\s*/);
|
|
}
|
|
|
|
var fields = _.compact(_.pluck(this.cols, 'descriptor'));
|
|
|
|
var grouping = _.map(all, function(name) {
|
|
var that = this;
|
|
var field = _.findWhere(fields, { name: name });
|
|
return {
|
|
getter: function(item) {
|
|
var value = item[name];
|
|
var formatter = Formatters[field.selection ? 'selection' : field.type];
|
|
if (field.jsonPath && field.jsonField) {
|
|
var jsonValue = item[field.jsonField];
|
|
if (jsonValue) {
|
|
jsonValue = angular.fromJson(jsonValue);
|
|
value = jsonValue[field.jsonPath];
|
|
}
|
|
}
|
|
return (formatter ? formatter(field, value, item, grid) : value) || _t('N/A');
|
|
},
|
|
formatter: function(g) {
|
|
var title = field.title + ": " + g.value;
|
|
return '<span class="slick-group-text">' + title + '</span>' + ' ' +
|
|
'<span class="slick-group-count">' + _t("({0} items)", g.count) + '</span>';
|
|
},
|
|
aggregators: aggregators,
|
|
aggregateCollapsed: false
|
|
};
|
|
}, this);
|
|
|
|
this._groups = all;
|
|
data.setGrouping(grouping);
|
|
};
|
|
|
|
function EditIconColumn(options) {
|
|
|
|
var _grid;
|
|
var _self = this;
|
|
var _handler = new Slick.EventHandler();
|
|
|
|
var _opts = _.extend({
|
|
onClick: angular.noop
|
|
}, options);
|
|
|
|
function init(grid) {
|
|
_grid = grid;
|
|
_handler.subscribe(grid.onClick, handleClick);
|
|
}
|
|
|
|
function handleClick(e, args) {
|
|
if (_grid.getColumns()[args.cell].id !== '_edit_column' || !$(e.target).is("i")) {
|
|
return;
|
|
}
|
|
return _opts.onClick(e, args);
|
|
}
|
|
|
|
function destroy() {
|
|
_handler.unsubscribeAll();
|
|
}
|
|
|
|
function editFormatter(row, cell, value, columnDef, dataContext) {
|
|
if (!dataContext || !dataContext.id) return '<i class="fa fa-minus"></i>';
|
|
return '<i class="fa fa-pencil"></i>';
|
|
}
|
|
|
|
function getColumnDefinition() {
|
|
return {
|
|
id : '_edit_column',
|
|
name : "<span class='slick-column-name'> </span>",
|
|
field : "edit",
|
|
width : 24,
|
|
resizable : false,
|
|
sortable : false,
|
|
cssClass: 'edit-icon',
|
|
formatter : editFormatter
|
|
};
|
|
}
|
|
|
|
$.extend(this, {
|
|
"init": init,
|
|
"destroy": destroy,
|
|
|
|
"getColumnDefinition": getColumnDefinition
|
|
});
|
|
}
|
|
|
|
ui.directive('uiSlickEditors', function() {
|
|
|
|
return {
|
|
restrict: 'EA',
|
|
replace: true,
|
|
controller: ['$scope', '$element', 'DataSource', 'ViewService', function($scope, $element, DataSource, ViewService) {
|
|
ui.ViewCtrl($scope, DataSource, ViewService);
|
|
ui.FormViewCtrl.call(this, $scope, $element);
|
|
$scope.setEditable();
|
|
$scope.onShow = function(viewPromise) {
|
|
|
|
};
|
|
|
|
var _getContext = $scope.getContext;
|
|
$scope.getContext = function() {
|
|
var context = _getContext();
|
|
var handler = $scope.handler || {};
|
|
var current = handler.dataView.getItem(_.first(handler.selection));
|
|
if (context && handler.field && handler.field.target) {
|
|
context._parent = handler.getContext();
|
|
}
|
|
if (context.id === 0) {
|
|
context.id = null;
|
|
}
|
|
if (current && current.id > 0 && (!context.id || context.id < 0)) {
|
|
context.id = current.id;
|
|
}
|
|
return context;
|
|
};
|
|
|
|
$scope.show();
|
|
}],
|
|
link: function(scope, element, attrs) {
|
|
|
|
var grid = null;
|
|
scope.canWatch = function () {
|
|
return true;
|
|
};
|
|
},
|
|
template: '<div ui-view-form x-handler="true" ui-watch-if="canWatch()"></div>'
|
|
};
|
|
});
|
|
|
|
ui.directive('uiSlickGrid', ['ViewService', 'ActionService', function(ViewService, ActionService) {
|
|
|
|
var types = {
|
|
'one-to-many' : 'one-to-many-inline',
|
|
'many-to-many' : 'many-to-many-inline'
|
|
};
|
|
|
|
function makeForm(scope, model, items, fields, forEdit, onNew) {
|
|
|
|
var _fields = fields || {},
|
|
_items = [];
|
|
|
|
_.each(items, function(item) {
|
|
var field = _fields[item.name] || item,
|
|
type = types[field.type];
|
|
|
|
// force lite html widget
|
|
if (item.widget && item.widget.toLowerCase() === 'html') {
|
|
item.lite = true;
|
|
}
|
|
|
|
if (!type && !forEdit) {
|
|
item.forEdit = false;
|
|
return;
|
|
}
|
|
|
|
var params = _.extend({}, item, { showTitle: false });
|
|
if (type) {
|
|
params.widget = type;
|
|
params.canEdit = forEdit;
|
|
}
|
|
|
|
_items.push(params);
|
|
});
|
|
|
|
var schema = {
|
|
cols: _items.length,
|
|
colWidths: '=',
|
|
viewType : 'form',
|
|
onNew: onNew,
|
|
items: _items
|
|
};
|
|
|
|
scope._viewParams = {
|
|
model: model,
|
|
views: [schema]
|
|
};
|
|
|
|
return ViewService.compile('<div ui-slick-editors></div>')(scope);
|
|
}
|
|
|
|
return {
|
|
restrict: 'EA',
|
|
replace: false,
|
|
scope: {
|
|
'view' : '=',
|
|
'dataView' : '=',
|
|
'handler' : '=',
|
|
'selector' : '@',
|
|
'editable' : '@',
|
|
'noFilter' : '@',
|
|
'onInit' : '&',
|
|
'onBeforeSave' : '&',
|
|
'onAfterSave' : '&'
|
|
},
|
|
link: function(scope, element, attrs) {
|
|
|
|
var grid = null,
|
|
schema = null,
|
|
handler = scope.handler,
|
|
initialized = false;
|
|
|
|
function doInit() {
|
|
if (initialized || !schema || !scope.dataView) return;
|
|
initialized = true;
|
|
|
|
if (attrs.editable === "false") {
|
|
schema.editable = false;
|
|
}
|
|
scope.selector = attrs.selector;
|
|
scope.noFilter = attrs.noFilter;
|
|
|
|
if (axelor.config["view.grid.selection"] === "checkbox" && !scope.selector) {
|
|
scope.selector = "checkbox";
|
|
}
|
|
|
|
var forEdit = schema.editable || false,
|
|
canEdit = schema.editable || false,
|
|
hasMulti = false;
|
|
|
|
hasMulti = _.find(schema.items, function(item) {
|
|
var field = handler.fields[item.name] || {};
|
|
return _.str.endsWith(field.type, '-many');
|
|
});
|
|
|
|
if (hasMulti) {
|
|
canEdit = true;
|
|
}
|
|
|
|
var form = null,
|
|
formScope = null;
|
|
|
|
if (handler.field && handler.field.onNew) {
|
|
schema.onNew = handler.field.onNew;
|
|
}
|
|
|
|
if (canEdit) {
|
|
formScope = scope.$new();
|
|
form = makeForm(formScope, handler._model, schema.items, handler.fields, forEdit, schema.onNew);
|
|
}
|
|
|
|
if (forEdit) {
|
|
element.addClass('slickgrid-editable');
|
|
}
|
|
|
|
grid = new Grid(scope, element, attrs, ViewService, ActionService);
|
|
if (form) {
|
|
formScope.grid = grid;
|
|
grid.setEditors(form, formScope, forEdit);
|
|
}
|
|
|
|
if (!handler._isPopup && schema.inlineHelp && !axelor.config["user.noHelp"]) {
|
|
addHelp(schema.inlineHelp);
|
|
}
|
|
|
|
// handle pending attrs change on dashlets
|
|
if (handler.$$pendingAttrs) {
|
|
_.each(handler.$$pendingAttrs, function (itemAttrs, itemName) {
|
|
_.each(itemAttrs, function (attrValue, attrName) {
|
|
switch (attrName) {
|
|
case 'hidden':
|
|
grid.showColumn(itemName, !attrValue);
|
|
break;
|
|
case 'title':
|
|
grid.setColumnTitle(itemName, attrValue);
|
|
break;
|
|
}
|
|
});
|
|
});
|
|
handler.$$pendingAttrs = undefined;
|
|
}
|
|
|
|
grid.adjustSize();
|
|
}
|
|
|
|
function addHelp(helpItem) {
|
|
|
|
var helpElem = $('<div>')
|
|
.css('visibility', 'hidden')
|
|
.addClass('help-item alert')
|
|
.html(helpItem.text);
|
|
|
|
var css = helpItem.css || '';
|
|
if (css.indexOf('alert-') === -1) {
|
|
css = (css + ' alert-info').trim();
|
|
}
|
|
|
|
helpElem.addClass(css);
|
|
helpElem.appendTo('body');
|
|
|
|
element.css('top', helpElem.height() + 36 + 40);
|
|
|
|
setTimeout(function () {
|
|
element.before(helpElem.css('visibility', ''));
|
|
});
|
|
}
|
|
|
|
element.addClass('slickgrid').hide();
|
|
var unwatch = scope.$watch("view.loaded", function gridSchemaWatch(viewLoaded) {
|
|
if (!viewLoaded || !scope.dataView) {
|
|
return;
|
|
}
|
|
unwatch();
|
|
schema = scope.view;
|
|
|
|
var field = handler.field || {};
|
|
if (field.canMove !== undefined) {
|
|
schema.canMove = field.canMove;
|
|
}
|
|
if (field.editable !== undefined) {
|
|
schema.editable = field.editable;
|
|
}
|
|
schema.rowHeight = field.rowHeight || schema.rowHeight;
|
|
schema.orderBy = field.orderBy || schema.orderBy;
|
|
schema.groupBy = field.groupBy || schema.groupBy;
|
|
schema.groupBy = (schema.editable || schema.groupBy === "false") ? false : schema.groupBy;
|
|
schema.canMassUpdate = !!_.find(schema.items, function (item) { return item.massUpdate; });
|
|
|
|
element.show();
|
|
doInit();
|
|
});
|
|
}
|
|
};
|
|
}]);
|
|
|
|
})();
|