Rewriting the Inspector, in progress.

This commit is contained in:
alekseybobkov 2015-09-19 12:57:31 -07:00
parent 86ca09970d
commit f823e6f0ba
6 changed files with 519 additions and 9 deletions

View File

@ -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);

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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:
*
* <div class="external-param-editor-container">
* <input> <-- original property editing input/markup
* <div class="external-editor">
* <div class="controls">
* <input type="text" tabindex="-1"/>
* <a href="#" tabindex="-1">
* <i class="oc-icon-terminal"></i>
* </a>
* </div>
* </div>
* </div>
*/
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);

View File

@ -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