ORIENT/modules/system/assets/ui/js/inspector.wrapper.base.js

413 lines
13 KiB
JavaScript

/*
* Inspector wrapper base class.
*/
+function ($) { "use strict";
// NAMESPACES
// ============================
if ($.oc.inspector === undefined)
$.oc.inspector = {}
if ($.oc.inspector.wrappers === undefined)
$.oc.inspector.wrappers = {}
// CLASS DEFINITION
// ============================
var Base = $.oc.foundation.base,
BaseProto = Base.prototype
var BaseWrapper = function($element, sourceWrapper, options) {
this.$element = $element
this.options = $.extend({}, BaseWrapper.DEFAULTS, typeof options == 'object' && options)
this.switched = false
this.configuration = null
Base.call(this)
if (!sourceWrapper) {
if (!this.triggerShowingAndInit()) {
// this.init() is called inside triggerShowing()
return
}
this.surface = null
this.title = null
this.description = null
}
else {
this.surface = sourceWrapper.surface
this.title = sourceWrapper.title
this.description = sourceWrapper.description
sourceWrapper = null
this.init()
}
}
BaseWrapper.prototype = Object.create(BaseProto)
BaseWrapper.prototype.constructor = Base
BaseWrapper.prototype.dispose = function() {
if (!this.switched) {
this.$element.removeClass('inspector-open')
this.setInspectorVisibleFlag(false)
this.$element.trigger('hidden.oc.inspector')
}
if (this.surface !== null && this.surface.options.onGetInspectableElement === this.proxy(this.onGetInspectableElement)) {
this.surface.options.onGetInspectableElement = null
}
this.surface = null
this.$element = null
this.title = null
this.description = null
this.configuration = null
BaseProto.dispose.call(this)
}
BaseWrapper.prototype.init = function() {
// Wrappers can create a new surface or inject an existing
// surface to the UI they manage.
//
// If there is no surface provided in the wrapper constructor,
// the wrapper first loads the Inspector configuration and values
// and then calls the createSurfaceAndUi() method with all information
// required for creating a new Inspector surface and UI.
if (!this.surface) {
this.loadConfiguration()
}
else {
this.adoptSurface()
}
this.$element.addClass('inspector-open')
}
//
// Helper methods
//
BaseWrapper.prototype.getElementValuesInput = function() {
return this.$element.find('> input[data-inspector-values]')
}
BaseWrapper.prototype.normalizePropertyCode = function(code, configuration) {
var lowerCaseCode = code.toLowerCase()
for (var index in configuration) {
var propertyInfo = configuration[index]
if (propertyInfo.property.toLowerCase() == lowerCaseCode) {
return propertyInfo.property
}
}
return code
}
BaseWrapper.prototype.isExternalParametersEditorEnabled = function() {
return this.$element.closest('[data-inspector-external-parameters]').length > 0
}
BaseWrapper.prototype.initSurface = function(containerElement, properties, values) {
var options = this.$element.data() || {}
options.enableExternalParameterEditor = this.isExternalParametersEditorEnabled()
options.onGetInspectableElement = this.proxy(this.onGetInspectableElement)
this.surface = new $.oc.inspector.surface(
containerElement,
properties,
values,
$.oc.inspector.helpers.generateElementUniqueId(this.$element.get(0)),
options)
}
BaseWrapper.prototype.isLiveUpdateEnabled = function() {
return false
}
//
// Wrapper API
//
BaseWrapper.prototype.createSurfaceAndUi = function(properties, values) {
}
BaseWrapper.prototype.setInspectorVisibleFlag = function(value) {
this.$element.data('oc.inspectorVisible', value)
}
BaseWrapper.prototype.adoptSurface = function() {
this.surface.options.onGetInspectableElement = this.proxy(this.onGetInspectableElement)
}
BaseWrapper.prototype.cleanupAfterSwitch = function() {
this.switched = true
this.dispose()
}
//
// Values
//
BaseWrapper.prototype.loadValues = function(configuration) {
var $valuesField = this.getElementValuesInput()
if ($valuesField.length > 0) {
var valuesStr = $.trim($valuesField.val())
try {
return valuesStr.length === 0 ? {} : $.parseJSON(valuesStr)
} catch (err) {
throw new Error('Error parsing Inspector field values. ' + err)
}
}
var values = {},
attributes = this.$element.get(0).attributes
for (var i=0, len = attributes.length; i < len; i++) {
var attribute = attributes[i],
matches = []
if (matches = attribute.name.match(/^data-property-(.*)$/)) {
// Important - values contained in data-property-xxx attributes are
// considered strings and never parsed with JSON. The use of the
// data-property-xxx attributes is very limited - they're only
// used in Pages for creating snippets from partials, where properties
// are created with a table UI widget, which doesn't allow creating
// properties of any complex types.
//
// There is no a technically reliable way to determine when a string
// is a JSON data or a regular string. Users can enter a value
// like [10], which is a proper JSON value, but meant to be a string.
//
// One possible way to resolve it, if to check the property type loaded
// from the configuration and see if the corresponding editor expects
// complex data.
var normalizedPropertyName = this.normalizePropertyCode(matches[1], configuration)
values[normalizedPropertyName] = attribute.value
}
}
return values
}
BaseWrapper.prototype.applyValues = function(liveUpdateMode) {
var $valuesField = this.getElementValuesInput(),
values = liveUpdateMode ?
this.surface.getValidValues() :
this.surface.getValues()
if (liveUpdateMode) {
// In the live update mode, when only valid values are applied,
// we don't want to change all other values (invalid properties).
var existingValues = this.loadValues(this.configuration)
for (var property in values) {
if (values[property] !== $.oc.inspector.invalidProperty) {
existingValues[property] = values[property]
}
}
// Properties that use settings like ignoreIfPropertyEmpty could
// be removed from the list returned by getValidValues(). Removed
// properties should be removed from the result list.
var filteredValues = {}
for (var property in existingValues) {
if (values.hasOwnProperty(property)) {
filteredValues[property] = existingValues[property]
}
}
values = filteredValues
}
if ($valuesField.length > 0) {
$valuesField.val(JSON.stringify(values))
}
else {
for (var property in values) {
var value = values[property]
if ($.isArray(value) ||$.isPlainObject(value)) {
throw new Error('Inspector data-property-xxx attributes do not support complex values. Property: ' + property)
}
this.$element.attr('data-property-' + property, value)
}
}
// In the live update mode the livechange event is triggered
// regardless of whether Surface properties match or don't match
// the original properties of the inspectable element. Without it
// there could be undesirable side effects.
if (liveUpdateMode) {
this.$element.trigger('livechange')
}
else {
var hasChanges = false
if (this.isLiveUpdateEnabled()) {
var currentValues = this.loadValues(this.configuration)
// If the Inspector setup supports the live update mode,
// evaluate changes as a difference between the current element
// properties and internal properties stored in the Surface.
// If there is no differences, the properties have already
// been applied with a preceding live update.
hasChanges = this.surface.hasChanges(currentValues)
}
else {
hasChanges = this.surface.hasChanges()
}
if (hasChanges) {
this.$element.trigger('change')
}
}
}
//
// Configuration
//
BaseWrapper.prototype.loadConfiguration = function() {
var configString = this.$element.data('inspector-config'),
result = {
properties: {},
title: null,
description: null
}
result.title = this.$element.data('inspector-title')
result.description = this.$element.data('inspector-description')
if (configString !== undefined) {
result.properties = this.parseConfiguration(configString)
this.configurationLoaded(result)
return
}
var $configurationField = this.$element.find('input[data-inspector-config]')
if ($configurationField.length > 0) {
result.properties = this.parseConfiguration($configurationField.val())
this.configurationLoaded(result)
return
}
var $form = this.$element.closest('form'),
data = this.$element.data(),
self = this
$.oc.stripeLoadIndicator.show()
var request = $form.request('onGetInspectorConfiguration', {
data: data
}).done(function inspectorConfigurationRequestDoneClosure(data) {
self.onConfigurartionRequestDone(data, result)
}).always(function() {
$.oc.stripeLoadIndicator.hide()
})
}
BaseWrapper.prototype.parseConfiguration = function(configuration) {
if (!$.isArray(configuration) && !$.isPlainObject(configuration)) {
if ($.trim(configuration) === 0) {
return {}
}
try {
return $.parseJSON(configuration)
} catch(err) {
throw new Error('Error parsing Inspector configuration. ' + err)
}
} else {
return configuration
}
}
BaseWrapper.prototype.configurationLoaded = function(configuration) {
var values = this.loadValues(configuration.properties)
this.title = configuration.title
this.description = configuration.description
this.configuration = configuration
this.createSurfaceAndUi(configuration.properties, values)
}
BaseWrapper.prototype.onConfigurartionRequestDone = function(data, result) {
result.properties = this.parseConfiguration(data.configuration.properties)
if (data.configuration.title !== undefined) {
result.title = data.configuration.title
}
if (data.configuration.description !== undefined) {
result.description = data.configuration.description
}
this.configurationLoaded(result)
}
//
// Events
//
BaseWrapper.prototype.triggerShowingAndInit = function() {
var e = $.Event('showing.oc.inspector')
this.$element.trigger(e, [{callback: this.proxy(this.init)}])
if (e.isDefaultPrevented()) {
this.$element = null
return false
}
if (!e.isPropagationStopped()) {
this.init()
}
}
BaseWrapper.prototype.triggerHiding = function() {
var hidingEvent = $.Event('hiding.oc.inspector'),
values = this.surface.getValues()
this.$element.trigger(hidingEvent, [{values: values}])
if (hidingEvent.isDefaultPrevented()) {
return false
}
return true
}
BaseWrapper.prototype.onGetInspectableElement = function() {
return this.$element
}
BaseWrapper.DEFAULTS = {
containerSupported: false
}
$.oc.inspector.wrappers.base = BaseWrapper
}(window.jQuery);