/*
* Copyright 2018 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.EditContent', {
singleton: true,
/**
* @property {Ametys.cms.content.Content} _content The modified content.
* @private
*/
/**
* @property {String} [_viewName=main] The name of the view to be displayed in attributes tree. Can be null to use the default value.
* @private
*/
/**
* @property {String} [_fallbackViewName=main] The name of the fallback view to be displayed in attributes tree. Can be null to use the default value.
* @private
*/
/**
* @property {Number} [_editWorkflowActionId=2] The id of the workflow action to edit the target content. Can be null to use the default value.
* @private
*/
/**
* @property {String} [_contentMessageTargetType=Ametys.message.MessageTarget#CONTENT] The message target type.
* @private
*/
/**
* @property {Ametys.form.ConfigurableFormPanel} _form The form panel
* @private
*/
/**
* @property {Ametys.window.DialogBox} _box The dialog box
* @private
*/
/**
* @property {Boolean} [_fullContents=false] True to returns {@link Ametys.cms.content.Content}. If false only the content identifiers will be returned
* @private
*/
/**
* @property {Function} [_cbFn=Ext.emptyFn] The callback function to call after the modification of the content
* @private
*/
/**
* @property {Function} [_cbScope=this] The scope for the #_cbFn callback function.
* @private
*/
/**
* @property {Function} [_validateCb=this._editContent] The callback function to call after the validation of the dialog box.
* @private
*/
/**
* @property {Function} [_validateCbScope=this] The scope for the #_validateCb callback function.
* @private
*/
/**
* @property {Boolean} _editionFormReady The _editionFormReady is set to true when the form is ready.
* @private
*/
/**
* Action function to be called by the controller.
* Will open the dialog box to edit content
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller calling this function
*/
act: function (controller)
{
var config = controller.initialConfig;
var target = controller.getMatchingTargets()[0];
if (target != null)
{
var contentId = target.getParameters().id;
Ametys.cms.content.ContentDAO.getContent(contentId, function(content) {
config.content = content;
Ametys.cms.uihelper.EditContent.open(config);
});
}
},
/**
* Open a dialog box to edit a content.
* @param {Object} config The configuration options. Has the following parameters:
* @param {Ametys.cms.content.Content} config.content (required) The content to edit
* @param {String} [config.contentMessageTargetType=Ametys.message.MessageTarget#CONTENT] The message target factory role to use for the event of the bus thrown after content edition
* @param {Number} [config.editWorkflowActionId=2] The id of the workflow edition action
* @param {String} [config.viewName=default-edition] The name of view to used for modification.
* @param {String} [config.fallbackViewName=main] The name of fallback view to used for modification.
* @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 {Object} [config.validateCallbackScope] The scope of the validate callback.
* @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.iconCls=ametysicon-edit5] The icon class for the dialog box.
* @param {String} [config.title] The title of the dialog box. If not provided, a default title 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 of the dialog box. If not provided, a default message will be displayed.
* @param {Object} [config.dialogConfig] The dialog configuration to override or add additional parameters to the edit dialog box.
* @param {Object} [config.formConfig] The configurable form panel configuration to override or add additional parameters to the edit configurable form panel.
* @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._content = config.content;
// Content is mandatory
if (!this._content)
{
Ametys.log.ErrorDialog.display({
title: "{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_EDITCONTENT_CONFIGURATION_ERROR}}",
text: "{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_EDITCONTENT_CONTENT_ERROR_TEXT}}",
category: Ext.getClassName(this)
});
return;
}
this._contentMessageTargetType = config.contentMessageTargetType || Ametys.message.MessageTarget.CONTENT;
this._editWorkflowActionId = config.editWorkflowActionId || 2;
this._viewName = config.viewName || 'default-edition';
this._fallbackViewName = config.fallbackViewName || 'main';
this._validateCb = config.validateCallback;
this._validateCbScope = config.validateCallbackScope;
this._cbFn = callback || Ext.emptyFn;
this._cbScope = scope || window;
this._fullContents = config.fullContents === true;
this._initializeAndShowDialogBox(config);
},
/**
* @private
* Initialize and show dialog box
* @param {Object} config The configuration
*/
_initializeAndShowDialogBox : function (config)
{
var title = config.title || Ext.String.format("{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_EDITCONTENT_TITLE}}", this._content.getTitle())
var iconCls = config.iconCls || 'ametysicon-edit5';
var helpMsg1 = config.helpmessage1 || "{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_EDITCONTENT_MESSAGE1}}";
var helpMsg2 = config.helpmessage2 || "{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_EDITCONTENT_MESSAGE2}}";
var width = config.width || 500;
var maxHeight = config.maxHeight || window.innerHeight*0.8;
var disallowEdition = config.disallowEdition != null ? config.disallowEdition : false;
var dialogConfig = config.dialogConfig || {};
var formConfig = config.formConfig || {};
// Form creation (can be empty)
this._form = Ext.create('Ametys.form.ConfigurableFormPanel', Ext.applyIf (formConfig, {
border : false,
scrollable : false,
labelAlign: config.labelAlign || 'top',
defaultFieldConfig : {
labelWidth : config.labelWidth || 130,
anchor : '100%'
},
bodyStyle : {
padding : 0
},
additionalWidgetsConf: {
standaloneEdition: true // standalone edition (use case: a popup without ribbon (for example richtext buttons) or FO edition)
},
additionalWidgetsConfFromParams: {
contentType: 'contentType', // some widgets require the contentType configuration
editableSource: 'editableSource' // for richtext widgets we want to check the right on content
},
fieldNamePrefix : 'content.input.',
displayGroupsDescriptions: false
}));
this._box = Ext.create('Ametys.window.DialogBox', Ext.applyIf (dialogConfig, {
title: title,
iconCls: iconCls,
width: width,
maxHeight: maxHeight,
scrollable: true,
layout: 'anchor',
defaultType: 'textfield',
defaults: {
cls: 'ametys',
labelAlign: 'right',
labelSeparator: '',
labelWidth: 130,
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
},
this._form,
{
xtype: 'component',
itemId: 'helpmessage2',
cls: 'a-text',
html: helpMsg2
}
],
selectDefaultFocus: true,
closeAction: 'destroy',
referenceHolder: true,
defaultButton: 'validate',
listeners:
{
scope: this,
close: function() {this._content.unlock(null, false);}
},
buttons : [{
reference: 'validate',
disabled: disallowEdition,
text :"{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_EDITCONTENT_OK}}",
handler: this._validate,
scope: this
}, {
text :"{{i18n plugin.cms:PLUGINS_CMS_CONTENT_UIHELPER_EDITCONTENT_CANCEL}}",
handler: Ext.bind(function() {this._box.close();}, this)
}]
}));
this._content.lock(Ext.bind(this._drawEdition, this), false);
},
/**
* Draw edition form
* @private
*/
_drawEdition: function (success)
{
if (!success)
{
Ametys.plugins.cms.content.actions.SaveAction.backToContent(this._content.getId());
return;
}
Ametys.data.ServerComm.suspend();
// 1 Ask server for view definition
Ametys.data.ServerComm.callMethod({
role: 'org.ametys.cms.content.ContentHelper',
methodName: 'getContentViewAsJSON',
parameters: [this._content.getId(), this._viewName, this._fallbackViewName, true],
callback: {
scope: this,
handler: this._drawEditionForm,
ignoreOnError: false
},
waitMessage: true
});
// 2 Ask server for view values
Ametys.data.ServerComm.send({
url: '_content.xml',
parameters: {contentId: this._content.getId(), isEdition: "true", viewName: this._viewName, fallbackViewName: this._fallbackViewName},
priority: Ametys.data.ServerComm.PRIORITY_MAJOR,
waitMessage: true,
callback: {
handler: this._fillEditionForm,
scope: this
}
});
Ametys.data.ServerComm.restart();
},
/**
* Callback function called after retrieving view from the content.
* 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
*/
_drawEditionForm: function (response, args)
{
if (response === undefined)
{
Ametys.notify({
title: Ext.String.format("{{i18n PLUGINS_CMS_TOOL_CONTENT_FORM_VIEW_ERROR_TITLE}}", this._content.getTitle()),
description: "{{i18n plugin.cms:PLUGINS_CMS_TOOL_CONTENT_FORM_VIEW_ERROR_MESSAGE}}",
type: "warn"
});
this.getLogger().error("Cannot modify content '" + this._content.getId() + "' because the view is not compatible with edition");
}
else
{
this._form.additionalWidgetsConf.contentInfo = {
contentId: this._content.getId()
};
this._form.configure(response.view.elements);
this._editionFormReady = true;
this._box.show();
}
},
/**
* Callback function called after retrieving metadata values from the
* content. This sets the the form field values
*
* @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
*/
_fillEditionForm: function (response, args)
{
if (Ametys.data.ServerComm.handleBadResponse("{{i18n plugin.cms:PLUGINS_CMS_TOOL_CONTENT_FORMDEFINITION_ERROR}} '" + this._content.getId() + "'", response, Ext.getClassName(this)))
{
return;
}
if (!this._editionFormReady)
{
if (this.getLogger().isWarnEnabled())
{
this.getLogger().warn("#_fillEditionForm has been called but the edition form is not drawn");
}
}
this._form.setValues(response, "metadata");
this._form.on({
afterlayout: {fn: this._focusForm, scope: this, single: true}
});
},
/**
* @private
* Focuses the form panel
*/
_focusForm: function()
{
this._form.focus();
},
/**
* This function is called when validating the dialog box.
* @private
*/
_validate: function()
{
var validateFn = this._validateCb ? this._validateCb : this._editContent;
var validateScope = this._validateCb && this._validateCbScope ? this._validateCbScope : this;
var invalidFields = Ext.Array.merge(this._form.getInvalidFields(), this._form.getInvalidRepeaters());
if (invalidFields.length > 0)
{
Ametys.Msg.show({
title: "{{i18n PLUGINS_CMS_SAVE_ACTION_SAVE}}", //{{i18n CONTENT_EDITION_SAVE_LABEL}}",
msg: "{{i18n PLUGINS_CMS_SAVE_ACTION_INVALIDFIELDS}}" + Ametys.form.SaveHelper.getInvalidFieldsAsReadableList(invalidFields), //"{{i18n CONTENT_EDITION_SAVE_INVALID_FIELD}}",
buttons: Ext.Msg.OK,
icon: Ext.MessageBox.ERROR
});
return;
}
var warnedFields = this._form.getWarnedFields();
if (!Ext.Object.isEmpty(warnedFields))
{
Ametys.Msg.show({
title: "{{i18n PLUGINS_CMS_SAVE_ACTION_SAVE}}",
msg: "{{i18n PLUGINS_CMS_SAVE_ACTION_WARNED_FIELDS_START}}" + Ametys.form.SaveHelper.getWarnedFieldsAsReadableList(warnedFields) + "{{i18n PLUGINS_CMS_SAVE_ACTION_WARNED_FIELDS_END}}",
buttons: Ext.Msg.YESNO,
icon: Ext.Msg.QUESTION,
modal: true,
fn: Ext.bind (this._bypassWarnedFields, this, [validateFn, validateScope], 1),
scope: this
});
return;
}
validateFn.call(validateScope);
},
/**
* Callback function called after modification process
* @param {Boolean} success If the edition was successful
* @param {Ametys.cms.content.Content} content The created content. Can be null if the creation failed
* @param {String[]} workflowErrors Errors messages related to the workflow
* @param {Object} fieldsInError The list of fields in error associated with their error message
* @param {Object} fieldsWithWarning The list of fields with warning associated with their warning message
* @param {Boolean} ignoreWarnings True to ignore warnings from server response
* @private
*/
_editContentCb: function (success, content, workflowErrors, fieldsInError, fieldsWithWarning, ignoreWarnings)
{
if (!success && !Ext.Object.isEmpty(fieldsInError))
{
this._form.markFieldsInvalid (fieldsInError);
return;
}
if (!success && ignoreWarnings)
{
// The ignore warning can only be set to true if there was warnings
// But it can be warnings from global validator, then fieldsWithWarning is empty
this._editContent(true);
return;
}
if (!success && !Ext.Object.isEmpty(fieldsWithWarning))
{
this._form.markFieldsWarning (fieldsWithWarning);
return;
}
if (!success)
{
return;
}
if (content != null)
{
// Success
this._box.close();
this._cbFn.call(this._cbScope, content);
}
},
/**
* @private
* The default callback function when the dialog is validated : its purpose it to transmis the edition to the server
* @param {Boolean} ignoreWarnings True to ignore warnings during content modification
*/
_editContent: function(ignoreWarnings)
{
var formValues = this._form.getJsonValues();
var params = {
contentId: this._content.getId(),
values: formValues,
viewName: this._viewName,
fallbackViewName: this._fallbackViewName,
editWorkflowActionId: this._editWorkflowActionId,
ignoreWarnings: ignoreWarnings || false,
bypassedWarnings: this._form.getWarnedFields()
};
Ametys.cms.content.ContentDAO.editContent(
params,
this._editContentCb,
this,
this._fullContents,
this._contentMessageTargetType,
null,
this._box
);
},
/**
* @private
* This function is called after validated confirm dialog box
* @param {String} answer The button answer ('yes' or 'no')
* @param {Function} validateFn The validation function
* @param {Object} validateScope The scope for validatation function
*/
_bypassWarnedFields : function (answer, validateFn, validateScope)
{
if (answer == 'yes')
{
validateFn.call(validateScope);
}
}
});