root.libs.composum.nodes.commons.components.js.dialogs.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of composum-nodes-commons Show documentation
Show all versions of composum-nodes-commons Show documentation
general components and objects to use the Sling API
/**
*
*
*/
(function () {
'use strict';
CPM.namespace('core.components');
(function (components, core) {
components.const = components.const || {};
components.const.dialog = {
alert: {
type: {
error: 'danger',
warn: 'warning'
}
},
translate: {
uri: {
object: '/bin/cpm/core/translate.object.json',
status: '/bin/cpm/core/translate.status.json'
}
},
load: {
base: core.const.composumBase + 'composum/nodes/commons/components/dialogs',
_path: '.path-select.html'
}
};
/**
* the basic dialog component as 'superclass' for all dialog components
*/
components.Dialog = Backbone.View.extend({
initialize: function (options) {
this.isLoaded = options.loaded || false;
this.$alert = this.$('.alert');
this.$messageHead = this.$('.messages .panel-heading');
this.$messageBody = this.$('.messages .panel-body');
this.setUpWidgets(this.el);
this.$el.on('shown.bs.modal', _.bind(this.onShown, this));
if (this.isLoaded) { // remove a loaded dialog on close
this.$el.on('hidden.bs.modal', _.bind(this.destroy, this));
}
this.$lock = this.$('.lock-curtain');
if (this.$lock.length < 1) {
this.$el.prepend('');
this.$lock = this.$('.lock-curtain');
}
this.$lock.click(function (event) {
event.preventDefault();
return false;
});
},
setUpWidgets: function (root) {
CPM.widgets.setUp(root);
},
/**
* returns the widget instance (view) for a DOM element
*/
widgetOf: function (element) {
return core.widgetOf(element);
},
show: function (initView, callback) {
this.initView = initView;
this.callback = callback;
this.$el.modal('show');
this.unlock();
},
onShown: function () {
this.resetOnShown();
if (_.isFunction(this.initView)) {
this.initView(this);
}
},
resetOnShown: function () {
if (!this.isLoaded) { // asuming that loaded dialogs are pre filled
this.reset();
}
},
hide: function () {
this.$el.modal('hide');
if (_.isFunction(this.callback)) {
this.callback(this);
}
this.reset();
},
lock: function () {
this.$el.addClass('dialog-locked');
if (!this.busyTimeout) {
this.busyTimeout = window.setTimeout(_.bind(function () {
this.busyTimeout = undefined;
this.$el.addClass('dialog-busy');
}, this), 500);
}
},
unlock: function () {
if (this.busyTimeout) {
window.clearTimeout(this.busyTimeout);
this.busyTimeout = undefined;
}
this.$el.removeClass('dialog-busy').removeClass('dialog-locked');
},
/**
* Resets all form values to an initial or undefined value.
*/
reset: function () {
this.unlock();
this.alert();
// reset all known View instances using their 'reset' method
this.$('.widget').each(function () {
if (this.view) {
if (_.isFunction(this.view.reset)) {
this.view.reset.apply(this.view);
}
}
});
},
/**
* remove dialog from the DOM (on closing loaded dialogs)
*/
destroy: function (event) {
this.$el.remove();
},
/**
* Displays a message text in a dialogs predefined alert or message panel box.
* @param type the message error level (success, info, warning, danger)
* @param message the message text to display; optional - if not present the alert will hide
* @param result an optional result object from an Ajax call; a hint from this result is added to the text
*/
alert: function (type, message, result) {
if (message) {
type = components.const.dialog.alert.type[type] || type;
if (core.isRestricted(result)) {
message = core.getRestrictedMessage();
result = undefined;
}
if (this.$messageBody.length === 1) {
this.$messageHead.html(result ? core.resultMessage(result, message) : message);
this.$messageHead.removeClass('hidden');
this.$messageBody.html('');
this.$messageBody.addClass('hidden');
this.$messageBody.parent().removeClass().addClass('panel').addClass('panel-' + type);
} else {
this.$alert.html(result ? core.resultMessage(result, message) : message);
this.$alert.removeClass().addClass('alert').addClass('alert-' + type);
}
} else {
if (this.$messageBody.length === 1) {
this.$messageBody.parent().removeClass().addClass('hidden');
this.$messageHead.html('');
this.$messageBody.html('');
} else {
this.$alert.html('');
this.$alert.removeClass().addClass('alert').addClass('hidden');
}
}
},
/**
* Displays a message list with title in a dialogs predefined message panel box.
* @param type the message error level (success, info, warning, danger)
* @param title the message text to display in the heading; optional - if not present the box will hide
* @param messages the message list with items like {level:(error,warn,info),text:'...'}
*/
messages: function (type, title, messages) {
if (this.$messageBody.length === 1) {
type = components.const.dialog.alert.type[type] || type;
this.$messageBody.parent().removeClass();
if (title) {
this.$messageHead.html(title);
this.$messageHead.removeClass('hidden');
this.$messageBody.parent().addClass('panel').addClass('panel-' + type);
} else {
this.$messageHead.addClass('hidden');
}
this.$messageBody.html('');
if (_.isArray(messages) && messages.length > 0) {
this.$messageBody.html('
');
var $list = this.$messageBody.find('ul');
for (var i = 0; i < messages.length; i++) {
var level = messages[i].level;
level = components.const.dialog.alert.type[level] || level;
$list.append(''
+ (messages[i].label ? '' + messages[i].label + ':' : '')
+ messages[i].text
+ (messages[i].hint ? '(' + messages[i].hint + ')' : '')
+ ' ')
}
this.$messageBody.removeClass('hidden');
} else {
this.$messageBody.html('');
this.$messageBody.addClass('hidden');
}
} else {
this.alert(type, title);
}
},
/**
* Submit the form of the dialog.
* @param onSuccess optional; 'true' or a callback function called after a successful request
*/
submitForm: function (onSuccess, onError, onComplete) {
this.lock();
core.submitForm(this.el,
_.bind(function (result) {
if (_.isFunction(onSuccess)) {
onSuccess(result);
} else {
if (onSuccess) { // use 'true' to show the success messages
this.unlock();
if (_.isObject(result) && _.isObject(result.response)) {
var response = result.response;
var messages = result.messages;
core.messages(response.level, response.text, messages);
}
}
}
this.hide();
}, this),
_.bind(function (xhr) {
this.unlock();
if (_.isFunction(onError)) {
onError(xhr);
} else {
if (onError === undefined || onError /* maybe 'false' */) {
this.onError(xhr);
}
}
}, this),
onComplete
);
},
submitFormPut: function (onSuccess, onError, onComplete) {
this.lock();
var form = core.getWidget(this.el, 'form.widget-form', core.components.FormWidget);
core.submitFormPut(this.el,
form ? form.getValues.apply(form) : undefined,
_.bind(function (result) {
if (_.isFunction(onSuccess)) {
onSuccess(result);
} else {
if (onSuccess) { // use 'true' to show the success messages
this.unlock();
if (_.isObject(result) && _.isObject(result.response)) {
var response = result.response;
var messages = result.messages;
core.messages(response.level, response.text, messages);
}
}
}
this.hide();
}, this),
_.bind(function (xhr) {
this.unlock();
if (_.isFunction(onError)) {
onError(xhr);
} else {
if (onError === undefined || onError /* maybe 'false' */) {
this.onError(xhr);
}
}
}, this),
onComplete
);
},
submitPUT: function (label, url, data, onSuccess) {
this.lock();
core.ajaxPut(url, JSON.stringify(data), {
dataType: 'json'
},
onSuccess, _.bind(this.onError, this), _.bind(function (xhr) {
if (xhr.status >= 200 && xhr.status < 299) {
if (xhr.status === 200 && _.isFunction(onSuccess)) {
onSuccess(xhr);
this.hide();
} else {
var detail = xhr.responseJSON;
if (xhr.status !== 200 && _.isObject(detail) && detail.messages) {
this.unlock();
this.onError(xhr, label);
} else {
this.hide();
}
}
} else {
this.unlock();
this.onError(xhr, label);
}
}, this));
},
onError: function (xhr, title) {
if (xhr.responseJSON && xhr.responseJSON.messages) {
var status = xhr.responseJSON;
this.messages(status.success ? (status.warning ? 'warn' : 'info') : 'error',
status.title || title, status.messages);
} else {
this.errorMessage(title ? title : "Error", xhr);
}
},
errorMessage: function (message, result) {
var detail = result.responseJSON;
if (_.isObject(detail) && detail.response) {
this.messages(detail.response.level, detail.response.text, detail.messages);
} else {
var level = result.status >= 300 && result.status < 399 ? 'info' : 'danger';
this.alert(level, message, result);
}
}
});
/**
* a dialog which can be 'opened' inside of another open dialog
*/
components.StackableDialog = components.Dialog.extend({
initialize: function (options) {
core.components.Dialog.prototype.initialize.call(this, options);
},
/**
*
*/
show: function (initView, callback) {
core.components.Dialog.prototype.show.call(this, initView, callback);
},
hide: function () {
core.components.Dialog.prototype.hide.call(this);
}
});
components.AbstractPathSelectDialog = components.StackableDialog.extend({
initialize: function (options) {
components.StackableDialog.prototype.initialize.call(this, options);
this.$title = this.$('.modal-title');
this.$label = this.$('.path-input-label');
this.input = core.getView(this.$(options.inputSelector || 'input.path-input'),
options.inputType || core.components.PathWidget);
this.input.$el.on('change', _.bind(this.inputChanged, this));
},
/**
* @extends components.StackableDialog - initialization after shown...
*/
onShown: function () {
components.StackableDialog.prototype.onShown.call(this);
this.inputChanged(); // simulate a change after shown to initialize the view
},
/**
* @override prevent from default callback handling;
* the callback must be triggerded by 'select' with the value (path) as parameter
*/
hide: function () {
this.$el.modal('hide');
this.reset();
},
/**
* defines the dialog title (default: 'default' data attribute of the title element)
*/
setTitle: function (title) {
this.$title.text(title ? title : this.$title.data('default'));
},
/**
* defines the input field label (default: 'default' data attribute of the label element)
*/
setLabel: function (label) {
this.$label.text(label ? label : this.$label.data('default'));
},
/**
* defines the root path for the selector (default: '/')
*/
setRootPath: function (rootPath) {
this.input.setRootPath(rootPath);
},
/**
* defines the node filter for the view; should be set according to the current value type
*/
setFilter: function (filter) {
this.input.setFilter(filter);
},
/**
* returns the current path value selected in this dialog
*/
getValue: function () {
return this.input.getValue();
},
/**
* defines the (initial) value - the current / old value
*/
setValue: function (value) {
this.input.setValue(value);
this.inputChanged(); // trigger state refresh
},
/**
* @abstract the callback on each change in the input field
*/
inputChanged: function () {
}
});
/**
* the dialog to select a repository path in a tree view
*/
components.SelectPathDialog = components.AbstractPathSelectDialog.extend({
initialize: function (options) {
components.AbstractPathSelectDialog.prototype.initialize.apply(this, [options]);
this.busy = false;
this.tree = core.getView(this.$('.path-select-tree'), components.Tree);
this.tree.onNodeSelected = _.bind(this.onNodeSelected, this);
this.$('button.select').click(_.bind(function () {
if (_.isFunction(this.callback)) {
this.callback(this.getValue());
}
this.hide();
}, this));
},
/**
* defines the root path for the tree (default: '/')
*/
setRootPath: function (rootPath) {
components.AbstractPathSelectDialog.prototype.setRootPath.call(this, rootPath);
this.tree.setRootPath(rootPath);
},
/**
* defines the node filter for the tree; should be set according to the current value type
*/
setFilter: function (filter) {
components.AbstractPathSelectDialog.prototype.setFilter.call(this, filter);
this.tree.setFilter(filter);
},
/**
* the callback on each change in the input field;
* selects the node in the tree view if the nodes exists
*/
inputChanged: function () {
if (!this.busy) {
this.busy = true;
var path = this.getValue();
if (path.indexOf('/') === 0) {
core.getJson('/bin/cpm/nodes/node.tree.json' + core.encodePath(path), _.bind(function (data) {
this.tree.selectNode.apply(this.tree, [data.path]);
}, this));
}
this.busy = false;
}
},
/**
* callback from the tree if a node is selected;
* sets the select path in the input field
*/
onNodeSelected: function (path) {
if (!this.busy) {
this.busy = true;
this.setValue(path);
this.busy = false;
}
},
/**
* extended to reset the selection in the tree
*/
reset: function () {
components.AbstractPathSelectDialog.prototype.reset.apply(this);
this.tree.reset.apply(this.tree);
}
});
/**
* load dialogs on demand and remove them on close
*/
components.LoadedDialog = components.Dialog.extend({
initialize: function (options) {
core.components.Dialog.prototype.initialize.call(this, _.extend({
loaded: true
}, options));
}
});
core.addLoadedDialog = function (viewType, html, config) {
var $body = $('body');
$body.append(html);
var $dialog = $body.children(':last-child');
return core.getWidget($body, $dialog[0], viewType, config
? _.extend({loaded: true}, config) : {loaded: true});
};
core.showLoadedDialog = function (viewType, html, config, initView, callback) {
var dialog = core.addLoadedDialog(viewType, html, config);
if (dialog) {
dialog.show(initView, callback);
}
};
core.openLoadedDialog = function (url, viewType, config, initView, callback) {
core.getHtml(url, function (content) {
core.showLoadedDialog(viewType, content, config, initView, callback);
});
};
/**
* a FormDialog supports validation before submit
* uses a 'config' attribute:
* config {
* validationUrl: a function or value for validation roundtrip and i18n translation of validation messages
* }
*/
components.FormDialog = components.LoadedDialog.extend({
initialize: function (options) {
var formType = options ? (options.formType || core.components.FormWidget) : core.components.FormWidget;
this.form = core.getWidget(this.el, "form", formType);
core.components.LoadedDialog.prototype.initialize.call(this, options);
this.validationHints = [];
this.initView();
this.initSubmit();
},
getConfig: function () {
return this.config;
},
dialogData: function () {
return {};
},
initSubmit: function () {
this.$('form').on('submit', _.bind(this.onSubmit, this));
},
initView: function () {
},
validationReset: function () {
this.$alert.addClass('alert-hidden');
this.$alert.html('');
this.validationHints = [];
this.form.validationReset();
},
onValidationFault: function () {
this.form.onValidationFault();
},
message: function (type, label, message, hint) {
if (message) {
this.alert(type, '' + label
+ '');
}
},
hintsMessage: function (level) {
this.messages(level ? level : 'warning', this.validationHints.length < 1
? 'validation error' : undefined, this.validationHints);
},
validationHint: function (type, label, message, hint) {
if (message) {
this.validationHints.push({level: type, label: label, text: message, hint: hint});
}
},
validateForm: function () {
this.validationReset();
return this.form.validate(_.bind(this.validationHint, this));
},
/**
* @returns {{dialog: (*|{}), messages: Array}} the data for a validation roundtrip
*/
validationData: function () {
return {
dialog: this.dialogData(),
messages: this.validationHints
};
},
/**
* the validation strategy with support for an asynchronous validation call;
* if a validationUrl configuration is supported a PUT request with the current validation hints is sent
* via PUT to that url for extended validation and/or validation hints i18n translation
* @param onSuccess called after successful validation
* @param onError called if a validation fault registered
*/
doValidate: function (onSuccess, onError) {
var valid = this.validateForm();
var config = this.getConfig();
if (config && config.validationUrl) {
var url = _.isFunction(config.validationUrl) ? config.validationUrl() : config.validationUrl;
var data = this.validationData();
core.ajaxPut(url, JSON.stringify(data), {
dataType: 'json'
}, undefined, undefined,
_.bind(function (xhr) {
var result = xhr.responseJSON;
if (result) {
if (result.messages) {
this.validationHints = result.messages;
}
if (result.success) {
onSuccess();
} else {
onError();
}
} else {
this.onError(xhr);
}
}, this))
} else {
if (valid) {
onSuccess();
} else {
onError();
}
}
},
/**
* @default the simplest 'save and return' - no message - override if not enough
*/
doSubmit: function () {
this.submitForm();
},
/**
* triggered if the submit button is clicked or activated somewhere else
*/
onSubmit: function (event) {
if (event) {
event.preventDefault();
}
this.form.prepare();
this.doValidate(_.bind(function () {
this.form.finalize();
this.doSubmit(_.bind(function (result) {
this.showResult(result);
}, this));
}, this), _.bind(function () {
if (this.validationHints.length < 1) {
core.i18n.get('Validation error', _.bind(function (text) {
this.messages('warning', text, this.validationHints);
}, this));
} else {
this.messages('warning', undefined, this.validationHints);
}
this.onValidationFault();
}, this));
return false;
},
showResult: function (result) {
if (_.isObject(result) && _.isObject(result.response)) {
var response = result.response;
var messages = result.messages;
if (_.isArray(messages) && messages.length > 0) {
core.messages(response.level, response.text, messages);
} else {
core.alert(response.level, response.title, response.text);
}
}
}
});
core.showFormDialog = function (viewType, html, config, initView, callback) {
core.showLoadedDialog(viewType, html, config, initView, callback);
};
core.openFormDialog = function (url, viewType, config, initView, callback) {
core.getHtml(url, function (content) {
core.showLoadedDialog(viewType, content, config, initView, callback);
});
};
})(CPM.core.components, CPM.core);
})();
© 2015 - 2025 Weber Informatics LLC | Privacy Policy