/*
* Copyright 2013 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This helper provides a dialog box to create a content.
*/
Ext.define('Ametys.cms.uihelper.CreateContent', {
singleton: true,
/**
* @property {String} [_parentContentType] The id of parent content type. Use for dialog box title. Can be null.
* @private
*/
/**
* @property {Ametys.cms.content.ContentType/Ametys.cms.content.ContentType[]} _contentTypes The list of allowed content type objects. Can not be null.
* @private
*/
/**
* @property {Ametys.window.DialogBox} _box The dialog box
* @private
*/
/**
* @property {Ext.data.Store} _store The content types store
* @private
*/
/**
* @property {Object} _creationCfg An object containing informations to create the content.
* @private
*/
/**
* @property {Function} _cbFn The call back function to call after the creation of the content
* @private
*/
/**
* @property {Boolean} _fullContents True to returns {@link Ametys.cms.content.Content}. If false only the content identifiers will be returned
* @private
*/
/**
* @property {Function} _cbScope The scope for the #_cbFn callback function.
* @private
*/
/**
* @property {Function} _closeCbFn The optional close callback function
*/
/**
* @property {Boolean} _hideTitle Set to true to hide the title
*/
/**
* Open a dialog box to create a new content
* @param {Object} config The configuration options. Has the following parameters
* @param {Ametys.cms.content.ContentType/Ametys.cms.content.ContentType[]} config.contentTypes (required) The content type to create or an array of content types to be proposed is a Combo box.
* @param {String} config.contentLanguage The language of the content to create or null to select language among the supported languages
* @param {String} [config.parentContentType] The id of parent content type, to use for dialog box default title.
* @param {Number} [config.initWorkflowActionId=1] The id of the workflow initialization action
* @param {Number} [config.initAndEditWorkflowActionId=11] The id of the workflow initialization action if edition is required
* @param {String} [config.viewName=creation] The name of view to used for creation.
* @param {String} [config.workflowName] The content workflow name. If empty the content type default workflow name will be used.
* @param {Object} [config.additionalWorkflowParameters] Additional workflow parameters to be added to the server request. All parameters will be prefixed by "workflow-".
* @param {String} [config.title] The title of the dialog box. If not provided, a default title will be displayed.
* @param {String} [config.icon] The full path to icon (16x16 pixels) for the dialog box. If not provided, the icon of the first content types in the #config.contentTypes will be displayed.
* @param {String} [config.helpmessage1] The message displayed at the top of the dialog box. If not provided, a default message will be displayed.
* @param {String} [config.helpmessage2] The message displayed at the bottom the dialog box. If not provided, a default message will be displayed.
* @param {String} [config.defaultContentTitle] The default content title to initialize the content title field. If not provided, a default title will be displayed.
* @param {Boolean} [config.fullContents=false] If `true`, the callback function receives a Content object as argument. If `false`, only the content id is provided.
* @param {String} [config.contentMessageTargetType=Ametys.message.MessageTarget#CONTENT] The message target factory role to use for the event of the bus thrown after content creation
* @param {String} [config.rootContentPath] When creating a content under a specific root contents.
* @param {Object} [config.initValues] A object with the initial values in creation form. These values only concerned the field of a creation view if exists.
* @param {Object} [config.forceValues] A object with the values to force in creation form. These values only concerned the field of a creation view if exists.
* @param {String} [config.forceMode=hide] If forceValues is used, a String which specifies how the forced field should appear : 'hide' to hide the fields, 'readOnly' to display the fields preveting the user from changing the fields or 'disabled' to disable the fields
* @param {Function} [config.validateCallback] If provided, this function will be called when the user clicks on 'OK' instead of the default function, which creates the content.
* @param {String} config.validateCallback.title The content title entered by the contributor.
* @param {String} config.validateCallback.contentType The content type selected by the contributor.
* @param {String} config.validateCallback.lang The lang of the content.
* @param {Object} config.validateCallback.formValues the fiedls'form values. Can be empty.
* @param {Function} config.closeCbFn The callback at the close moment
* @param {Ametys.window.DialogBox} config.validateCallback.dialogbox The create content dialog box.
* @param {Object} [config.validateCallbackScope] The scope of the validate callback.
* @param {Function} callback The callback function invoked when the content has been created. The callback function will received the following parameters:
* @param {String/Ametys.cms.content.Content} callback.content The created content as an id or a {@link Ametys.cms.content.Content}. See #config.fullContents
* @param {Object} [scope=window] The callback scope, default to window.
*/
open: function (config, callback, scope)
{
this._parentContentType = config.parentContentType;
this._contentMessageTargetType = config.contentMessageTargetType || Ametys.message.MessageTarget.CONTENT;
this._contentTypes = Ext.Array.from(config.contentTypes);
// Content types are mandatory
if (this._contentTypes.length == 0)
{
Ametys.log.ErrorDialog.display({
title: "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_CONFIGURATION_ERROR}}",
text: "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_CONTENTTYPES_ERROR_TEXT}}",
category: Ext.getClassName(this)
});
return;
}
this._contentLanguage = config.contentLanguage;
this._rootContentPath = config.rootContentPath;
this._initWorkflowActionId = config.initWorkflowActionId || 1;
this._initAndEditWorkflowActionId = config.initAndEditWorkflowActionId || 11;
this._editWorkflowActionId = config.editWorkflowActionId || 2;
this._viewName = config.viewName || 'creation';
this._workflowName = config.workflowName;
this._additionalWorkflowParameters = config.additionalWorkflowParameters || {};
this._validateCb = config.validateCallback;
this._validateCbScope = config.validateCallbackScope;
this._cbFn = callback || Ext.emptyFn;
this._cbScope = scope || window;
this._closeCbFn = config.closeCbFn || Ext.emptyFn;
this._fullContents = config.fullContents === true;
this._defaultContentTitle = config.defaultContentTitle;
this._hideTitle = config.hideTitle || false;
config.icon = config.icon || "/plugins/cms/resources/img/content/content_16.png";
if (!config.title)
{
if (this._parentContentType)
{
// Get dialog box's title and icon from parent content type
var contentType = Ametys.cms.content.ContentTypeDAO.getContentType(this._parentContentType);
config.title = "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_TITLE}}" + " " + contentType.getLabel() + "...";
config.icon = contentType.getIconSmall();
}
else
{
config.title = "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_TITLE_UNKNOWN}}";
}
}
this._initializeAndShowDialogBox(config);
},
/**
* @private
* Initialize and show dialog box
* @param {Object} config The configuration
*/
_initializeAndShowDialogBox : function (config)
{
var cTypeIconGlyph = this._contentTypes[0].getIconGlyph();
if (cTypeIconGlyph && this._contentTypes[0].getIconDecorator())
{
cTypeIconGlyph = " " + this._contentTypes[0].getIconDecorator();
}
var iconCls = config.iconCls || cTypeIconGlyph;
this._delayedInitialize (
config.title,
iconCls ? null : Ametys.CONTEXT_PATH + config.icon,
iconCls,
config.helpmessage1 || (this._contentTypes.length == 1 ? (this._contentLanguage ? "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_MESSAGE1}}" : "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_MULTI_LANGUAGES_MESSAGE1}}") : (this._contentLanguage ? "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_MULTI_CTYPES_MESSAGE1}}" : "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_MULTI_CTYPES_LANGUAGES_MESSAGE1}}")),
config.helpmessage2 || "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_MESSAGE2}}"
);
this._form.destroyComponents();
this._titleForm.destroyComponents();
if (config.initValues || config.forceValues)
{
this._form.executeFormReady(Ext.bind(this._initValues, this, [config.initValues, config.forceValues, config.forceMode || 'hide']));
}
this._init();
},
/**
* @private
* Initialize the form creation with the initial values and forced values
* @param {Object} initValues The initial value
* @param {Object} forceValues The values to force
* @param {String} forceMode The force mode : 'hide', 'readOnly' or 'disabled'
*/
_initValues: function (initValues, forceValues, forceMode)
{
if (initValues)
{
var prefix = this._form.getFieldNamePrefix();
for (key in initValues)
{
var fd = this._form.getField(prefix + key);
if (fd)
{
fd.setValue(initValues[key]);
}
}
}
if (forceValues)
{
this._initForceValues(forceValues, forceMode);
}
},
/**
* @private
* Initialize the form creation with the forced values
* @param {Object} values The values to force
* @param {String} forceMode The force mode : 'hide', 'readOnly' or 'disabled'
*/
_initForceValues: function (values, forceMode)
{
var prefix = this._form.getFieldNamePrefix();
for (key in values)
{
var fd = this._form.getField(prefix + key);
if (fd)
{
fd.setValue(values[key]);
if (forceMode == 'readOnly')
{
fd.setReadOnly(true)
}
else if (forceMode == 'disabled')
{
fd.setDisabled(true)
}
else
{
fd.setVisible(false);
}
}
}
},
/**
* Initialize the dialog box fields
* @private
*/
_init: function ()
{
var langFd = this._box.down('#content-languages');
if (this._contentLanguage != null)
{
langFd.setValue(this._contentLanguage);
langFd.hide();
}
else
{
langFd.show();
}
this._store.removeAll();
this._loadContentTypes();
var cTypeFd = this._box.down('#content-types');
var firstRecord = this._store.first();
cTypeFd.setValue(firstRecord);
this._onSelectContentTypes (cTypeFd, firstRecord != null ? [firstRecord] : []);
if (this._contentTypes.length == 1)
{
cTypeFd.hide();
}
else
{
cTypeFd.show();
}
this._box.show();
},
/**
* Creates or update the dialog box
* @param {String} title The title of the dialog box.
* @param {String} icon The full path to icon (16x16) for the dialog box
* @param {String} iconCls The css classname to display a glyph
* @param {String} helpMsg1 The message displayed at the top of the dialog box.
* @param {String} helpMsg2 The message displayed at the bottom of the dialog box.
* @private
*/
_delayedInitialize: function (title, icon, iconCls, helpMsg1, helpMsg2)
{
if (!this._box)
{
// Title form creation
this._titleForm = this._createConfigurableFormPanel();
// Form creation (can be empty)
this._form = this._createConfigurableFormPanel();
this._box = Ext.create('Ametys.window.DialogBox', {
title: title,
icon: iconCls ? null : icon,
iconCls: iconCls,
width: 500,
maxHeight: window.innerHeight*0.8,
scrollable: true,
layout: 'anchor',
defaultType: 'textfield',
defaults: {
cls: 'ametys',
labelAlign: 'top',
labelSeparator: '',
anchor: '100%',
style: 'margin-right:' + (Ametys.form.ConfigurableFormPanel.OFFSET_FIELDSET + 10 + Ametys.form.ConfigurableFormPanel.PADDING_TAB) + 'px'
},
items: [{
xtype: 'component',
itemId: 'helpmessage1',
cls: 'a-text',
html: helpMsg1
}, {
xtype: 'combobox',
name: 'contentTypes',
itemId: 'content-types',
fieldLabel: "* " + "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_TYPESFIELD_LABEL}}",
ametysDescription: "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_TYPESFIELD_DESC}}",
store: this._getContentTypesStore(),
queryMode: 'local',
displayField: 'label',
valueField: 'value',
typeAhead: true,
editable: true,
forceSelection: true,
triggerAction: 'all',
lastQuery: '',
allowBlank: false,
listeners: {
select: {fn: this._onSelectContentTypes, scope: this}
}
},
this._titleForm,
Ametys.cms.language.LanguageDAO.createComboBox({
name: 'contentLanguage',
itemId: 'content-languages',
fieldLabel: "* " + "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_LANGUAGE_LABEL}}",
ametysDescription: "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_LANGUAGE_DESC}}",
anchor: '100%',
style: 'margin-right:' + (Ametys.form.ConfigurableFormPanel.OFFSET_FIELDSET + 10 + 5) + 'px'
}),
this._form,
{
xtype: 'component',
itemId: 'helpmessage2',
cls: 'a-text',
html: helpMsg2
}
],
selectDefaultFocus: true,
closeAction: 'hide',
referenceHolder: true,
defaultButton: 'validate',
buttons : [{
reference: 'validate',
text :"{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_OK}}",
handler: this._validate,
scope: this
}, {
text :"{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_CANCEL}}",
handler: Ext.bind(function() {this._box.close();}, this)
}],
listeners: {
close: Ext.bind(this._closeCbFn, this)
}
});
}
else
{
this._box.setTitle(title);
this._box.setIcon(iconCls ? null : icon);
this._box.setIconCls(iconCls);
this._box.down('#helpmessage1').update(helpMsg1);
this._box.down('#helpmessage2').update(helpMsg2);
}
},
/**
* Create a configurable form panel.
* @return {Object} The configurable form panel
* @private
*/
_createConfigurableFormPanel: function()
{
return Ext.create ('Ametys.form.ConfigurableFormPanel', {
border: false,
scrollable: false,
defaultFieldConfig: {
anchor: '100%'
},
bodyStyle: {
padding: 0
},
additionalWidgetsConfFromParams: {
contentType: 'contentType' // some widgets requires the contentType configuration
},
fieldNamePrefix: 'content.input.',
displayGroupsDescriptions: false
});
},
/**
* Create or retrieves the store containing the content types data.
* @return {Object[]} The items
* @private
*/
_getContentTypesStore: function()
{
if (!this._store)
{
this._store = Ext.create('Ext.data.Store', {
fields: [
'value',
{name: 'label', type: 'string'}
],
autoLoad: false,
sorters: {property: 'label'}
});
}
return this._store;
},
/**
* @private
* Listener on the change event of the content types combobox
* @param {Ext.form.field.ComboBox} combobox the combobox field
* @param {Array} records The selected records
*/
_onSelectContentTypes: function(combobox, records)
{
this._form.destroyComponents();
this._titleForm.destroyComponents();
var contentTypeId = combobox.getValue();
if (!Ext.isEmpty(contentTypeId))
{
Ametys.data.ServerComm.callMethod({
role: 'org.ametys.cms.contenttype.ContentTypesHelper',
methodName: 'getTitleViewAsJSON',
parameters: [contentTypeId, true],
callback: {
scope: this,
handler: Ext.bind(this._drawTitleForm, this, [contentTypeId], 2)
},
waitMessage: true, // The tool is refreshing
errorMessage: {
msg: "{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_FORMDEFINITION_ERROR}}",
category: Ext.getClassName(this)
}
});
Ametys.data.ServerComm.callMethod({
role: 'org.ametys.cms.contenttype.ContentTypesHelper',
methodName: 'getViewAsJSON',
parameters: [contentTypeId, this._viewName, true],
callback: {
scope: this,
handler: this._drawCreationForm
},
waitMessage: true, // The tool is refreshing
errorMessage: {
msg: "{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_FORMDEFINITION_ERROR}}",
category: Ext.getClassName(this)
}
});
}
},
/**
* Callback function called after retrieving title information from the content type.
* This draws the title form for content edition
* @param {Object} response The XML response provided by the {@link Ametys.data.ServerComm}
* @param {Object} args The callback parameters passed to the {@link Ametys.data.ServerComm#send} method
* @param {String} contentTypeId The content type id
* @private
*/
_drawTitleForm: function (response, args, contentTypeId)
{
if (response.view)
{
this._titleForm.configure(response.view.elements);
this._titleForm.setValues(); // setValues must always be called for configurable form panel in order to complete its initialization
// Set the default title
var defaultTitle = this._defaultContentTitle || response.contentType.defaultTitle || "{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_DEFAULT_NAME}}";
var titleField = this._titleForm.getField("content.input.title");
var languageField = this._box.down('#content-languages');
if (response.view.elements.title.type != "multilingual-string")
{
titleField.setValue(defaultTitle);
if (!this._contentLanguage)
{
languageField.show();
}
}
else
{
// It's a multilingual title, so hide the language comboBox and transform default title to object
var lang = languageField.getValue();
var multiLangTitle = {};
multiLangTitle[lang] = defaultTitle;
titleField.setValue(multiLangTitle);
languageField.hide();
}
titleField.setVisible(!this._hideTitle);
titleField.focus(true, 100);
}
},
/**
* Callback function called after retrieving metadata-set from the content type.
* This draws the form for content edition
* @param {Object} response The XML response provided by the {@link Ametys.data.ServerComm}
* @param {Object} args The callback parameters passed to the {@link Ametys.data.ServerComm#send} method
* @private
*/
_drawCreationForm: function (response, args)
{
this._hasTitleInCreationView = false;
if (response.view)
{
var elements = response.view.elements;
var formJson = {};
for (let key in elements)
{
if (key != 'title')
{
formJson[key] = elements[key];
}
else
{
this._hasTitleInCreationView = true;
}
}
// Draw form
this._disableAdditionalCreation(response.view.elements);
this._form.configure(formJson);
this._form.setValues(); // setValues must always be called for configurable form panel in order to complete its initialization
}
},
/**
* @private
* Disable additional creation during this creation
* @param {Object} viewElements The elements of the view for creation
*/
_disableAdditionalCreation: function (viewElements)
{
for (var i in viewElements)
{
var element = viewElements[i];
var type = element.type ? element.type : null;
if (!type || type == 'composite' || type == 'repeater')
{
this._disableAdditionalCreation (element.elements);
}
else
{
element['widget-params'] = element['widget-params'] || {};
element['widget-params'].creatingContent = true;
}
}
},
/**
* Load the content types data into the store
* @private
*/
_loadContentTypes: function()
{
var data = [];
// Populate data array with content type informations/
Ext.Array.forEach(this._contentTypes, function(contentType) {
data.push({
label: contentType.getLabel(),
value: contentType.getId()
});
});
this._store.loadData(data);
},
/**
* This function is called when validating the dialog box.
* @private
*/
_validate: function()
{
var validateFn = this._validateCb ? this._validateCb : this._create;
var validateScope = this._validateCb && this._validateCbScope ? this._validateCbScope : this;
var titleField = this._titleForm.getField("content.input.title");
var typesField = this._box.down('#content-types');
var langsField = this._box.down('#content-languages');
if (this._titleForm.getInvalidFields().length > 0 || !typesField.isValid() || !langsField.isValid() || this._form.getInvalidFields().length > 0)
{
return null;
}
// For the dialog box, title is displayed just once but can be used for edit content function, so add it
var titleValue = titleField.getValue();
var values = this._form.getJsonValues();
if (this._hasTitleInCreationView)
{
values["content.input.title"] = titleValue;
}
validateFn.call(validateScope, titleValue, typesField.getValue(), langsField.getValue(), values, this._box);
},
/**
* Creates the content
* @param {String} title The content title entered by the contributor.
* @param {String} contentType The content type selected by the contributor.
* @param {String} contentLanguage The content language code
* @param {Object} formValues the fiedls' form values. Can be empty.
* @private
*/
_create: function (title, contentType, contentLanguage, formValues)
{
var params = {
contentType: contentType,
contentTitle: title,
contentLanguage: contentLanguage,
rootContentPath: this._rootContentPath,
initWorkflowActionId: this._initWorkflowActionId,
initAndEditWorkflowActionId: this._initAndEditWorkflowActionId,
editWorkflowActionId: this._editWorkflowActionId,
workflowName: this._workflowName,
additionalWorkflowParameters: this._additionalWorkflowParameters,
viewName: this._viewName,
editFormValues: formValues
};
// Create the content, and manage callback.
// Hide the box at the end.
Ametys.cms.content.ContentDAO.createContent(
params,
this._createCb,
this, // scope
this._fullContents,
this._contentMessageTargetType,
null,
this._box
);
},
/**
* Callback function called after creation process
* @param {Ametys.cms.content.Content} content The created content. Can be null if the creation failed
* @param {Object} fieldsInError Fields of the creation form which contain errors
* @param {Object} workflowErrors The workflow errors
* @private
*/
_createCb: function (content, fieldsInError, workflowErrors)
{
if (content != null)
{
// Success
this._box.hide();
this._cbFn.call(this._cbScope, content);
}
else
{
var invalidFormFields = {};
if (workflowErrors && Ext.isArray(workflowErrors) && workflowErrors.length > 0)
{
var detailedMsg = '<ul>';
for (var i=0; i < workflowErrors.length; i++)
{
detailedMsg += '<li>' + workflowErrors[i] + '</li>';
}
detailedMsg += '</ul>';
Ametys.Msg.show({
title: "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_ERROR_TITLE}}",
msg: "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_ERROR_WORKFLOW}}" + detailedMsg,
buttons: Ext.Msg.OK,
icon: Ext.Msg.ERROR
});
return;
}
var detailedMsg = '<ul>';
if (fieldsInError && Ext.isObject(fieldsInError))
{
for (var fdName in fieldsInError)
{
if (fdName == '_global')
{
detailedMsg += '<li>' + fieldsInError[fdName] + '</li>';
}
else
{
var fd = this._form.getForm().findField('content.input.' + fdName);
detailedMsg += '<li><b>' + (fd != null ? fd.getFieldLabel() : fdName) + '</b>: ' + fieldsInError[fdName] + '</li>';
invalidFormFields['content.input.' + fdName] = fieldsInError[fdName];
}
}
}
detailedMsg += '</ul>';
this._form.markFieldsInvalid (invalidFormFields);
Ametys.Msg.show({
title: "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_ERROR_TITLE}}",
msg: "{{i18n PLUGINS_CMS_CONTENT_UIHELPER_CREATECONTENT_ERROR_FORM}}" + detailedMsg,
buttons: Ext.Msg.OK,
icon: Ext.Msg.ERROR
});
}
}
});