413 lines
13 KiB
JavaScript
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); |