1508 lines
39 KiB
JavaScript
1508 lines
39 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');
|
|
|
|
ui.prepareContext = function(model, values, dummyValues, parentContext) {
|
|
|
|
var context = _.extend({}, values);
|
|
var IGNORE = ['$changed', '$editorModel', '$version', '$fetched', '$fetchedRelated'];
|
|
|
|
function findDummy(item) {
|
|
var dummy = {};
|
|
if (item) {
|
|
_.each(item, function (v, k) {
|
|
if (k[0] === '$' && IGNORE.indexOf(k) === -1) {
|
|
dummy[k.substring(1)] = v;
|
|
}
|
|
});
|
|
}
|
|
return dummy;
|
|
}
|
|
|
|
function compact(item) {
|
|
if (!item || _.isNumber(item)) return item;
|
|
if (item.id > 0 && item.version === undefined && !item.$dirty) {
|
|
return _.extend({
|
|
id: item.id,
|
|
selected: item.selected,
|
|
$version: item.$version
|
|
}, findDummy(item));
|
|
}
|
|
item = _.extend({}, item);
|
|
if (item.id <= 0) {
|
|
item.id = null;
|
|
}
|
|
if (item.version === undefined && item.$version !== undefined) {
|
|
item.version = item.$version;
|
|
}
|
|
return item;
|
|
}
|
|
|
|
var dummy = _.extend({}, dummyValues);
|
|
_.each(dummy, function (value, name) {
|
|
if (value && value.$updatedValues) {
|
|
dummy[name] = value.$updatedValues;
|
|
}
|
|
if (name.indexOf('$') === 0) {
|
|
dummy[name.substring(1)] = dummy[name];
|
|
}
|
|
});
|
|
|
|
context = _.extend(context, dummy);
|
|
context._model = model || context._model;
|
|
context._parent = parentContext || context._parent;
|
|
|
|
if (context.id <= 0) {
|
|
context.id = null;
|
|
}
|
|
|
|
// use selected flag for o2m/m2m fields
|
|
// see onSelectionChanged in o2m controller
|
|
_.each(context.$many, function (getItems, name) {
|
|
if (!getItems) return;
|
|
if (name.indexOf('$') === 0) name = name.substring(1);
|
|
var items = getItems();
|
|
var value = context[name] || [];
|
|
if (items && items.length === value.length) {
|
|
context[name] = _.map(items, compact);
|
|
}
|
|
});
|
|
|
|
// compact o2m/m2m records
|
|
_.each(context, function (value, name) {
|
|
if (_.isArray(value)) {
|
|
context[name] = _.map(value, compact);
|
|
}
|
|
// make sure to have proper selected flags in nested o2m/m2m values, see uiPanelEditor
|
|
if (value && value.$editorModel) {
|
|
|
|
context[name] = ui.prepareContext(value.$editorModel, value, findDummy(value));
|
|
}
|
|
});
|
|
|
|
return context;
|
|
};
|
|
|
|
ui.controller('FormViewCtrl', FormViewCtrl);
|
|
|
|
ui.FormViewCtrl = FormViewCtrl;
|
|
ui.FormViewCtrl.$inject = ['$scope', '$element'];
|
|
|
|
function FormViewCtrl($scope, $element) {
|
|
|
|
ui.DSViewCtrl('form', $scope, $element);
|
|
|
|
var ds = $scope._dataSource;
|
|
|
|
$scope.fields = {};
|
|
$scope.fields_view = {};
|
|
$scope.fields_related = {};
|
|
|
|
$scope.record = {};
|
|
$scope.$$original = null;
|
|
$scope.$$dirty = false;
|
|
$scope.$$dirtyGrids = [];
|
|
|
|
$scope.$events = {};
|
|
|
|
/**
|
|
* Get field attributes.
|
|
*
|
|
* @param field field name or field element
|
|
*/
|
|
$scope.getViewDef = function(field) {
|
|
var id = field,
|
|
name = field,
|
|
elem = $(field),
|
|
attrs = {};
|
|
|
|
if (_.isObject(field)) { // assume element
|
|
name = elem.attr('x-field') || elem.attr('data-field') || elem.attr('name');
|
|
id = elem.attr('x-for-widget') || elem.attr('id') || name;
|
|
}
|
|
|
|
attrs = _.extend({}, this.fields[name], this.fields_view[id]);
|
|
|
|
return attrs;
|
|
};
|
|
|
|
$scope.doRead = function(id) {
|
|
var params = {
|
|
fields : _.pluck($scope.fields, 'name'),
|
|
related: $scope.fields_related
|
|
};
|
|
var promise = ds.read(id, params);
|
|
promise.success(function (record) {
|
|
record.$fetched = true;
|
|
});
|
|
return promise;
|
|
};
|
|
|
|
function doEdit(id, dummy, fireOnLoad) {
|
|
return $scope.doRead(id).success(function(record){
|
|
if ($scope.viewType && $scope.viewType !== 'form') {
|
|
$scope.edit(null);
|
|
return;
|
|
}
|
|
if (dummy) {
|
|
record = _.extend(dummy, record);
|
|
}
|
|
$scope.edit(record, fireOnLoad);
|
|
});
|
|
}
|
|
|
|
var initialized = false;
|
|
var routeId = null;
|
|
$scope.onShow = function(viewPromise) {
|
|
|
|
var params = this._viewParams;
|
|
var context = params.context || {};
|
|
var recordId = params.recordId || context._showRecord;
|
|
|
|
if (params.recordId) {
|
|
params.recordId = undefined;
|
|
}
|
|
|
|
if (context._showRecord) {
|
|
context._showRecord = undefined;
|
|
}
|
|
|
|
$scope.$locationChangeCheck();
|
|
$scope.$broadcast("on:form-show");
|
|
|
|
if (recordId) {
|
|
routeId = recordId;
|
|
return viewPromise.then(function(){
|
|
doEdit(recordId);
|
|
});
|
|
}
|
|
|
|
if (!initialized && params.options && params.options.mode === "edit") {
|
|
initialized = true;
|
|
$scope._routeSearch = params.options.search;
|
|
recordId = +params.options.state;
|
|
if (recordId > 0) {
|
|
routeId = recordId;
|
|
return viewPromise.then(function(){
|
|
doEdit(recordId);
|
|
});
|
|
}
|
|
}
|
|
|
|
var page = ds.page(),
|
|
record = null;
|
|
|
|
if (page.index > -1) {
|
|
record = ds.at(page.index);
|
|
}
|
|
|
|
routeId = record && record.id > 0 ? record.id : null;
|
|
|
|
viewPromise.then(function(){
|
|
$scope.ajaxStop(function(){
|
|
record = ($scope.record || {}).id ? $scope.record : record;
|
|
if (record === undefined) {
|
|
$scope.edit(null, false);
|
|
return $scope.ajaxStop(function(){
|
|
if (!$scope.record || !$scope.record.id) {
|
|
$scope.$broadcast("on:new");
|
|
}
|
|
});
|
|
}
|
|
if (record && record.id)
|
|
return doEdit(record.id);
|
|
$scope.edit(record);
|
|
});
|
|
});
|
|
};
|
|
|
|
var editable = false;
|
|
|
|
$scope.isForceEdit = function () {
|
|
var params = this._viewParams || {};
|
|
return params.forceEdit || (params.params && params.params.forceEdit);
|
|
};
|
|
|
|
$scope.isForceReadonly = function () {
|
|
var params = this._viewParams || {};
|
|
return params.forceReadonly || (params.params && params.params.forceReadonly);
|
|
};
|
|
|
|
$scope.isEditable = function() {
|
|
return $scope.isForceEdit() || (editable && !$scope.isForceReadonly());
|
|
};
|
|
|
|
$scope.setEditable = function() {
|
|
editable = arguments.length === 1 ? _.first(arguments) : true;
|
|
};
|
|
|
|
$scope.$$resetForm = function $$resetForm() {
|
|
routeId = null;
|
|
$scope.setEditable(false);
|
|
$scope.editRecord(null);
|
|
};
|
|
|
|
var locationChangeOff = null;
|
|
|
|
function locationChangeCheck() {
|
|
if (locationChangeOff) {
|
|
return;
|
|
}
|
|
|
|
var tab = $scope.selectedTab || {};
|
|
var params = $scope._viewParams;
|
|
|
|
if (tab !== params) {
|
|
return;
|
|
}
|
|
|
|
locationChangeOff = $scope.$on("$locationChangeStart", function (event, newUrl, oldUrl) {
|
|
// block navigation if popup is open
|
|
var hasDialogs = $('body .ui-dialog:visible').length > 0;
|
|
if (hasDialogs) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
|
|
var $location = $scope.$location;
|
|
|
|
if (!$location || tab !== params || tab.$viewScope != $scope || !$scope.isDirty()) {
|
|
$scope.$timeout(function () {
|
|
if (params && params.viewType !== 'form') {
|
|
$scope.$$resetForm();
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
var path = $location.path();
|
|
var search = $location.search();
|
|
|
|
// only handle /ds path changes
|
|
if (path.indexOf("/ds/") !== 0 || oldUrl.indexOf("#/ds/") === -1) {
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
$scope.confirmDirty(function() {
|
|
$scope.$$resetForm();
|
|
$scope.$locationChangeOff();
|
|
$location.path(path).search(search);
|
|
}, function () {
|
|
$scope.$locationChangeOff();
|
|
locationChangeCheck();
|
|
});
|
|
});
|
|
}
|
|
|
|
$scope.$locationChangeOff = function () {
|
|
if (locationChangeOff) {
|
|
locationChangeOff();
|
|
locationChangeOff = null;
|
|
}
|
|
};
|
|
|
|
$scope.$locationChangeCheck = function () {
|
|
$scope._viewPromise.then(function () {
|
|
$scope.waitForActions(locationChangeCheck, 200);
|
|
});
|
|
};
|
|
|
|
$scope.$on("$destroy", function () {
|
|
$scope.$locationChangeOff();
|
|
});
|
|
|
|
$scope.getRouteOptions = function() {
|
|
var rec = $scope.record,
|
|
args = [];
|
|
|
|
if (rec && rec.id > 0) {
|
|
args.push(rec.id);
|
|
} else if (routeId > 0) {
|
|
args.push(routeId);
|
|
}
|
|
|
|
return {
|
|
mode: 'edit',
|
|
args: args,
|
|
query: $scope._routeSearch
|
|
};
|
|
};
|
|
|
|
$scope._routeSearch = {};
|
|
$scope.setRouteOptions = function(options) {
|
|
var opts = options || {},
|
|
record = $scope.record || {},
|
|
state = +opts.state || null;
|
|
|
|
$scope.$locationChangeCheck();
|
|
$scope._routeSearch = opts.search;
|
|
if (record.id == state) {
|
|
return $scope.updateRoute();
|
|
}
|
|
if (routeId === state && state) {
|
|
return record.id && record.id !== state ? doEdit(state) : $scope.updateRoute();
|
|
}
|
|
|
|
var params = $scope._viewParams;
|
|
if (params.viewType !== "form") {
|
|
return $scope.show();
|
|
}
|
|
return state ? doEdit(state) : $scope.edit(null, false);
|
|
};
|
|
|
|
$scope.edit = function(record, fireOnLoad) {
|
|
$scope.$$disableDirtyCheck = true;
|
|
$scope.editRecord(record);
|
|
$scope.updateRoute();
|
|
$scope.$applyAsync(function () {
|
|
$scope.$$disableDirtyCheck = false;
|
|
if (fireOnLoad === false) return;
|
|
$scope._viewPromise.then(function(){
|
|
$scope.ajaxStop(function(){
|
|
var handler = $scope.$events.onLoad,
|
|
record = $scope.record;
|
|
if (handler && !ds.equals({}, record)) {
|
|
setTimeout(handler);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
$scope.$$fixUndefined = function (orig, current) {
|
|
_.keys(current).forEach(function (name) {
|
|
var value = current[name];
|
|
if (value == undefined && orig[name] === null) {
|
|
current[name] = null;
|
|
}
|
|
});
|
|
};
|
|
|
|
$scope.editRecord = function(record) {
|
|
$scope.$$original = angular.copy(record) || {};
|
|
$scope.$$dirty = false;
|
|
$scope.record = angular.copy($scope.$$original);
|
|
$scope._viewPromise.then(function(){
|
|
$scope.ajaxStop(function(){
|
|
$scope.$$fixUndefined($scope.$$original, $scope.record);
|
|
$scope.$broadcast("on:edit", $scope.record);
|
|
$scope.$broadcast("on:record-change", $scope.record);
|
|
if ($scope.__canForceEdit && $scope.canEdit()) {
|
|
$scope.__canForceEdit = undefined;
|
|
$scope.onEdit();
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
$scope.getContext = function() {
|
|
var dummy = $scope.getDummyValues();
|
|
var context = _.extend({}, $scope._routeSearch, $scope.record);
|
|
if ($scope.$parent && $scope.$parent.getContext) {
|
|
context._parent = $scope.$parent.getContext();
|
|
} else {
|
|
context = _.extend({}, $scope._viewParams.context, context);
|
|
}
|
|
if (!$scope.$hasPanels) {
|
|
context._form = true;
|
|
}
|
|
$scope.$broadcast('on:update-context', context);
|
|
return ui.prepareContext(ds._model, context, dummy);
|
|
};
|
|
|
|
$scope.$dirtyGrid = function (gridId, dirty) {
|
|
var i = $scope.$$dirtyGrids.indexOf(gridId);
|
|
if (dirty && i === -1) {
|
|
$scope.$$dirtyGrids.push(gridId);
|
|
} else if (!dirty && i > -1) {
|
|
$scope.$$dirtyGrids.splice(i, 1);
|
|
}
|
|
return $scope.isDirty();
|
|
};
|
|
|
|
$scope.isDirty = function() {
|
|
$scope.$$dirty = $scope.$$disableDirtyCheck ? false : $scope.$$dirtyGrids.length > 0 || !ds.equals($scope.record, $scope.$$original);
|
|
return $scope.$$dirty;
|
|
};
|
|
|
|
$scope.$watch("record", function formRecordWatch(rec, old) {
|
|
if (rec === old) {
|
|
$scope.$$dirty = false;
|
|
return;
|
|
}
|
|
$scope.$broadcast("on:record-change", rec);
|
|
$scope.$$dirty = $scope.isDirty();
|
|
}, true);
|
|
|
|
$scope.$broadcastRecordChange = function () {
|
|
$scope.$broadcast("on:record-change", $scope.record);
|
|
};
|
|
|
|
$scope.$on("on:record-change", function () {
|
|
var view = $scope.schema;
|
|
if (view && view.readonlyIf) {
|
|
var readonly = axelor.$eval($scope, view.readonlyIf, _.extend({}, $scope._context, $scope.record));
|
|
if (_.isFunction($scope.attr)) {
|
|
$scope.attr('readonly', readonly);
|
|
}
|
|
editable = !readonly;
|
|
}
|
|
});
|
|
|
|
$scope.isValid = function() {
|
|
return $scope.form && $scope.form.$valid;
|
|
};
|
|
|
|
$scope.canNew = function() {
|
|
return $scope.hasButton('new');
|
|
};
|
|
|
|
$scope.canEdit = function() {
|
|
return $scope.hasButton('edit');
|
|
};
|
|
|
|
$scope.canSave = function() {
|
|
return $scope.hasPermission('write') && $scope.$$dirty && $scope.isValid();
|
|
};
|
|
|
|
$scope.canDelete = function() {
|
|
return $scope.hasButton('delete') && ($scope.record || {}).id > 0;
|
|
};
|
|
|
|
$scope.canArchive = function() {
|
|
return $scope.hasPermission('write')
|
|
&& $scope.hasButton('archive')
|
|
&& ($scope.record || {}).id > 0
|
|
&& !$scope.$$dirty
|
|
&& !$scope.record.archived
|
|
&& $scope.isValid();
|
|
};
|
|
|
|
$scope.canUnarchive = function() {
|
|
return $scope.hasPermission('write')
|
|
&& $scope.hasButton('archive')
|
|
&& ($scope.record || {}).id > 0
|
|
&& !$scope.$$dirty
|
|
&& $scope.record.archived
|
|
&& $scope.isValid();
|
|
};
|
|
|
|
$scope.canCopy = function() {
|
|
return $scope.canNew() && $scope.hasButton('copy') && !$scope.$$dirty && ($scope.record || {}).id;
|
|
};
|
|
|
|
$scope.canAttach = function() {
|
|
return $scope.hasButton('attach');
|
|
};
|
|
|
|
$scope.canCancel = function() {
|
|
return $scope.$$dirty;
|
|
};
|
|
|
|
$scope.canBack = function() {
|
|
return !$scope.$$dirty;
|
|
};
|
|
|
|
$scope.onNew = function() {
|
|
var defer = $scope._defer();
|
|
$scope.confirmDirty(function(){
|
|
routeId = null;
|
|
$scope.$locationChangeOff();
|
|
$scope.edit(null, false);
|
|
$scope.setEditable();
|
|
$scope.$broadcast("on:new");
|
|
$scope.$locationChangeCheck();
|
|
defer.resolve();
|
|
}, defer.reject);
|
|
return defer.promise;
|
|
};
|
|
|
|
$scope.onNewPromise = null;
|
|
$scope.defaultValues = null;
|
|
|
|
$scope.onNewHandler = function onNewHandler(event) {
|
|
|
|
routeId = null;
|
|
|
|
function handleOnNew() {
|
|
|
|
var handler = $scope.$events.onNew;
|
|
var last = $scope.$parent.onNewPromise || $scope.onNewPromise;
|
|
var params = ($scope._viewParams || {}).params || {};
|
|
|
|
function reset() {
|
|
$scope.onNewPromise = null;
|
|
}
|
|
|
|
function handle(defaults) {
|
|
var promise = handler();
|
|
if (promise && promise.then) {
|
|
promise.then(reset, reset);
|
|
promise = promise.then(function () {
|
|
if ($scope.record && $scope.record.id > 0) return; // record may have been saved, see RM-13558
|
|
if ($scope.isDirty()) {
|
|
var rec = _.extend({}, defaults, $scope.record);
|
|
var old = $scope.$$original;
|
|
var res = $scope.editRecord(rec);
|
|
if (rec) {
|
|
rec._dirty = true;
|
|
}
|
|
if (!params['details-view']) {
|
|
$scope.$$original = old;
|
|
}
|
|
return res;
|
|
} else if (defaults) {
|
|
$scope.editRecord(defaults);
|
|
}
|
|
});
|
|
}
|
|
return promise;
|
|
}
|
|
|
|
$scope.setEditable();
|
|
|
|
if (handler && $scope.record) {
|
|
if (last) {
|
|
$scope.onNewPromise = last.then(handle);
|
|
return;
|
|
}
|
|
$scope.onNewPromise = handle($scope.defaultValues);
|
|
} else if ($scope.defaultValues) {
|
|
$scope.editRecord($scope.defaultValues);
|
|
}
|
|
}
|
|
|
|
function afterVewLoaded() {
|
|
if ($scope.defaultValues === null) {
|
|
var defaultValues = {};
|
|
_.each($scope.fields, function (field, name) {
|
|
if (field.defaultValue !== undefined) {
|
|
defaultValues[name] = field.defaultValue;
|
|
}
|
|
});
|
|
$scope.defaultValues = _.isEmpty(defaultValues) ? undefined : defaultValues;
|
|
}
|
|
|
|
// ensure correct date/datetime
|
|
_.each($scope.fields, function (field, name) {
|
|
if (field.defaultNow && $scope.defaultValues[name] !== undefined) {
|
|
$scope.defaultValues[name] = field.type === 'date'
|
|
? moment().startOf('day').format('YYYY-MM-DD')
|
|
: moment().toISOString();
|
|
}
|
|
});
|
|
return handleOnNew();
|
|
}
|
|
|
|
$scope._viewPromise.then(function() {
|
|
$scope.waitForActions(afterVewLoaded);
|
|
});
|
|
};
|
|
|
|
$scope.$on("on:new", function (event) {
|
|
$scope.onNewHandler(event);
|
|
});
|
|
|
|
$scope.$on("on:nav-click", function(event, tab) {
|
|
var record, context, checkVersion;
|
|
if (event.defaultPrevented || tab.$viewScope !== $scope) {
|
|
return;
|
|
}
|
|
event.preventDefault();
|
|
context = tab.context || {};
|
|
record = $scope.record || {};
|
|
checkVersion = "" + axelor.config["view.form.check-version"];
|
|
if (context.__check_version !== undefined) {
|
|
checkVersion = "" + context.__check_version;
|
|
}
|
|
|
|
if (!record.id || !(checkVersion === "true" || checkVersion === "silent")) {
|
|
return;
|
|
}
|
|
|
|
return $scope.checkVersion(true, function (verified) {
|
|
var params = $scope._viewParams;
|
|
if (verified) {
|
|
return;
|
|
}
|
|
if (checkVersion === "silent"
|
|
&& (!$scope.isDirty() || (params.params && params.params['show-confirm'] === false))) {
|
|
return $scope.onRefresh();
|
|
}
|
|
|
|
axelor.dialogs.confirm(
|
|
_t("The record has been updated or delete by another action.") + "<br>" +
|
|
_t("Would you like to reload the current record?"),
|
|
function(confirmed){
|
|
if (confirmed) {
|
|
$scope.onRefresh();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
$scope.checkVersion = function (callback) {
|
|
var record = $scope.record || {};
|
|
var done = callback;
|
|
var graph = callback === true;
|
|
if (graph) {
|
|
done = arguments[1];
|
|
}
|
|
done = _.isFunction(done) ? done : angular.noop;
|
|
|
|
function compact(rec) {
|
|
var res = {
|
|
id: rec.id,
|
|
version: rec.version
|
|
};
|
|
if (res.version === undefined) {
|
|
res.version = rec.$version;
|
|
}
|
|
if (graph) {
|
|
_.each(rec, function(v, k) {
|
|
if (!v) return;
|
|
if (v.id) res[k] = compact(v);
|
|
if (_.isArray(v)) res[k] = _.map(v, compact);
|
|
});
|
|
}
|
|
return res;
|
|
}
|
|
|
|
if (!record.id) {
|
|
return done(true);
|
|
}
|
|
|
|
return ds.verify(compact(record))
|
|
.success(function(res) {
|
|
done(res.status === 0);
|
|
}).error(function (err) {
|
|
done(false);
|
|
});
|
|
};
|
|
|
|
$scope.onEdit = function() {
|
|
$scope.setEditable();
|
|
};
|
|
|
|
$scope.onCopy = function() {
|
|
var record = $scope.record;
|
|
ds.copy(record.id).success(function(record){
|
|
$scope.$locationChangeOff();
|
|
routeId = null;
|
|
$scope.edit(record);
|
|
$scope.setEditable();
|
|
$scope.$timeout(function () {
|
|
record._dirty = true;
|
|
$scope.$$original = {};
|
|
locationChangeCheck();
|
|
});
|
|
});
|
|
};
|
|
|
|
$scope.getDummyValues = function() {
|
|
if (!$scope.record) return {};
|
|
var fields = _.keys($scope.fields);
|
|
var extra = _.chain($scope.fields_view)
|
|
.filter(function(f) { return f.name && !_.contains(fields, f.name); })
|
|
.pluck('name')
|
|
.compact()
|
|
.value();
|
|
|
|
if ($scope._model === 'com.axelor.auth.db.User') {
|
|
extra = extra.filter(function (n) {
|
|
return ['change', 'oldPassword', 'newPassword', 'chkPassword'].indexOf(n) === -1;
|
|
});
|
|
}
|
|
|
|
return _.pick($scope.record, extra);
|
|
};
|
|
|
|
$scope.onSave = function(options) {
|
|
|
|
var opts = _.extend({ fireOnLoad: true }, options);
|
|
var defer = $scope._defer();
|
|
var saveAction = $scope.$events.onSave;
|
|
var fireOnLoad = opts.fireOnLoad;
|
|
|
|
function fireBeforeSave() {
|
|
var event = $scope.$broadcast('on:before-save', $scope.record);
|
|
if (event.defaultPrevented) {
|
|
if (event.error) {
|
|
axelor.dialogs.error(event.error);
|
|
}
|
|
setTimeout(function() {
|
|
defer.reject(event.error);
|
|
});
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (opts.callOnSave === false) {
|
|
saveAction = null;
|
|
fireOnLoad = false;
|
|
}
|
|
|
|
if (fireBeforeSave() === false) {
|
|
return defer.promise;
|
|
}
|
|
|
|
function doSave() {
|
|
var dummy = $scope.getDummyValues(),
|
|
values = _.extend({}, $scope.record, opts.values),
|
|
promise;
|
|
|
|
values = ds.diff(values, $scope.$$original);
|
|
promise = ds.save(values).success(function(record, page) {
|
|
$scope.$$dirtyGrids.length = 0;
|
|
return doEdit(record.id, dummy, fireOnLoad);
|
|
});
|
|
|
|
promise.success(function(record) {
|
|
defer.resolve(record);
|
|
});
|
|
promise.error(function(error) {
|
|
defer.reject(error);
|
|
});
|
|
}
|
|
|
|
function waitForActions(callback) {
|
|
if (opts.wait === false) {
|
|
return callback();
|
|
}
|
|
return $scope.waitForActions(callback, 100);
|
|
}
|
|
|
|
function doOnSave() {
|
|
if (!$scope.hasPermission('write') || !$scope.isValid()) {
|
|
$scope.showErrorNotice();
|
|
defer.reject();
|
|
return defer.promise;
|
|
}
|
|
if (saveAction) {
|
|
return saveAction().then(doSave);
|
|
}
|
|
// repeat on:before-save to ensure if any o2m/m2m is updated gets applied
|
|
if (fireBeforeSave()) {
|
|
waitForActions(doSave);
|
|
}
|
|
}
|
|
|
|
waitForActions(doOnSave);
|
|
|
|
return defer.promise;
|
|
};
|
|
|
|
$scope.confirmDirty = function(callback, cancelCallback) {
|
|
var params = $scope._viewParams || {};
|
|
if (!$scope.isDirty() || (params.params && params.params['show-confirm'] === false)) {
|
|
return callback();
|
|
}
|
|
axelor.dialogs.confirm(_t("Current changes will be lost. Do you really want to proceed?"), function(confirmed){
|
|
if (!confirmed) {
|
|
if (cancelCallback) {
|
|
cancelCallback();
|
|
}
|
|
return;
|
|
}
|
|
$scope.$applyAsync(callback);
|
|
});
|
|
};
|
|
|
|
$scope.onDelete = function() {
|
|
var record = $scope.record || {};
|
|
if (!record.id || record.id < 0) {
|
|
return;
|
|
}
|
|
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){
|
|
if ($scope.switchBack() === false) {
|
|
$scope.onNew();
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
$scope.onArchive = function() {
|
|
var record = $scope.record || {};
|
|
if (!record.id || record.id < 0) {
|
|
return;
|
|
}
|
|
axelor.dialogs.confirm(_t("Do you really want to archive the selected record?"),
|
|
function(confirmed) {
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
var item = _.extend({}, record, { archived: true });
|
|
ds.saveAll([item]).success(function() {
|
|
$scope.switchBack();
|
|
});
|
|
});
|
|
};
|
|
|
|
$scope.onUnarchive = function() {
|
|
var record = $scope.record || {};
|
|
if (!record.id || record.id < 0) {
|
|
return;
|
|
}
|
|
axelor.dialogs.confirm(_t("Do you really want to unarchive the selected record?"),
|
|
function(confirmed) {
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
var item = _.extend({}, record, { archived: false });
|
|
ds.saveAll([item]).success(function() {
|
|
$scope.reload();
|
|
});
|
|
});
|
|
};
|
|
|
|
$scope.onBack = function() {
|
|
var record = $scope.record || {};
|
|
var editable = $scope.isEditable();
|
|
|
|
if (record.id && editable && $scope.canEdit()) {
|
|
$scope.setEditable(false);
|
|
return;
|
|
}
|
|
|
|
$scope.switchBack();
|
|
};
|
|
|
|
$scope.onRefresh = function() {
|
|
$scope.confirmDirty($scope.reload);
|
|
};
|
|
|
|
$scope.reload = function() {
|
|
var record = $scope.record;
|
|
if (record && record.id) {
|
|
return doEdit(record.id).success(function (rec) {
|
|
var shared = ds.get(record.id);
|
|
if (shared) {
|
|
shared = _.extend(shared, rec);
|
|
}
|
|
});
|
|
}
|
|
$scope.edit(null, false);
|
|
$scope.$broadcast("on:new");
|
|
};
|
|
|
|
$scope.onCancel = function() {
|
|
var e = $scope.$broadcast("cancel:grid-edit");
|
|
if (e.defaultPrevented) {
|
|
return;
|
|
}
|
|
$scope.confirmDirty(function() {
|
|
$scope.reload();
|
|
});
|
|
};
|
|
|
|
var __switchTo = $scope.switchTo;
|
|
|
|
$scope.switchBack = function () {
|
|
var __switchBack = null;
|
|
if($scope._viewTypeLast && $scope._viewTypeLast !== "form") {
|
|
__switchBack = $scope._viewTypeLast;
|
|
}
|
|
if (__switchBack === null) {
|
|
var views = ($scope._viewParams||{}).views || [];
|
|
for (var i = 0 ; i < views.length; i++) {
|
|
var view = views[i];
|
|
if (view.type !== "form") {
|
|
__switchBack = view.type;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (__switchBack) {
|
|
return $scope.switchTo(__switchBack);
|
|
}
|
|
return false;
|
|
};
|
|
|
|
$scope.switchTo = function(type, callback) {
|
|
$scope.waitForActions(function () {
|
|
$scope.confirmDirty(function() {
|
|
$scope.setEditable(false);
|
|
$scope.editRecord(null);
|
|
__switchTo(type, function () {
|
|
$scope.$locationChangeOff();
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
$scope.onSearch = function() {
|
|
var e = $scope.$broadcast("cancel:grid-edit");
|
|
if (e.defaultPrevented) {
|
|
return;
|
|
}
|
|
|
|
$scope.switchBack();
|
|
};
|
|
|
|
$scope.pagerText = function() {
|
|
var page = ds.page(),
|
|
record = $scope.record || {};
|
|
|
|
if (page && page.from !== undefined) {
|
|
if (page.total === 0 || page.index === -1 || !record.id) return null;
|
|
return _t("{0} of {1}", (page.from + page.index + 1), page.total);
|
|
}
|
|
};
|
|
|
|
$scope.canNext = function() {
|
|
var page = ds.page();
|
|
return (page.index < page.size - 1) || (page.from + page.index < page.total - 1);
|
|
};
|
|
|
|
$scope.canPrev = function() {
|
|
var page = ds.page();
|
|
return page.index > 0 || ds.canPrev();
|
|
};
|
|
|
|
$scope.onNext = function() {
|
|
$scope.confirmDirty(function() {
|
|
ds.nextItem(function(record){
|
|
if (record && record.id) doEdit(record.id);
|
|
});
|
|
});
|
|
};
|
|
|
|
$scope.onPrev = function() {
|
|
$scope.confirmDirty(function() {
|
|
ds.prevItem(function(record){
|
|
if (record && record.id) doEdit(record.id);
|
|
});
|
|
});
|
|
};
|
|
|
|
function focusFirst() {
|
|
$scope._viewPromise.then(function() {
|
|
setTimeout(function() {
|
|
$element.find('form :input:visible').not('[readonly],[type=checkbox]').first().focus().select();
|
|
});
|
|
});
|
|
}
|
|
|
|
function showLog() {
|
|
ds.read($scope.record.id, {
|
|
fields: ['createdBy', 'createdOn', 'updatedBy', 'updatedOn']
|
|
}).success(function (record) {
|
|
showLogDialog(record);
|
|
});
|
|
}
|
|
|
|
function showLogDialog(record) {
|
|
|
|
function nameOf(user) {
|
|
if (!user) {
|
|
return "";
|
|
}
|
|
var name = axelor.config['user.nameField'] || 'name';
|
|
return user[name] || "";
|
|
}
|
|
|
|
var info = {};
|
|
if (record.createdOn) {
|
|
info.createdOn = moment(record.createdOn).format('DD/MM/YYYY HH:mm');
|
|
info.createdBy = nameOf(record.createdBy);
|
|
}
|
|
if (record.updatedOn) {
|
|
info.updatedOn = moment(record.updatedOn).format('DD/MM/YYYY HH:mm');
|
|
info.updatedBy = nameOf(record.updatedBy);
|
|
}
|
|
var table = $("<table class='table field-details'>");
|
|
var tr;
|
|
|
|
tr = $("<tr></tr>").appendTo(table);
|
|
$("<th></th>").text(_t("Created By:")).appendTo(tr);
|
|
$("<td></td>").text(info.createdBy).appendTo(tr);
|
|
|
|
tr = $("<tr></tr>").appendTo(table);
|
|
$("<th></th>").text(_t("Created On:")).appendTo(tr);
|
|
$("<td></td>").text(info.createdOn).appendTo(tr);
|
|
|
|
tr = $("<tr></tr>").appendTo(table);
|
|
$("<th></th>").text(_t("Updated By:")).appendTo(tr);
|
|
$("<td></td>").text(info.updatedBy).appendTo(tr);
|
|
|
|
tr = $("<tr></tr>").appendTo(table);
|
|
$("<th></th>").text(_t("Updated On:")).appendTo(tr);
|
|
$("<td></td>").text(info.updatedOn).appendTo(tr);
|
|
|
|
var text = $('<div>').append(table).html();
|
|
|
|
axelor.dialogs.say(text);
|
|
}
|
|
|
|
$scope.toolmenu = [{
|
|
isButton: true,
|
|
items: [{
|
|
title: _t('Refresh'),
|
|
click: function(e) {
|
|
$scope.onRefresh();
|
|
}
|
|
}, {
|
|
title: _t('Delete'),
|
|
active: function () {
|
|
return $scope.canDelete();
|
|
},
|
|
click: function(e) {
|
|
$scope.onDelete();
|
|
}
|
|
}, {
|
|
title: _t('Duplicate'),
|
|
active: function () {
|
|
return $scope.canCopy();
|
|
},
|
|
click: function(e) {
|
|
$scope.onCopy();
|
|
}
|
|
}, {
|
|
visible: function () {
|
|
return $scope.canArchive() || $scope.canUnarchive();
|
|
},
|
|
}, {
|
|
title: _t('Archive'),
|
|
active: function () {
|
|
return $scope.canArchive();
|
|
},
|
|
visible: function () {
|
|
return $scope.canArchive();
|
|
},
|
|
click: function(e) {
|
|
$scope.onArchive();
|
|
},
|
|
}, {
|
|
title: _t('Unarchive'),
|
|
active: function () {
|
|
return $scope.canUnarchive();
|
|
},
|
|
visible: function () {
|
|
return $scope.canUnarchive();
|
|
},
|
|
click: function(e) {
|
|
$scope.onUnarchive();
|
|
},
|
|
}, {
|
|
}, {
|
|
active: function () {
|
|
return $scope.hasAuditLog();
|
|
},
|
|
title: _t('Last modified...'),
|
|
click: showLog
|
|
}]
|
|
}];
|
|
|
|
$scope.onHotKey = function (e, action) {
|
|
|
|
if (action === "save") {
|
|
if (!$scope.canSave()) {
|
|
$scope.showErrorNotice();
|
|
} else if ($scope.hasButton('save')) {
|
|
$(e.target).blur().focus();
|
|
$scope.onSave();
|
|
}
|
|
}
|
|
if (action === "refresh") {
|
|
$scope.onRefresh();
|
|
}
|
|
if (action === "new") {
|
|
$scope.onNew();
|
|
}
|
|
if (action === "edit") {
|
|
if ($scope.canEdit()) {
|
|
$scope.onEdit();
|
|
}
|
|
focusFirst();
|
|
}
|
|
if (action === "select") {
|
|
focusFirst();
|
|
}
|
|
if (action === "prev" && $scope.canPrev()) {
|
|
$scope.onPrev();
|
|
}
|
|
if (action === "next" && $scope.canNext()) {
|
|
$scope.onNext();
|
|
}
|
|
if (action === "search") {
|
|
$scope.onBack();
|
|
}
|
|
|
|
$scope.$applyAsync();
|
|
|
|
return false;
|
|
};
|
|
|
|
$scope.$text = function (name) {
|
|
var field = $scope.fields[name] || {},
|
|
format = ui.formatters[field.type],
|
|
record = $scope.record || {};
|
|
if (format) {
|
|
return format(field, record[name]);
|
|
}
|
|
return record[name];
|
|
};
|
|
|
|
$scope.$evalViewerExpr = axelor.$evalViewerExpr;
|
|
}
|
|
|
|
ui.formBuild = function (scope, schema, fields) {
|
|
|
|
var path = scope.formPath || "";
|
|
var hasPanels = false;
|
|
|
|
function update(e, attrs) {
|
|
_.each(attrs, function(v, k) {
|
|
if (_.isUndefined(v)) return;
|
|
e.attr(k, v);
|
|
});
|
|
}
|
|
|
|
function process(items, parent) {
|
|
|
|
$(items).each(function(){
|
|
|
|
if (this.type == 'break') {
|
|
return $('<br>').appendTo(parent);
|
|
}
|
|
if (this.type == 'field') {
|
|
delete this.type;
|
|
}
|
|
if (['panel', 'panel-json', 'panel-related'].indexOf(this.type) > -1) {
|
|
scope.$hasPanels = hasPanels = true;
|
|
}
|
|
|
|
var widget = this.widget,
|
|
widgetAttrs = {},
|
|
attrs = {};
|
|
|
|
_.extend(attrs, this.widgetAttrs);
|
|
|
|
_.each(this.widgetAttrs, function(value, key) {
|
|
widgetAttrs['x-' + key] = value;
|
|
});
|
|
|
|
var item = $('<div></div>').appendTo(parent),
|
|
field = fields[this.name] || {},
|
|
widgetId = _.uniqueId('_formWidget'),
|
|
type = widget;
|
|
|
|
attrs = angular.extend(attrs, field, this);
|
|
widget = widget || attrs.widget;
|
|
type = ui.getWidget(widget) ||
|
|
ui.getWidget(attrs.type) ||
|
|
ui.getWidget(attrs.serverType) ||
|
|
attrs.type || attrs.serverType || 'string';
|
|
|
|
if (_.isArray(attrs.selectionList) && !widget) {
|
|
type = attrs.multiple ? 'multi-select' : 'select';
|
|
}
|
|
|
|
if (attrs.password) {
|
|
type = 'password';
|
|
}
|
|
if (attrs.image) {
|
|
type = "image";
|
|
}
|
|
if (type == 'label') {
|
|
type = 'static-label';
|
|
}
|
|
|
|
if (attrs.type == 'panel-related') {
|
|
type = 'panel-' + (field.type || attrs.serverType || type);
|
|
if (attrs.items && attrs.items.length) {
|
|
attrs.views = [{
|
|
type: 'grid',
|
|
title: attrs.title || field.title || field.autoTitle,
|
|
items: attrs.items,
|
|
fields: attrs.fields,
|
|
canMove: attrs.canMove,
|
|
orderBy: attrs.orderBy,
|
|
editable: attrs.editable,
|
|
editIcon: attrs.editIcon === undefined ? true : attrs.editIcon
|
|
}];
|
|
}
|
|
this.items = attrs.items = null;
|
|
}
|
|
|
|
if ((attrs.editor || attrs.viewer) && attrs.target && type !== 'image') {
|
|
type = 'inline-' + type;
|
|
}
|
|
|
|
attrs.serverType = field.serverType || attrs.serverType || attrs.type;
|
|
attrs.type = type;
|
|
|
|
item.attr('ui-' + type, '');
|
|
item.attr('id', widgetId);
|
|
|
|
if (parent.is('[ui-panel-tabs]')) {
|
|
item.attr('ui-panel-tab', '');
|
|
if (attrs.showTitle === undefined) {
|
|
attrs.showTitle = false;
|
|
}
|
|
if (attrs.showFrame === undefined) {
|
|
attrs.showFrame = false;
|
|
}
|
|
}
|
|
|
|
scope.fields_view[widgetId] = attrs;
|
|
|
|
//TODO: cover all attributes
|
|
var _attrs = _.extend({}, attrs.attrs, this.attrs, widgetAttrs, {
|
|
'name' : attrs.name || this.name,
|
|
'x-cols' : this.cols,
|
|
'x-colspan' : this.colSpan || (type === 'help' ? 12 : undefined),
|
|
'x-coloffset' : this.colOffset,
|
|
'x-rowspan' : this.rowSpan,
|
|
'x-sidebar' : this.sidebar,
|
|
'x-stacked' : this.stacked,
|
|
'x-flexbox' : this.flexbox,
|
|
'x-widths' : this.colWidths,
|
|
'x-field' : this.name,
|
|
'x-title' : attrs.title
|
|
});
|
|
|
|
if (attrs.showTitle !== undefined) {
|
|
attrs.showTitle = attrs.showTitle !== false;
|
|
_attrs['x-show-title'] = attrs.showTitle;
|
|
}
|
|
|
|
if (attrs.required)
|
|
_attrs['ng-required'] = true;
|
|
if (attrs.readonly)
|
|
_attrs['x-readonly'] = true;
|
|
|
|
if (_attrs.name) {
|
|
_attrs['x-path'] = path ? path + "." + _attrs.name : _attrs.name;
|
|
}
|
|
|
|
update(item, _attrs);
|
|
|
|
// enable actions & conditional expressions
|
|
item.attr('ui-actions', '');
|
|
item.attr('ui-widget-states', '');
|
|
|
|
if (type == 'button' || type == 'static-label') {
|
|
item.html(this.title);
|
|
}
|
|
|
|
if (/button|group|tabs|tab|separator|spacer|static|static-label/.test(type)) {
|
|
item.attr('x-show-title', false);
|
|
}
|
|
|
|
if (attrs.translatable && (attrs.serverType === 'string' || attrs.serverType === 'text')) {
|
|
item.attr('ui-translate-icon', '');
|
|
}
|
|
|
|
var items = this.items || this.pages;
|
|
if (items && this.type != 'panel-related') {
|
|
process(items, item);
|
|
if (type === 'panel') {
|
|
item.attr('ui-panel-layout', '');
|
|
item.attr('x-item-span', attrs.itemSpan);
|
|
} else if (['tabs', 'panel-tabs', 'panel-stack', 'panel-related', 'panel-mail', 'button-group'].indexOf(type) == -1) {
|
|
item.attr('ui-table-layout', '');
|
|
}
|
|
}
|
|
if (type === 'group' && _.all(items, function (x){ return x.type === 'button'; })) {
|
|
item.addClass('button-group');
|
|
}
|
|
});
|
|
return parent;
|
|
}
|
|
|
|
var elem = $('<form name="form" ui-form-gate ui-form ui-table-layout ui-actions ui-widget-states></form>');
|
|
elem.attr('x-cols', schema.cols)
|
|
.attr('x-widths', schema.colWidths);
|
|
|
|
if (schema.css) {
|
|
elem.addClass(schema.css);
|
|
}
|
|
|
|
process(schema.items, elem);
|
|
|
|
if (hasPanels) {
|
|
elem.removeAttr('ui-table-layout').attr('ui-bar-layout', '');
|
|
}
|
|
|
|
return elem;
|
|
};
|
|
|
|
ui.directive('uiViewForm', ['$compile', 'ViewService', function($compile, ViewService){
|
|
|
|
return function(scope, element, attrs) {
|
|
|
|
scope.canShowAttachments = function() {
|
|
return scope.canAttach() && (scope.record || {}).id;
|
|
};
|
|
|
|
scope.onShowAttachments = function(){
|
|
var popup = ViewService.compile('<div ui-dms-popup></div>')(scope.$new(true));
|
|
popup.isolateScope().showPopup(scope);
|
|
};
|
|
|
|
scope.hasAuditLog = function() {
|
|
return scope.record && scope.record.id > -1;
|
|
};
|
|
|
|
scope.hasWidth = function() {
|
|
var view = scope.schema;
|
|
return view && view.width;
|
|
};
|
|
|
|
var translatted = null;
|
|
|
|
scope.showErrorNotice = function () {
|
|
var form = scope.form || $(element).data('$formController'),
|
|
names;
|
|
|
|
if (!form || form.$valid) {
|
|
return;
|
|
}
|
|
|
|
var elems = element.find('[x-field].ng-invalid:not(fieldset)').filter(function() {
|
|
var isInline = $(this).parents('.slickgrid,.nested-not-required').length > 0;
|
|
if (isInline) {
|
|
return false;
|
|
}
|
|
var elemScope = $(this).scope();
|
|
if (elemScope.isHidden &&
|
|
elemScope.isHidden()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
var items = elems.map(function () {
|
|
return {
|
|
name: $(this).attr('x-field'),
|
|
title: $(this).attr('x-title')
|
|
};
|
|
});
|
|
|
|
items = _.unique(_.compact(items), function (item) { return item.name; });
|
|
|
|
if (items.length === 0) {
|
|
return;
|
|
}
|
|
|
|
if (!translatted) {
|
|
translatted = {};
|
|
_.each(scope.fields_view, function (v, k) {
|
|
if (v.name) {
|
|
translatted[v.name] = v.title;
|
|
}
|
|
});
|
|
}
|
|
|
|
items = _.map(items, function(item) {
|
|
var value = item.title;
|
|
if (item.name) {
|
|
value = translatted[item.name] || value;
|
|
}
|
|
return '<li>' + value + '</li>';
|
|
});
|
|
|
|
items = '<ul>' + items.join('') + '</ul>';
|
|
|
|
axelor.notify.error(items, {
|
|
title: _t("The following fields are invalid:")
|
|
});
|
|
};
|
|
|
|
element.scroll(function (e) {
|
|
$(document).trigger('adjust:scroll', element);
|
|
});
|
|
|
|
scope.$on("on:form-show", function () {
|
|
setTimeout(function () {
|
|
element.animate({ scrollTop: 0 }, 200);
|
|
}, 300);
|
|
});
|
|
|
|
var unwatch = scope.$watch('schema.loaded', function formSchemaWatch(viewLoaded){
|
|
|
|
if (!viewLoaded) return;
|
|
|
|
unwatch();
|
|
|
|
var preparing = true;
|
|
|
|
scope.$watchChecker(function () {
|
|
return !preparing;
|
|
});
|
|
|
|
var params = (scope._viewParams || {}).params || {};
|
|
var schema = scope.schema;
|
|
var form = ui.formBuild(scope, schema, scope.fields);
|
|
|
|
form = $compile(form)(scope);
|
|
|
|
var numFields = form.find('[x-field]').length;
|
|
|
|
if (!scope._isPopup && !scope._isPanelForm) {
|
|
element.addClass('has-width');
|
|
}
|
|
|
|
var width = schema.width || params.width;
|
|
if (width && !(/^(large|mid|mini)$/.test(width))) {
|
|
if (width === '100%' || width === '*') {
|
|
element.removeClass('has-width');
|
|
}
|
|
form.css({
|
|
width: width,
|
|
minWidth: schema.minWidth || params.minWidth,
|
|
maxWidth: schema.maxWidth || params.maxWidth
|
|
});
|
|
form.removeClass('large-form mid-form mini-form');
|
|
}
|
|
scope.$timeout(function () {
|
|
element.append(form);
|
|
preparing = false;
|
|
if (scope._viewResolver) {
|
|
scope._viewResolver.resolve(schema, element);
|
|
scope.$broadcast("adjust:dialog");
|
|
}
|
|
}, Math.min(300, numFields * 2));
|
|
});
|
|
|
|
element.on('dblclick', '[x-field].readonly', function (e) {
|
|
if (!scope.isEditable()) {
|
|
element.prev('.record-toolbar')
|
|
.find('button[ng-click="onEdit()"] i')
|
|
.effect('pulsate', { times: 3 }, 600);
|
|
}
|
|
});
|
|
};
|
|
}]);
|
|
|
|
})();
|