link manager dev

This commit is contained in:
yuri
2015-01-27 16:21:22 +02:00
parent bc65975f74
commit 9b846b45bc
13 changed files with 530 additions and 64 deletions
@@ -24,7 +24,6 @@
"Email Templates": "Email Templates",
"Import": "Import",
"Layout Manager": "Layout Manager",
"Field Manager": "Field Manager",
"User Interface": "User Interface",
"Auth Tokens": "Auth Tokens",
"Authentication": "Authentication",
@@ -43,7 +42,8 @@
"Uninstalling...": "Uninstalling...",
"Uninstalled": "Uninstalled",
"Create Entity": "Create Entity",
"Edit Entity": "Edit Entity"
"Edit Entity": "Edit Entity",
"Create Link": "Create Link"
},
"layouts": {
"list": "List",
@@ -130,8 +130,7 @@
"emailTemplates": "Templates for outbound emails.",
"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.",
"entityManager": "Create custom entities, edit existing ones. Manage field and relationships.",
"userInterface": "Configure UI.",
"authTokens": "Active auth sessions. IP address and last access date.",
"authentication": "Authentication settings.",
@@ -16,6 +16,11 @@
"": "None",
"Base": "Base",
"Person": "Person"
},
"linkType": {
"manyToMany": "Many-to-Many",
"oneToMany": "One-to-Many",
"manyToOne": "Many-to-One"
}
},
"messages": {
@@ -107,11 +107,6 @@
"label":"Layout Manager",
"description":"layoutManager"
},
{
"url":"#Admin/fieldManager",
"label":"Field Manager",
"description":"fieldManager"
},
{
"url":"#Admin/entityManager",
"label":"Entity Manager",
@@ -4,11 +4,15 @@
<button class="btn btn-primary" data-action="createEntity">{{translate 'Create Entity' scope='Admin'}}</button>
</div>
<table class="table table-bordered table-hover">
<table class="table table-hover">
<tr>
<th>{{translate 'name' scope='EntityManager' category='fields'}}</th>
<th>{{translate 'label' scope='EntityManager' category='fields'}}</th>
<th>{{translate 'type' scope='EntityManager' category='fields'}}</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
{{#each scopeDataList}}
<tr data-scope="{{name}}">
@@ -30,7 +34,7 @@
</td>
<td width="10%">
{{#if customizable}}
<a href="#Admin/relationshipManager/scope={{name}}">{{translate 'Relationships' scope='EntityManager'}}</a>
<a href="#Admin/linkManager/scope={{name}}">{{translate 'Relationships' scope='EntityManager'}}</a>
{{/if}}
</td>
<td align="right" width="10%">
@@ -5,6 +5,8 @@
<div class="row">
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-body">
<div class="cell cell-type form-group">
<label class="control-label">{{translate 'type' scope='Admin' category='fields'}}</label>
<div class="field field-type">{{translate type scope='Admin' category='fieldTypes'}}</div>
@@ -17,7 +19,6 @@
<label class="control-label">{{translate 'label' scope='Admin' category='fields'}}</label>
<div class="field field-label">{{{label}}}</div>
</div>
{{#each params}}
{{#unless hidden}}
<div class="cell cell-{{../name}} form-group">
@@ -27,4 +28,6 @@
{{/unless}}
{{/each}}
</div>
</div>
</div>
</div>
@@ -1,19 +1,20 @@
<div class="page-header"><h3><a href="#Admin">{{translate 'Administration'}}</a> &raquo {{translate 'Field Manager' scope='Admin'}}</h3></div>
<div class="page-header">
<h3>
<a href="#Admin">{{translate 'Administration'}}</a>
&raquo
<a href="#Admin/entityManager">{{translate 'Entity Manager' scope='Admin'}}</a>
&raquo
{{translate scope category='scopeNames'}}
&raquo
{{translate 'Fields' scope='EntityManager'}}
</h3>
</div>
<div class="row">
<div id="scopes-menu" class="col-sm-3">
<ul class="list-group">
{{#each scopeList}}
<li class="list-group-item"><a href="javascript:" class="scope-link" data-scope="{{./this}}">{{translate this category='scopeNamesPlural'}}</a></li>
{{/each}}
</ul>
</div>
<div id="fields-panel" class="col-sm-9">
<h4 id="fields-header" style="margin-top: 0px;"></h4>
<div id="fields-content">
{{{content}}}
</div>
</div>
</div>
</div>
@@ -0,0 +1,56 @@
<div class="page-header">
<h3>
<a href="#Admin">{{translate 'Administration'}}</a>
&raquo
<a href="#Admin/entityManager">{{translate 'Entity Manager' scope='Admin'}}</a>
&raquo
{{translate scope category='scopeNames'}}
&raquo
{{translate 'Relationships' scope='EntityManager'}}
</h3>
</div>
<div class="button-container">
<button class="btn btn-primary" data-action="createLink">{{translate 'Create Link' scope='Admin'}}</button>
</div>
<table class="table">
{{#each linkDataList}}
<tr data-scope="{{name}}">
<td width="15%" align="left">
{{translate entity category='scopeNames'}}
</td>
<td width="15%" align="left">
{{nameForeign}}
</td>
<td width="10%" align="center">
<strong>
{{translateOption type field='linkType' scope='EntityManager'}}
</strong>
</td>
<td width="15%" align="right">
{{name}}
</td>
<td width="15%" align="right">
{{translate foreignEntity category='scopeNames'}}
</td>
<td align="right" width="10%">
{{#if isCustom}}
<a href="javascript:" data-action="editLink" data-link="{{name}}">
{{translate 'Edit'}}
</a>
{{/if}}
</td>
<td align="right" width="10%">
{{#if isCustom}}
<a href="javascript:" data-action="removeLink" data-link="{{name}}">
{{translate 'Remove'}}
</a>
{{/if}}
</td>
</tr>
{{/each}}
</table>
@@ -0,0 +1,38 @@
<div class="row">
<div class="cell cell-name form-group col-md-6">
<label class="field-label-name control-label">{{translate 'name' category='fields' scope='EntityManager'}}</label>
<div class="field field-name">
{{{name}}}
</div>
</div>
<div class="cell cell-type form-group col-md-6">
<label class="field-label-type control-label">{{translate 'type' category='fields' scope='EntityManager'}}</label>
<div class="field field-type">
{{{type}}}
</div>
</div>
</div>
<div class="row">
<div class="cell cell-labelSingular form-group col-md-6">
<label class="field-label-labelSingular control-label">{{translate 'labelSingular' category='fields' scope='EntityManager'}}</label>
<div class="field field-labelSingular">
{{{labelSingular}}}
</div>
</div>
<div class="cell cell-labelPlural form-group col-md-6">
<label class="field-label-labelPlural control-label">{{translate 'labelPlural' category='fields' scope='EntityManager'}}</label>
<div class="field field-labelPlural">
{{{labelPlural}}}
</div>
</div>
</div>
<div class="row">
{{#if stream}}
<div class="cell cell-stream form-group col-md-6">
<label class="field-label-stream control-label">{{translate 'stream' category='fields' scope='EntityManager'}}</label>
<div class="field field-stream">
{{{stream}}}
</div>
</div>
{{/if}}
</div>
+2 -3
View File
@@ -53,11 +53,10 @@ Espo.define('Controllers.Admin', 'Controller', function (Dep) {
this.main('Admin.EntityManager.Index', {scope: scope});
},
relationshipManager: function (options) {
linkManager: function (options) {
var scope = options.scope || null;
var link = options.link || null;
this.main('Admin.RelationshipManager.Index', {scope: scope, link: link});
this.main('Admin.LinkManager.Index', {scope: scope});
},
upgrade: function (options) {
@@ -29,8 +29,6 @@ Espo.define('Views.Admin.EntityManager.Index', 'View', function (Dep) {
scope: null,
type: null,
data: function () {
return {
scopeDataList: this.scopeDataList,
@@ -44,7 +42,7 @@ Espo.define('Views.Admin.EntityManager.Index', 'View', function (Dep) {
this.editEntity(scope);
},
'click button[data-action="createEntity"]': function (e) {
this.editEntity();
this.createEntity();
},
'click [data-action="removeEntity"]': function (e) {
var scope = $(e.currentTarget).data('scope');
@@ -81,9 +79,6 @@ Espo.define('Views.Admin.EntityManager.Index', 'View', function (Dep) {
this.setupScopeData();
this.on('after:render', function () {
this.renderHeader();
});
},
createEntity: function () {
@@ -128,20 +123,6 @@ Espo.define('Views.Admin.EntityManager.Index', 'View', function (Dep) {
}.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'));
},
@@ -76,7 +76,6 @@ Espo.define('Views.Admin.FieldManager.Index', 'View', function (Dep) {
this.field = this.options.field || null;
this.on('after:render', function () {
this.renderFieldsHeader();
if (!this.scope) {
this.renderDefaultPage();
} else {
@@ -100,7 +99,6 @@ Espo.define('Views.Admin.FieldManager.Index', 'View', function (Dep) {
el: '#fields-content',
scope: scope,
}, function (view) {
this.renderFieldsHeader();
view.render();
this.notify(false);
$(window).scrollTop(0);
@@ -119,7 +117,6 @@ Espo.define('Views.Admin.FieldManager.Index', 'View', function (Dep) {
scope: scope,
field: field,
}, function (view) {
this.renderFieldsHeader();
view.render();
this.notify(false);
$(window).scrollTop(0);
@@ -138,7 +135,6 @@ Espo.define('Views.Admin.FieldManager.Index', 'View', function (Dep) {
scope: scope,
type: type,
}, function (view) {
this.renderFieldsHeader();
view.render();
this.notify(false);
$(window).scrollTop(0);
@@ -146,23 +142,9 @@ Espo.define('Views.Admin.FieldManager.Index', 'View', function (Dep) {
},
renderDefaultPage: function () {
$('#fields-header').html('').hide();
$('#fields-content').html(this.translate('selectEntityType', 'messages', 'Admin'));
},
renderFieldsHeader: function () {
if (!this.scope) {
$('#fields-header').html('');
return;
}
if (this.field) {
$('#fields-header').show().html('<a href="#Admin/fieldManager/scope='+this.scope+'">' + this.getLanguage().translate(this.scope, 'scopeNamesPlural') + '</a> » ' + this.field);
} else {
$('#fields-header').show().html(this.getLanguage().translate(this.scope, 'scopeNamesPlural'));
}
},
updatePageTitle: function () {
this.setPageTitle(this.getLanguage().translate('Field Manager', 'labels', 'Admin'));
},
@@ -0,0 +1,177 @@
/************************************************************************
* 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.LinkManager.Index', 'View', function (Dep) {
return Dep.extend({
template: 'admin.link-manager.index',
scopeDataList: null,
scope: null,
data: function () {
return {
linkDataList: this.linkDataList,
scope: this.scope,
};
},
events: {
'click a[data-action="editLink"]': function (e) {
var link = $(e.currentTarget).data('link');
this.editLink(link);
},
'click button[data-action="createLink"]': function (e) {
this.createLink();
},
'click [data-action="removeLink"]': function (e) {
var link = $(e.currentTarget).data('link');
if (confirm(this.translate('confirmation', 'messages'))) {
this.removeLink(link);
}
}
},
setupLinkData: function () {
this.linkDataList = [];
var links = this.getMetadata().get('entityDefs.' + this.scope + '.links');
var linkList = Object.keys(links).sort(function (v1, v2) {
return v1.localeCompare(v2);
}.bind(this));
linkList.forEach(function (name) {
var d = links[name];
if (!d.foreign) return;
if (!d.entity) return;
var foreignType = this.getMetadata().get('entityDefs.' + d.entity + '.links.' + d.foreign + '.type');
var type;
if (d.type == 'hasMany') {
if (foreignType == 'hasMany') {
type = 'manyToMany';
} else if (foreignType == 'belongsTo') {
type = 'oneToMany';
} else {
return;
}
} else if (d.type == 'belongsTo') {
if (foreignType == 'hasMany') {
type = 'manyToOne';
} else {
return;
}
} else {
return;
}
this.linkDataList.push({
name: name,
isCustom: d.isCustom,
customizable: d.customizable,
type: type,
foreignEntity: d.entity,
entity: this.scope,
nameForeign: d.foreign,
label: this.getLanguage().translate(name, 'links', this.scope),
labelForeign: this.getLanguage().translate(d.foreign, 'links', d.entity)
});
}, this);
},
setup: function () {
this.scope = this.options.scope || null;
this.setupLinkData();
this.on('after:render', function () {
this.renderHeader();
});
},
createLink: function () {
this.createView('edit', 'Admin.LinkManager.Modals.Edit', {
scope: this.scope
}, function (view) {
view.render();
this.listenTo(view, 'after:save', function () {
this.setupLinkData();
this.render();
}, this);
}.bind(this));
},
editLink: function (link) {
this.createView('edit', 'Admin.LinkManager.Modals.Edit', {
scope: this.scope,
link: link
}, function (view) {
view.render();
this.listenTo(view, 'after:save', function () {
this.setupLinkData();
this.render();
}, this);
}.bind(this));
},
removeEntity: function (link) {
$.ajax({
url: 'EntityManager/action/removeLink',
type: 'POST',
data: JSON.stringify({
scope: this.scope,
link: link
})
}).done(function () {
this.$el.find('table tr[data-link="'+link+'"]').remove();
this.getMetadata().load(function () {
this.getConfig().load(function () {
this.setupLinkData();
this.render();
}.bind(this));
}.bind(this));
}.bind(this));
},
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'));
},
});
});
@@ -0,0 +1,226 @@
/************************************************************************
* 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.LinkManager.Modals.Edit', 'Views.Modal', function (Dep) {
return Dep.extend({
cssName: 'edit',
template: 'admin.link-manager.modals.edit',
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
}
}
});
},
afterRender: function () {
this.getView('name').on('change', function (m) {
var name = this.model.get('name');
name = name.charAt(0).toUpperCase() + name.slice(1);
this.model.set('labelSingular', name);
this.model.set('labelPlural', name + 's') ;
if (name) {
name = name.replace(/\-/g, ' ').replace(/_/g, ' ').replace(/[^\w\s]/gi, '').replace(/ (.)/g, function (match, g) {
return g.toUpperCase();
}).replace(' ', '');
if (name.length) {
name = name.charAt(0).toUpperCase() + name.slice(1);
}
}
this.model.set('name', name);
}, this);
},
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';
}
var name = this.model.get('name');
$.ajax({
url: url,
type: 'POST',
data: JSON.stringify({
name: name,
labelSingular: this.model.get('labelSingular'),
labelPlural: this.model.get('labelPlural'),
type: this.model.get('type'),
stream: this.model.get('stream')
}),
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'));
}
var global = ((this.getLanguage().data || {}) || {}).Global;
(global.scopeNames || {})[name] = this.model.get('labelSingular');
(global.scopeNamesPlural || {})[name] = this.model.get('labelPlural');
this.getMetadata().load(function () {
this.getConfig().load(function () {
this.trigger('after:save');
this.close();
}.bind(this));
}.bind(this));
}.bind(this));
},
});
});