/* * Axelor Business Solutions * * Copyright (C) 2005-2019 Axelor (). * * 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 . */ (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 || "").trim(); if (template.indexOf('<') !== 0) { template = "" + template + ""; } 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); } }; }]); })();