Custom module editor
For cases when data cannot be edited inline - for example: a (match) score widget, for which the editor must select the match id, the list of matches is returned from an API, a custom module editor can be used.
To create a custom module editor, create a JS file in admin/view/module_editor
, use the file's name as wf-module-editor
attribute of that composite module. This should return a Backbone view that will be used to edit the module's data in a modal dialog.
Example:
<div wf-module="wfed/composite/module"
wf-role="score"
wf-module-editor="score_editor"
wf-new
wf-allow=""
>
<div wf-module="wfed/dynamic/generic"
wf-role="score"
wf-url="{{ path('app_widget_score', {sportId: "__sportId__"}) }}"
wf-bind="urlParams:sportId|module_sportId"
wf-toolbar-position="none"
wf-new
></div>
</div>
Notes:
wf-module-editor
attribute on the composite module points to the editor's filenamemodule_XXX
is used to bind the url param to the module's data
Create the file admin/view/module_editor/score_editor.js
:
define(["i18n", "jquery", "backbone", "backbone.epoxy"], function (
i18n,
$,
Backbone,
Epoxy
) {
const Base = Backbone.Epoxy.View;
return Base.extend({
events: {
"click .btn-save": "onSaveClick",
},
initialize(options) {
Base.prototype.initialize.apply(this, arguments);
this.module = options.module;
this.model = new Backbone.Model(this.module.model.attributes);
this.viewModel = new Backbone.Model({
errorMessage: null,
loading: false,
});
this.viewModel.on("change:loading", (_model, loading) => {
loading && this.viewModel.set("errorMessage", null);
});
this.fetchSportChoices();
},
getTitle() {
return "score";
},
getIcon() {
return "score";
},
getDialogOptions() {
return { minHeight: 350 };
},
render() {
this.el.innerHTML = `
<div class="score-form">
<div class="alert alert-danger" style="overflow: auto; max-height: 100px"
data-bind="text:errorMessage,toggle:errorMessage"></div>
<div class="form-row controls-group">
<label for="sportId" class="control-label">
${i18n.get("score-form.label.sport")}
</label>
<input type="hidden" id="sportId"
data-placeholder="${i18n.get(
"score.placeholder.sport"
)}"
data-bind="value:sportId" />
</div>
<div class="form-row" style="text-align: center; color: #999; font-style: italic;">
<label data-bind="toggle:loading">
${i18n.get("score-form.label.loading")}
</label>
</div>
<div class="form-row" style="text-align: center;"
data-bind="toggle:all(sportId)">
<button class="btn btn-primary btn-save" style="margin-bottom: 0;">
${i18n.get("score.button.save")}
</button>
</div>
</div>
`;
this.applyBindings();
return this;
},
fetchSportChoices() {
return this.getJSON("/widget/score/sports").then((choices) => {
// implement logic to update the form with the choices received
});
},
getJSON(url) {
this.viewModel.set("loading", true);
const stopLoading = () => this.viewModel.set("loading", false);
return $.getJSON(url)
.fail((response) => {
this.viewModel.set(
"errorMessage",
_get(
response,
"responseJSON.detail",
response.responseText
)
);
})
.always(stopLoading);
},
onSaveClick() {
this.module.model.set(this.model.toJSON());
this.trigger("done", this.model);
},
});
});
The custom module editor receives a module
option with the instance of the module that's being edited. It creates a copy of those module's attributes (options.module.model.attributes
) to work on. When the form's editing is finished, it saves the data back to the module's mode (in onSaveClick
: this.module.model.set(this.model.toJSON())
). Triggering done
event will close the editor.
The editor view may implement the following (all optional) methods, they are used by the CMS to customize the editor's popup:
getTitle
the title of the dialog, by default it uses the module's role will be usedgetIcon
if you want to display an icon to the left of the modal's title (you must implement the.icon-__ICON__
class in the CSS if you're not using one that's already available in the CMS)getDialogOptions
to overwrite the default options for the dialog that Xalok uses for the editor.