First commit waiting for Budget Alert
This commit is contained in:
661
sophal/js/view/view.kanban.js
Normal file
661
sophal/js/view/view.kanban.js
Normal file
@ -0,0 +1,661 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user