diff --git a/modules/system/assets/ui/js/inspector.datainteraction.js b/modules/system/assets/ui/js/inspector.datainteraction.js new file mode 100644 index 000000000..3881fb4ed --- /dev/null +++ b/modules/system/assets/ui/js/inspector.datainteraction.js @@ -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); \ No newline at end of file diff --git a/modules/system/assets/ui/js/inspector.editor.base.js b/modules/system/assets/ui/js/inspector.editor.base.js index 5ff780c67..a45d63d4d 100644 --- a/modules/system/assets/ui/js/inspector.editor.base.js +++ b/modules/system/assets/ui/js/inspector.editor.base.js @@ -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. diff --git a/modules/system/assets/ui/js/inspector.editor.objectlist.js b/modules/system/assets/ui/js/inspector.editor.objectlist.js index dd0681552..53dcf2821 100644 --- a/modules/system/assets/ui/js/inspector.editor.objectlist.js +++ b/modules/system/assets/ui/js/inspector.editor.objectlist.js @@ -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) { diff --git a/modules/system/assets/ui/js/inspector.editor.popupbase.js b/modules/system/assets/ui/js/inspector.editor.popupbase.js index 47b51a2fb..4f9133068 100644 --- a/modules/system/assets/ui/js/inspector.editor.popupbase.js +++ b/modules/system/assets/ui/js/inspector.editor.popupbase.js @@ -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) { diff --git a/modules/system/assets/ui/js/inspector.editor.text.js b/modules/system/assets/ui/js/inspector.editor.text.js index 0fde344f0..28dc74026 100644 --- a/modules/system/assets/ui/js/inspector.editor.text.js +++ b/modules/system/assets/ui/js/inspector.editor.text.js @@ -78,7 +78,6 @@ value = $.trim($textarea.val()) this.inspector.setPropertyValue(this.propertyDefinition.property, value) -// TODO: validate here } $.oc.inspector.propertyEditors.text = TextEditor diff --git a/modules/system/assets/ui/js/inspector.engine.js b/modules/system/assets/ui/js/inspector.engine.js index 155bdcec5..b8abbb11a 100644 --- a/modules/system/assets/ui/js/inspector.engine.js +++ b/modules/system/assets/ui/js/inspector.engine.js @@ -87,5 +87,4 @@ return result } - }(window.jQuery); diff --git a/modules/system/assets/ui/js/inspector.helpers.js b/modules/system/assets/ui/js/inspector.helpers.js index 71afde027..728911f03 100644 --- a/modules/system/assets/ui/js/inspector.helpers.js +++ b/modules/system/assets/ui/js/inspector.helpers.js @@ -29,4 +29,5 @@ $.oc.inspector.helpers.generateUniqueId = function() { return "inspectorid-" + Math.floor(Math.random() * new Date().getTime()); } + }(window.jQuery) \ No newline at end of file diff --git a/modules/system/assets/ui/js/inspector.manager.js b/modules/system/assets/ui/js/inspector.manager.js new file mode 100644 index 000000000..3e138106c --- /dev/null +++ b/modules/system/assets/ui/js/inspector.manager.js @@ -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); \ No newline at end of file diff --git a/modules/system/assets/ui/js/inspector.surface.js b/modules/system/assets/ui/js/inspector.surface.js index 8f8196a07..b395f7a2d 100644 --- a/modules/system/assets/ui/js/inspector.surface.js +++ b/modules/system/assets/ui/js/inspector.surface.js @@ -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 diff --git a/modules/system/assets/ui/js/inspector.wrapper.base.js b/modules/system/assets/ui/js/inspector.wrapper.base.js new file mode 100644 index 000000000..12e4fbcf4 --- /dev/null +++ b/modules/system/assets/ui/js/inspector.wrapper.base.js @@ -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); \ No newline at end of file diff --git a/modules/system/assets/ui/js/inspector.wrapper.container.js b/modules/system/assets/ui/js/inspector.wrapper.container.js new file mode 100644 index 000000000..f96205ab7 --- /dev/null +++ b/modules/system/assets/ui/js/inspector.wrapper.container.js @@ -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); \ No newline at end of file diff --git a/modules/system/assets/ui/js/inspector.wrapper.popup.js b/modules/system/assets/ui/js/inspector.wrapper.popup.js new file mode 100644 index 000000000..a8a22f09e --- /dev/null +++ b/modules/system/assets/ui/js/inspector.wrapper.popup.js @@ -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 '
{{description}}
\ - {{/description}} \ - \ -Popover content
',width:false,modal:false,highlightModalTarget:false,closeOnPageClick:true,closeOnEsc:true,container:false,containerClass:null,offset:15,useAnimation:false} +Popover.DEFAULTS={placement:'bottom',fallbackPlacement:'bottom',content:'
Popover content
',width:false,modal:false,highlightModalTarget:false,closeOnPageClick:true,closeOnEsc:true,container:false,containerClass:null,offset:15,useAnimation:false,onCheckDocumentClickTarget:null} var old=$.fn.ocPopover $.fn.ocPopover=function(option){var args=arguments;return this.each(function(){var $this=$(this) var data=$this.data('oc.popover') diff --git a/modules/system/assets/ui/storm.css b/modules/system/assets/ui/storm.css index 65fcbe2c2..2042be673 100644 --- a/modules/system/assets/ui/storm.css +++ b/modules/system/assets/ui/storm.css @@ -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} diff --git a/modules/system/assets/ui/storm.js b/modules/system/assets/ui/storm.js index 04eb02578..59269d8f6 100644 --- a/modules/system/assets/ui/storm.js +++ b/modules/system/assets/ui/storm.js @@ -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