/* * 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 ds = angular.module('axelor.ds', ['ngResource']); var forEach = angular.forEach, extend = angular.extend, isArray = angular.isArray; ds.factory('MenuService', ['$http', function($http) { function get(parent) { return $http.get('ws/action/menu', { cache: true, params : { parent : parent } }); } function all() { return $http.get('ws/action/menu/all', { cache: true }); } function tags() { return $http.get('ws/action/menu/tags', { silent: true, transformRequest: [] }); } function action(name, options) { return $http.post('ws/action/' + name, { model : 'com.axelor.meta.db.MetaAction', data : options }); } return { get: get, all: all, tags: tags, action: action }; }]); ds.factory('TagService', ['$q', '$timeout', '$rootScope', 'MenuService', function($q, $timeout, $rootScope, MenuService) { var POLL_INTERVAL = 10000; var pollResult = {}; var pollPromise = null; var pollIdle = null; var listeners = []; function cancelPolling() { if (pollPromise) { $timeout.cancel(pollPromise); pollPromise = null; } if (pollIdle) { clearTimeout(pollIdle); pollIdle = null; } } function startPolling() { if (pollPromise === null) { findTags(); } } var starting = false; function findTags() { if (starting) { return; } if (pollPromise) { $timeout.cancel(pollPromise); } starting = true; MenuService.tags().success(function (res) { var data = _.first(res.data); var values = data.values; for (var i = 0; i < listeners.length; i++) { listeners[i](values); } pollPromise = $timeout(findTags, POLL_INTERVAL); if (pollIdle === null) { pollIdle = setTimeout(cancelPolling, POLL_INTERVAL * 2); } starting = false; }); } window.addEventListener("mousemove", startPolling, false); window.addEventListener("mousedown", startPolling, false); window.addEventListener("keypress", startPolling, false); window.addEventListener("DOMMouseScroll", startPolling, false); window.addEventListener("mousewheel", startPolling, false); window.addEventListener("touchmove", startPolling, false); window.addEventListener("MSPointerMove", startPolling, false); // start polling startPolling(); return { find: findTags, listen: function(listener) { listeners.push(listener); return function () { var i = listeners.indexOf(listener); if (i >= 0) { listeners.splice(i, 1); } return listener; }; } }; }]); ds.factory('ViewService', ['$http', '$q', '$cacheFactory', '$compile', function($http, $q, $cacheFactory, $compile) { var ViewService = function() { }; ViewService.prototype.accept = function(params) { var views = {}; forEach(params.views, function(view){ var type = view.type || view.viewType; params.viewType = params.viewType || type; views[type] = extend({}, view, { deferred: $q.defer() }); }); return views; }; ViewService.prototype.compile = function(template) { return $compile(template); }; ViewService.prototype.process = function(meta, view, parent) { var fields = {}; meta = meta || {}; view = view || {}; if (meta.jsonAttrs && view && view.items) { if (view.type === 'grid') { view.items = (function (items) { var button = _.findWhere(items, { type: 'button' }); var index = items.indexOf(button); if (index < 0) { index = items.length; } items.splice(index, 0, { type: 'field', name: 'attrs', jsonFields: meta.jsonAttrs }); return items; })(view.items); } if (view.type === 'form') { view.items.push({ type: 'panel', title: _t('Attributes'), itemSpan: 12, items: [{ type: 'field', name: 'attrs', jsonFields: meta.jsonAttrs }] }); } } view = processJsonForm(view); meta.fields = processFields(meta.fields); (function () { var helps = meta.helps = meta.helps || {}; var items = []; if (view.helpOverride && view.helpOverride.length) { helps = _.groupBy(view.helpOverride || [], 'type'); helps = meta.helps = _.object(_.map(helps, function(items, key) { return [key, _.reduce(items, function(memo, item) { memo[item.field] = item; return memo; }, {})]; })); if (helps.tooltip && helps.tooltip.__top__) { view.help = helps.tooltip.__top__.help; } } var help = helps.tooltip || {}; var placeholder = helps.placeholder || {}; var inline = helps.inline || {}; forEach(view.items, function (item) { if (help[item.name]) { item.help = help[item.name].help; } if (meta.view && meta.view.type === 'form') { if (placeholder[item.name]) { item.placeholder = placeholder[item.name].help; } if (inline[item.name] && !inline[item.name].used) { inline[item.name].used = true; items.push({ type: 'help', text: inline[item.name].help, css: inline[item.name].style, colSpan: 12 }); } } items.push(item); }); forEach(view.toolbar, function (item) { if (help[item.name]) { item.help = help[item.name].help; } }); if (items.length) { view.items = items; } })(); forEach(view.items || view.pages, function(item) { processWidget(item); processSelection(item); forEach(fields[item.name], function(value, key){ if (!item.hasOwnProperty(key)) { item[key] = value; } }); ["canNew", "canView", "canEdit", "canRemove", "canSelect"].forEach(function (name) { if (item[name] === "false" || item[name] === "true") { item[name] = item[name] === "true"; } }); if (item.items || item.pages) { ViewService.prototype.process(meta, item, view); } if (item.password) { item.widget = "password"; } if (item.jsonFields && item.widget !== 'json-raw') { var editor = { layout: view.type === 'panel-json' ? 'table' : undefined, flexbox: true, items: [], }; var panel = null; var panelTab = null; item.jsonFields.sort(function (x, y) { return x.sequence - y.sequence; }); item.jsonFields.forEach(function (field) { if (field.widgetAttrs) { field.widgetAttrs = angular.fromJson(field.widgetAttrs); processWidget(field); if (field.widgetAttrs.showTitle !== undefined) { field.showTitle = field.widgetAttrs.showTitle; } if (field.widgetAttrs.multiline) { field.type = 'text'; } if (field.widgetAttrs.targetName) { field.targetName = field.widgetAttrs.targetName; } } if (field.type === 'panel' || field.type === 'separator') { field.visibleInGrid = false; } if (field.type === 'panel') { panel = _.extend({}, field, { items: [] }); if ((field.widgetAttrs || {}).sidebar && parent) { panel.sidebar = true; parent.width = 'large'; } if ((field.widgetAttrs || {}).tab) { panelTab = panelTab || { type: 'panel-tabs', colSpan: 12, items: [] }; panelTab.items.push(panel); } else { editor.items.push(panel); } return; } if (field.type !== 'separator') { field.title = field.title || field.autoTitle; } var colSpan = (field.widgetAttrs||{}).colSpan || field.colSpan; if (field.type == 'one-to-many') { field.type = 'many-to-many'; field.canSelect = false; } if (field.type == 'separator' || (field.type == 'many-to-many' && !field.widget)) { field.showTitle = false; field.colSpan = colSpan || 12; } if (panel) { panel.items.push(field); } else { editor.items.push(field); } }); if (panelTab) { editor.items.push(panelTab); } item.widget = 'json-field'; item.editor = editor; if (!item.viewer) { item.editor.viewer = true; } } }); // include json fields in grid if (view.type === 'grid') { var items = []; _.each(view.items, function (item) { if (item.jsonFields) { _.each(item.jsonFields, function (field) { var type = field.type || 'text'; if (type.indexOf('-to-many') === -1 && field.visibleInGrid) { items.push(_.extend({}, field, { name: item.name + '.' + field.name })); } }); } else { items.push(item); } }); items = items.sort(function (x, y) { return x.columnSequence - y.columnSequence; }); view.items = items; } }; function processJsonForm(view) { if (view.type !== 'form') return view; if (view.model !== 'com.axelor.meta.db.MetaJsonRecord') return view; var panel = _.first(view.items) || {}; var jsonField = _.first(panel.items) || {}; var jsonFields = jsonField.jsonFields || []; var first = _.first(jsonFields) || {}; if (first.type === 'panel') { panel.type = 'panel-json'; if (first.widgetAttrs) { var attrs = angular.fromJson(first.widgetAttrs); view.width = view.width || attrs.width; } } return view; } function processFields(fields) { var result = {}; if (isArray(fields)) { forEach(fields, function(field){ field.type = _.chain(field.type || 'string').underscored().dasherize().value(); field.title = field.title || field.autoTitle; result[field.name] = field; // if nested field then make it readonly if (field.name.indexOf('.') > -1) { field.readonly = true; field.required = false; } processSelection(field); }); } else { result = fields || {}; } return result; } function processSelection(field) { _.each(field.selectionList, function (item) { if (_.isString(item.data)) { item.data = angular.fromJson(item.data); } }); } function processWidget(field) { var attrs = {}; _.each(field.widgetAttrs || {}, function (value, name) { if (value === "true") value = true; if (value === "false") value = false; if (value === "null") value = null; if (/^(-)?\d+$/.test(value)) value = +(value); attrs[_.str.camelize(name)] = value; }); if (field.serverType) { field.serverType = _.chain(field.serverType).underscored().dasherize().value(); } field.widgetAttrs = attrs; } function useIncluded(view) { function useMenubar(menubar) { if (!menubar) return; var my = view.menubar || menubar; if (my !== menubar && menubar) { my = my.concat(menubar); } view.menubar = my; } function useToolbar(toolbar) { if (!toolbar) return; var my = view.toolbar || toolbar; if (my !== toolbar) { my = my.concat(toolbar); } view.toolbar = my; } function useItems(view) { return useIncluded(view); } var items = []; _.each(view.items, function(item) { if (item.type === "include") { if (item.view) { items = items.concat(useItems(item.view)); useMenubar(item.view.menubar); useToolbar(item.view.toolbar); } } else { items.push(item); } }); return items; } function findFields(view, res) { var result = res || { fields: [], related: {} }; var items = result.fields; var fields = view.items || view.pages; if (!fields) return items; if (view.items && !view._included) { view._included = true; fields = view.items = useIncluded(view); } function acceptEditor(item) { var collect = items; var editor = item.editor; if (item.target) { collect = result.related[item.name] || (result.related[item.name] = []); } if (editor.fields) { editor.fields = processFields(editor.fields); } var acceptItems = function (items) { _.each(items, function (child) { if (child.name && collect.indexOf(child.name) === -1 && child.type === 'field') { collect.push(child.name); } else if (child.type === 'panel') { acceptItems(child.items); } if (/RefSelect|ref-select/.test(child.widget)) { collect.push(child.related); } if (child.depends) { child.depends.split(/\s*,\s*/).forEach(function (name) { collect.push(name); }); } }); }; acceptItems(editor.items); } function acceptViewer(item) { var collect = items; var viewer = item.viewer; if (item.target) { collect = result.related[item.name] || (result.related[item.name] = []); } _.each(viewer.fields, function (item) { collect.push(item.name); }); if (viewer.fields) { viewer.fields = processFields(viewer.fields); } } _.each(fields, function(item) { if (item.editor) acceptEditor(item); if (item.viewer) acceptViewer(item); if (item.type === 'panel-related') { items.push(item.name); } else if (item.items || item.pages) { findFields(item, result); } else if (item.type === 'field') { items.push(item.name); } }); if (view.type === "calendar") { items.push(view.eventStart); items.push(view.eventStop); items.push(view.colorBy); } if (view.type === "kanban") { items.push(view.columnBy); items.push(view.sequenceBy); } if (view.type === "gantt") { items.push(view.taskUser); } return result; } var viewCache = $cacheFactory("viewCache", { capacity: 1000 }); function createStore(prefix) { var toKey = function (name) { return prefix + ':' + axelor.config['user.id'] + ':' + name; }; return { get: function (name) { return new $q(function (resolve) { resolve(angular.copy(viewCache.get(toKey(name)))); }); }, set: function (name, value) { if (value) { return new $q(function (resolve) { var val = viewCache.put(toKey(name), angular.copy(value)); resolve(val); }); } return $q.resolve(value); } }; } var PENDING_REQUESTS = {}; var FIELDS = createStore('f'); var VIEWS = createStore('v'); var PERMS = createStore('p'); ViewService.prototype.getMetaDef = function(model, view, context) { var self = this; var deferred = $q.defer(); var promise = deferred.promise; promise.success = function(fn) { promise.then(function(res) { fn(res.fields, res.view); }); return promise; }; function process(data) { data.view.perms = data.view.perms || data.perms; self.process(data, data.view); if (data.perms && data.perms.write === false) { data.view.editable = false; } return data; } function updateFields(fetched) { return FIELDS.get(model).then(function (current) { current = current || {}; if (current !== fetched.fields) { _.extend(current, _.object(_.pluck(fetched.fields, 'name'), fetched.fields)); } return $q.all([FIELDS.set(model, current), PERMS.set(model, fetched.perms)]); }); } function fetchFields(data) { var fields_data = findFields(data.view); var fields = _.unique(_.compact(fields_data.fields.sort())); data.related = fields_data.related; if (_.isArray(data.fields) && data.fields.length > 0) { updateFields(data).then(function () { deferred.resolve(process(data)); }); return promise; } if (!model || _.isEmpty(fields)) { deferred.resolve(data); return promise; } function resolve(fetched) { return updateFields(fetched).then(function (res) { var current = res[0]; var perms = res[1]; var result = _.extend({}, fetched, { fields: _.map(fields, function(n) { return current[n]; }), perms: perms }); result.view = data.view || view; result.fields = _.compact(result.fields); result.related = data.related; result = process(result); deferred.resolve(result); return promise; }); } $q.all([FIELDS.get(model), PERMS.get(model)]).then(function (res) { var fetchedFields = res[0] || {}; var pendingFields = _.filter(fields, function (n) { return !fetchedFields.hasOwnProperty(n); }); if (pendingFields.length == 0) { resolve({ fields: _.values(fetchedFields), perms: res[1] }); return promise; } var key = _.flatten([model, pendingFields]).join(); var pending = PENDING_REQUESTS[key]; function clear() { delete PENDING_REQUESTS[key]; } if (pending) { pending.then(clear, clear); pending.then(function (response) { resolve((response.data || {}).data); }); return promise; } pending = $http.post('ws/meta/view/fields', { model: model, fields: pendingFields }).then(function (response) { resolve((response.data || {}).data); }); pending.then(clear, clear); PENDING_REQUESTS[key] = pending; }); return promise; } function fetchView() { var key = [model, view.type, view.name].join(':'); function resolve(response) { var result = (response.data || {}).data; if (!result || !result.view) { return deferred.reject('view not found', view); } if (result.searchForm) { result.view.searchForm = result.searchForm; } if (_.isArray(result.view.items)) { var fieldsPromise = fetchFields(result, key); if (!_.isArray(view.items)) { // only cache fetched views return fieldsPromise.then(function (res) { return VIEWS.set(key, _.extend({}, res, { view: result.view })); }); } return fieldsPromise; } result = { fields: result.view.items, view: result.view }; VIEWS.set(key, result).then(function () { deferred.resolve(result); }); } VIEWS.get(key).then(function (loaded) { if (loaded) { return deferred.resolve(loaded); } function clear() { delete PENDING_REQUESTS[key]; } var pending = PENDING_REQUESTS[key]; if (pending) { pending.then(clear, clear); return pending.then(resolve); } pending = $http.post('ws/meta/view', { model: model, data: { type: view.type, name: view.name, context: context } }); pending.then(resolve); pending.then(clear, clear); PENDING_REQUESTS[key] = pending; }); return promise; } if (_.isArray(view.items)) { return fetchFields({ view: view, fields: view.fields }); } return fetchView(); }; ViewService.prototype.defer = function() { return $q.defer(); }; ViewService.prototype.action = function(action, model, context, data) { var params = { model: model, action: action, data: data || { context: _.extend({ _model: model }, context) } }; var promise = $http.post('ws/action', params); promise.success = function(fn) { promise.then(function(response){ fn(response.data); }); return promise; }; promise.error = function(fn) { promise.then(null, fn); return promise; }; return promise; }; ViewService.prototype.getFields = function(model, jsonModel) { var that = this, promise = $http.get('ws/meta/fields/' + model, { cache: true, params: jsonModel ? { jsonModel: jsonModel } : undefined }); promise.success = function(fn) { promise.then(function(response) { var res = response.data, data = res.data; that.process(data); fn(data.fields, data.jsonFields); }); return promise; }; promise.error = function(fn) { promise.then(null, fn); return promise; }; return promise; }; ViewService.prototype.save = function(schema) { var promise = $http.post("ws/meta/view/save", { data: schema }); promise.success = function(fn) { promise.then(function(response) { var res = response.data, data = res.data; fn(data); }); return promise; }; promise.error = function(fn) { promise.then(null, fn); return promise; }; return promise; }; return new ViewService(); }]); })();