diff --git a/modules/system/assets/ui/js/inspector.editor.base.js b/modules/system/assets/ui/js/inspector.editor.base.js
index 5274a2893..5bd9e1e53 100644
--- a/modules/system/assets/ui/js/inspector.editor.base.js
+++ b/modules/system/assets/ui/js/inspector.editor.base.js
@@ -54,5 +54,25 @@
BaseEditor.prototype.registerHandlers = function() {
}
+ BaseEditor.prototype.onInspectorPropertyChanged = function(property, value) {
+ }
+
+ BaseEditor.prototype.onExternalPropertyEditorHidden = function() {
+ }
+
+ BaseEditor.prototype.focus = function() {
+ }
+
+ /**
+ * Updates displayed value in the editor UI. The value is already set
+ * in the Inspector and should be loaded from Inspector.
+ */
+ BaseEditor.prototype.updateDisplayedValue = function(value) {
+ }
+
+ BaseEditor.prototype.getPropertyName = function() {
+ return this.propertyDefinition.property
+ }
+
$.oc.inspector.propertyEditors.base = BaseEditor
}(window.jQuery);
\ No newline at end of file
diff --git a/modules/system/assets/ui/js/inspector.editor.checkbox.js b/modules/system/assets/ui/js/inspector.editor.checkbox.js
index 0590c1f18..b20d46414 100644
--- a/modules/system/assets/ui/js/inspector.editor.checkbox.js
+++ b/modules/system/assets/ui/js/inspector.editor.checkbox.js
@@ -66,6 +66,14 @@
return this.containerCell.querySelector('input')
}
+ CheckboxEditor.prototype.focus = function() {
+ this.getInput().parentNode.focus()
+ }
+
+ CheckboxEditor.prototype.updateDisplayedValue = function(value) {
+ this.getInput().checked = this.normalizeCheckedValue(value)
+ }
+
CheckboxEditor.prototype.registerHandlers = function() {
var input = this.getInput()
diff --git a/modules/system/assets/ui/js/inspector.editor.dropdown.js b/modules/system/assets/ui/js/inspector.editor.dropdown.js
index 8bcaa8257..e31f4bac1 100644
--- a/modules/system/assets/ui/js/inspector.editor.dropdown.js
+++ b/modules/system/assets/ui/js/inspector.editor.dropdown.js
@@ -151,6 +151,31 @@
this.inspector.setPropertyValue(this.propertyDefinition.property, select.value, this.initialization)
}
+ DropdownEditor.prototype.onInspectorPropertyChanged = function(property, value) {
+ if (!this.propertyDefinition.depends || this.propertyDefinition.depends.indexOf(property) === -1) {
+ return
+ }
+
+ var dependencyValues = this.getDependencyValues()
+
+ if (this.prevDependencyValues === undefined || this.prevDependencyValues != dependencyValues)
+ this.loadDynamicOptions()
+ }
+
+ DropdownEditor.prototype.onExternalPropertyEditorHidden = function() {
+ this.loadDynamicOptions(false)
+ }
+
+ //
+ // Editor API methods
+ //
+
+ DropdownEditor.prototype.updateDisplayedValue = function(value) {
+ var select = this.getSelect()
+
+ select.value = value
+ }
+
//
// Disposing
//
@@ -224,8 +249,9 @@
DropdownEditor.prototype.getDependencyValues = function() {
var result = ''
- for (var property in this.propertyDefinition.depends) {
- var value = this.inspector.getPropertyValue(property)
+ for (var i = 0, len = this.propertyDefinition.depends.length; i < len; i++) {
+ var property = this.propertyDefinition.depends[i],
+ value = this.inspector.getPropertyValue(property)
if (value === undefined) {
value = '';
@@ -252,7 +278,11 @@
DropdownEditor.prototype.optionsRequestDone = function(data, currentValue, initialization) {
var select = this.getSelect()
+ // Without destroying and recreating the custom select
+ // there could be detached DOM nodes.
+ this.destroyCustomSelect()
this.clearOptions(select)
+ this.initCustomSelect()
this.createPlaceholder(select)
diff --git a/modules/system/assets/ui/js/inspector.editor.string.js b/modules/system/assets/ui/js/inspector.editor.string.js
index 43be4cf41..ce3580276 100644
--- a/modules/system/assets/ui/js/inspector.editor.string.js
+++ b/modules/system/assets/ui/js/inspector.editor.string.js
@@ -39,10 +39,19 @@
this.containerCell.appendChild(editor)
}
+ StringEditor.prototype.updateDisplayedValue = function(value) {
+ this.getInput().value = value
+ }
+
StringEditor.prototype.getInput = function() {
return this.containerCell.querySelector('input')
}
+ StringEditor.prototype.focus = function() {
+ this.getInput().focus()
+ this.onInputFocus()
+ }
+
StringEditor.prototype.registerHandlers = function() {
var input = this.getInput()
diff --git a/modules/system/assets/ui/js/inspector.externalparametereditor.js b/modules/system/assets/ui/js/inspector.externalparametereditor.js
new file mode 100644
index 000000000..6ac6ed5df
--- /dev/null
+++ b/modules/system/assets/ui/js/inspector.externalparametereditor.js
@@ -0,0 +1,313 @@
+/*
+ * External parameter editor for Inspector.
+ *
+ * The external parameter editor allows to use URL and
+ * other external parameters as values for the inspectable
+ * properties.
+ *
+ */
++function ($) { "use strict";
+
+ // NAMESPACES
+ // ============================
+
+ if ($.oc === undefined)
+ $.oc = {}
+
+ if ($.oc.inspector === undefined)
+ $.oc.inspector = {}
+
+ // CLASS DEFINITION
+ // ============================
+
+ var Base = $.oc.foundation.base,
+ BaseProto = Base.prototype
+
+ var ExternalParameterEditor = function(inspector, propertyDefinition, containerCell) {
+ this.inspector = inspector
+ this.propertyDefinition = propertyDefinition
+ this.containerCell = containerCell
+
+ Base.call(this)
+
+ this.init()
+ }
+
+ ExternalParameterEditor.prototype = Object.create(BaseProto)
+ ExternalParameterEditor.prototype.constructor = Base
+
+ ExternalParameterEditor.prototype.dispose = function() {
+ this.disposeControls()
+ this.unregisterHandlers()
+
+ this.inspector = null
+ this.propertyDefinition = null
+ this.containerCell = null
+
+ BaseProto.dispose.call(this)
+ }
+
+ ExternalParameterEditor.prototype.init = function() {
+ this.tooltipText = 'Click to enter the external parameter name to load the property value from'
+
+ this.build()
+ this.registerHandlers()
+ this.setInitialValue()
+ }
+
+ /**
+ * Builds the external parameter editor markup:
+ *
+ *
+ *
<-- original property editing input/markup
+ *
+ *
+ */
+ ExternalParameterEditor.prototype.build = function() {
+ var container = document.createElement('div'),
+ editor = document.createElement('div'),
+ controls = document.createElement('div'),
+ input = document.createElement('input'),
+ link = document.createElement('a'),
+ icon = document.createElement('i')
+
+ container.setAttribute('class', 'external-param-editor-container')
+ editor.setAttribute('class', 'external-editor')
+ controls.setAttribute('class', 'controls')
+ input.setAttribute('type', 'text')
+ input.setAttribute('tabindex', '-1')
+
+ link.setAttribute('href', '#')
+ link.setAttribute('class', 'external-editor-link')
+ link.setAttribute('tabindex', '-1')
+ link.setAttribute('title', this.tooltipText)
+ $(link).tooltip({'container': 'body', delay: 500})
+
+ icon.setAttribute('class', 'oc-icon-terminal')
+
+ link.appendChild(icon)
+ controls.appendChild(input)
+ controls.appendChild(link)
+ editor.appendChild(controls)
+
+ while (this.containerCell.firstChild) {
+ var child = this.containerCell.firstChild
+
+ container.appendChild(child)
+ }
+
+ container.appendChild(editor)
+ this.containerCell.appendChild(container)
+ }
+
+ ExternalParameterEditor.prototype.setInitialValue = function() {
+ var propertyValue = this.inspector.getPropertyValue(this.propertyDefinition.property)
+
+ if (!propertyValue) {
+ return
+ }
+
+ if (typeof propertyValue !== 'string') {
+ return
+ }
+
+ var matches = []
+ if (matches = propertyValue.match(/^\{\{([^\}]+)\}\}$/)) {
+ var value = $.trim(matches[1])
+
+ if (value.length > 0) {
+ this.showEditor(true)
+ this.getInput().value = value
+ this.inspector.setPropertyValue(this.propertyDefinition.property, null, true, true)
+ }
+ }
+ }
+
+ ExternalParameterEditor.prototype.showEditor = function(building) {
+ var editor = this.getEditor(),
+ input = this.getInput(),
+ container = this.getContainer(),
+ link = this.getLink()
+
+ var position = $(editor).position()
+
+ if (!building) {
+ editor.style.right = 0
+ editor.style.left = position.left + 'px'
+ }
+ else {
+ editor.style.right = 0
+ }
+
+ setTimeout(this.proxy(this.repositionEditor), 0)
+
+ $.oc.foundation.element.addClass(container, 'editor-visible')
+ link.setAttribute('data-original-title', 'Click to enter the property value')
+
+ this.toggleEditorVisibility(false)
+ input.setAttribute('tabindex', 0)
+
+ if (!building) {
+ input.focus()
+ }
+ }
+
+ ExternalParameterEditor.prototype.repositionEditor = function() {
+ this.getEditor().style.left = 0
+ this.containerCell.scrollTop = 0
+ }
+
+ ExternalParameterEditor.prototype.hideEditor = function() {
+ var editor = this.getEditor(),
+ container = this.getContainer()
+
+ editor.style.left = 'auto'
+ editor.style.right = '30px'
+
+ $.oc.foundation.element.removeClass(container, 'editor-visible')
+ $.oc.foundation.element.removeClass(this.containerCell, 'active')
+
+ var propertyEditor = this.inspector.findPropertyEditor(this.propertyDefinition.property)
+
+ if (propertyEditor) {
+ propertyEditor.onExternalPropertyEditorHidden()
+ }
+ }
+
+ ExternalParameterEditor.prototype.toggleEditor = function(ev) {
+ $.oc.foundation.event.stop(ev)
+
+ var link = this.getLink(),
+ container = this.getContainer(),
+ editor = this.getEditor()
+
+ $(link).tooltip('hide')
+
+ if (!this.isEditorVisible()) {
+ this.showEditor()
+ return
+ }
+
+ var left = container.offsetWidth
+
+ editor.style.left = left + 'px'
+ link.setAttribute('data-original-title', this.tooltipText)
+ this.getInput().setAttribute('tabindex', '-1')
+
+ this.toggleEditorVisibility(true)
+
+ setTimeout(this.proxy(this.hideEditor), 200)
+ }
+
+ ExternalParameterEditor.prototype.toggleEditorVisibility = function(show) {
+ var container = this.getContainer(),
+ children = container.children,
+ height = 0
+
+ if (!show) {
+ height = this.containerCell.getAttribute('data-inspector-cell-height')
+
+ if (!height) {
+ height = $(this.containerCell).height()
+ this.containerCell.setAttribute('data-inspector-cell-height', height)
+ }
+ }
+
+ for (var i = 0, len = children.length; i < len; i++) {
+ var element = children[i]
+
+ if ($.oc.foundation.element.hasClass(element, 'external-editor')) {
+ continue
+ }
+
+ if (show) {
+ $.oc.foundation.element.removeClass(element, 'hide')
+ }
+ else {
+ container.style.height = height + 'px'
+ $.oc.foundation.element.addClass(element, 'hide')
+ }
+ }
+ }
+
+ ExternalParameterEditor.prototype.focus = function() {
+ this.getInput().focus()
+ }
+
+ //
+ // Event handlers
+ //
+
+ ExternalParameterEditor.prototype.registerHandlers = function() {
+ var input = this.getInput()
+
+ this.getLink().addEventListener('click', this.proxy(this.toggleEditor))
+ input.addEventListener('focus', this.proxy(this.onInputFocus))
+ input.addEventListener('change', this.proxy(this.onInputChange))
+ }
+
+ ExternalParameterEditor.prototype.onInputFocus = function() {
+ this.inspector.makeCellActive(this.containerCell)
+ }
+
+ ExternalParameterEditor.prototype.onInputChange = function() {
+ this.inspector.markPropertyChanged(this.propertyDefinition.property, true)
+ }
+
+ //
+ // Disposing
+ //
+
+ ExternalParameterEditor.prototype.unregisterHandlers = function() {
+ var input = this.getInput()
+
+ this.getLink().removeEventListener('click', this.proxy(this.toggleEditor))
+ input.removeEventListener('focus', this.proxy(this.onInputFocus))
+ input.removeEventListener('change', this.proxy(this.onInputChange))
+ }
+
+ ExternalParameterEditor.prototype.disposeControls = function() {
+ $(this.getLink()).tooltip('destroy')
+ }
+
+ //
+ // Helpers
+ //
+
+ ExternalParameterEditor.prototype.getInput = function() {
+ return this.containerCell.querySelector('div.external-editor input')
+ }
+
+ ExternalParameterEditor.prototype.getValue = function() {
+ return this.getInput().value
+ }
+
+ ExternalParameterEditor.prototype.getLink = function() {
+ return this.containerCell.querySelector('a.external-editor-link')
+ }
+
+ ExternalParameterEditor.prototype.getContainer = function() {
+ return this.containerCell.querySelector('div.external-param-editor-container')
+ }
+
+ ExternalParameterEditor.prototype.getEditor = function() {
+ return this.containerCell.querySelector('div.external-editor')
+ }
+
+ ExternalParameterEditor.prototype.getPropertyName = function() {
+ return this.propertyDefinition.property
+ }
+
+ ExternalParameterEditor.prototype.isEditorVisible = function() {
+ return $.oc.foundation.element.hasClass(this.getContainer(), 'editor-visible')
+ }
+
+ $.oc.inspector.externalParameterEditor = ExternalParameterEditor
+}(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 b6213284e..5c29f0cbc 100644
--- a/modules/system/assets/ui/js/inspector.surface.js
+++ b/modules/system/assets/ui/js/inspector.surface.js
@@ -40,7 +40,7 @@
throw new Error('Inspector surface unique ID should be defined.')
}
- this.options = $.extend({}, Surface.DEFAULTS, typeof option == 'object' && option)
+ this.options = $.extend({}, Surface.DEFAULTS, typeof options == 'object' && options)
this.rawProperties = properties
this.parsedProperties = $.oc.inspector.engine.processPropertyGroups(properties)
this.container = containerElement
@@ -50,6 +50,7 @@
this.idCounter = 1
this.editors = []
+ this.externalParameterEditors = []
this.tableContainer = null
Base.call(this)
@@ -62,7 +63,9 @@
Surface.prototype.dispose = function() {
this.unregisterHandlers()
+ this.disposeControls()
this.removeElements()
+ this.disposeExternalParameterEditors()
this.disposeEditors()
this.container = null
@@ -70,6 +73,7 @@
this.rawProperties = null
this.parsedProperties = null
this.editors = null
+ this.externalParameterEditors = null
this.values = null
this.originalValues = null
@@ -187,6 +191,37 @@
this.tableContainer.appendChild(dataTable)
this.container.appendChild(this.tableContainer)
+
+ if (this.options.enableExternalParameterEditor) {
+ this.buildExternalParameterEditor(tbody)
+ }
+
+ this.focusFirstEditor()
+ }
+
+ Surface.prototype.focusFirstEditor = function() {
+ if (this.editors.length == 0) {
+ return
+ }
+
+ for (var i = 0, len = this.editors.length; i < len; i++) {
+ var editor = this.editors[i],
+ group = editor.propertyDefinition.group
+
+ if (group && !this.isGroupExpanded(group)) {
+ continue
+ }
+
+ var externalParameterEditor = this.findExternalParameterEditor(editor.getPropertyName())
+
+ if (externalParameterEditor && externalParameterEditor.isEditorVisible()) {
+ externalParameterEditor.focus()
+ return
+ }
+
+ editor.focus()
+ return
+ }
}
Surface.prototype.applyGroupIndexAttribute = function(property, row) {
@@ -241,9 +276,30 @@
span.setAttribute('title', this.escapeJavascriptString(property.description))
span.setAttribute('class', 'info oc-icon-info with-tooltip')
+ $(span).tooltip({ placement: 'auto right', container: 'body', delay: 500 })
+
return span
}
+ Surface.prototype.buildExternalParameterEditor = function(tbody) {
+ var rows = tbody.children
+
+ for (var i = 0, len = rows.length; i < len; i++) {
+ var row = rows[i],
+ property = row.getAttribute('data-property')
+
+ if ($.oc.foundation.element.hasClass(row, 'no-external-parameter') || !property) {
+ continue
+ }
+
+ var cell = row.querySelector('td'),
+ propertyDefinition = this.findPropertyDefinition(property),
+ editor = new $.oc.inspector.externalParameterEditor(this, propertyDefinition, cell)
+
+ this.externalParameterEditors.push(editor)
+ }
+ }
+
//
// Field grouping
//
@@ -365,6 +421,10 @@
Surface.prototype.getValues = function() {
var result = {}
+ // TODO: implement validation in this method. It should be optional,
+ // as the method is used by other classes internally, but the validation
+ // is required only for the external callers.
+
for (var i=0, len = this.parsedProperties.properties.length; i < len; i++) {
var property = this.parsedProperties.properties[i]
@@ -372,10 +432,19 @@
continue
}
- var value = this.getPropertyValue(property.property)
+ var value = null,
+ externalParameterEditor = this.findExternalParameterEditor(property.property)
- if (value === undefined) {
- value = property.default
+ if (!externalParameterEditor || !externalParameterEditor.isEditorVisible()) {
+ value = this.getPropertyValue(property.property)
+
+ if (value === undefined) {
+ value = property.default
+ }
+ }
+ else {
+ value = externalParameterEditor.getValue()
+ value = '{{ ' + value + ' }}'
}
result[property.property] = value
@@ -384,7 +453,7 @@
return result
}
- Surface.prototype.setPropertyValue = function(property, value, supressChangeEvents) {
+ Surface.prototype.setPropertyValue = function(property, value, supressChangeEvents, forceEditorUpdate) {
this.values[property] = value
if (!supressChangeEvents) {
@@ -395,12 +464,27 @@
this.markPropertyChanged(property, false)
}
-// TODO: here we should force dependent editors to update
+ this.notifyEditorsPropertyChanged(property, value)
+ }
+
+ if (forceEditorUpdate) {
+ var editor = this.findPropertyEditor(property)
+ if (editor) {
+ editor.updateDisplayedValue(value)
+ }
}
return value
}
+ Surface.prototype.notifyEditorsPropertyChanged = function(property, value) {
+ for (var i = 0, len = this.editors.length; i < len; i++) {
+ var editor = this.editors[i]
+
+ editor.onInspectorPropertyChanged(property, value)
+ }
+ }
+
Surface.prototype.makeCellActive = function(cell) {
var tbody = cell.parentNode.parentNode.parentNode, // cell / row / tbody
cells = tbody.querySelectorAll('tr td')
@@ -423,6 +507,36 @@
}
}
+ Surface.prototype.findPropertyEditor = function(property) {
+ for (var i = 0, len = this.editors.length; i < len; i++) {
+ if (this.editors[i].getPropertyName() == property)
+ return this.editors[i]
+ }
+
+ return null
+ }
+
+ Surface.prototype.findExternalParameterEditor = function(property) {
+ for (var i = 0, len = this.externalParameterEditors.length; i < len; i++) {
+ if (this.externalParameterEditors[i].getPropertyName() == property)
+ return this.externalParameterEditors[i]
+ }
+
+ return null
+ }
+
+ Surface.prototype.findPropertyDefinition = function(property) {
+ for (var i=0, len = this.parsedProperties.properties.length; i < len; i++) {
+ var definition = this.parsedProperties.properties[i]
+
+ if (definition.property == property) {
+ return definition
+ }
+ }
+
+ return null
+ }
+
//
// Disposing
//
@@ -439,6 +553,22 @@
}
}
+ Surface.prototype.disposeExternalParameterEditors = function() {
+ for (var i = 0, len = this.externalParameterEditors.length; i < len; i++) {
+ var editor = this.externalParameterEditors[i]
+
+ editor.dispose()
+ }
+ }
+
+ Surface.prototype.disposeControls = function() {
+ var tooltipControls = this.tableContainer.querySelectorAll('.with-tooltip')
+
+ for (var i = 0, len = tooltipControls.length; i < len; i++) {
+ $(tooltipControls[i]).tooltip('destroy')
+ }
+ }
+
//
// Helpers
//
@@ -462,7 +592,7 @@
// ============================
Surface.DEFAULTS = {
- showExternalParam: false
+ enableExternalParameterEditor: false
}
// REGISTRATION