diff --git a/application/Espo/Controllers/EntityManager.php b/application/Espo/Controllers/EntityManager.php
new file mode 100644
index 0000000000..19af966a68
--- /dev/null
+++ b/application/Espo/Controllers/EntityManager.php
@@ -0,0 +1,65 @@
+getUser()->isAdmin()) {
+ throw new Forbidden();
+ }
+ }
+
+ public function actionCreateEntity($params, $data, $request)
+ {
+ if (!$request->isPost()) {
+ throw new BadRequest();
+ }
+
+ if (empty($data['name']) || empty($data['type'])) {
+ throw new BadRequest();
+ }
+
+ $name = $data['name'];
+ $type = $data['type'];
+
+ $params = array();
+
+ if (!empty($data['labelSingular'])) {
+ $params['labelSingular'] = $data['labelSingular'];
+ }
+ if (!empty($data['labelPlural'])) {
+ $params['labelPlural'] = $data['labelPlural'];
+ }
+ if (!empty($data['stream'])) {
+ $params['stream'] = $data['stream'];
+ }
+
+ return $this->getContainer()->get('entityManagerUtil')->create($name, $type, $params);
+ }
+}
+
diff --git a/application/Espo/Core/Loaders/EntityManagerUtil.php b/application/Espo/Core/Loaders/EntityManagerUtil.php
new file mode 100644
index 0000000000..e6ad8e9152
--- /dev/null
+++ b/application/Espo/Core/Loaders/EntityManagerUtil.php
@@ -0,0 +1,38 @@
+getContainer()->get('metadata'),
+ $this->getContainer()->get('language'),
+ $this->getContainer()->get('fileManager')
+ );
+
+ return $entityManager;
+ }
+}
+
diff --git a/application/Espo/Core/Templates/Controllers/Base.php b/application/Espo/Core/Templates/Controllers/Base.php
new file mode 100644
index 0000000000..1f06cf5f79
--- /dev/null
+++ b/application/Espo/Core/Templates/Controllers/Base.php
@@ -0,0 +1,30 @@
+metadata = $metadata;
+ $this->language = $language;
+ $this->fileManager = $fileManager;
+
+ $this->metadataUtils = new \Espo\Core\Utils\Metadata\Utils($this->metadata);
+ }
+
+ protected function getMetadata()
+ {
+ return $this->metadata;
+ }
+
+ protected function getLanguage()
+ {
+ return $this->language;
+ }
+
+ protected function getFileManager()
+ {
+ return $this->fileManager;
+ }
+
+ protected function getMetadataUtils()
+ {
+ return $this->metadataUtils;
+ }
+
+ public function read($name, $scope)
+ {
+ $fieldDef = $this->getFieldDef($name, $scope);
+
+ $fieldDef['label'] = $this->getLanguage()->translate($name, 'fields', $scope);
+
+ return $fieldDef;
+ }
+
+ public function create($name, $type)
+ {
+ if ($this->getMetadata()->get('scope.' . $name)) {
+ throw new Conflict('Entity ['.$name.'] already exists.');
+ }
+
+ $contents = "<" . "?" . "php\n".
+ "namespace Espo\Custom\Entities;\n".
+ "class {$name} extends \Espo\Core\Templates\Entities\\{$type}\n".
+ "{\n".
+ "}\n";
+
+ $filePath = "custom/Espo/Custom/Entities/{$name}.php";
+ $this->getFileManager()->putContents($filePath, $contents);
+
+ $contents = "<" . "?" . "php\n".
+ "namespace Espo\Custom\Controllers;\n".
+ "class {$name} extends \Espo\Core\Templates\Controllers\\{$type}\n".
+ "{\n".
+ "}\n";
+ $filePath = "custom/Espo/Custom/Controllers/{$name}.php";
+ $this->getFileManager()->putContents($filePath, $contents);
+
+ $contents = "<" . "?" . "php\n".
+ "namespace Espo\Custom\Services;\n".
+ "class {$name} extends \Espo\Core\Templates\Services\\{$type}\n".
+ "{\n".
+ "}\n";
+ $filePath = "custom/Espo/Custom/Services/{$name}.php";
+ $this->getFileManager()->putContents($filePath, $contents);
+
+ $contents = "<" . "?" . "php\n".
+ "namespace Espo\Custom\Repositories;\n".
+ "class {$name} extends \Espo\Core\Templates\Repositories\\{$type}\n".
+ "{\n".
+ "}\n";
+
+ $filePath = "custom/Espo/Custom/Repositories/{$name}.php";
+ $this->getFileManager()->putContents($filePath, $contents);
+
+ $scopeData = array(
+ 'entity' => true,
+ 'layouts' => true,
+ 'tab' => true,
+ 'acl' => true,
+ 'module' => 'Custom',
+ 'isCustom' => true,
+ 'customizable' => true,
+ 'importable' => true,
+ 'type' => $type
+ );
+ $this->getMetadata()->set($scopeData, 'scopes', $name);
+
+ $filePath = "application/Espo/Core/Templates/Metadata/{$type}/entityDefs.json";
+ $entityDefsData = Json::decode($this->getFileManager()->getContents($filePath), true);
+ $this->getMetadata()->set($entityDefsData, 'entityDefs', $name);
+
+ $filePath = "application/Espo/Core/Templates/Metadata/{$type}/clientDefs.json";
+ $clientDefsData = Json::decode($this->getFileManager()->getContents($filePath), true);
+ $this->getMetadata()->set($clientDefsData, 'clientDefs', $name);
+ }
+
+ public function update($name, $fieldDef, $scope)
+ {
+ /*Add option to metadata to identify the custom field*/
+ if (!$this->isCore($name, $scope)) {
+ $fieldDef['isCustom'] = true;
+ }
+
+ $res = true;
+ if (isset($fieldDef['label'])) {
+ $this->setLabel($name, $fieldDef['label'], $scope);
+ }
+
+ if (isset($fieldDef['type']) && $fieldDef['type'] == 'enum') {
+ if (isset($fieldDef['translatedOptions'])) {
+ $this->setTranslatedOptions($name, $fieldDef['translatedOptions'], $scope);
+ }
+ }
+
+ if (isset($fieldDef['label']) || isset($fieldDef['translatedOptions'])) {
+ $res &= $this->getLanguage()->save();
+ }
+
+ if ($this->isDefsChanged($name, $fieldDef, $scope)) {
+ $res &= $this->setEntityDefs($name, $fieldDef, $scope);
+ }
+
+ return (bool) $res;
+ }
+
+ public function delete($name, $scope)
+ {
+ if ($this->isCore($name, $scope)) {
+ throw new Error('Cannot delete core field ['.$name.'] in '.$scope);
+ }
+
+ $unsets = array(
+ 'fields.'.$name,
+ 'links.'.$name,
+ );
+
+ $res = $this->getMetadata()->delete($unsets, $this->metadataType, $scope);
+ $res &= $this->deleteLabel($name, $scope);
+
+ return (bool) $res;
+ }
+
+ protected function setEntityDefs($name, $fieldDef, $scope)
+ {
+ $fieldDef = $this->normalizeDefs($name, $fieldDef, $scope);
+
+ $data = Json::encode($fieldDef);
+ $res = $this->getMetadata()->set($data, $this->metadataType, $scope);
+
+ return $res;
+ }
+
+ protected function setTranslatedOptions($name, $value, $scope)
+ {
+ return $this->getLanguage()->set($name, $value, 'options', $scope);
+ }
+
+ protected function setLabel($name, $value, $scope)
+ {
+ return $this->getLanguage()->set($name, $value, 'fields', $scope);
+ }
+
+ protected function deleteLabel($name, $scope)
+ {
+ $this->getLanguage()->delete($name, 'fields', $scope);
+ return $this->getLanguage()->save();
+ }
+
+ protected function getFieldDef($name, $scope)
+ {
+ return $this->getMetadata()->get($this->metadataType.'.'.$scope.'.fields.'.$name);
+ }
+
+ protected function getLinkDef($name, $scope)
+ {
+ return $this->getMetadata()->get($this->metadataType.'.'.$scope.'.links.'.$name);
+ }
+
+ /**
+ * Prepare input fieldDefs, remove unnecessary fields
+ *
+ * @param string $fieldName
+ * @param array $fieldDef
+ * @param string $scope
+ * @return array
+ */
+ protected function prepareFieldDef($name, $fieldDef, $scope)
+ {
+ $unnecessaryFields = array(
+ 'name',
+ 'label',
+ );
+
+ foreach ($unnecessaryFields as $fieldName) {
+ if (isset($fieldDef[$fieldName])) {
+ unset($fieldDef[$fieldName]);
+ }
+ }
+
+ if (isset($fieldDef['linkDefs'])) {
+ $linkDefs = $fieldDef['linkDefs'];
+ unset($fieldDef['linkDefs']);
+ }
+
+ $currentOptionList = array_keys((array) $this->getFieldDef($name, $scope));
+ foreach ($fieldDef as $defName => $defValue) {
+ if ( (!isset($defValue) || $defValue === '') && !in_array($defName, $currentOptionList) ) {
+ unset($fieldDef[$defName]);
+ }
+ }
+
+ return $fieldDef;
+ }
+
+ /**
+ * Add all needed block for a field defenition
+ *
+ * @param string $fieldName
+ * @param array $fieldDef
+ * @param string $scope
+ * @return array
+ */
+ protected function normalizeDefs($fieldName, array $fieldDef, $scope)
+ {
+ $fieldDef = $this->prepareFieldDef($fieldName, $fieldDef, $scope);
+
+ $metaFieldDef = $this->getMetadataUtils()->getFieldDefsInFieldMeta($fieldDef);
+ if (isset($metaFieldDef)) {
+ $fieldDef = Util::merge($metaFieldDef, $fieldDef);
+ }
+
+ $defs = array(
+ 'fields' => array(
+ $fieldName => $fieldDef,
+ ),
+ );
+
+ /** Save links for a field. */
+ $metaLinkDef = $this->getMetadataUtils()->getLinkDefsInFieldMeta($scope, $fieldDef);
+ if (isset($linkDefs) || isset($metaLinkDef)) {
+ $linkDefs = Util::merge((array) $metaLinkDef, (array) $linkDefs);
+ $defs['links'] = array(
+ $fieldName => $linkDefs,
+ );
+ }
+
+ return $defs;
+ }
+
+ /**
+ * Check if changed metadata defenition for a field except 'label'
+ *
+ * @return boolean
+ */
+ protected function isDefsChanged($name, $fieldDef, $scope)
+ {
+ $fieldDef = $this->prepareFieldDef($name, $fieldDef, $scope);
+ $currentFieldDef = $this->getFieldDef($name, $scope);
+
+ $this->isChanged = Util::isEquals($fieldDef, $currentFieldDef) ? false : true;
+
+ return $this->isChanged;
+ }
+
+ /**
+ * Only for update method
+ *
+ * @return boolean
+ */
+ public function isChanged()
+ {
+ return $this->isChanged;
+ }
+
+ /**
+ * Check if a field is core field
+ *
+ * @param string $name
+ * @param string $scope
+ * @return boolean
+ */
+ protected function isCore($name, $scope)
+ {
+ $existingField = $this->getFieldDef($name, $scope);
+ if (isset($existingField) && (!isset($existingField['isCustom']) || !$existingField['isCustom'])) {
+ return true;
+ }
+
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/application/Espo/Resources/i18n/en_US/Admin.json b/application/Espo/Resources/i18n/en_US/Admin.json
index 4df9f83b0a..7c4ce7fdce 100644
--- a/application/Espo/Resources/i18n/en_US/Admin.json
+++ b/application/Espo/Resources/i18n/en_US/Admin.json
@@ -9,6 +9,8 @@
"Customization": "Customization",
"Available Fields": "Available Fields",
"Layout": "Layout",
+ "Entity Manager": "Entity Manager",
+ "Relationship Manager": "Relationship Manager",
"Add Panel": "Add Panel",
"Add Field": "Add Field",
"Settings": "Settings",
@@ -40,7 +42,9 @@
"Install": "Install",
"Ready for installation": "Ready for installation",
"Uninstalling...": "Uninstalling...",
- "Uninstalled": "Uninstalled"
+ "Uninstalled": "Uninstalled",
+ "Create Entity": "Create Entity",
+ "Edit Entity": "Edit Entity"
},
"layouts": {
"list": "List",
@@ -128,6 +132,8 @@
"import": "Import data from CSV file.",
"layoutManager": "Customize layouts (list, detail, edit, search, mass update).",
"fieldManager": "Create new fields or customize existing ones.",
+ "entityManager": "Create custom entities. Edit existing ones.",
+ "relationshipManager": "Manage relationships between entities.",
"userInterface": "Configure UI.",
"authTokens": "Active auth sessions. IP address and last access date.",
"authentication": "Authentication settings.",
diff --git a/application/Espo/Resources/i18n/en_US/EntityManager.json b/application/Espo/Resources/i18n/en_US/EntityManager.json
new file mode 100644
index 0000000000..4da335dffe
--- /dev/null
+++ b/application/Espo/Resources/i18n/en_US/EntityManager.json
@@ -0,0 +1,24 @@
+{
+ "labels": {
+ "Fields": "Fields",
+ "Relationships": "Relationships"
+ },
+ "fields": {
+ "systemName": "System Name",
+ "name": "Name",
+ "type": "Type",
+ "labelSingular": "Label Singular",
+ "labelPlural": "Label Plural",
+ "stream": "Stream"
+ },
+ "options": {
+ "type": {
+ "": "None",
+ "Base": "Base",
+ "Person": "Person"
+ }
+ },
+ "messages": {
+ "entityCreated": "Entity has been created"
+ }
+}
diff --git a/application/Espo/Resources/metadata/app/adminPanel.json b/application/Espo/Resources/metadata/app/adminPanel.json
index 3214279871..5204469291 100644
--- a/application/Espo/Resources/metadata/app/adminPanel.json
+++ b/application/Espo/Resources/metadata/app/adminPanel.json
@@ -107,6 +107,16 @@
"label":"Field Manager",
"description":"fieldManager"
},
+ {
+ "url":"#Admin/entityManager",
+ "label":"Entity Manager",
+ "description":"entityManager"
+ },
+ {
+ "url":"#Admin/relationshipManager",
+ "label":"Relationship Manager",
+ "description":"relationshipManager"
+ },
{
"url":"#Admin/userInterface",
"label":"User Interface",
diff --git a/frontend/client/res/templates/admin/entity-manager/index.tpl b/frontend/client/res/templates/admin/entity-manager/index.tpl
new file mode 100644
index 0000000000..7944c33041
--- /dev/null
+++ b/frontend/client/res/templates/admin/entity-manager/index.tpl
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
diff --git a/frontend/client/res/templates/admin/entity-manager/modals/edit-entity.tpl b/frontend/client/res/templates/admin/entity-manager/modals/edit-entity.tpl
new file mode 100644
index 0000000000..bce983b19e
--- /dev/null
+++ b/frontend/client/res/templates/admin/entity-manager/modals/edit-entity.tpl
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+{{#if stream}}
+
+{{/if}}
diff --git a/frontend/client/src/controllers/admin.js b/frontend/client/src/controllers/admin.js
index c455ac86a4..cb54462693 100644
--- a/frontend/client/src/controllers/admin.js
+++ b/frontend/client/src/controllers/admin.js
@@ -47,6 +47,19 @@ Espo.define('Controllers.Admin', 'Controller', function (Dep) {
this.main('Admin.FieldManager.Index', {scope: scope, field: field});
},
+ entityManager: function (options) {
+ var scope = options.scope || null;
+
+ this.main('Admin.EntityManager.Index', {scope: scope});
+ },
+
+ relationshipManager: function (options) {
+ var scope = options.scope || null;
+ var link = options.link || null;
+
+ this.main('Admin.RelationshipManager.Index', {scope: scope, link: link});
+ },
+
upgrade: function (options) {
this.main('Admin.Upgrade.Index');
},
@@ -71,7 +84,7 @@ Espo.define('Controllers.Admin', 'Controller', function (Dep) {
},
});
}, this);
- model.fetch();
+ model.fetch();
},
outboundEmail: function () {
diff --git a/frontend/client/src/views/admin/entity-manager/index.js b/frontend/client/src/views/admin/entity-manager/index.js
new file mode 100644
index 0000000000..5103c5df59
--- /dev/null
+++ b/frontend/client/src/views/admin/entity-manager/index.js
@@ -0,0 +1,139 @@
+/************************************************************************
+ * This file is part of EspoCRM.
+ *
+ * EspoCRM - Open Source CRM application.
+ * Copyright (C) 2014-2015 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
+ * Website: http://www.espocrm.com
+ *
+ * EspoCRM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * EspoCRM 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
+ ************************************************************************/
+
+Espo.define('Views.Admin.EntityManager.Index', 'View', function (Dep) {
+
+ return Dep.extend({
+
+ template: 'admin.entity-manager.index',
+
+ scopeDataList: null,
+
+ scope: null,
+
+ type: null,
+
+ data: function () {
+ return {
+ scopeDataList: this.scopeDataList,
+ scope: this.scope,
+ };
+ },
+
+ events: {
+ 'click a[data-action="editEntity"]': function (e) {
+ var scope = $(e.currentTarget).data('scope');
+ this.editEntity(scope);
+ },
+ 'click button[data-action="createEntity"]': function (e) {
+ this.editEntity();
+ },
+ 'click [data-action="removeEntity"]': function (e) {
+ var scope = $(e.currentTarget).data('scope');
+ if (confirm(this.translate('confirmation', 'messages'))) {
+ this.removeEntity(scope);
+ }
+ }
+ },
+
+ setup: function () {
+ this.scopeDataList = [];
+
+ var scopeList = Object.keys(this.getMetadata().get('scopes')).sort(function (v1, v2) {
+ return this.translate(v1, 'scopeNames').localeCompare(this.translate(v2, 'scopeNames'));
+ }.bind(this));
+
+ scopeList.forEach(function (scope) {
+ var d = this.getMetadata().get('scopes.' + scope);
+ if (d.entity) {
+ this.scopeDataList.push({
+ name: scope,
+ isCustom: d.isCustom,
+ customizable: d.customizable,
+ type: d.type
+ });
+
+ }
+ }, this);
+
+ this.scope = this.options.scope || null;
+
+ this.on('after:render', function () {
+ this.renderHeader();
+ if (!this.scope) {
+ this.renderDefaultPage();
+ } else {
+ if (!this.field) {
+ this.openScope(this.scope);
+ } else {
+ this.openField(this.scope, this.field);
+ }
+ }
+ });
+ },
+
+ createEntity: function () {
+ this.createView('edit', 'Admin.EntityManager.Modals.EditEntity', {}, function (view) {
+ view.render();
+ });
+ },
+
+ editEntity: function (scope) {
+ this.createView('edit', 'Admin.EntityManager.Modals.EditEntity', {
+ scope: scope
+ }, function (view) {
+ view.render();
+ });
+ },
+
+ removeEntity: function (scope) {
+ $.ajax({
+ url: 'EntityManager/action/removeEntity',
+ type: 'POST',
+ data: JSON.stringify({
+ scope: scope
+ })
+ }).done(function () {
+ this.$el.find('table tr[data-scope="'+scope+'"]').remove();
+ }.bind(this));
+ },
+
+ renderDefaultPage: function () {
+ $('#fields-header').html('').hide();
+ $('#fields-content').html(this.translate('selectEntityType', 'messages', 'Admin'));
+ },
+
+ renderHeader: function () {
+ if (!this.scope) {
+ $('#scope-header').html('');
+ return;
+ }
+
+ $('#scope-header').show().html(this.getLanguage().translate(this.scope, 'scopeNames'));
+ },
+
+ updatePageTitle: function () {
+ this.setPageTitle(this.getLanguage().translate('Entity Manager', 'labels', 'Admin'));
+ },
+ });
+});
+
+
diff --git a/frontend/client/src/views/admin/entity-manager/modals/edit-entity.js b/frontend/client/src/views/admin/entity-manager/modals/edit-entity.js
new file mode 100644
index 0000000000..056c9071ac
--- /dev/null
+++ b/frontend/client/src/views/admin/entity-manager/modals/edit-entity.js
@@ -0,0 +1,195 @@
+/************************************************************************
+ * This file is part of EspoCRM.
+ *
+ * EspoCRM - Open Source CRM application.
+ * Copyright (C) 2014-2015 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
+ * Website: http://www.espocrm.com
+ *
+ * EspoCRM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * EspoCRM 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
+ ************************************************************************/
+
+Espo.define('Views.Admin.EntityManager.Modals.EditEntity', 'Views.Modal', function (Dep) {
+
+ return Dep.extend({
+
+ cssName: 'edit-entity',
+
+ template: 'admin.entity-manager.modals.edit-entity',
+
+ setup: function () {
+
+ this.buttons = [
+ {
+ name: 'save',
+ label: 'Save',
+ style: 'danger',
+ onClick: function (dialog) {
+ this.save();
+ }.bind(this)
+ },
+ {
+ name: 'cancel',
+ label: 'Cancel',
+ onClick: function (dialog) {
+ dialog.close();
+ }
+ }
+ ];
+
+ var scope = this.scope = this.options.scope || false;
+
+ var header = 'Create Entity';
+ if (scope) {
+ header = 'Edit Entity';
+ }
+
+ this.header = this.translate(header, 'labels', 'Admin');
+
+ var model = this.model = new Espo.Model();
+ model.name = 'EntityManager';
+
+ this.hasStreamField = true;
+ if (scope) {
+ this.hasStreamField = this.getMetadata().get('scopes.' + scope + '.customizable') || false;
+ }
+
+ if (scope) {
+ this.model.set('name', scope);
+ this.model.set('labelSingular', this.translate(scope, 'scopeNames'));
+ this.model.set('labelPlural', this.translate(scope, 'scopeNamesPlural'));
+ this.model.set('type', this.getMetadata().get('scopes.' + scope + '.type') || '');
+ this.model.set('stream', this.getMetadata().get('scopes.' + scope + '.stream') || false);
+ }
+
+ this.createView('type', 'Fields.Enum', {
+ model: model,
+ mode: 'edit',
+ el: this.options.el + ' .field-type',
+ defs: {
+ name: 'type',
+ params: {
+ required: true,
+ options: ['Base', 'Person']
+ }
+ },
+ readOnly: scope != false
+ });
+
+ if (this.hasStreamField) {
+ this.createView('stream', 'Fields.Bool', {
+ model: model,
+ mode: 'edit',
+ el: this.options.el + ' .field-stream',
+ defs: {
+ name: 'stream'
+ }
+ });
+ }
+
+ this.createView('name', 'Fields.Varchar', {
+ model: model,
+ mode: 'edit',
+ el: this.options.el + ' .field-name',
+ defs: {
+ name: 'name',
+ params: {
+ required: true
+ }
+ },
+ readOnly: scope != false
+ });
+ this.createView('labelSingular', 'Fields.Varchar', {
+ model: model,
+ mode: 'edit',
+ el: this.options.el + ' .field-labelSingular',
+ defs: {
+ name: 'labelSingular',
+ params: {
+ required: true
+ }
+ }
+ });
+ this.createView('labelPlural', 'Fields.Varchar', {
+ model: model,
+ mode: 'edit',
+ el: this.options.el + ' .field-labelPlural',
+ defs: {
+ name: 'labelPlural',
+ params: {
+ required: true
+ }
+ }
+ });
+ },
+
+ save: function () {
+ var arr = [
+ 'name',
+ 'type',
+ 'labelSingular',
+ 'labelPlural',
+ 'stream'
+ ];
+
+ var notValid = false;
+
+ arr.forEach(function (item) {
+ if (!this.hasView(item)) return;
+ if (this.getView(item).mode != 'edit') return;
+ this.getView(item).fetchToModel();
+ }, this);
+
+ arr.forEach(function (item) {
+ if (!this.hasView(item)) return;
+ if (this.getView(item).mode != 'edit') return;
+ notValid = this.getView(item).validate() || notValid;
+ }, this);
+
+ if (notValid) {
+ return;
+ }
+
+ this.$el.find('button[data-name="save"]').addClass('disabled');
+
+ var url = 'EntityManager/action/createEntity';
+ if (this.scope) {
+ url = 'EntityManager/action/updateEntity';
+ }
+
+ $.ajax({
+ url: url,
+ type: 'POST',
+ data: JSON.stringify({
+ name: this.model.get('name'),
+ labelSingular: this.model.get('labelSingular'),
+ labelPlural: this.model.get('labelPlural'),
+ type: this.model.get('type')
+ }),
+ error: function () {
+ this.$el.find('button[data-name="save"]').removeClass('disabled');
+ }.bind(this)
+ }).done(function () {
+ if (this.scope) {
+ Espo.Ui.success(this.translate('Saved'));
+ } else {
+ Espo.Ui.success(this.translate('entityCreated', 'messages', 'EntityManager'));
+ }
+ this.trigger('saved');
+ this.close();
+ }.bind(this));
+ },
+
+ });
+});
+
diff --git a/frontend/client/src/views/admin/field-manager/index.js b/frontend/client/src/views/admin/field-manager/index.js
index 6564356603..252b9c1bfb 100644
--- a/frontend/client/src/views/admin/field-manager/index.js
+++ b/frontend/client/src/views/admin/field-manager/index.js
@@ -17,7 +17,7 @@
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
- ************************************************************************/
+ ************************************************************************/
Espo.define('Views.Admin.FieldManager.Index', 'View', function (Dep) {
@@ -43,16 +43,16 @@ Espo.define('Views.Admin.FieldManager.Index', 'View', function (Dep) {
var scope = $(e.currentTarget).data('scope');
this.openScope(scope);
},
-
+
'click #fields-content a.field-link': function (e) {
var scope = $(e.currentTarget).data('scope');
var field = $(e.currentTarget).data('field');
this.openField(scope, field);
},
-
+
'click a[data-action="addField"]': function (e) {
var scope = $(e.currentTarget).data('scope');
- var type = $(e.currentTarget).data('type');
+ var type = $(e.currentTarget).data('type');
this.createField(scope, type);
},
},
@@ -68,15 +68,15 @@ Espo.define('Views.Admin.FieldManager.Index', 'View', function (Dep) {
if (this.getMetadata().get('scopes.' + scope + '.entity')) {
if (this.getMetadata().get('scopes.' + scope + '.customizable')) {
this.scopeList.push(scope);
- }
+ }
}
}.bind(this));
this.scope = this.options.scope || null;
- this.field = this.options.field || null;
-
- this.on('after:render', function () {
- this.renderFieldsHeader();
+ this.field = this.options.field || null;
+
+ this.on('after:render', function () {
+ this.renderFieldsHeader();
if (!this.scope) {
this.renderDefaultPage();
} else {
@@ -85,35 +85,35 @@ Espo.define('Views.Admin.FieldManager.Index', 'View', function (Dep) {
} else {
this.openField(this.scope, this.field);
}
- }
- });
+ }
+ });
},
openScope: function (scope) {
this.scope = scope;
this.field = null;
-
+
this.getRouter().navigate('#Admin/fieldManager/scope=' + scope, {trigger: false});
-
- this.notify('Loading...');
+
+ this.notify('Loading...');
this.createView('content', 'Admin.FieldManager.List', {
el: '#fields-content',
scope: scope,
}, function (view) {
this.renderFieldsHeader();
view.render();
- this.notify(false);
+ this.notify(false);
$(window).scrollTop(0);
}.bind(this));
},
-
+
openField: function (scope, field) {
this.scope = scope;
this.field = field
-
+
this.getRouter().navigate('#Admin/fieldManager/scope=' + scope + '&field=' + field, {trigger: false});
-
- this.notify('Loading...');
+
+ this.notify('Loading...');
this.createView('content', 'Admin.FieldManager.Edit', {
el: '#fields-content',
scope: scope,
@@ -125,14 +125,14 @@ Espo.define('Views.Admin.FieldManager.Index', 'View', function (Dep) {
$(window).scrollTop(0);
}.bind(this));
},
-
+
createField: function (scope, type) {
this.scope = scope;
this.type = type;
-
+
this.getRouter().navigate('#Admin/fieldManager/scope=' + scope + '&type=' + type + '&create=true', {trigger: false});
-
- this.notify('Loading...');
+
+ this.notify('Loading...');
this.createView('content', 'Admin.FieldManager.Edit', {
el: '#fields-content',
scope: scope,
@@ -155,7 +155,7 @@ Espo.define('Views.Admin.FieldManager.Index', 'View', function (Dep) {
$('#fields-header').html('');
return;
}
-
+
if (this.field) {
$('#fields-header').show().html('' + this.getLanguage().translate(this.scope, 'scopeNamesPlural') + ' ยป ' + this.field);
} else {
@@ -164,7 +164,7 @@ Espo.define('Views.Admin.FieldManager.Index', 'View', function (Dep) {
},
updatePageTitle: function () {
- this.setPageTitle(this.getLanguage().translate('Field Manager'));
+ this.setPageTitle(this.getLanguage().translate('Field Manager', 'labels', 'Admin'));
},
});
});
diff --git a/frontend/client/src/views/admin/layouts/index.js b/frontend/client/src/views/admin/layouts/index.js
index 5a5c3771e8..bc6973092f 100644
--- a/frontend/client/src/views/admin/layouts/index.js
+++ b/frontend/client/src/views/admin/layouts/index.js
@@ -17,7 +17,7 @@
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
- ************************************************************************/
+ ************************************************************************/
Espo.define('Views.Admin.Layouts.Index', 'View', function (Dep) {
@@ -28,7 +28,7 @@ Espo.define('Views.Admin.Layouts.Index', 'View', function (Dep) {
scopeList: null,
typeList: ['list', 'detail', 'listSmall', 'detailSmall', 'filters', 'massUpdate', 'relationships'],
-
+
additionalLayouts: {
'Opportunity': ['detailConvert'],
'Contact': ['detailConvert'],
@@ -53,9 +53,9 @@ Espo.define('Views.Admin.Layouts.Index', 'View', function (Dep) {
(this.additionalLayouts[scope] || []).forEach(function (item) {
d.typeList.push(item);
});
-
+
dataList.push(d);
- }, this);
+ }, this);
return dataList;
}).call(this)
};
@@ -77,7 +77,7 @@ Espo.define('Views.Admin.Layouts.Index', 'View', function (Dep) {
},
setup: function () {
- this.scopeList = [];
+ this.scopeList = [];
var scopesAll = Object.keys(this.getMetadata().get('scopes')).sort(function (v1, v2) {
return this.translate(v1, 'scopeNamesPlural').localeCompare(this.translate(v2, 'scopeNamesPlural'));
@@ -88,7 +88,7 @@ Espo.define('Views.Admin.Layouts.Index', 'View', function (Dep) {
this.getMetadata().get('scopes.' + scope + '.layouts')) {
this.scopeList.push(scope);
}
- }.bind(this));
+ }.bind(this));
this.on('after:render', function () {
$("#layouts-menu button[data-scope='" + this.options.scope + "'][data-type='" + this.options.type + "']").addClass('disabled');
@@ -139,7 +139,7 @@ Espo.define('Views.Admin.Layouts.Index', 'View', function (Dep) {
},
updatePageTitle: function () {
- this.setPageTitle(this.getLanguage().translate('Layout Manager'));
+ this.setPageTitle(this.getLanguage().translate('Layout Manager', 'labels', 'Admin'));
},
});
});