Files
ERP/sophal/js/view/view.kanban.js

662 lines
18 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() {
"use strict";
var ui = angular.module('axelor.ui');
function BaseCardsCtrl(type, $scope, $element) {
ui.DSViewCtrl(type, $scope, $element);
$scope.getRouteOptions = function() {
return {
mode: type,
args: []
};
};
$scope.setRouteOptions = function(options) {
if (!$scope.isNested) {
$scope.updateRoute();
}
};
var ds = $scope._dataSource;
var initialized = false;
$scope.onShow = function (viewPromise) {
if (initialized) {
return $scope.onRefresh();
}
initialized = true;
viewPromise.then(function (meta) {
$scope.parse(meta.fields, meta.view);
});
};
$scope.parse = function (fields, view) {
};
$scope.onNew = function () {
ds._page.index = -1;
$scope.switchTo('form', function (formScope) {
formScope.edit(null);
formScope.setEditable();
formScope.$broadcast("on:new");
});
};
$scope.onRefresh = function () {
return $scope.filter({});
};
function update(records) {
$scope.records = records;
}
$scope.handleEmpty = function () {
};
$scope.filter = function(options) {
var view = $scope.schema;
var opts = {
fields: _.pluck($scope.fields, 'name')
};
var handleEmpty = $scope.handleEmpty.bind($scope);
if (options.criteria || options._domains) {
opts.filter = options;
}
if (options.archived !== undefined) {
opts.archived = options.archived;
}
if (view.orderBy) {
opts.sortBy = view.orderBy.split(',');
}
var promise = ds.search(opts);
promise.then(handleEmpty, handleEmpty);
return promise.success(update).then(function () {
$scope.handleEmpty();
return ds.fixPage();
});
};
$scope.pagerText = function() {
var page = ds._page;
if (page && page.from !== undefined) {
if (page.total === 0) return null;
return _t("{0} to {1} of {2}", page.from + 1, page.to, page.total);
}
};
$scope.onNext = function() {
var fields = _.pluck($scope.fields, 'name');
return ds.next(fields).success(update);
};
$scope.onPrev = function() {
var fields = _.pluck($scope.fields, 'name');
return ds.prev(fields).success(update);
};
$scope.getActionData = function(context) {
return _.extend({
_domain: ds._lastDomain,
_domainContext: _.extend({}, ds._lastContext, context),
_archived: ds._showArchived
}, ds._filter);
};
}
ui.controller("CardsCtrl", ['$scope', '$element', function CardsCtrl($scope, $element) {
BaseCardsCtrl.call(this, 'cards', $scope, $element);
$scope.viewItems = {};
$scope.parse = function (fields, view) {
var viewItems = {};
_.each(view.items, function (item) {
if (item.name) {
viewItems[item.name] = _.extend({}, item, fields[item.name], item.widgetAttrs);
}
});
$scope.viewItems = viewItems;
$scope.onRefresh();
$scope.waitForActions(axelor.$adjustSize);
};
$scope.onExport = function (full) {
var fields = full ? [] : _.pluck($scope.viewItems, 'name');
return $scope._dataSource.export_(fields).success(function(res) {
var fileName = res.fileName;
var filePath = 'ws/rest/' + $scope._model + '/export/' + fileName;
ui.download(filePath, fileName);
});
};
}]);
ui.controller("KanbanCtrl", ['$scope', '$element', 'ActionService', function KanbanCtrl($scope, $element, ActionService) {
BaseCardsCtrl.call(this, 'kanban', $scope, $element);
$scope.parse = function (fields, view) {
var params = $scope._viewParams.params || {};
var hideCols = (params['kanban-hide-columns'] || '').split(',');
var columnBy = fields[view.columnBy] || {};
var columns = _.filter(columnBy.selectionList, function (item) {
return hideCols.indexOf(item.value) === -1;
});
var first = _.first(columns);
if (view.onNew) {
first.canCreate = true;
}
var sequenceBy = fields[view.sequenceBy] || {};
if (["integer", "long"].indexOf(sequenceBy.type) === -1 || ["id", "version"].indexOf(sequenceBy.name) > -1) {
throw new Error("Invalid sequenceBy field in view: " + view.name);
}
$scope.sortableOptions.disabled = !view.draggable || !$scope.hasPermission('write');
$scope.columns = columns;
$scope.colWidth = params['kanban-column-width'];
};
$scope.move = function (record, to, next, prev) {
if(!record) {
return;
}
var ds = $scope._dataSource._new($scope._model);
var view = $scope.schema;
var rec = _.pick(record, "id", "version", view.sequenceBy);
var prv = prev ? _.pick(prev, "id", "version", view.sequenceBy) : null;
var nxt = next ? _.pick(next, "id", "version", view.sequenceBy) : null;
// update columnBy
rec[view.columnBy] = to;
// update sequenceBy
var all = _.compact([prv, rec, nxt]);
var offset = _.min(_.pluck(all, view.sequenceBy)) || 0;
_.each(all, function (item, i) {
item[view.sequenceBy] = offset + i;
});
function doSave() {
return ds.saveAll(all).success(function (records) {
_.each(_.compact([prev, rec, next]), function (item) {
_.extend(item, _.pick(ds.get(item.id), "version", view.sequenceBy));
});
_.extend(record, rec);
}).error(function () {
$scope.onRefresh();
});
}
if (view.onMove) {
var actScope = $scope.$new();
actScope.record = rec;
actScope.getContext = function () {
return _.extend({}, $scope._context, rec);
};
return ActionService.handler(actScope, $(), { action: view.onMove }).handle().then(function () {
return doSave();
}, function (err) {
$scope.onRefresh();
});
}
return doSave();
};
$scope.onRefresh = function () {
$scope.$broadcast("on:refresh");
};
$scope.filter = function(searchFilter) {
var options = {};
if (searchFilter.criteria || searchFilter._domains) {
options = {
filter: searchFilter
};
if (searchFilter.archived !== undefined) {
options.archived = searchFilter.archived;
}
$scope.$broadcast("on:filter", options);
}
};
$scope.sortableOptions = {
connectWith: ".kanban-card-list",
items: ".kanban-card",
tolerance: "pointer",
helper: "clone",
stop: function (event, ui) {
$scope.$broadcast('on:re-attach-click');
var item = ui.item;
var sortable = item.sortable;
var source = sortable.source.scope();
var target = (sortable.droptarget || $(this)).scope();
var next = item.next().scope();
var prev = item.prev().scope();
if (next) next = next.record;
if (prev) prev = prev.record;
var index = sortable.dropindex;
if (source === target && sortable.index === index) {
return;
}
$scope.move(target.records[index], target.column.value, next, prev);
$scope.$applyAsync();
}
};
}]);
ui.directive('uiKanbanColumn', ["ActionService", function (ActionService) {
return {
scope: true,
link: function (scope, element, attrs) {
var ds = scope._dataSource._new(scope._model);
var view = scope.schema;
var elemMore = element.children(".kanban-more");
ds._context = _.extend({}, scope._dataSource._context);
ds._context[view.columnBy] = scope.column.value;
ds._page.limit = view.limit || 20;
var domain = "self." + view.columnBy + " = :" + view.columnBy;
ds._domain = scope._dataSource._domain ? scope._dataSource._domain + " AND " + domain : domain;
scope.records = [];
function handleEmpty() {
element.toggleClass('empty', scope.isEmpty());
}
function fetch(options) {
var opts = _.extend({
offset: 0,
sortBy: [view.sequenceBy],
fields: _.pluck(scope.fields, 'name')
}, options);
elemMore.hide();
var promise = ds.search(opts);
promise.success(function (records) {
scope.records = scope.records.concat(records);
elemMore.fadeIn('slow');
});
return promise.then(handleEmpty, handleEmpty);
}
scope.$watch('records.length', handleEmpty);
scope.hasMore = function () {
var page = ds._page;
var next = page.from + page.limit;
return next < page.total;
};
scope.isEmpty = function () {
return scope.records.length == 0;
};
scope.onMore = function () {
var page = ds._page;
var next = scope.records.length;
if (next < page.total) {
return fetch({
offset: next
});
}
};
var onNew = null;
scope.getContext = function () {
var ctx = _.extend({}, scope._context);
ctx._value = scope.newItem;
return ctx;
};
scope.newItem = null;
scope.onCreate = function () {
var rec = scope.record = {};
var view = scope.schema;
rec[view.columnBy] = scope.column.value;
if (onNew === null) {
onNew = ActionService.handler(scope, element, {
action: view.onNew
});
}
var ds = scope._dataSource;
var promise = onNew.handle();
promise.then(function () {
ds.save(scope.record).success(function (rec) {
scope.newItem = null;
scope.records.unshift(rec);
});
});
};
scope.onEdit = function (record, readonly) {
scope.switchTo('form', function (formScope) {
formScope.edit(record);
formScope.setEditable(!readonly && scope.hasPermission('write') && formScope.canEdit());
});
};
scope.onDelete = function (record) {
axelor.dialogs.confirm(_t("Do you really want to delete the selected record?"),
function(confirmed) {
if (!confirmed) {
return;
}
ds.removeAll([record]).success(function(records, page) {
var index = scope.records.indexOf(record);
scope.records.splice(index, 1);
});
});
};
scope.$on("on:refresh", function (e) {
scope.newItem = null;
scope.records.length = 0;
fetch();
});
scope.$on("on:filter", function (e, options) {
scope.newItem = null;
scope.records.length = 0;
return fetch(options);
});
element.on("click", ".kanban-card", function (e) {
var elem = $(e.target);
var selector = '[ng-click],[ui-action-click],button,a,.iswitch,.ibox,.kanban-card-menu';
if (elem.is(selector) || element.find(selector).has(elem).length) {
return;
}
var record = $(this).scope().record;
scope.onEdit(record, true);
scope.$applyAsync();
});
if (scope.colWidth) {
element.width(scope.colWidth);
}
setTimeout(function () {
element.find('[ui-sortable]').sortable("option", "appendTo", element.parent());
});
fetch();
}
};
}]);
ui.directive('uiCards', function () {
return function (scope, element, attrs) {
var onRefresh = scope.onRefresh;
scope.onRefresh = function () {
scope.records = null;
return onRefresh.apply(scope, arguments);
};
scope.onEdit = function (record, readonly) {
var ds = scope._dataSource;
var page = ds._page;
page.index = record ? ds._data.indexOf(record) : -1;
scope.switchTo('form', function (formScope) {
formScope.setEditable(!readonly && scope.hasPermission('write') && formScope.canEdit());
});
};
scope.onDelete = function (record) {
axelor.dialogs.confirm(_t("Do you really want to delete the selected record?"),
function(confirmed) {
if (!confirmed) {
return;
}
var ds = scope._dataSource;
ds.removeAll([record]).success(function() {
scope.onRefresh();
});
});
};
scope.isEmpty = function () {
return (scope.records||[]).length == 0;
};
scope.handleEmpty = function () {
element.toggleClass('empty', scope.isEmpty());
};
};
});
ui.directive('uiCard', ["$compile", function ($compile) {
return {
scope: true,
link: function (scope, element, attrs) {
var body = element.find(".kanban-card-body");
var record = scope.record;
var evalScope = axelor.$evalScope(scope);
evalScope.record = record;
evalScope.getContext = scope.getContext = function () {
var ctx = _.extend({}, scope._context, scope.record);
ctx._model = scope._model;
return ctx;
};
if (!record.$processed) {
element.hide();
}
function process(record) {
if (record.$processed) {
return record;
}
record.$processed = true;
for (var name in record) {
if (!record.hasOwnProperty(name) || name.indexOf('.') === -1) {
continue;
}
var nested = record;
var names = name.split('.');
var head = _.first(names, names.length - 1);
var last = _.last(names);
var i, n;
for (i = 0; i < head.length; i++) {
n = head[i];
nested = nested[n] || (nested[n] = {});
}
nested[last] = record[name];
}
return record;
}
evalScope.$watch("record", function cardRecordWatch(record) {
_.extend(evalScope, process(record));
}, true);
evalScope.$image = function (fieldName, imageName) {
return ui.formatters.$image(scope, fieldName, imageName);
};
evalScope.$fmt = function (fieldName) {
return ui.formatters.$fmt(scope, fieldName, evalScope[fieldName]);
};
var template = (scope.schema.template || "<span></span>").trim();
if (template.indexOf('<') !== 0) {
template = "<span>" + template + "</span>";
}
scope.hilite = null;
$compile(template)(evalScope).appendTo(body);
var hilites = scope.schema.hilites || [];
for (var i = 0; i < hilites.length; i++) {
var hilite = hilites[i];
if (axelor.$eval(evalScope, hilite.condition, scope.record)) {
scope.hilite = hilite;
break;
}
}
if (scope.schema.width) {
element.parent().css("width", scope.schema.width);
}
if (scope.schema.minWidth) {
element.parent().css("min-width", scope.schema.minWidth);
}
if (scope.schema.maxWidth) {
element.parent().css("max-width", scope.schema.maxWidth);
}
function onClick(e) {
var elem = $(e.target);
var selector = '[ng-click],[ui-action-click],button,a,.iswitch,.ibox,.kanban-card-menu';
if (elem.is(selector) || element.find(selector).has(elem).length) {
return;
}
var record = $(this).scope().record;
scope.onEdit(record, true);
scope.$applyAsync();
}
function attachClick() {
element.on('click', onClick);
}
attachClick();
scope.$on('on:re-attach-click', function () {
element.off('click', onClick);
setTimeout(attachClick, 100);
});
element.fadeIn("slow");
var summaryHandler;
var summaryPlacement;
var summary = body.find('.card-summary.popover');
var configureSummary = _.once(function configureSummary() {
element.popover({
placement: function (tip, el) {
summaryPlacement = setTimeout(function () {
$(tip).css('visibility', 'hidden').css('max-width', 400).position({
my: 'left',
at: 'right',
of: el,
using: function (pos, feedback) {
$(feedback.element.element)
.css(pos)
.css('visibility', '')
.removeClass('left right')
.addClass(feedback.horizontal === 'left' ? 'right' : 'left');
summaryPlacement = null;
}
});
});
},
container: 'body',
trigger: 'manual',
title: summary.attr('title'),
content: summary.html(),
html: true
});
});
function showSummary() {
configureSummary();
if (summaryPlacement) {
clearTimeout(summaryPlacement);
summaryPlacement = null;
}
summaryHandler = setTimeout(function () {
summaryHandler = null;
element.popover('show');
}, 500);
}
function hideSummary() {
if (summaryPlacement) {
clearTimeout(summaryPlacement);
summaryPlacement = null;
}
if (summaryHandler) {
clearTimeout(summaryHandler);
summaryHandler = null;
}
element.popover('hide');
}
if (summary.length > 0) {
element.on('mouseenter.summary', showSummary);
element.on('mouseleave.summary', hideSummary);
element.on('mousedown.summary', hideSummary);
}
function destroy() {
if (summaryHandler) {
clearTimeout(summaryHandler);
summaryHandler = null;
}
if (element) {
element.off('mouseenter.summary');
element.off('mouseleave.summary');
element.off('mousedown.summary');
element.popover('destroy');
element = null;
}
}
element.on('$destroy', destroy);
scope.$on('$destroy', destroy);
}
};
}]);
})();