From dba955da3a028ee88693e505e009815946296575 Mon Sep 17 00:00:00 2001 From: alekseybobkov Date: Sun, 13 Sep 2015 18:11:12 -0700 Subject: [PATCH] Rebuilding the Inspector --- modules/backend/ServiceProvider.php | 10 - modules/backend/lang/en/lang.php | 3 +- .../system/assets/ui/js/foundation.element.js | 18 +- .../assets/ui/js/inspector.editor.base.js | 58 +++ .../assets/ui/js/inspector.editor.string.js | 69 ++++ .../system/assets/ui/js/inspector.engine.js | 91 +++++ .../system/assets/ui/js/inspector.helpers.js | 32 ++ .../system/assets/ui/js/inspector.surface.js | 333 ++++++++++++++++++ modules/system/assets/ui/storm-min.js | 8 +- 9 files changed, 601 insertions(+), 21 deletions(-) create mode 100644 modules/system/assets/ui/js/inspector.editor.base.js create mode 100644 modules/system/assets/ui/js/inspector.editor.string.js create mode 100644 modules/system/assets/ui/js/inspector.engine.js create mode 100644 modules/system/assets/ui/js/inspector.helpers.js create mode 100644 modules/system/assets/ui/js/inspector.surface.js diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php index a22da1905..3937b82cc 100644 --- a/modules/backend/ServiceProvider.php +++ b/modules/backend/ServiceProvider.php @@ -45,16 +45,6 @@ class ServiceProvider extends ModuleServiceProvider public function boot() { parent::boot('backend'); - - Event::listen('pages.builder.registerControls', function($controlLibrary) { - $controlLibrary->registerControl('text', - 'backend::lang.form.control_text', - $controlLibrary::GROUP_STANDARD, - 'icon-terminal', - $controlLibrary->getStandardProperties(), - null - ); - }); } /** diff --git a/modules/backend/lang/en/lang.php b/modules/backend/lang/en/lang.php index 8506b0bf3..65810c9b6 100644 --- a/modules/backend/lang/en/lang.php +++ b/modules/backend/lang/en/lang.php @@ -202,8 +202,7 @@ return [ 'insert_row_below' => 'Insert Row Below', 'delete_row' => 'Delete Row', 'concurrency_file_changed_title' => 'File was changed', - 'concurrency_file_changed_description' => "The file you're editing has been changed on disk by another user. You can either reload the file and lose your changes or override the file on the disk.", - 'control_text' => 'Text field', + 'concurrency_file_changed_description' => "The file you're editing has been changed on disk by another user. You can either reload the file and lose your changes or override the file on the disk." ], 'relation' => [ 'missing_config' => "Relation behavior does not have any configuration for ':config'.", diff --git a/modules/system/assets/ui/js/foundation.element.js b/modules/system/assets/ui/js/foundation.element.js index 713dc7547..60a4cfc81 100644 --- a/modules/system/assets/ui/js/foundation.element.js +++ b/modules/system/assets/ui/js/foundation.element.js @@ -26,13 +26,19 @@ }, addClass: function(el, className) { - if (this.hasClass(el, className)) - return + var classes = className.split(' ') - if (el.classList) - el.classList.add(className); - else - el.className += ' ' + className; + for (var i = 0, len = classes.length; i < len; i++) { + var currentClass = classes[i].trim() + + if (this.hasClass(el, currentClass)) + return + + if (el.classList) + el.classList.add(currentClass); + else + el.className += ' ' + currentClass; + } }, removeClass: function(el, className) { diff --git a/modules/system/assets/ui/js/inspector.editor.base.js b/modules/system/assets/ui/js/inspector.editor.base.js new file mode 100644 index 000000000..5274a2893 --- /dev/null +++ b/modules/system/assets/ui/js/inspector.editor.base.js @@ -0,0 +1,58 @@ +/* + * Inspector editor base class. + */ ++function ($) { "use strict"; + + // NAMESPACES + // ============================ + + if ($.oc === undefined) + $.oc = {} + + if ($.oc.inspector === undefined) + $.oc.inspector = {} + + if ($.oc.inspector.propertyEditors === undefined) + $.oc.inspector.propertyEditors = {} + + // CLASS DEFINITION + // ============================ + + var Base = $.oc.foundation.base, + BaseProto = Base.prototype + + var BaseEditor = function(inspector, propertyDefinition, containerCell) { + this.inspector = inspector + this.propertyDefinition = propertyDefinition + this.containerCell = containerCell + + Base.call(this) + + this.init() + } + + BaseEditor.prototype = Object.create(BaseProto) + BaseEditor.prototype.constructor = Base + + BaseEditor.prototype.dispose = function() { + this.inspector = null + this.propertyDefinition = null + this.containerCell = null + + BaseProto.dispose.call(this) + } + + BaseEditor.prototype.init = function() { + this.build() + this.registerHandlers() + } + + BaseEditor.prototype.build = function() { + return null + } + + BaseEditor.prototype.registerHandlers = function() { + } + + $.oc.inspector.propertyEditors.base = BaseEditor +}(window.jQuery); \ No newline at end of file diff --git a/modules/system/assets/ui/js/inspector.editor.string.js b/modules/system/assets/ui/js/inspector.editor.string.js new file mode 100644 index 000000000..33cf0e897 --- /dev/null +++ b/modules/system/assets/ui/js/inspector.editor.string.js @@ -0,0 +1,69 @@ +/* + * Inspector string editor class. + */ ++function ($) { "use strict"; + + var Base = $.oc.inspector.propertyEditors.base, + BaseProto = Base.prototype + + var StringEditor = function(inspector, propertyDefinition, containerCell) { + Base.call(this, inspector, propertyDefinition, containerCell) + } + + StringEditor.prototype = Object.create(BaseProto) + StringEditor.prototype.constructor = Base + + StringEditor.prototype.dispose = function() { + this.unregisterHandlers() + + BaseProto.dispose.call(this) + } + + StringEditor.prototype.build = function() { + var editor = document.createElement('input'), + placeholder = this.propertyDefinition.placeholder !== undefined ? this.propertyDefinition.placeholder : '', + value = this.inspector.getPropertyValue(this.propertyDefinition.property) + + editor.setAttribute('type', 'text') + editor.setAttribute('class', 'string-editor') + editor.setAttribute('placeholder', 'placeholder') + + if (value === undefined) { + value = this.propertyDefinition.default + } + + editor.value = value + + $.oc.foundation.element.addClass(this.containerCell, 'text') + + this.containerCell.appendChild(editor) + } + + StringEditor.prototype.getInput = function() { + return this.containerCell.querySelector('input') + } + + StringEditor.prototype.registerHandlers = function() { + var input = this.getInput() + + input.addEventListener('focus', this.proxy(this.onInputFocus)) + input.addEventListener('change', this.proxy(this.onInputChange)) + } + + StringEditor.prototype.unregisterHandlers = function() { + var input = this.getInput() + + input.removeEventListener('focus', this.proxy(this.onInputFocus)) + input.removeEventListener('change', this.proxy(this.onInputChange)) + } + + StringEditor.prototype.onInputFocus = function(ev) { + this.inspector.makeCellActive(this.containerCell) + } + + StringEditor.prototype.onInputChange = function() { + + } + + $.oc.inspector.propertyEditors.string = StringEditor +}(window.jQuery); \ No newline at end of file diff --git a/modules/system/assets/ui/js/inspector.engine.js b/modules/system/assets/ui/js/inspector.engine.js new file mode 100644 index 000000000..b97aedd5e --- /dev/null +++ b/modules/system/assets/ui/js/inspector.engine.js @@ -0,0 +1,91 @@ +/* + * Inspector engine helpers. + * + * The helpers are used mostly by the Inspector Surface. + * + */ ++function ($) { "use strict"; + + // NAMESPACES + // ============================ + + if ($.oc === undefined) + $.oc = {} + + if ($.oc.inspector === undefined) + $.oc.inspector = {} + + $.oc.inspector.engine = {} + + // CLASS DEFINITION + // ============================ + + function findGroup(group, properties) { + for (var i = 0, len = properties.length; i < len; i++) { + var property = properties[i] + + if (property.itemType !== undefined && property.itemType == 'group' && item.title == group) { + return property + } + } + + return null + } + + $.oc.inspector.engine.processPropertyGroups = function(properties) { + var fields = [], + result = { + hasGroups: false, + properties: [] + }, + groupIndex = 0 + + + for (var i = 0, len = properties.length; i < len; i++) { + var property = properties[i] + + property.itemType = 'property' + + if (property.group === undefined) { + fields.push(property) + } + else { + var group = findGroup(property.group) + + if (!group) { + group = { + itemType: 'group', + title: property.group, + properties: [], + groupIndex: groupIndex + } + + groupIndex++ + fields.push(group) + } + + property.groupIndex = group.groupIndex + group.properties.push(property) + } + } + + for (var i = 0, len = properties.length; i < len; i++) { + var property = properties[i] + + result.properties.push(property) + + if (property.itemType == 'group') { + result.hasGroups = true + + for (var j = 0, propertiesLen = property.properties.length; j < propertiesLen; j++) { + result.properties.push(property.properties[j]) + } + + delete property.properties + } + } + + return result + } + +}(window.jQuery); diff --git a/modules/system/assets/ui/js/inspector.helpers.js b/modules/system/assets/ui/js/inspector.helpers.js new file mode 100644 index 000000000..71afde027 --- /dev/null +++ b/modules/system/assets/ui/js/inspector.helpers.js @@ -0,0 +1,32 @@ +/* + * Inspector helper functions. + * + */ ++function ($) { "use strict"; + + // NAMESPACES + // ============================ + + if ($.oc === undefined) + $.oc = {} + + if ($.oc.inspector === undefined) + $.oc.inspector = {} + + $.oc.inspector.helpers = {} + + $.oc.inspector.helpers.generateElementUniqueId = function(element) { + if (element.hasAttribute('data-inspector-id')) { + return element.getAttribute('data-inspector-id') + } + + var id = $.oc.inspector.helpers.generateUniqueId() + element.setAttribute('data-inspector-id', id) + + return id + } + + $.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.surface.js b/modules/system/assets/ui/js/inspector.surface.js new file mode 100644 index 000000000..95302198e --- /dev/null +++ b/modules/system/assets/ui/js/inspector.surface.js @@ -0,0 +1,333 @@ +/* + * Inspector Surface class. + * + * The class creates Inspector user interface and all the editors + * corresponding to the passed configuration in a specified container + * element. + * + */ ++function ($) { "use strict"; + + // NAMESPACES + // ============================ + + if ($.oc === undefined) + $.oc = {} + + if ($.oc.inspector === undefined) + $.oc.inspector = {} + + // CLASS DEFINITION + // ============================ + + var Base = $.oc.foundation.base, + BaseProto = Base.prototype + + /** + * Creates the Inspector surface in a container. + * - containerElement container DOM element + * - properties array (array of objects) + * - values - property values, an object + * - inspectorUniqueId - a string containing the unique inspector identifier. + * The identifier should be a constant for an inspectable element. Use + * $.oc.inspector.helpers.generateElementUniqueId(element) to generate a persistent ID + * for an element. Use $.oc.inspector.helpers.generateUniqueId() to generate an ID + * 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) { + this.options = $.extend({}, Surface.DEFAULTS, typeof option == 'object' && option) + this.rawProperties = properties + this.parsedProperties = $.oc.inspector.engine.processPropertyGroups(properties) + this.container = containerElement + this.inspectorUniqueId = inspectorUniqueId + this.values = values + + this.editors = [] + this.tableContainer = null + + Base.call(this) + + this.init() + } + + Surface.prototype = Object.create(BaseProto) + Surface.prototype.constructor = Surface + + Surface.prototype.dispose = function() { + this.removeElements() + this.disposeEditors() + + this.container = null + this.tableContainer = null + this.rawProperties = null + this.parsedProperties = null + this.editors = null + this.values = null + + BaseProto.dispose.call(this) + } + + // INTERNAL METHODS + // ============================ + + Surface.prototype.init = function() { + this.build() + } + + // + // Building + // + + /** + * Builds the Inspector table. The markup generated by this method looks + * like this: + * + *
+ * + * + * + * + * + * + * + *
+ *
+ *
+ * + * Expand/Collapse + * Label + * + *
+ *
+ *
+ * Editor markup + *
+ *
+ */ + Surface.prototype.build = function() { + this.tableContainer = document.createElement('div') + + var dataTable = document.createElement('table'), + tbody = document.createElement('tbody') + + $.oc.foundation.element.addClass(dataTable, 'inspector-fields') + if (this.parsedProperties.hasGroups) { + $.oc.foundation.element.addClass(dataTable, 'has-groups') + } + + for (var i=0, len = this.parsedProperties.properties.length; i < len; i++) { + var property = this.parsedProperties.properties[i], + row = document.createElement('tr'), + th = document.createElement('th'), + titleSpan = document.createElement('span'), + description = this.buildPropertyDescription(property) + + // Table row + // + row.setAttribute('data-property', property.property) + this.applyGroupIndexAttribute(property, row) + $.oc.foundation.element.addClass(row, this.getRowCssClass(property)) + + // Property head + // + this.applyHeadColspan(th, property) + + titleSpan.setAttribute('class', 'title-element') + titleSpan.setAttribute('title', this.escapeJavascriptString(property.title)) + this.buildGroupExpandControl(titleSpan, property) + titleSpan.innerHTML += this.escapeJavascriptString(property.title) + + var outerDiv = document.createElement('div'), + innerDiv = document.createElement('div') + + innerDiv.appendChild(titleSpan) + + if (description) { + innerDiv.appendChild(description) + } + + outerDiv.appendChild(innerDiv) + th.appendChild(outerDiv) + row.appendChild(th) + + // Editor + // + this.buildEditor(row, property) + + tbody.appendChild(row) + } + + dataTable.appendChild(tbody) + this.tableContainer.appendChild(dataTable) + + this.container.appendChild(this.tableContainer) + } + + Surface.prototype.applyGroupIndexAttribute = function(property, row) { + if (property.groupIndex !== undefined && property.itemType == 'property') { + row.setAttribute('data-group-index', property.groupIndex) + } + } + + Surface.prototype.getRowCssClass = function(property) { + var result = property.itemType + + if (property.itemType == 'property' && property.groupIndex !== undefined) { + result += ' grouped' + result += this.isGroupExpanded(property.group) ? ' expanded' : ' collapsed' + } + + if (property.itemType == 'property' && !property.showExternalParam) { + result += ' no-external-parameter' + } + + return result + } + + Surface.prototype.applyHeadColspan = function(th, property) { + if (property.itemType == 'group') { + th.setAttribute('colspan', 2) + } + } + + Surface.prototype.buildGroupExpandControl = function(titleSpan, property) { + if (property.itemType !== 'group') { + return + } + + var statusClass = this.isGroupExpanded(property.title) ? 'expanded' : '', + anchor = document.createElement('a') + + anchor.setAttribute('class', 'expandControl ' + statusClass) + anchor.setAttribute('href', 'javascript:;') + anchor.setAttribute('data-group-index', property.groupIndex) + anchor.innerHTML = 'Expand/collapse' + + titleSpan.appendChild(anchor) + } + + Surface.prototype.buildPropertyDescription = function(property) { + if (property.description === undefined || property.description === null) { + return null + } + + var span = document.createElement('span') + span.setAttribute('title', this.escapeJavascriptString(property.description)) + span.setAttribute('class', 'info oc-icon-info with-tooltip') + + return span + } + + // + // Field grouping + // + + Surface.prototype.isGroupExpanded = function(group) { + var statuses = this.loadGroupStatuses() + + if (statuses[group] !== undefined) + return statuses[group] + + return false + } + + Surface.prototype.loadGroupStatuses = function() { + var statuses = this.getInspectorGroupStatuses() + + if (statuses[this.inspectorUniqueId] !== undefined) { + return statuses[this.inspectorUniqueId] + } + + return {} + } + + Surface.prototype.getInspectorGroupStatuses = function() { + var statuses = document.body.getAttribute('data-inspector-group-statuses') + + if (statuses !== null) { + return JSON.parse(statuses) + } + + return {} + } + + // + // Editors + // + + Surface.prototype.buildEditor = function(row, property) { + if (property.itemType !== 'property') { + 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.') + + var cell = document.createElement('td'), + editor = new $.oc.inspector.propertyEditors[property.type](this, property, cell) + + this.editors.push(editor) + + row.appendChild(cell) + } + + // + // Internal API for the editors + // + + Surface.prototype.getPropertyValue = function(property) { + return this.values[property] + } + + Surface.prototype.makeCellActive = function(cell) { + var tbody = cell.parentNode.parentNode.parentNode, // cell / row / tbody + cells = tbody.querySelectorAll('tr td') + + for (var i = 0, len = cells.length; i < len; i++) { + $.oc.foundation.element.removeClass(cells[i], 'active') + } + + $.oc.foundation.element.addClass(cell, 'active') + } + + // + // Disposing + // + + Surface.prototype.removeElements = function() { + this.tableContainer.parentNode.removeChild(this.tableContainer); + } + + Surface.prototype.disposeEditors = function() { + for (var i = 0, len = this.editors.length; i < len; i++) { + var editor = this.editors[i] + + editor.dispose() + } + } + + // + // Helpers + // + + Surface.prototype.escapeJavascriptString = function(str) { + var div = document.createElement('div') + div.appendChild(document.createTextNode(str)) + return div.innerHTML + } + + // DEFAULT OPTIONS + // ============================ + + Surface.DEFAULTS = { + showExternalParam: false + } + + // REGISTRATION + // ============================ + + $.oc.inspector.surface = Surface + +}(window.jQuery); \ No newline at end of file diff --git a/modules/system/assets/ui/storm-min.js b/modules/system/assets/ui/storm-min.js index 0a0db5a2d..460429edc 100644 --- a/modules/system/assets/ui/storm-min.js +++ b/modules/system/assets/ui/storm-min.js @@ -19,11 +19,13 @@ $.oc={} if($.oc.foundation===undefined) $.oc.foundation={} var Element={hasClass:function(el,className){if(el.classList) -return el.classList.contains(className);return new RegExp('(^| )'+className+'( |$)','gi').test(el.className);},addClass:function(el,className){if(this.hasClass(el,className)) +return el.classList.contains(className);return new RegExp('(^| )'+className+'( |$)','gi').test(el.className);},addClass:function(el,className){var classes=className.split(' ') +for(var i=0,len=classes.length;i