Added popup and container wrappers for Inspector, minor fixes in Inspector editors, minor extensions in the popup API.
This commit is contained in:
parent
409c40c248
commit
6ddc366808
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Inspector data interaction class.
|
||||
*
|
||||
* Provides methods for loading and writing Inspector configuration
|
||||
* and values form and to inspectable elements.
|
||||
*/
|
||||
+function ($) { "use strict";
|
||||
|
||||
// CLASS DEFINITION
|
||||
// ============================
|
||||
|
||||
var Base = $.oc.foundation.base,
|
||||
BaseProto = Base.prototype
|
||||
|
||||
var DataInteraction = function(element) {
|
||||
this.element = element
|
||||
|
||||
Base.call(this)
|
||||
}
|
||||
|
||||
DataInteraction.prototype = Object.create(BaseProto)
|
||||
DataInteraction.prototype.constructor = Base
|
||||
|
||||
DataInteraction.prototype.dispose = function() {
|
||||
this.element = null
|
||||
|
||||
BaseProto.dispose.call(this)
|
||||
}
|
||||
|
||||
DataInteraction.prototype.getElementValuesInput = function() {
|
||||
return this.element.querySelector('input[data-inspector-values]')
|
||||
}
|
||||
|
||||
DataInteraction.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
|
||||
}
|
||||
|
||||
DataInteraction.prototype.loadValues = function(configuration) {
|
||||
var valuesField = this.getElementValuesInput()
|
||||
|
||||
if (valuesField) {
|
||||
var valuesStr = $.trim(valuesField.value)
|
||||
|
||||
try {
|
||||
return valuesStr.length === 0 ? {} : $.parseJSON(valuesStr)
|
||||
} catch (err) {
|
||||
throw new Error('Error parsing Inspector field values. ' + err)
|
||||
}
|
||||
}
|
||||
|
||||
var values = {},
|
||||
attributes = this.element.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 = normalizePropertyCode(matches[1], configuration)
|
||||
values[normalizedPropertyName] = attribute.value
|
||||
}
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
DataInteraction.prototype.loadConfiguration = function(onComplete) {
|
||||
var configurationField = this.element.querySelector('input[data-inspector-config]'),
|
||||
result = {
|
||||
configuration: {},
|
||||
title: null,
|
||||
description: null
|
||||
},
|
||||
$element = $(this.element)
|
||||
|
||||
result.title = $element.data('inspector-title')
|
||||
result.description = $element.data('inspector-description')
|
||||
|
||||
if (configurationField) {
|
||||
result.configuration = this.parseConfiguration(configurationField.value)
|
||||
|
||||
onComplete(result, this)
|
||||
return
|
||||
}
|
||||
|
||||
var $form = $element.closest('form'),
|
||||
data = $element.data(),
|
||||
self = this
|
||||
|
||||
$.oc.stripeLoadIndicator.show()
|
||||
var request = $form.request('onGetInspectorConfiguration', {
|
||||
data: data
|
||||
}).done(function inspectorConfigurationRequestDoneClosure(data) {
|
||||
self.configurartionRequestDone(data, onComplete, result)
|
||||
}).always(function() {
|
||||
$.oc.stripeLoadIndicator.hide()
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// Internal methods
|
||||
//
|
||||
|
||||
DataInteraction.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
|
||||
}
|
||||
}
|
||||
|
||||
DataInteraction.prototype.configurartionRequestDone = function(data, onComplete, result) {
|
||||
result.configuration = 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
|
||||
}
|
||||
|
||||
onComplete(result, this)
|
||||
}
|
||||
|
||||
$.oc.inspector.dataInteraction = DataInteraction
|
||||
}(window.jQuery);
|
||||
|
|
@ -81,6 +81,10 @@
|
|||
return this.childInspector !== null
|
||||
}
|
||||
|
||||
BaseEditor.prototype.getRootSurface = function() {
|
||||
return this.inspector.getRootSurface()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates displayed value in the editor UI. The value is already set
|
||||
* in the Inspector and should be loaded from Inspector.
|
||||
|
|
|
|||
|
|
@ -631,6 +631,7 @@
|
|||
this.popup = popup.get(0)
|
||||
|
||||
this.buildPopupContents(this.popup)
|
||||
this.getRootSurface().popupDisplayed()
|
||||
}
|
||||
|
||||
ObjectListEditor.prototype.onPopupHidden = function(ev, link, popup) {
|
||||
|
|
@ -642,6 +643,7 @@
|
|||
$.oc.foundation.controlUtils.disposeControls(this.popup)
|
||||
|
||||
this.popup = null
|
||||
this.getRootSurface().popupHidden()
|
||||
}
|
||||
|
||||
ObjectListEditor.prototype.onSubmit = function(ev) {
|
||||
|
|
|
|||
|
|
@ -110,11 +110,15 @@
|
|||
this.popup = popup.get(0)
|
||||
|
||||
this.configurePopup(popup)
|
||||
|
||||
this.getRootSurface().popupDisplayed()
|
||||
}
|
||||
|
||||
PopupBase.prototype.onPopupHidden = function(ev, link, popup) {
|
||||
$(popup).off('.inspector', 'form', this.proxy(this.onSubmit))
|
||||
this.popup = null
|
||||
|
||||
this.getRootSurface().popupHidden()
|
||||
}
|
||||
|
||||
PopupBase.prototype.onSubmit = function(ev) {
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@
|
|||
value = $.trim($textarea.val())
|
||||
|
||||
this.inspector.setPropertyValue(this.propertyDefinition.property, value)
|
||||
// TODO: validate here
|
||||
}
|
||||
|
||||
$.oc.inspector.propertyEditors.text = TextEditor
|
||||
|
|
|
|||
|
|
@ -87,5 +87,4 @@
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
}(window.jQuery);
|
||||
|
|
|
|||
|
|
@ -29,4 +29,5 @@
|
|||
$.oc.inspector.helpers.generateUniqueId = function() {
|
||||
return "inspectorid-" + Math.floor(Math.random() * new Date().getTime());
|
||||
}
|
||||
|
||||
}(window.jQuery)
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Inspector management functions.
|
||||
*
|
||||
* Watches inspectable elements clicks and creates Inspector surfaces in popups
|
||||
* and containers.
|
||||
*/
|
||||
+function ($) { "use strict";
|
||||
var Base = $.oc.foundation.base,
|
||||
BaseProto = Base.prototype
|
||||
|
||||
var InspectorManager = function() {
|
||||
Base.call(this)
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
InspectorManager.prototype = Object.create(BaseProto)
|
||||
InspectorManager.prototype.constructor = Base
|
||||
|
||||
InspectorManager.prototype.init = function() {
|
||||
$(document).on('click', '[data-inspectable]', this.proxy(this.onInspectableClicked))
|
||||
}
|
||||
|
||||
InspectorManager.prototype.getContainerElement = function($element) {
|
||||
var $containerHolder = $element.closest('[data-inspector-container]')
|
||||
if ($containerHolder.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
var $container = $containerHolder.find($containerHolder.data('inspector-container'))
|
||||
if ($container.length === 0) {
|
||||
throw new Error('Inspector container ' + $containerHolder.data['inspector-container'] + ' element is not found.')
|
||||
}
|
||||
|
||||
return $container
|
||||
}
|
||||
|
||||
InspectorManager.prototype.createInspectorPopup = function($element, containerSupported) {
|
||||
new $.oc.inspector.wrappers.popup($element, null, {
|
||||
containerSupported: containerSupported
|
||||
})
|
||||
}
|
||||
|
||||
InspectorManager.prototype.createInspectorContainer = function($element, $container) {
|
||||
new $.oc.inspector.wrappers.container($element, null, {
|
||||
containerSupported: true,
|
||||
container: $container
|
||||
})
|
||||
}
|
||||
|
||||
InspectorManager.prototype.switchToPopup = function(wrapper) {
|
||||
new $.oc.inspector.wrappers.popup(wrapper.$element, wrapper, {
|
||||
containerSupported: true
|
||||
})
|
||||
|
||||
wrapper.cleanupAfterSwitch()
|
||||
this.setContainerPreference(false)
|
||||
}
|
||||
|
||||
InspectorManager.prototype.switchToContainer = function(wrapper) {
|
||||
var $container = this.getContainerElement(wrapper.$element)
|
||||
|
||||
if (!$container) {
|
||||
throw new Error('Cannot switch to container: a container element is not found')
|
||||
}
|
||||
|
||||
new $.oc.inspector.wrappers.container(wrapper.$element, wrapper, {
|
||||
containerSupported: true,
|
||||
container: $container
|
||||
})
|
||||
|
||||
wrapper.cleanupAfterSwitch()
|
||||
this.setContainerPreference(true)
|
||||
}
|
||||
|
||||
InspectorManager.prototype.createInspector = function($element) {
|
||||
var $container = this.getContainerElement($element)
|
||||
|
||||
// If there's no container option, create the Inspector popup
|
||||
//
|
||||
if (!$container) {
|
||||
this.createInspectorPopup($element, false)
|
||||
}
|
||||
else {
|
||||
// If the container is already in use, apply values to the inspectable elements
|
||||
if (!this.applyValuesFromContainer($container)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Dispose existing container wrapper, if any
|
||||
$.oc.foundation.controlUtils.disposeControls($container.get(0))
|
||||
|
||||
if (!this.getContainerPreference()) {
|
||||
// If container is not a preferred option, create Inspector popoup
|
||||
this.createInspectorPopup($element, true)
|
||||
}
|
||||
else {
|
||||
// Otherwise, create Inspector in the container
|
||||
this.createInspectorContainer($element, $container)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InspectorManager.prototype.getContainerPreference = function() {
|
||||
if (!Modernizr.localstorage) {
|
||||
return false
|
||||
}
|
||||
|
||||
return localStorage.getItem('oc.inspectorUseContainer') === "true"
|
||||
}
|
||||
|
||||
InspectorManager.prototype.setContainerPreference = function(value) {
|
||||
if (!Modernizr.localstorage) {
|
||||
return
|
||||
}
|
||||
|
||||
return localStorage.setItem('oc.inspectorUseContainer', value ? "true" : "false")
|
||||
}
|
||||
|
||||
InspectorManager.prototype.applyValuesFromContainer = function($container) {
|
||||
var applyEvent = $.Event('apply.oc.inspector')
|
||||
|
||||
$container.trigger(applyEvent)
|
||||
if (applyEvent.isDefaultPrevented()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
InspectorManager.prototype.onInspectableClicked = function(ev) {
|
||||
var $element = $(ev.currentTarget)
|
||||
|
||||
if ($element.data('oc.inspectorVisible'))
|
||||
return false
|
||||
|
||||
this.createInspector($element)
|
||||
}
|
||||
|
||||
$.oc.inspector.manager = new InspectorManager()
|
||||
}(window.jQuery);
|
||||
|
|
@ -48,6 +48,7 @@
|
|||
this.values = values
|
||||
this.originalValues = $.extend(true, {}, values) // Clone the values hash
|
||||
this.idCounter = 1
|
||||
this.popupCounter = 0
|
||||
this.parentSurface = parentSurface
|
||||
|
||||
this.editors = []
|
||||
|
|
@ -88,6 +89,8 @@
|
|||
this.values = null
|
||||
this.originalValues = null
|
||||
this.options.onChange = null
|
||||
this.options.onPopupDisplayed = null
|
||||
this.options.onPopupHidden = null
|
||||
this.parentSurface = null
|
||||
this.groupManager = null
|
||||
this.group = null
|
||||
|
|
@ -212,6 +215,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
Surface.prototype.moveToContainer = function(newContainer) {
|
||||
this.container = newContainer
|
||||
|
||||
this.container.appendChild(this.tableContainer)
|
||||
}
|
||||
|
||||
Surface.prototype.buildRow = function(property, group) {
|
||||
var row = document.createElement('tr'),
|
||||
th = document.createElement('th'),
|
||||
|
|
@ -611,6 +620,26 @@
|
|||
}
|
||||
}
|
||||
|
||||
Surface.prototype.popupDisplayed = function() {
|
||||
if (this.popupCounter === 0 && this.options.onPopupDisplayed !== null) {
|
||||
this.options.onPopupDisplayed()
|
||||
}
|
||||
|
||||
this.popupCounter++
|
||||
}
|
||||
|
||||
Surface.prototype.popupHidden = function() {
|
||||
this.popupCounter--
|
||||
|
||||
if (this.popupCounter < 0) {
|
||||
this.popupCounter = 0
|
||||
}
|
||||
|
||||
if (this.popupCounter === 0 && this.options.onPopupHidden !== null) {
|
||||
this.options.onPopupHidden()
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Nested surfaces support
|
||||
//
|
||||
|
|
@ -809,7 +838,9 @@
|
|||
|
||||
Surface.DEFAULTS = {
|
||||
enableExternalParameterEditor: false,
|
||||
onChange: null
|
||||
onChange: null,
|
||||
onPopupDisplayed: null,
|
||||
onPopupHidden: null
|
||||
}
|
||||
|
||||
// REGISTRATION
|
||||
|
|
|
|||
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
if (!sourceWrapper) {
|
||||
this.surface = surface
|
||||
this.title = null
|
||||
this.description = null
|
||||
}
|
||||
else {
|
||||
this.surface = sourceWrapper.surface
|
||||
this.title = sourceWrapper.title
|
||||
this.description = sourceWrapper.description
|
||||
|
||||
sourceWrapper = null
|
||||
}
|
||||
|
||||
this.options = $.extend({}, BaseWrapper.DEFAULTS, typeof options == 'object' && options)
|
||||
this.switched = false
|
||||
|
||||
Base.call(this)
|
||||
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.surface = null
|
||||
this.$element = null
|
||||
this.title = null
|
||||
this.description = 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 = {
|
||||
enableExternalParameterEditor: this.isExternalParametersEditorEnabled()
|
||||
}
|
||||
|
||||
this.surface = new $.oc.inspector.surface(
|
||||
containerElement,
|
||||
properties,
|
||||
values,
|
||||
$.oc.inspector.helpers.generateElementUniqueId(this.$element.get(0)),
|
||||
options)
|
||||
}
|
||||
|
||||
//
|
||||
// Wrapper API
|
||||
//
|
||||
|
||||
BaseWrapper.prototype.createSurfaceAndUi = function(properties, values) {
|
||||
|
||||
}
|
||||
|
||||
BaseWrapper.prototype.setInspectorVisibleFlag = function(value) {
|
||||
this.$element.data('oc.inspectorVisible', value)
|
||||
}
|
||||
|
||||
BaseWrapper.prototype.adoptSurface = function() {
|
||||
|
||||
}
|
||||
|
||||
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() {
|
||||
var $valuesField = this.getElementValuesInput(),
|
||||
values = this.surface.getValues()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Configuration
|
||||
//
|
||||
|
||||
BaseWrapper.prototype.loadConfiguration = function() {
|
||||
var $configurationField = this.$element.find('input[data-inspector-config]'),
|
||||
result = {
|
||||
properties: {},
|
||||
title: null,
|
||||
description: null
|
||||
}
|
||||
|
||||
result.title = this.$element.data('inspector-title')
|
||||
result.description = this.$element.data('inspector-description')
|
||||
|
||||
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.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)
|
||||
}
|
||||
|
||||
BaseWrapper.DEFAULTS = {
|
||||
containerSupported: false
|
||||
}
|
||||
|
||||
$.oc.inspector.wrappers.base = BaseWrapper
|
||||
}(window.jQuery);
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* Inspector container wrapper.
|
||||
*/
|
||||
+function ($) { "use strict";
|
||||
|
||||
// CLASS DEFINITION
|
||||
// ============================
|
||||
|
||||
var Base = $.oc.inspector.wrappers.base,
|
||||
BaseProto = Base.prototype
|
||||
|
||||
var InspectorContainer = function($element, surface, options) {
|
||||
if (!options.container) {
|
||||
throw new Error('Cannot create Inspector container wrapper without a container element.')
|
||||
}
|
||||
|
||||
this.surfaceContainer = null
|
||||
|
||||
Base.call(this, $element, surface, options)
|
||||
}
|
||||
|
||||
InspectorContainer.prototype = Object.create(BaseProto)
|
||||
InspectorContainer.prototype.constructor = Base
|
||||
|
||||
InspectorContainer.prototype.init = function() {
|
||||
this.registerHandlers()
|
||||
|
||||
BaseProto.init.call(this)
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.dispose = function() {
|
||||
this.unregisterHandlers()
|
||||
this.removeControls()
|
||||
|
||||
this.surfaceContainer = null
|
||||
|
||||
BaseProto.dispose.call(this)
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.createSurfaceAndUi = function(properties, values) {
|
||||
this.buildUi()
|
||||
|
||||
this.initSurface(this.surfaceContainer, properties, values)
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.adoptSurface = function() {
|
||||
this.buildUi()
|
||||
|
||||
this.surface.moveToContainer(this.surfaceContainer)
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.buildUi = function() {
|
||||
var scrollable = this.isScrollable(),
|
||||
head = this.buildHead(),
|
||||
layoutElements = this.buildLayout()
|
||||
|
||||
layoutElements.headContainer.appendChild(head)
|
||||
|
||||
if (scrollable) {
|
||||
var scrollpad = this.buildScrollpad()
|
||||
|
||||
this.surfaceContainer = scrollpad.container
|
||||
layoutElements.bodyContainer.appendChild(scrollpad.scrollpad)
|
||||
|
||||
$(scrollpad.scrollpad).scrollpad()
|
||||
}
|
||||
else {
|
||||
this.surfaceContainer = layoutElements.bodyContainer
|
||||
}
|
||||
|
||||
this.setInspectorVisibleFlag(true)
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.buildHead = function() {
|
||||
var container = document.createElement('div'),
|
||||
header = document.createElement('h3'),
|
||||
paragraph = document.createElement('p'),
|
||||
detachButton = document.createElement('span'),
|
||||
closeButton = document.createElement('span')
|
||||
|
||||
container.setAttribute('class', 'inspector-header')
|
||||
detachButton.setAttribute('class', 'oc-icon-external-link-square detach')
|
||||
closeButton.setAttribute('class', 'close')
|
||||
|
||||
header.textContent = this.title
|
||||
paragraph.textContent = this.description
|
||||
closeButton.innerHTML = '×';
|
||||
|
||||
container.appendChild(header)
|
||||
container.appendChild(paragraph)
|
||||
container.appendChild(detachButton)
|
||||
container.appendChild(closeButton)
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.buildScrollpad = function() {
|
||||
var scrollpad = document.createElement('div'),
|
||||
scrollWrapper = document.createElement('div'),
|
||||
scrollableContainer = document.createElement('div')
|
||||
|
||||
scrollpad.setAttribute('class', 'control-scrollpad')
|
||||
scrollpad.setAttribute('data-control', 'scrollpad')
|
||||
scrollWrapper.setAttribute('class', 'scroll-wrapper inspector-wrapper')
|
||||
|
||||
scrollpad.appendChild(scrollWrapper)
|
||||
scrollWrapper.appendChild(scrollableContainer)
|
||||
|
||||
return {
|
||||
scrollpad: scrollpad,
|
||||
container: scrollableContainer
|
||||
}
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.buildLayout = function() {
|
||||
var layout = document.createElement('div'),
|
||||
headRow = document.createElement('div'),
|
||||
bodyRow = document.createElement('div'),
|
||||
bodyCell = document.createElement('div'),
|
||||
layoutRelative = document.createElement('div')
|
||||
|
||||
layout.setAttribute('class', 'layout')
|
||||
headRow.setAttribute('class', 'layout-row min-size')
|
||||
bodyRow.setAttribute('class', 'layout-row')
|
||||
bodyCell.setAttribute('class', 'layout-cell')
|
||||
layoutRelative.setAttribute('class', 'layout-relative')
|
||||
|
||||
bodyCell.appendChild(layoutRelative)
|
||||
bodyRow.appendChild(bodyCell)
|
||||
|
||||
layout.appendChild(headRow)
|
||||
layout.appendChild(bodyRow)
|
||||
|
||||
this.options.container.get(0).appendChild(layout)
|
||||
|
||||
$.oc.foundation.controlUtils.markDisposable(layout)
|
||||
this.registerLayoutHandlers(layout)
|
||||
|
||||
return {
|
||||
headContainer: headRow,
|
||||
bodyContainer: layoutRelative
|
||||
}
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.validateAndApply = function() {
|
||||
if (!this.surface.validate()) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.applyValues()
|
||||
return true
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.isScrollable = function() {
|
||||
return this.options.container.data('inspector-scrollable') !== undefined
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.getLayout = function() {
|
||||
return this.options.container.get(0).querySelector('div.layout')
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.registerLayoutHandlers = function(layout) {
|
||||
var $layout = $(layout)
|
||||
|
||||
$layout.one('dispose-control', this.proxy(this.dispose))
|
||||
$layout.on('click', 'span.close', this.proxy(this.onClose))
|
||||
$layout.on('click', 'span.detach', this.proxy(this.onDetach))
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.registerHandlers = function() {
|
||||
this.options.container.on('apply.oc.inspector', this.proxy(this.onApplyValues))
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.unregisterHandlers = function() {
|
||||
var $layout = $(this.getLayout())
|
||||
|
||||
this.options.container.off('apply.oc.inspector', this.proxy(this.onApplyValues))
|
||||
$layout.off('dispose-control', this.proxy(this.dispose))
|
||||
$layout.off('click', 'span.close', this.proxy(this.onClose))
|
||||
$layout.off('click', 'span.detach', this.proxy(this.onDetach))
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.removeControls = function() {
|
||||
if (this.isScrollable()) {
|
||||
this.options.container.find('.control-scrollpad').scrollpad('dispose')
|
||||
}
|
||||
|
||||
var layout = this.getLayout()
|
||||
layout.parentNode.removeChild(layout)
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.onApplyValues = function(ev) {
|
||||
if (!this.validateAndApply()) {
|
||||
ev.preventDefault()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.onClose = function(ev) {
|
||||
if (!this.validateAndApply()) {
|
||||
ev.preventDefault()
|
||||
return false
|
||||
}
|
||||
|
||||
this.surface.dispose()
|
||||
|
||||
this.dispose()
|
||||
}
|
||||
|
||||
InspectorContainer.prototype.onDetach = function() {
|
||||
$.oc.inspector.manager.switchToPopup(this)
|
||||
}
|
||||
|
||||
$.oc.inspector.wrappers.container = InspectorContainer
|
||||
}(window.jQuery);
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* Inspector popup wrapper.
|
||||
*/
|
||||
+function ($) { "use strict";
|
||||
|
||||
// CLASS DEFINITION
|
||||
// ============================
|
||||
|
||||
var Base = $.oc.inspector.wrappers.base,
|
||||
BaseProto = Base.prototype
|
||||
|
||||
var InspectorPopup = function($element, surface, options) {
|
||||
this.$popoverContainer = null
|
||||
this.popoverObj = null
|
||||
this.cleaningUp = false
|
||||
|
||||
Base.call(this, $element, surface, options)
|
||||
}
|
||||
|
||||
InspectorPopup.prototype = Object.create(BaseProto)
|
||||
InspectorPopup.prototype.constructor = Base
|
||||
|
||||
InspectorPopup.prototype.dispose = function() {
|
||||
this.unregisterHandlers()
|
||||
|
||||
this.$popoverContainer = null
|
||||
this.popoverObj = null
|
||||
|
||||
BaseProto.dispose.call(this)
|
||||
}
|
||||
|
||||
InspectorPopup.prototype.createSurfaceAndUi = function(properties, values, title, description) {
|
||||
this.showPopover()
|
||||
|
||||
this.initSurface(this.$popoverContainer.find('[data-surface-container]').get(0), properties, values)
|
||||
this.registerPopupHandlers()
|
||||
}
|
||||
|
||||
InspectorPopup.prototype.adoptSurface = function() {
|
||||
this.showPopover()
|
||||
|
||||
this.surface.moveToContainer(this.$popoverContainer.find('[data-surface-container]').get(0))
|
||||
this.registerPopupHandlers()
|
||||
}
|
||||
|
||||
InspectorPopup.prototype.cleanupAfterSwitch = function() {
|
||||
this.cleaningUp = true
|
||||
this.switched = true
|
||||
|
||||
this.forceClose()
|
||||
|
||||
// The parent cleanupAfterSwitch() is not called because
|
||||
// disposing happens in onHide() triggered by forceClose()
|
||||
}
|
||||
|
||||
InspectorPopup.prototype.getPopoverContents = function() {
|
||||
return '<div class="popover-head"> \
|
||||
<h3 data-inspector-title></h3> \
|
||||
<p data-inspector-description></p> \
|
||||
<button type="button" class="close" \
|
||||
data-dismiss="popover" \
|
||||
aria-hidden="true">×</button> \
|
||||
</div> \
|
||||
<form autocomplete="off" onsubmit="return false"> \
|
||||
<div data-surface-container></div> \
|
||||
<form>'
|
||||
}
|
||||
|
||||
InspectorPopup.prototype.showPopover = function() {
|
||||
var offset = this.$element.data('inspector-offset'),
|
||||
offsetX = this.$element.data('inspector-offset-x'),
|
||||
offsetY = this.$element.data('inspector-offset-y'),
|
||||
placement = this.$element.data('inspector-placement'),
|
||||
fallbackPlacement = this.$element.data('inspector-fallback-placement')
|
||||
|
||||
if (offset === undefined) {
|
||||
offset = 15
|
||||
}
|
||||
|
||||
if (placement === undefined) {
|
||||
placement = 'bottom'
|
||||
}
|
||||
|
||||
if (fallbackPlacement === undefined) {
|
||||
fallbackPlacement = 'bottom'
|
||||
}
|
||||
|
||||
this.$element.ocPopover({
|
||||
content: this.getPopoverContents(),
|
||||
highlightModalTarget: true,
|
||||
modal: true,
|
||||
placement: placement,
|
||||
fallbackPlacement: fallbackPlacement,
|
||||
containerClass: 'control-inspector',
|
||||
container: this.$element.data('inspector-container'),
|
||||
offset: offset,
|
||||
offsetX: offsetX,
|
||||
offsetY: offsetY,
|
||||
width: 400
|
||||
})
|
||||
|
||||
this.setInspectorVisibleFlag(true)
|
||||
|
||||
this.popoverObj = this.$element.data('oc.popover')
|
||||
this.$popoverContainer = this.popoverObj.$container
|
||||
|
||||
if (this.options.containerSupported) {
|
||||
var moveToContainerButton = $('<span class="inspector-move-to-container oc-icon-download">')
|
||||
|
||||
this.$popoverContainer.find('.popover-head').append(moveToContainerButton)
|
||||
}
|
||||
|
||||
this.$popoverContainer.find('[data-inspector-title]').text(this.title)
|
||||
this.$popoverContainer.find('[data-inspector-description]').text(this.description)
|
||||
}
|
||||
|
||||
InspectorPopup.prototype.forceClose = function() {
|
||||
this.$popoverContainer.trigger('close.oc.popover')
|
||||
}
|
||||
|
||||
InspectorPopup.prototype.registerPopupHandlers = function() {
|
||||
this.surface.options.onPopupDisplayed = this.proxy(this.onPopupEditorDisplayed)
|
||||
this.surface.options.onPopupHidden = this.proxy(this.onPopupEditorHidden)
|
||||
this.popoverObj.options.onCheckDocumentClickTarget = this.proxy(this.onCheckDocumentClickTarget)
|
||||
|
||||
this.$element.on('hiding.oc.popover', this.proxy(this.onBeforeHide))
|
||||
this.$element.on('hide.oc.popover', this.proxy(this.onHide))
|
||||
this.$popoverContainer.on('keydown', this.proxy(this.onPopoverKeyDown))
|
||||
|
||||
if (this.options.containerSupported) {
|
||||
this.$popoverContainer.on('click', 'span.inspector-move-to-container', this.proxy(this.onMoveToContainer))
|
||||
}
|
||||
}
|
||||
|
||||
InspectorPopup.prototype.unregisterHandlers = function() {
|
||||
this.popoverObj.options.onCheckDocumentClickTarget = null
|
||||
|
||||
this.$element.off('hiding.oc.popover', this.proxy(this.onBeforeHide))
|
||||
this.$element.off('hide.oc.popover', this.proxy(this.onHide))
|
||||
this.$popoverContainer.off('keydown', this.proxy(this.onPopoverKeyDown))
|
||||
|
||||
if (this.options.containerSupported) {
|
||||
this.$popoverContainer.off('click', 'span.inspector-move-to-container', this.proxy(this.onMoveToContainer))
|
||||
}
|
||||
|
||||
this.surface.options.onPopupDisplayed = null
|
||||
this.surface.options.onPopupHidden = null
|
||||
}
|
||||
|
||||
InspectorPopup.prototype.onBeforeHide = function(ev) {
|
||||
if (this.cleaningUp) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.surface.validate()) {
|
||||
ev.preventDefault()
|
||||
return false
|
||||
}
|
||||
|
||||
var hidingEvent = $.Event('hiding.oc.inspector'),
|
||||
values = this.surface.getValues()
|
||||
|
||||
this.$element.trigger(hidingEvent, [{values: values}])
|
||||
if (hidingEvent.isDefaultPrevented()) {
|
||||
ev.preventDefault()
|
||||
return false
|
||||
}
|
||||
|
||||
this.applyValues()
|
||||
}
|
||||
|
||||
InspectorPopup.prototype.onHide = function(ev) {
|
||||
this.dispose()
|
||||
}
|
||||
|
||||
InspectorPopup.prototype.onPopoverKeyDown = function(ev) {
|
||||
if(ev.keyCode == 13) {
|
||||
$(ev.currentTarget).trigger('close.oc.popover')
|
||||
}
|
||||
}
|
||||
|
||||
InspectorPopup.prototype.onPopupEditorDisplayed = function() {
|
||||
this.popoverObj.options.closeOnPageClick = false
|
||||
this.popoverObj.options.closeOnEsc = false
|
||||
}
|
||||
|
||||
InspectorPopup.prototype.onPopupEditorHidden = function() {
|
||||
this.popoverObj.options.closeOnPageClick = true
|
||||
this.popoverObj.options.closeOnEsc = true
|
||||
}
|
||||
|
||||
InspectorPopup.prototype.onCheckDocumentClickTarget = function(element) {
|
||||
if ($.contains(this.$element, element) || this.$element.get(0) === element) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
InspectorPopup.prototype.onMoveToContainer = function() {
|
||||
$.oc.inspector.manager.switchToContainer(this)
|
||||
}
|
||||
|
||||
$.oc.inspector.wrappers.popup = InspectorPopup
|
||||
}(window.jQuery);
|
||||
|
|
@ -97,6 +97,7 @@
|
|||
$(document).off('.oc.popover')
|
||||
|
||||
this.docClickHandler = null
|
||||
this.options.onCheckDocumentClickTarget = null
|
||||
}
|
||||
|
||||
Popover.prototype.show = function(options) {
|
||||
|
|
@ -185,6 +186,10 @@
|
|||
if ($(e.target).hasClass('select2-offscreen'))
|
||||
return false
|
||||
|
||||
if (!self.options.closeOnEsc) { // The value of the option could be changed after the popover is displayed
|
||||
return false
|
||||
}
|
||||
|
||||
if (e.keyCode == 27) {
|
||||
self.hide()
|
||||
return false
|
||||
|
|
@ -320,6 +325,10 @@
|
|||
if (!this.options.closeOnPageClick)
|
||||
return
|
||||
|
||||
if (this.options.onCheckDocumentClickTarget && this.options.onCheckDocumentClickTarget(e.target)) {
|
||||
return
|
||||
}
|
||||
|
||||
if ($.contains(this.$container.get(0), e.target))
|
||||
return
|
||||
|
||||
|
|
@ -338,7 +347,8 @@
|
|||
container: false,
|
||||
containerClass: null,
|
||||
offset: 15,
|
||||
useAnimation: false
|
||||
useAnimation: false,
|
||||
onCheckDocumentClickTarget: null
|
||||
}
|
||||
|
||||
// POPOVER PLUGIN DEFINITION
|
||||
|
|
|
|||
|
|
@ -59,10 +59,10 @@
|
|||
@zindex-navbar: 1000;
|
||||
@zindex-dropdown: 1000;
|
||||
@zindex-popover: 1010;
|
||||
@zindex-tooltip: 1030;
|
||||
@zindex-navbar-fixed: 1030;
|
||||
@zindex-modal-background: 1040;
|
||||
@zindex-modal: 1050;
|
||||
@zindex-tooltip: 1060; // Tooltips should always be on the top
|
||||
|
||||
//
|
||||
// Typography
|
||||
|
|
|
|||
|
|
@ -153,6 +153,10 @@ div.control-popover {
|
|||
font-size: 13px;
|
||||
font-weight: 100;
|
||||
margin: 10px 0 0 0;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
|
|
@ -162,6 +166,7 @@ div.control-popover {
|
|||
top: 12px;
|
||||
color: @color-popover-head-text;
|
||||
outline: none;
|
||||
.opacity(0.4);
|
||||
|
||||
&:hover {
|
||||
.opacity(1);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -516,7 +516,7 @@ input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"
|
|||
.btn-text{font-size:13px;padding:9px 0;vertical-align:middle;display:inline-block}
|
||||
.btn-text a{color:#666666}
|
||||
.btn-text a:hover{color:#0181b9;text-decoration:none}
|
||||
.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}
|
||||
.tooltip{position:absolute;z-index:1060;display:block;visibility:visible;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}
|
||||
.tooltip.in{opacity:0.9;filter:alpha(opacity=90)}
|
||||
.tooltip.top{margin-top:-3px;padding:5px 0}
|
||||
.tooltip.right{margin-left:3px;padding:0 5px}
|
||||
|
|
@ -1202,7 +1202,8 @@ div.control-popover .popover-head{background:#d35400;padding:14px 16px;position:
|
|||
div.control-popover .popover-head:before{z-index:602;position:absolute}
|
||||
div.control-popover .popover-head h3{font-size:14px;font-weight:600;margin-top:0;margin-bottom:0;padding-right:15px;line-height:130%}
|
||||
div.control-popover .popover-head p{font-size:13px;font-weight:100;margin:10px 0 0 0}
|
||||
div.control-popover .popover-head .close{float:none;position:absolute;right:11px;top:12px;color:#ffffff;outline:none}
|
||||
div.control-popover .popover-head p:empty{display:none}
|
||||
div.control-popover .popover-head .close{float:none;position:absolute;right:11px;top:12px;color:#ffffff;outline:none;opacity:0.4;filter:alpha(opacity=40)}
|
||||
div.control-popover .popover-head .close:hover{opacity:1;filter:alpha(opacity=100)}
|
||||
div.control-popover.placement-bottom .popover-head:before{content:'';display:block;width:0;height:0;border-left:7.5px solid transparent;border-right:7.5px solid transparent;border-bottom:8px solid #d35400;left:15px;top:-8px}
|
||||
div.control-popover.placement-left .popover-head:before{content:'';display:block;width:0;height:0;border-top:7.5px solid transparent;border-bottom:7.5px solid transparent;border-left:8px solid #d35400;right:-8px;top:7px}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
=require js/foundation.js
|
||||
=require js/flashmessage.js
|
||||
=require js/inspector.js
|
||||
=require js/checkbox.js
|
||||
=require js/dropdown.js
|
||||
=require js/callout.js
|
||||
|
|
|
|||
Loading…
Reference in New Issue