/* * 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"); ui.ViewCtrl = ViewCtrl; ui.ViewCtrl.$inject = ['$scope', 'DataSource', 'ViewService']; function ViewCtrl($scope, DataSource, ViewService) { $scope._viewParams = $scope._viewParams || $scope.selectedTab; if (!$scope._viewParams) { throw "View parameters are not provided."; } var params = $scope._viewParams; $scope._views = ViewService.accept(params); $scope._viewType = params.viewType; if ($scope.$parent && $scope.$parent._model) { $scope._parentModel = $scope.$parent._model; } $scope._model = params.model; $scope._fields = {}; $scope._dataSource = null; $scope._domain = params.domain; $scope._context = params.context; if (params.model) { $scope._dataSource = DataSource.create(params.model, params); } $scope._defer = function() { return ViewService.defer(); }; $scope.loadView = function(viewType, viewName) { var view = $scope._views[viewType] || { type: viewType, name: viewName }; var ctx = $scope._context; if ($scope.getContext) { ctx = $scope.getContext(); } return ViewService.getMetaDef($scope._model, view, ctx); }; $scope.loadFields = function() { return ViewService.getFields($scope._model); }; $scope.updateRoute = function() { this.$emit("on:update-route"); }; $scope.getRouteOptions = function() { throw "Not Implemented."; }; $scope.setRouteOptions = function(options) { throw "Not Implemented."; }; var switchedTo = null; $scope.switchTo = function(viewType, /* optional */ callback) { var view = $scope._views[viewType]; if (!view) { return; } var promise = view.deferred.promise; promise.then(function(viewScope){ if (!viewScope || switchedTo === viewType) { return; } switchedTo = viewType; $scope._viewTypeLast = $scope._viewType === 'form' ? $scope._viewTypeLast : $scope._viewType; $scope._viewType = viewType; $scope._viewParams.viewType = viewType; //XXX: remove $scope._viewParams.$viewScope = viewScope; viewScope.show(); if (viewScope.updateRoute) { viewScope.updateRoute(); } if (callback) { callback(viewScope); } }); }; if (!params.action) { return; } // hide toolbar button titles $scope.tbTitleHide = !axelor.config['view.toolbar.titles']; function switchAndEdit(id, readonly) { $scope.switchTo('form', function(scope) { scope._viewPromise.then(function() { scope.doRead(id).success(function(record) { scope.edit(record); scope.setEditable(!readonly); }); }); }); } // show single or default record if specified var context = params.context || {}; if (context._showSingle || context._showRecord) { var ds = $scope._dataSource; var forceEdit = (params.params||{}).forceEdit === true; if (context._showRecord > 0) { params.viewType = "form"; return $scope.switchTo('form'); } return ds.search({ offset: 0, limit: 2, fields: ["id"] }).success(function(records, page){ if (page.total === 1 && records.length === 1) { return switchAndEdit(records[0].id, !forceEdit); } return $scope.switchTo($scope._viewType || 'grid'); }); } // switch to the the current viewType $scope.switchTo($scope._viewType || 'grid'); } /** * Base controller for DataSource views. This controller should not be used * directly but actual controller should inherit from it. * */ ui.DSViewCtrl = function DSViewCtrl(type, $scope, $element) { if (!type) { throw "No view type provided."; } if (!$scope._dataSource) { throw "DataSource is not provided."; } $scope._viewResolver = $scope._defer(); $scope._viewPromise = $scope._viewResolver.promise; var ds = $scope._dataSource; var view = $scope._views[type] || {}; var viewPromise = null; var hiddenButtons = {}; var params = $scope._viewParams; if (params.params && params.params.limit) { if (ds && ds._page) { ds._page.limit = +(params.params.limit) || ds._page.limit; } } $scope.fields = {}; $scope.fields_related = {}; $scope.schema = null; $scope.show = function() { if (!viewPromise) { viewPromise = $scope.loadView(type, view.name); viewPromise.then(function(meta){ var schema = meta.view; var fields = meta.fields || params.fields; var toolbar = []; _.each(schema.toolbar, function(button){ button.custom = true; if (/^(new|edit|save|delete|copy|cancel|back|refresh|search|export|log|files)$/.test(button.name)) { hiddenButtons[button.name] = button; button.custom = false; } toolbar.push(button); }); var forceTitle = params.forceTitle; if (forceTitle === undefined) { forceTitle = (params.params||{}).forceTitle; } if (!forceTitle && schema.title) { $scope.viewTitle = schema.title; } $scope.fields = fields; $scope.fields_related = meta.related; $scope.schema = schema; $scope.toolbar = toolbar; $scope.menubar = schema.menubar; $scope.toolbarAsMenu = _.isEmpty(toolbar) ? null : [{ icon: 'fa-wrench', isButton: true, items: _.map(toolbar, function (item) { return _.extend({}, item, { name: item.name, action: item.onClick, title: item.title || item.autoTitle || item.name }); }) }]; // watch on view.loaded to improve performance schema.loaded = true; }); } $scope.onShow(viewPromise); }; $scope.onShow = function(promise) { }; $scope.canNext = function() { return ds && ds.canNext(); }; $scope.canPrev = function() { return ds && ds.canPrev(); }; $scope.getPageSize = function() { var page = ds && ds._page; if (page) { return page.limit; } return 40; }; $scope.setPageSize = function(value) { var page = ds && ds._page, limit = Math.max(0, +value) || 40; if (page && page.limit != limit) { page.limit = limit; $scope.onRefresh(); } }; var can = (function (scope) { var fn = null; var perms = { 'new': 'create', 'copy': 'create', 'edit': 'write', 'save': 'write', 'delete': 'remove', 'archive': 'remove', 'export': 'export' }; var actions = { 'new': 'canNew', 'edit': 'canEdit', 'save': 'canSave', 'copy': 'canCopy', 'delete': 'canDelete', 'archive': 'canArchive', 'attach': 'canAttach' }; function attr(which) { if (fn === null && _.isFunction(scope.attr)) { fn = scope.attr; } return !fn || fn(which) !== false; } function perm(which) { return which === undefined || scope.hasPermission(which); } return function can(what) { return attr(actions[what]) && perm(perms[what]); }; })($scope); $scope.hasButton = function(name) { if (!can(name)) { return false; } if (_(hiddenButtons).has(name)) { var button = hiddenButtons[name]; if (button.isHidden) { return !button.isHidden(); } return !button.hidden; } return true; }; $scope.hasPermission = function(perm) { var view = $scope.schema; var defaultValue = arguments.length === 2 ? arguments[1] : true; if (!view || !view.perms) return defaultValue; var perms = view.perms; var permitted = perms[perm]; if (permitted === undefined) { return defaultValue; } return _.toBoolean(permitted); }; $scope.isPermitted = function(perm, record, callback) { var ds = this._dataSource; ds.isPermitted(perm, record).success(function(res){ var errors = res.errors; if (errors) { return axelor.dialogs.error(errors.read); } callback(); }); }; $scope.canShowToolbar = function() { var params = ($scope._viewParams || {}).params; if (params && params['show-toolbar'] === false) { return false; } return true; }; $scope.hasHelp = function() { var view = $scope.schema; return view ? view.helpLink : false; }; $scope.onShowHelp = function() { if ($scope.hasHelp()) { window.open($scope.schema.helpLink); } }; if (view.deferred) { view.deferred.resolve($scope); } $scope.$on('on:tab-reload', function(e, tab) { if ($scope === e.targetScope && $scope.onRefresh) { $scope.onRefresh(); } }); }; ui.directive('uiViewPane', function() { return { replace: true, controller: ['$scope', '$attrs', 'DataSource', 'ViewService', function ($scope, $attrs, DataSource, ViewService) { var params = $scope.$eval($attrs.uiViewPane); $scope._viewParams = params; ViewCtrl.call(this, $scope, DataSource, ViewService); $scope.viewList = []; $scope.viewType = null; var switchTo = $scope.switchTo; $scope.switchTo = function (type, callback) { var view = $scope._views[type]; if (view && $scope.viewList.indexOf(type) === -1) { $scope.viewList.push(type); } var viewScope = !$scope._isPopup && $scope.selectedTab && $scope.selectedTab.$viewScope; if (viewScope && viewScope.viewType === 'form' && viewScope.viewType !== type) { viewScope.$$resetForm(); } $scope.viewType = type; return switchTo(type, callback); }; $scope.$watch('selectedTab.viewType', function viewTypeWatch(type) { var params = $scope._viewParams; if (params && params.$viewScope !== ($scope.selectedTab || {}).$viewScope) { return; } if ($scope.viewType !== type && type) { $scope.switchTo(type); } }); $scope.viewTemplate = function (type) { var tname = "ui-template:" + type; var template = type; if (params.params && params.params[tname]) { template = params.params[tname]; } return 'partials/views/' + template + '.html'; }; var type = params.viewType || params.type; $scope.keepAttached = $scope._isPopup || (params.params||{}).popup || type === 'html'; $scope.switchTo(type); }], link: function(scope, element, attrs) { }, template: "
" + "
" + "
" }; }); ui.directive('uiViewPopup', function() { return { controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) { var params = $scope.$eval($attrs.uiViewPopup); $scope.tab = params; $scope._isPopup = true; $scope.onHotKey = function (e, action) { return false; }; var canClose = false; $scope.onOK = function () { $scope.closeTab($scope.tab, function() { canClose = true; $element.dialog('close'); }); }; $scope.onBeforeClose = function(e) { if (canClose) { return; } e.preventDefault(); e.stopPropagation(); $scope.onOK(); }; $scope.onPopupClose = function () { var tab = $scope.tab, params = tab.params || {}, parent = tab.$popupParent; while (parent && parent.$$destroyed && parent.tab) { parent = parent.tab.$popupParent; } if (parent && parent.reload && params.popup === "reload") { parent.reload(); } $scope.$applyAsync(); }; $scope.onPopupOK = function () { var viewScope = $scope._viewParams.$viewScope; if (!viewScope.onSave || (!viewScope.isDirty() && viewScope.id)) { return $scope.onOK(); } return viewScope.onSave({ fireOnLoad: false }).then(function(record, page) { viewScope.edit(record); viewScope.$timeout($scope.onOK.bind($scope)); }); }; params = $scope.tab.params || {}; if (params['popup-save'] === false) { $scope.onPopupOK = false; } }], link: function (scope, element, attrs) { scope.$watch('viewTitle', function viewTitleWatch(title) { scope._setTitle(title); }); scope.waitForActions(function () { if (scope._viewParams.viewType === 'html') { scope.viewTitle = scope.tabTitle(scope._viewParams); scope._doShow(); return; } var unwatch = scope.$watch("_viewParams.$viewScope.schema.loaded", function viewLoadedWatch(loaded) { if (!loaded) { return; } unwatch(); var viewScope = scope._viewParams.$viewScope; var viewPromise = viewScope._viewPromise; scope.viewTitle = scope.tabTitle(scope._viewParams); scope.$broadcast('grid:adjust-size', viewScope); scope._doShow(viewPromise); }); }); }, replace: true, template: '
' + '
' + '
' }; }); ui.directive('uiRecordPager', function(){ return { replace: true, link: function(scope, element, attrs) { var elText = element.find('.record-pager-text').show(), elChanger = element.find('.record-pager-change').hide(), elInput = elChanger.find('input'); scope.showText = attrs.uiRecordPager !== "no-text"; function updatePageSize() { var size = +(elInput.val()) || 0; if (scope.setPageSize && size > 0) { scope.setPageSize(size); } elText.add(elChanger).toggle(); } elText.click(function(e) { elText.add(elChanger).toggle(); elInput.zIndex(elInput.parent().zIndex() + 1).focus().select(); }); elInput.on('click', function () { elInput.zIndex(elInput.parent().zIndex() + 1).focus().select(); }); elChanger.on('click', 'button', function() { updatePageSize(); }); elChanger.keyup(function(e) { if(e.keyCode == 13) { // ENTER updatePageSize(); } }); }, template: '
'+ ''+ '{{pagerText()}}'+ ''+ ''+ ''+ ''+ ''+ '
'+ ''+ ''+ '
'+ '
' }; }); ui.directive('uiViewCustomize', ['NavService', function(NavService) { return { scope: true, link: function (scope, element, attrs) { scope.canShow = function () { if (!axelor.config['user.technical']) { return false; } var viewScope = (scope.selectedTab || {}).$viewScope; var view = viewScope && viewScope.schema; return view && (view.viewId || view.modelId); }; scope.hasViewID = function () { var viewScope = (scope.selectedTab || {}).$viewScope; var view = viewScope && viewScope.schema; return view && view.viewId; }; scope.hasModelID = function () { var viewScope = (scope.selectedTab || {}).$viewScope; var view = viewScope && viewScope.schema; return view && view.modelId; }; scope.hasActionID = function () { return (scope.selectedTab || {}).actionId; }; scope.onShowView = function () { var id = scope.hasViewID(); NavService.openTabByName("form::com.axelor.meta.db.MetaView", { mode: "edit", state: id }); scope.waitForActions(function () { var vs = (scope.selectedTab || {}).$viewScope; if (vs && vs.setEditable) { vs.setEditable(); } }); }; scope.onShowModel = function () { var id = scope.hasModelID(); NavService.openTabByName("form::com.axelor.meta.db.MetaModel", { mode: "edit", state: id }); }; scope.onShowAction = function () { var id = scope.hasActionID(); NavService.openTabByName("form::com.axelor.meta.db.MetaAction", { mode: "edit", state: id }); }; }, replace: true, template: "" }; }]); function viewSwitcher(scope, element, attrs) { var params = (scope._viewParams || scope.tab); var viewTypes = _.pluck(params.views, 'type'); if ((params.viewType || params.type) === 'dashboard') { element.hide(); return; } element.find("[x-view-type]").click(function(e) { if (this.disabled) { return; } var type = $(this).attr("x-view-type"); var vs = params.$viewScope || (scope.selectedTab || {}).$viewScope; var ds = vs._dataSource; var page = ds && ds._page; if (type === "form" && page) { if (page.index === -1) page.index = 0; } if ((scope.selectedTab || {}).viewType === 'grid') { var items = vs.getItems() || []; var index = _.first(vs.selection || []); if (index === undefined && items.length === 0 && vs.schema.canNew === false) { return; } if (index !== undefined) page.index = index; } vs.switchTo(type); vs.$applyAsync(); }).each(function() { var type = $(this).attr("x-view-type"); if (viewTypes.indexOf(type) === -1) { $(this).hide(); } }); var watchExpr = scope._viewParams ? '_viewType' : 'tab.viewType'; scope.$watch(watchExpr, function viewTypeWatch(type) { element.find("[x-view-type]").attr("disabled", false); element.find("[x-view-type][x-view-type=" + type + "]").attr("disabled", true); }); } ui.directive('uiViewSwitcher', function(){ return { scope: true, link: function(scope, element, attrs) { element.parents('.view-container:first').addClass('has-toolbar'); viewSwitcher(scope, element, attrs); }, replace: true, template: '
'+ '
'+ ''+ ''+ ''+ ''+ ''+ ''+ ''+ '
'+ '
' }; }); ui.directive('uiViewSwitcherMenu', function(){ return { scope: true, link: function(scope, element, attrs) { viewSwitcher(scope, element, attrs); }, replace: true, template: "" + "" + "" + "" }; }); ui.directive('uiHotKeys', function() { var keys = { 45: 'new', // insert 69: 'edit', // e 83: 'save', // s 68: 'delete', // d 82: 'refresh', // r 70: 'search', // f 71: 'select', // g 74: 'prev', // j 75: 'next', // n 77: 'focus-menu', // m 120: 'toggle-menu', // F9 81: 'close' // q }; return function(scope, element, attrs) { var loginWindow = $("#loginWindow"); $(document).on('keydown.axelor-keys', function (e) { if (loginWindow.is(":visible")) { return; } // disable backspace as back button if (e.which === 8 && e.target === document.body) { e.preventDefault(); return false; } var action = keys[e.which]; if (action === "toggle-menu") { $('#offcanvas-toggle a').click(); return false; } if (e.altKey || e.shiftKey || !e.ctrlKey) { return; } if (action === "focus-menu") { var activeMenu = $('.sidebar .nav-tree li.active'); if (activeMenu.length === 0) { activeMenu = $('.sidebar .nav-tree li:first'); } var navTree = activeMenu.parents('[nav-tree]:first'); if (navTree.length) { navTree.navtree('selectItem', activeMenu); } return false; } var tab = scope.selectedTab, dlg = $('[ui-editor-popup]:visible:last,[ui-view-popup]:visible:last,[ui-dms-popup]:visible:last').first(), vs = tab ? tab.$viewScope : null; if (dlg.length) { vs = dlg.scope(); } if (!vs || !keys.hasOwnProperty(e.which)) { return; } if (action === "close") { scope.closeTab(tab, function() { scope.$applyAsync(); }); return false; } if (action === "search") { var filterBox = $('.filter-box .search-query:visible'); if (filterBox.length) { filterBox.focus().select(); return false; } } if (_.isFunction(vs.onHotKey)) { return vs.onHotKey(e, action); } }); scope.$on('$destroy', function() { $(document).off('keydown.axelor-keys'); }); }; }); })();