diff --git a/modules/system/assets/ui/js/inspector.editor.base.js b/modules/system/assets/ui/js/inspector.editor.base.js index 56ca3eb6e..b40ead281 100644 --- a/modules/system/assets/ui/js/inspector.editor.base.js +++ b/modules/system/assets/ui/js/inspector.editor.base.js @@ -25,7 +25,9 @@ this.inspector = inspector this.propertyDefinition = propertyDefinition this.containerCell = containerCell + this.containerRow = containerCell.parentNode this.groupIndex = null + this.childInspector = null Base.call(this) @@ -36,9 +38,15 @@ BaseEditor.prototype.constructor = Base BaseEditor.prototype.dispose = function() { + if (this.childInspector) { + this.childInspector.dispose() + } + this.inspector = null this.propertyDefinition = null this.containerCell = null + this.containerRow = null + this.childInspector = null BaseProto.dispose.call(this) } @@ -72,12 +80,16 @@ return false } + BaseEditor.prototype.hasChildSurface = function() { + return this.childInspector !== null + } + BaseEditor.prototype.getGroupIndex = function() { if (this.groupIndex !== null) { return this.groupIndex } - this.groupIndex = this.inspector.generateSequencedId() + this.groupIndex = this.inspector.generateGroupIndex(this.propertyDefinition.property) return this.groupIndex } @@ -94,6 +106,54 @@ row.setAttribute('data-group-index', this.getGroupIndex()) } + BaseEditor.prototype.getChildInspectorRows = function(level) { + if (level === undefined) { + level = 0 + } + + if (!this.childInspector) { + return [this.containerRow] + } + + var result = [] + + if (level > 0) { + result.push(this.containerRow) + } + + for (var i = 0, len = this.childInspector.editors.length; i < len; i++) { + var editor = this.childInspector.editors[i], + childRows = editor.getChildInspectorRows(level+1) + + for (var j = 0, rowsLength = childRows.length; j < rowsLength; j++) { + result.push(childRows[j]) + } + } + + return result + } + + BaseEditor.prototype.findEditorByInspectorIdAndPropertyName = function(inspectorId, propertyName) { + if (this.inspector.getInspectorUniqueId() == inspectorId && this.propertyDefinition.property == propertyName) { + return this + } + + if (!this.hasChildSurface()) { + return null + } + + for (var i = this.childInspector.editors.length-1; i >= 0; i--) { + var editor = this.childInspector.editors[i], + result = editor.findEditorByInspectorIdAndPropertyName(inspectorId, propertyName) + + if (result) { + return result + } + } + + return null + } + /** * 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.object.js b/modules/system/assets/ui/js/inspector.editor.object.js new file mode 100644 index 000000000..e40df9b8d --- /dev/null +++ b/modules/system/assets/ui/js/inspector.editor.object.js @@ -0,0 +1,65 @@ +/* + * Inspector object editor class. + * + * This class uses other editors. + */ ++function ($) { "use strict"; + + var Base = $.oc.inspector.propertyEditors.base, + BaseProto = Base.prototype + + var ObjectEditor = function(inspector, propertyDefinition, containerCell) { + + if (propertyDefinition.properties === undefined) { + throw new Error('The properties property should be specified in the object editor configuration. Property: ' + propertyDefinition.property) + } + + Base.call(this, inspector, propertyDefinition, containerCell) + } + + ObjectEditor.prototype = Object.create(BaseProto) + ObjectEditor.prototype.constructor = Base + + // + // Building + // + + ObjectEditor.prototype.build = function() { + var currentRow = this.containerCell.parentNode, + inspectorContainer = document.createElement('div'), + options = { + enableExternalParameterEditor: false, + surfaceLevel: this.inspector.options.surfaceLevel + 1 + }, + values = this.inspector.getPropertyValue(this.propertyDefinition.property) + + if (values === undefined) { + values = {} + } + + // this.containerCell.appendChild(inspectorContainer) + + this.childInspector = new $.oc.inspector.surface(inspectorContainer, + this.propertyDefinition.properties, + values, + this.inspector.getInspectorUniqueId() + '-' + this.propertyDefinition.property, + options, + this.inspector) + + this.inspector.mergeChildSurface(this.childInspector, currentRow) + } + + // + // Editor API methods + // + + ObjectEditor.prototype.supportsExternalParameterEditor = function() { + return false + } + + ObjectEditor.prototype.isGroupedEditor = function() { + return true + } + + $.oc.inspector.propertyEditors.object = ObjectEditor +}(window.jQuery); \ No newline at end of file diff --git a/modules/system/assets/ui/js/inspector.editor.set.js b/modules/system/assets/ui/js/inspector.editor.set.js index 2cc8afb10..488a2f548 100644 --- a/modules/system/assets/ui/js/inspector.editor.set.js +++ b/modules/system/assets/ui/js/inspector.editor.set.js @@ -52,8 +52,17 @@ } SetEditor.prototype.loadStaticItems = function() { + var itemArray = [] + for (var itemValue in this.propertyDefinition.items) { - this.buildItemEditor(itemValue, this.propertyDefinition.items[itemValue]) + itemArray.push({ + value: itemValue, + title: this.propertyDefinition.items[itemValue] + }) + } + + for (var i = itemArray.length-1; i >=0; i--) { + this.buildItemEditor(itemArray[i].value, itemArray[i].title) } } @@ -76,6 +85,10 @@ $.oc.foundation.element.removeClass(link, 'placeholder') } else { text = this.propertyDefinition.placeholder + + if ((typeof text === 'string' && text.length == 0) || text === undefined) { + text = '[ ]' + } $.oc.foundation.element.addClass(link, 'placeholder') } @@ -106,6 +119,8 @@ default: this.isCheckedByDefault(value) }, editor = new $.oc.inspector.propertyEditors.checkbox(this, property, cell) + + this.editors.push[editor] } SetEditor.prototype.isCheckedByDefault = function(value) { @@ -147,7 +162,7 @@ this.loadedItems = {} if (data.options) { - for (var i = 0, len = data.options.length; i < len; i++) { + for (var i = data.options.length-1; i >= 0; i--) { this.buildItemEditor(data.options[i].value, data.options[i].title) this.loadedItems[data.options[i].value] = data.options[i].title diff --git a/modules/system/assets/ui/js/inspector.editor.string.js b/modules/system/assets/ui/js/inspector.editor.string.js index ce3580276..f9f85a58f 100644 --- a/modules/system/assets/ui/js/inspector.editor.string.js +++ b/modules/system/assets/ui/js/inspector.editor.string.js @@ -26,12 +26,16 @@ editor.setAttribute('type', 'text') editor.setAttribute('class', 'string-editor') - editor.setAttribute('placeholder', 'placeholder') + editor.setAttribute('placeholder', placeholder) if (value === undefined) { value = this.propertyDefinition.default } + if (value === undefined) { + value = '' + } + editor.value = value $.oc.foundation.element.addClass(this.containerCell, 'text') diff --git a/modules/system/assets/ui/js/inspector.surface.js b/modules/system/assets/ui/js/inspector.surface.js index 3f1fe3d9d..bfe60fcb1 100644 --- a/modules/system/assets/ui/js/inspector.surface.js +++ b/modules/system/assets/ui/js/inspector.surface.js @@ -35,7 +35,7 @@ * not associated with an element. Inspector uses the ID for storing configuration * related to an element in the document DOM. */ - var Surface = function(containerElement, properties, values, inspectorUniqueId, options) { + var Surface = function(containerElement, properties, values, inspectorUniqueId, options, parentSurface) { if (inspectorUniqueId === undefined) { throw new Error('Inspector surface unique ID should be defined.') } @@ -48,6 +48,7 @@ this.values = values this.originalValues = $.extend(true, {}, values) // Clone the values hash this.idCounter = 1 + this.parentSurface = parentSurface this.editors = [] this.externalParameterEditors = [] @@ -77,6 +78,7 @@ this.values = null this.originalValues = null this.options.onChange = null + this.parentSurface = null BaseProto.dispose.call(this) } @@ -181,6 +183,9 @@ this.applyGroupIndexAttribute(property, row) $.oc.foundation.element.addClass(row, this.getRowCssClass(property)) + row.setAttribute('data-inspector-level', this.options.surfaceLevel) + row.setAttribute('data-inspector-id', this.getInspectorUniqueId()) + // Property head // this.applyHeadColspan(th, property) @@ -253,17 +258,23 @@ } } - Surface.prototype.buildGroupExpandControl = function(titleSpan, property, force) { + Surface.prototype.buildGroupExpandControl = function(titleSpan, property, force, hasChildSurface) { if (property.itemType !== 'group' && !force) { return } - var statusClass = this.isGroupExpanded(property.title) ? 'expanded' : '', + var groupIndex = this.generateGroupIndex(property.property), + statusClass = this.isGroupExpanded(groupIndex) ? 'expanded' : '', anchor = document.createElement('a') anchor.setAttribute('class', 'expandControl ' + statusClass) anchor.setAttribute('href', 'javascript:;') - anchor.setAttribute('data-group-index', property.groupIndex) + anchor.setAttribute('data-group-index', groupIndex) + + if (hasChildSurface) { + anchor.setAttribute('data-has-child-surface', 'true') + } + anchor.innerHTML = 'Expand/collapse' titleSpan.appendChild(anchor) @@ -327,19 +338,21 @@ } Surface.prototype.loadGroupStatuses = function() { - var statuses = this.getInspectorGroupStatuses() + var statuses = this.getInspectorGroupStatuses(), + root = this.getRootSurface() - if (statuses[this.inspectorUniqueId] !== undefined) { - return statuses[this.inspectorUniqueId] + if (statuses[root.inspectorUniqueId] !== undefined) { + return statuses[root.inspectorUniqueId] } return {} } Surface.prototype.writeGroupStatuses = function(updatedStatuses) { - var statuses = this.getInspectorGroupStatuses() + var statuses = this.getInspectorGroupStatuses(), + root = this.getRootSurface() - statuses[this.inspectorUniqueId] = updatedStatuses + statuses[root.inspectorUniqueId] = updatedStatuses this.setInspectorGroupStatuses(statuses) } @@ -361,20 +374,34 @@ Surface.prototype.toggleGroup = function(row) { var link = row.querySelector('a'), groupIndex = link.getAttribute('data-group-index'), - propertyRows = this.tableContainer.querySelectorAll('tr[data-group-index="'+groupIndex+'"]'), + hasChildSurface = link.getAttribute('data-has-child-surface'), collapse = true, statuses = this.loadGroupStatuses(), - title = row.querySelector('span.title-element').getAttribute('title'), - duration = Math.round(50 / propertyRows.length), + propertyRows = [] + + if (!hasChildSurface) { + propertyRows = this.tableContainer.querySelectorAll('tr[data-group-index="'+groupIndex+'"]') + } + else { + var editor = this.findRowPropertyEditor(row) + + if (!editor) { + throw new Error('Cannot find editor for the property ' + property) + } + + propertyRows = editor.getChildInspectorRows() + } + + var duration = Math.round(50 / propertyRows.length), rowsArray = Array.prototype.slice.call(propertyRows) if ($.oc.foundation.element.hasClass(link, 'expanded')) { $.oc.foundation.element.removeClass(link, 'expanded') - statuses[title] = false + statuses[groupIndex] = false } else { $.oc.foundation.element.addClass(link, 'expanded') collapse = false - statuses[title] = true + statuses[groupIndex] = true } this.expandOrCollapseRows(rowsArray, collapse, duration) @@ -405,14 +432,18 @@ return } - if ($.oc.inspector.propertyEditors[property.type] === undefined) - throw new Error('The Inspector editor class "' + property.type + - '" is not defined in the $.oc.inspector.propertyEditors namespace.') + this.validateEditorType(property.type) + + var cell = document.createElement('td'), + type = property.type - var cell = document.createElement('td') row.appendChild(cell) - var editor = new $.oc.inspector.propertyEditors[property.type](this, property, cell) + if (type === undefined) { + type = 'string' + } + + var editor = new $.oc.inspector.propertyEditors[type](this, property, cell) if (editor.isGroupedEditor()) { $.oc.foundation.element.addClass(dataTable, 'has-groups') @@ -420,7 +451,15 @@ property.groupIndex = editor.getGroupIndex() property.groupedControl = true - this.buildGroupExpandControl(row.querySelector('span.title-element'), property, true) + this.buildGroupExpandControl(row.querySelector('span.title-element'), property, true, editor.hasChildSurface()) + + if (cell.children.length == 0) { + // If the editor hasn't added any elements to the cell, + // and it's a grouped control, remove the cell and + // make the group title full-width. + row.querySelector('th').setAttribute('colspan', 2) + row.removeChild(cell) + } } this.editors.push(editor) @@ -507,6 +546,25 @@ return null } + Surface.prototype.findRowPropertyEditor = function(row) { + var inspectorId = row.getAttribute('data-inspector-id'), + propertyName = row.getAttribute('data-property') + + if (!inspectorId || !propertyName) { + throw new Error('Cannot find property editor for a row with unknown property name or inspector ID.') + } + + for (var i = 0, len = this.editors.length; i < len; i++) { + var result = this.editors[i].findEditorByInspectorIdAndPropertyName(inspectorId, propertyName) + + if (result) { + return result + } + } + + return null + } + Surface.prototype.findExternalParameterEditor = function(property) { for (var i = 0, len = this.externalParameterEditors.length; i < len; i++) { if (this.externalParameterEditors[i].getPropertyName() == property) @@ -527,7 +585,70 @@ return null } - + + Surface.prototype.validateEditorType = function(type) { + if (type === undefined) { + type = 'string' + } + + if ($.oc.inspector.propertyEditors[type] === undefined) { + throw new Error('The Inspector editor class "' + type + + '" is not defined in the $.oc.inspector.propertyEditors namespace.') + } + } + + Surface.prototype.generateGroupIndex = function(propertyName) { + return this.getInspectorUniqueId() + '-' + propertyName + } + + // + // Nested surfaces support + // + + Surface.prototype.mergeChildSurface = function(surface, mergeAfterRow) { + var rows = surface.tableContainer.querySelectorAll('table.inspector-fields > tbody > tr') + + for (var i = rows.length-1; i >= 0; i--) { + var row = rows[i], + th = this.getRowHeadElement(row) + + if (th === null) { + throw new Error('Cannot find TH element for the Inspector row') + } + + mergeAfterRow.parentNode.insertBefore(row, mergeAfterRow.nextSibling) + th.children[0].style.marginLeft = row.getAttribute('data-inspector-level')*10 + 'px' + } + } + + Surface.prototype.getRowHeadElement = function(row) { + for (var i = row.children.length-1; i >= 0; i--) { + var element = row.children[i] + + if (element.tagName === 'TH') { + return element + } + } + + return null + } + + Surface.prototype.getInspectorUniqueId = function() { + return this.inspectorUniqueId + } + + Surface.prototype.getRootSurface = function() { + var current = this + + while (current) { + if (!current.parentSurface) { + return current + } + + current = current.parentSurface + } + } + // // Disposing // @@ -642,6 +763,7 @@ Surface.DEFAULTS = { enableExternalParameterEditor: false, + surfaceLevel: 0, // For internal use onChange: null }