From f6dec96c86b2eed159ba91b50e687c6235457ebb Mon Sep 17 00:00:00 2001 From: alekseybobkov Date: Fri, 22 Jan 2016 21:43:45 -0800 Subject: [PATCH 1/6] Improved memory management in the table widget client-side scripts. Minor CSS fixes. --- modules/backend/assets/css/october.css | 2 ++ .../assets/less/controls/autocomplete.less | 7 +++++++ modules/backend/assets/less/october.less | 1 + .../widgets/table/assets/js/build-min.js | 11 +++++++++-- .../backend/widgets/table/assets/js/table.js | 18 ++++++++++++++++++ modules/system/assets/ui/less/inspector.less | 1 + modules/system/assets/ui/storm.css | 2 +- 7 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 modules/backend/assets/less/controls/autocomplete.less diff --git a/modules/backend/assets/css/october.css b/modules/backend/assets/css/october.css index c9cce185f..17d7ca8c2 100644 --- a/modules/backend/assets/css/october.css +++ b/modules/backend/assets/css/october.css @@ -484,6 +484,8 @@ div.control-scrollpad > .scrollpad-scrollbar[data-visible]{opacity:0.7} div.control-scrollpad > .scrollpad-scrollbar[data-hidden]{display:none} div.control-scrollpad[data-direction=horizontal] > .scrollpad-scrollbar{top:auto;left:0;width:auto;height:11px} div.control-scrollpad[data-direction=horizontal] > .scrollpad-scrollbar .drag-handle{right:auto;top:2px;height:7px;min-height:0;min-width:10px;width:auto} +.autocomplete.dropdown-menu{background:white} +.autocomplete.dropdown-menu li a{padding:3px 12px} @font-face{font-family:'Open Sans';src:url('../font/OpenSans-Light.eot');src:url('../font/OpenSans-Light.eot?#iefix') format('embedded-opentype'),url('../font/OpenSans-Light.svg#open_sanslight') format('svg'),url('../font/OpenSans-Light.woff') format('woff'),url('../font/OpenSans-Light.ttf') format('truetype');font-style:normal;font-weight:300} @font-face{font-family:'Open Sans';src:url('../font/OpenSans-Regular.eot');src:url('../font/OpenSans-Regular.eot?#iefix') format('embedded-opentype'),url('../font/OpenSans-Regular.svg#open_sansregular') format('svg'),url('../font/OpenSans-Regular.woff') format('woff'),url('../font/OpenSans-Regular.ttf') format('truetype');font-style:normal;font-weight:400} @font-face{font-family:'Open Sans';src:url('../font/OpenSans-Semibold.eot');src:url('../font/OpenSans-Semibold.eot?#iefix') format('embedded-opentype'),url('../font/OpenSans-Semibold.svg#open_sanssemibold') format('svg'),url('../font/OpenSans-Semibold.woff') format('woff'),url('../font/OpenSans-Semibold.ttf') format('truetype');font-style:normal;font-weight:600} diff --git a/modules/backend/assets/less/controls/autocomplete.less b/modules/backend/assets/less/controls/autocomplete.less new file mode 100644 index 000000000..26ed4d335 --- /dev/null +++ b/modules/backend/assets/less/controls/autocomplete.less @@ -0,0 +1,7 @@ +.autocomplete.dropdown-menu { + background: white; + + li a { + padding: 3px 12px; + } +} \ No newline at end of file diff --git a/modules/backend/assets/less/october.less b/modules/backend/assets/less/october.less index 4fa7b625d..4c1f03313 100644 --- a/modules/backend/assets/less/october.less +++ b/modules/backend/assets/less/october.less @@ -35,6 +35,7 @@ @import "controls/tree-path.less"; @import "controls/namevaluelist.less"; @import "controls/scrollpad.less"; +@import "controls/autocomplete.less"; // // October Storm UI diff --git a/modules/backend/widgets/table/assets/js/build-min.js b/modules/backend/widgets/table/assets/js/build-min.js index 752a5a174..a55024c7d 100644 --- a/modules/backend/widgets/table/assets/js/build-min.js +++ b/modules/backend/widgets/table/assets/js/build-min.js @@ -6,6 +6,7 @@ $.oc.table={} var Table=function(element,options){this.el=element this.$el=$(element) this.options=options +this.disposed=false this.dataSource=null this.cellProcessors={} this.activeCellProcessor=null @@ -24,7 +25,9 @@ if(this.options.postback&&this.options.clientDataSourceClass=='client') this.formSubmitHandler=this.onFormSubmit.bind(this) this.navigation=null this.recordsAddedOrDeleted=0 -this.init()} +this.disposeBound=this.dispose.bind(this) +this.init() +$.oc.foundation.controlUtils.markDisposable(element)} Table.prototype.init=function(){this.createDataSource() this.initCellProcessors() this.navigation=new $.oc.table.helper.navigation(this) @@ -41,6 +44,7 @@ throw new Error('The table client-side data source class "'+dataSourceClass+'" i this.dataSource=new $.oc.table.datasource[dataSourceClass](this)} Table.prototype.registerHandlers=function(){this.el.addEventListener('click',this.clickHandler) this.el.addEventListener('keydown',this.keydownHandler) +this.$el.one('dispose-control',this.disposeBound) document.addEventListener('click',this.documentClickHandler) if(this.options.postback&&this.options.clientDataSourceClass=='client') this.$el.closest('form').bind('oc.beforeRequest',this.formSubmitHandler) @@ -307,7 +311,10 @@ return if(this.activeCellProcessor&&this.activeCellProcessor.elementBelongsToProcessor(target)) return this.unfocusTable()} -Table.prototype.dispose=function(){this.unfocusTable() +Table.prototype.dispose=function(){if(this.disposed){return} +this.disposed=true +this.disposeBound=true +this.unfocusTable() this.dataSource.dispose() this.dataSource=null this.unregisterHandlers() diff --git a/modules/backend/widgets/table/assets/js/table.js b/modules/backend/widgets/table/assets/js/table.js index 2526ca33b..a813e7a1d 100644 --- a/modules/backend/widgets/table/assets/js/table.js +++ b/modules/backend/widgets/table/assets/js/table.js @@ -23,6 +23,7 @@ this.$el = $(element) this.options = options + this.disposed = false // // State properties @@ -77,11 +78,16 @@ // Number of records added or deleted during the session this.recordsAddedOrDeleted = 0 + // Bound reference to dispose() - ideally the class should use the October foundation library base class + this.disposeBound = this.dispose.bind(this) + // // Initialization // this.init() + + $.oc.foundation.controlUtils.markDisposable(element) } // INTERNAL METHODS @@ -136,6 +142,8 @@ Table.prototype.registerHandlers = function() { this.el.addEventListener('click', this.clickHandler) this.el.addEventListener('keydown', this.keydownHandler) + this.$el.one('dispose-control', this.disposeBound) + document.addEventListener('click', this.documentClickHandler) if (this.options.postback && this.options.clientDataSourceClass == 'client') @@ -822,6 +830,16 @@ // ============================ Table.prototype.dispose = function() { + if (this.disposed) { + // Prevent errors when legacy code executes the dispose() method + // directly, bypassing $.oc.foundation.controlUtils.disposeControls(container) + return + } + + this.disposed = true + + this.disposeBound = true + // Remove an editor and commit the data if needed this.unfocusTable() diff --git a/modules/system/assets/ui/less/inspector.less b/modules/system/assets/ui/less/inspector.less index 27846845e..799edd806 100644 --- a/modules/system/assets/ui/less/inspector.less +++ b/modules/system/assets/ui/less/inspector.less @@ -137,6 +137,7 @@ li a { padding: 5px 12px; white-space: normal; + word-wrap: break-word; } } diff --git a/modules/system/assets/ui/storm.css b/modules/system/assets/ui/storm.css index f82ee90e0..f20bebe58 100644 --- a/modules/system/assets/ui/storm.css +++ b/modules/system/assets/ui/storm.css @@ -2461,7 +2461,7 @@ table.table.data tr.list-tree-level-10 td.list-cell-index-1{padding-left:125px} .inspector-fields td.autocomplete{padding:0} .inspector-fields td.autocomplete .autocomplete-container input[type=text]{padding:5px 12px} .inspector-fields td.autocomplete .autocomplete-container ul.dropdown-menu{background:white;font-size:12px} -.inspector-fields td.autocomplete .autocomplete-container ul.dropdown-menu li a{padding:5px 12px;white-space:normal} +.inspector-fields td.autocomplete .autocomplete-container ul.dropdown-menu li a{padding:5px 12px;white-space:normal;word-wrap:break-word} .inspector-fields td.autocomplete .autocomplete-container .loading-indicator span{margin-top:-12px;right:10px;left:auto} .inspector-fields td.trigger-cell{padding:0 !important} .inspector-fields td.trigger-cell a.trigger{display:block;padding:5px 12px 7px 12px;overflow:hidden;min-height:29px;text-overflow:ellipsis;color:#333333;text-decoration:none} From 6aef72bb87e734603b51e400df802f75b207b5d1 Mon Sep 17 00:00:00 2001 From: alekseybobkov Date: Sat, 23 Jan 2016 20:27:57 -0800 Subject: [PATCH 2/6] Added "depend" parameter to the Inspector autocomplete editor. Minor fix in the Inspector. --- .../ui/js/inspector.editor.autocomplete.js | 67 +++++++++++++++++-- .../assets/ui/js/inspector.editor.dropdown.js | 2 +- modules/system/assets/ui/storm-min.js | 27 ++++++-- 3 files changed, 85 insertions(+), 11 deletions(-) diff --git a/modules/system/assets/ui/js/inspector.editor.autocomplete.js b/modules/system/assets/ui/js/inspector.editor.autocomplete.js index 574c2ed3c..c6a97733a 100644 --- a/modules/system/assets/ui/js/inspector.editor.autocomplete.js +++ b/modules/system/assets/ui/js/inspector.editor.autocomplete.js @@ -9,6 +9,8 @@ BaseProto = Base.prototype var AutocompleteEditor = function(inspector, propertyDefinition, containerCell, group) { + this.autoUpdateTimeout = null + Base.call(this, inspector, propertyDefinition, containerCell, group) } @@ -16,6 +18,7 @@ AutocompleteEditor.prototype.constructor = Base AutocompleteEditor.prototype.dispose = function() { + this.clearAutoUpdateTimeout() this.removeAutocomplete() BaseProto.dispose.call(this) @@ -63,10 +66,18 @@ items = [] } - $(input).autocomplete({ - source: this.prepareItems(items), - matchWidth: true - }) + var $input = $(input), + autocomplete = $input.data('autocomplete') + + if (!autocomplete) { + $input.autocomplete({ + source: this.prepareItems(items), + matchWidth: true + }) + } + else { + autocomplete.source = this.prepareItems(items) + } } AutocompleteEditor.prototype.removeAutocomplete = function() { @@ -110,6 +121,46 @@ $(this.getInput()).off('change', this.proxy(this.onInputKeyUp)) } + AutocompleteEditor.prototype.saveDependencyValues = function() { + this.prevDependencyValues = this.getDependencyValues() + } + + AutocompleteEditor.prototype.getDependencyValues = function() { + var result = '' + + 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 = ''; + } + + result += property + ':' + value + '-' + } + + return result + } + + AutocompleteEditor.prototype.onInspectorPropertyChanged = function(property, value) { + if (!this.propertyDefinition.depends || this.propertyDefinition.depends.indexOf(property) === -1) { + return + } + + this.clearAutoUpdateTimeout() + + if (this.prevDependencyValues === undefined || this.prevDependencyValues != dependencyValues) { + this.autoUpdateTimeout = setTimeout(this.proxy(this.loadDynamicItems), 200) + } + } + + AutocompleteEditor.prototype.clearAutoUpdateTimeout = function() { + if (this.autoUpdateTimeout !== null) { + clearTimeout(this.autoUpdateTimeout) + this.autoUpdateTimeout = null + } + } + // // Dynamic items // @@ -132,8 +183,14 @@ } AutocompleteEditor.prototype.loadDynamicItems = function() { + if (this.isDisposed()) { + return + } + + this.clearAutoUpdateTimeout() + var container = this.getContainer(), - data = this.inspector.getValues(), + data = this.getRootSurface().getValues(), $form = $(container).closest('form') $.oc.foundation.element.addClass(container, 'loading-indicator-container size-small') diff --git a/modules/system/assets/ui/js/inspector.editor.dropdown.js b/modules/system/assets/ui/js/inspector.editor.dropdown.js index f77937371..ea666597f 100644 --- a/modules/system/assets/ui/js/inspector.editor.dropdown.js +++ b/modules/system/assets/ui/js/inspector.editor.dropdown.js @@ -306,7 +306,7 @@ DropdownEditor.prototype.loadDynamicOptions = function(initialization) { var currentValue = this.inspector.getPropertyValue(this.propertyDefinition.property), - data = this.inspector.getValues(), + data = this.getRootSurface().getValues(), self = this, $form = $(this.getSelect()).closest('form') diff --git a/modules/system/assets/ui/storm-min.js b/modules/system/assets/ui/storm-min.js index 4d70c4816..de97156dc 100644 --- a/modules/system/assets/ui/storm-min.js +++ b/modules/system/assets/ui/storm-min.js @@ -4260,7 +4260,7 @@ this.createPlaceholder(select) this.createOptions(select,this.propertyDefinition.options) if(value===undefined){value=this.propertyDefinition.default} select.value=value} -DropdownEditor.prototype.loadDynamicOptions=function(initialization){var currentValue=this.inspector.getPropertyValue(this.propertyDefinition.property),data=this.inspector.getValues(),self=this,$form=$(this.getSelect()).closest('form') +DropdownEditor.prototype.loadDynamicOptions=function(initialization){var currentValue=this.inspector.getPropertyValue(this.propertyDefinition.property),data=this.getRootSurface().getValues(),self=this,$form=$(this.getSelect()).closest('form') if(currentValue===undefined){currentValue=this.propertyDefinition.default} var callback=function dropdownOptionsRequestDoneClosure(data){self.hideLoadingIndicator() self.optionsRequestDone(data,currentValue,true)} @@ -4980,10 +4980,12 @@ break;}} DictionaryEditor.prototype.onKeyDown=function(ev){if(ev.keyCode==40){return this.navigateDown(ev)} else if(ev.keyCode==38){return this.navigateUp(ev)}} $.oc.inspector.propertyEditors.dictionary=DictionaryEditor}(window.jQuery);+function($){"use strict";var Base=$.oc.inspector.propertyEditors.string,BaseProto=Base.prototype -var AutocompleteEditor=function(inspector,propertyDefinition,containerCell,group){Base.call(this,inspector,propertyDefinition,containerCell,group)} +var AutocompleteEditor=function(inspector,propertyDefinition,containerCell,group){this.autoUpdateTimeout=null +Base.call(this,inspector,propertyDefinition,containerCell,group)} AutocompleteEditor.prototype=Object.create(BaseProto) AutocompleteEditor.prototype.constructor=Base -AutocompleteEditor.prototype.dispose=function(){this.removeAutocomplete() +AutocompleteEditor.prototype.dispose=function(){this.clearAutoUpdateTimeout() +this.removeAutocomplete() BaseProto.dispose.call(this)} AutocompleteEditor.prototype.build=function(){var container=document.createElement('div'),editor=document.createElement('input'),placeholder=this.propertyDefinition.placeholder!==undefined?this.propertyDefinition.placeholder:'',value=this.inspector.getPropertyValue(this.propertyDefinition.property) editor.setAttribute('type','text') @@ -5000,7 +5002,9 @@ if(this.propertyDefinition.items!==undefined){this.buildAutoComplete(this.proper else{this.loadDynamicItems()}} AutocompleteEditor.prototype.buildAutoComplete=function(items){var input=this.getInput() if(items===undefined){items=[]} -$(input).autocomplete({source:this.prepareItems(items),matchWidth:true})} +var $input=$(input),autocomplete=$input.data('autocomplete') +if(!autocomplete){$input.autocomplete({source:this.prepareItems(items),matchWidth:true})} +else{autocomplete.source=this.prepareItems(items)}} AutocompleteEditor.prototype.removeAutocomplete=function(){var input=this.getInput() $(input).autocomplete('destroy')} AutocompleteEditor.prototype.prepareItems=function(items){var result={} @@ -5013,13 +5017,26 @@ AutocompleteEditor.prototype.registerHandlers=function(){BaseProto.registerHandl $(this.getInput()).on('change',this.proxy(this.onInputKeyUp))} AutocompleteEditor.prototype.unregisterHandlers=function(){BaseProto.unregisterHandlers.call(this) $(this.getInput()).off('change',this.proxy(this.onInputKeyUp))} +AutocompleteEditor.prototype.saveDependencyValues=function(){this.prevDependencyValues=this.getDependencyValues()} +AutocompleteEditor.prototype.getDependencyValues=function(){var result='' +for(var i=0,len=this.propertyDefinition.depends.length;i Date: Tue, 26 Jan 2016 19:53:35 -0800 Subject: [PATCH 3/6] Added new Inspector editor - String List Autocomplete --- .../ui/js/inspector.editor.dictionary.js | 2 +- ...inspector.editor.stringlistautocomplete.js | 543 ++++++++++++++++++ modules/system/assets/ui/less/inspector.less | 25 +- modules/system/assets/ui/storm-min.js | 184 ++++++ modules/system/assets/ui/storm.css | 4 +- modules/system/assets/ui/storm.js | 1 + 6 files changed, 749 insertions(+), 10 deletions(-) create mode 100644 modules/system/assets/ui/js/inspector.editor.stringlistautocomplete.js diff --git a/modules/system/assets/ui/js/inspector.editor.dictionary.js b/modules/system/assets/ui/js/inspector.editor.dictionary.js index 4e0352897..ed8534cbb 100644 --- a/modules/system/assets/ui/js/inspector.editor.dictionary.js +++ b/modules/system/assets/ui/js/inspector.editor.dictionary.js @@ -1,5 +1,5 @@ /* - * Inspector text editor class. + * Inspector dictionary editor class. */ +function ($) { "use strict"; diff --git a/modules/system/assets/ui/js/inspector.editor.stringlistautocomplete.js b/modules/system/assets/ui/js/inspector.editor.stringlistautocomplete.js new file mode 100644 index 000000000..d363d7a3e --- /dev/null +++ b/modules/system/assets/ui/js/inspector.editor.stringlistautocomplete.js @@ -0,0 +1,543 @@ +/* + * Inspector string list with autocompletion editor class. + * + * TODO: validation is not implemented in this editor. See the Dictionary editor for reference. + */ ++function ($) { "use strict"; + + var Base = $.oc.inspector.propertyEditors.popupBase, + BaseProto = Base.prototype + + var StringListAutocomplete = function(inspector, propertyDefinition, containerCell, group) { + this.items = null + + Base.call(this, inspector, propertyDefinition, containerCell, group) + } + + StringListAutocomplete.prototype = Object.create(BaseProto) + StringListAutocomplete.prototype.constructor = Base + + StringListAutocomplete.prototype.dispose = function() { + BaseProto.dispose.call(this) + } + + StringListAutocomplete.prototype.init = function() { + BaseProto.init.call(this) + } + + StringListAutocomplete.prototype.supportsExternalParameterEditor = function() { + return false + } + + StringListAutocomplete.prototype.setLinkText = function(link, value) { + var value = value !== undefined ? value + : this.inspector.getPropertyValue(this.propertyDefinition.property) + + if (value === undefined) { + value = this.propertyDefinition.default + } + + this.checkValueType(value) + + if (!value) { + value = this.propertyDefinition.placeholder + $.oc.foundation.element.addClass(link, 'placeholder') + + if (!value) { + value = '[]' + } + + link.textContent = value + } + else { + $.oc.foundation.element.removeClass(link, 'placeholder') + + link.textContent = '[' + value.join(', ') + ']' + } + } + + StringListAutocomplete.prototype.checkValueType = function(value) { + if (value && Object.prototype.toString.call(value) !== '[object Array]') { + this.throwError('The string list value should be an array.') + } + } + + // + // Popup editor methods + // + + StringListAutocomplete.prototype.getPopupContent = function() { + return '
\ + \ + \ + \ +
' + } + + StringListAutocomplete.prototype.configurePopup = function(popup) { + this.initAutocomplete() + + this.buildItemsTable(popup.get(0)) + + this.focusFirstInput() + } + + StringListAutocomplete.prototype.handleSubmit = function($form) { + return this.applyValues() + } + + // + // Building and row management + // + + StringListAutocomplete.prototype.buildItemsTable = function(popup) { + var table = popup.querySelector('table.inspector-dictionary-table'), + tbody = document.createElement('tbody'), + items = this.inspector.getPropertyValue(this.propertyDefinition.property) + + if (items === undefined) { + items = this.propertyDefinition.default + } + + if (items === undefined || this.getValueKeys(items).length === 0) { + var row = this.buildEmptyRow() + + tbody.appendChild(row) + } + else { + for (var key in items) { + var row = this.buildTableRow(items[key]) + + tbody.appendChild(row) + } + } + + table.appendChild(tbody) + this.updateScrollpads() + } + + StringListAutocomplete.prototype.buildTableRow = function(value) { + var row = document.createElement('tr'), + valueCell = document.createElement('td') + + this.createInput(valueCell, value) + + row.appendChild(valueCell) + + return row + } + + StringListAutocomplete.prototype.buildEmptyRow = function() { + return this.buildTableRow(null) + } + + StringListAutocomplete.prototype.createInput = function(container, value) { + var input = document.createElement('input'), + controlContainer = document.createElement('div') + + input.setAttribute('type', 'text') + input.setAttribute('class', 'form-control') + input.value = value + + controlContainer.appendChild(input) + container.appendChild(controlContainer) + } + + StringListAutocomplete.prototype.setActiveCell = function(input) { + var activeCells = this.popup.querySelectorAll('td.active') + + for (var i = activeCells.length-1; i >= 0; i--) { + $.oc.foundation.element.removeClass(activeCells[i], 'active') + } + + var activeCell = input.parentNode.parentNode // input / div / td + $.oc.foundation.element.addClass(activeCell, 'active') + + this.buildAutoComplete(input) + } + + StringListAutocomplete.prototype.createItem = function() { + var activeRow = this.getActiveRow(), + newRow = this.buildEmptyRow(), + tbody = this.getTableBody(), + nextSibling = activeRow ? activeRow.nextElementSibling : null + + tbody.insertBefore(newRow, nextSibling) + + this.focusAndMakeActive(newRow.querySelector('input')) + this.updateScrollpads() + } + + StringListAutocomplete.prototype.deleteItem = function() { + var activeRow = this.getActiveRow(), + tbody = this.getTableBody() + + if (!activeRow) { + return + } + + var nextRow = activeRow.nextElementSibling, + prevRow = activeRow.previousElementSibling, + input = this.getRowInputByIndex(activeRow, 0) + + if (input) { + this.removeAutocomplete(input) + } + + tbody.removeChild(activeRow) + + var newSelectedRow = nextRow ? nextRow : prevRow + + if (!newSelectedRow) { + newSelectedRow = this.buildEmptyRow() + tbody.appendChild(newSelectedRow) + } + + this.focusAndMakeActive(newSelectedRow.querySelector('input')) + this.updateScrollpads() + } + + StringListAutocomplete.prototype.applyValues = function() { + var tbody = this.getTableBody(), + dataRows = tbody.querySelectorAll('tr'), + link = this.getLink(), + result = [] + + for (var i = 0, len = dataRows.length; i < len; i++) { + var dataRow = dataRows[i], + valueInput = this.getRowInputByIndex(dataRow, 0), + value = $.trim(valueInput.value) + + if (value.length == 0) { + continue + } + + result.push(value) + } + + this.inspector.setPropertyValue(this.propertyDefinition.property, result) + this.setLinkText(link, result) + } + + // + // Helpers + // + + StringListAutocomplete.prototype.getValueKeys = function(value) { + var result = [] + + for (var key in value) { + result.push(key) + } + + return result + } + + StringListAutocomplete.prototype.getActiveRow = function() { + var activeCell = this.popup.querySelector('td.active') + + if (!activeCell) { + return null + } + + return activeCell.parentNode + } + + StringListAutocomplete.prototype.getTableBody = function() { + return this.popup.querySelector('table.inspector-dictionary-table tbody') + } + + StringListAutocomplete.prototype.updateScrollpads = function() { + $('.control-scrollpad', this.popup).scrollpad('update') + } + + StringListAutocomplete.prototype.focusFirstInput = function() { + var input = this.popup.querySelector('td input') + + if (input) { + input.focus() + this.setActiveCell(input) + } + } + + StringListAutocomplete.prototype.getEditorCell = function(cell) { + return cell.parentNode.parentNode // cell / div / td + } + + StringListAutocomplete.prototype.getEditorRow = function(cell) { + return cell.parentNode.parentNode.parentNode // cell / div / td / tr + } + + StringListAutocomplete.prototype.focusAndMakeActive = function(input) { + input.focus() + this.setActiveCell(input) + } + + StringListAutocomplete.prototype.getRowInputByIndex = function(row, index) { + return row.cells[index].querySelector('input') + } + + // + // Navigation + // + + StringListAutocomplete.prototype.navigateDown = function(ev) { + var cell = this.getEditorCell(ev.currentTarget), + row = this.getEditorRow(ev.currentTarget), + nextRow = row.nextElementSibling + + if (!nextRow) { + return + } + + var newActiveEditor = nextRow.cells[cell.cellIndex].querySelector('input') + + this.focusAndMakeActive(newActiveEditor) + } + + StringListAutocomplete.prototype.navigateUp = function(ev) { + var cell = this.getEditorCell(ev.currentTarget), + row = this.getEditorRow(ev.currentTarget), + prevRow = row.previousElementSibling + + if (!prevRow) { + return + } + + var newActiveEditor = prevRow.cells[cell.cellIndex].querySelector('input') + + this.focusAndMakeActive(newActiveEditor) + } + + // + // Autocomplete + // + + StringListAutocomplete.prototype.initAutocomplete = function() { + if (this.propertyDefinition.items !== undefined) { + this.items = this.prepareItems(this.propertyDefinition.items) + this.initializeAutocompleteForCurrentInput() + } + else { + this.loadDynamicItems() + } + } + + StringListAutocomplete.prototype.initializeAutocompleteForCurrentInput = function() { + var activeElement = document.activeElement + + if (!activeElement) { + return + } + + var inputs = this.popup.querySelectorAll('td input.form-control') + + if (!inputs) { + return + } + + for (var i=inputs.length-1; i>=0; i--) { + if (inputs[i] === activeElement) { + this.buildAutoComplete(inputs[i]) + return + } + } + } + + StringListAutocomplete.prototype.buildAutoComplete = function(input) { + if (this.items === null) { + return + } + + $(input).autocomplete({ + source: this.items, + matchWidth: true, + menu: '', + bodyContainer: true + }) + } + + StringListAutocomplete.prototype.removeAutocomplete = function(input) { + var $input = $(input) + + if (!$input.data('autocomplete')) { + return + } + + $input.autocomplete('destroy') + } + + StringListAutocomplete.prototype.prepareItems = function(items) { + var result = {} + + if ($.isArray(items)) { + for (var i = 0, len = items.length; i < len; i++) { + result[items[i]] = items[i] + } + } + else { + result = items + } + + return result + } + + StringListAutocomplete.prototype.loadDynamicItems = function() { + if (this.isDisposed()) { + return + } + + var data = this.getRootSurface().getValues(), + $form = $(this.popup).find('form') + + if (this.triggerGetItems(data) === false) { + return + } + + data['inspectorProperty'] = this.propertyDefinition.property + data['inspectorClassName'] = this.inspector.options.inspectorClass + + $form.request('onInspectableGetOptions', { + data: data, + }) + .done(this.proxy(this.itemsRequestDone)) + } + + StringListAutocomplete.prototype.triggerGetItems = function(values) { + var $inspectable = this.getInspectableElement() + if (!$inspectable) { + return true + } + + var itemsEvent = $.Event('autocompleteitems.oc.inspector') + + $inspectable.trigger(itemsEvent, [{ + values: values, + callback: this.proxy(this.itemsRequestDone), + property: this.inspector.getPropertyPath(this.propertyDefinition.property), + propertyDefinition: this.propertyDefinition + }]) + + if (itemsEvent.isDefaultPrevented()) { + return false + } + + return true + } + + StringListAutocomplete.prototype.itemsRequestDone = function(data) { + if (this.isDisposed()) { + // Handle the case when the asynchronous request finishes after + // the editor is disposed + return + } + + var loadedItems = {} + + if (data.options) { + for (var i = data.options.length-1; i >= 0; i--) { + loadedItems[data.options[i].value] = data.options[i].title + } + } + + this.items = this.prepareItems(loadedItems) + this.initializeAutocompleteForCurrentInput() + } + + StringListAutocomplete.prototype.removeAutocompleteFromAllRows = function() { + var inputs = this.popup.querySelector('td input.form-control') + + for (var i=inputs.length-1; i>=0; i--) { + this.removeAutocomplete(inputs[i]) + } + } + + // + // Event handlers + // + + StringListAutocomplete.prototype.onPopupShown = function(ev, link, popup) { + BaseProto.onPopupShown.call(this,ev, link, popup) + + popup.on('focus.inspector', 'td input', this.proxy(this.onFocus)) + popup.on('blur.inspector', 'td input', this.proxy(this.onBlur)) + popup.on('keydown.inspector', 'td input', this.proxy(this.onKeyDown)) + popup.on('click.inspector', '[data-cmd]', this.proxy(this.onCommand)) + } + + StringListAutocomplete.prototype.onPopupHidden = function(ev, link, popup) { + popup.off('.inspector', 'td input') + popup.off('.inspector', '[data-cmd]', this.proxy(this.onCommand)) + + this.removeAutocompleteFromAllRows() + this.items = null + + BaseProto.onPopupHidden.call(this, ev, link, popup) + } + + StringListAutocomplete.prototype.onFocus = function(ev) { + this.setActiveCell(ev.currentTarget) + } + + StringListAutocomplete.prototype.onBlur = function(ev) { + this.removeAutocomplete(ev.currentTarget) + } + + StringListAutocomplete.prototype.onCommand = function(ev) { + var command = ev.currentTarget.getAttribute('data-cmd') + + switch (command) { + case 'create-item' : + this.createItem() + break; + case 'delete-item' : + this.deleteItem() + break; + } + } + + StringListAutocomplete.prototype.onKeyDown = function(ev) { + if (ev.keyCode == 40) { + return this.navigateDown(ev) + } + else if (ev.keyCode == 38) { + return this.navigateUp(ev) + } + } + + $.oc.inspector.propertyEditors.stringListAutocomplete = StringListAutocomplete +}(window.jQuery); \ No newline at end of file diff --git a/modules/system/assets/ui/less/inspector.less b/modules/system/assets/ui/less/inspector.less index 799edd806..9872f0565 100644 --- a/modules/system/assets/ui/less/inspector.less +++ b/modules/system/assets/ui/less/inspector.less @@ -22,6 +22,18 @@ // Inspector // -------------------------------------------------- +.inspector-autocomplete-list() { + background: white; + font-size: 12px; + z-index: 100000; // It's safe to set z-index any high value for drop-downs + + li a { + padding: 5px 12px; + white-space: normal; + word-wrap: break-word; + } +} + .inspector-fields { min-width: 220px; border-collapse: collapse; @@ -131,14 +143,7 @@ } ul.dropdown-menu { - background: white; - font-size: 12px; - - li a { - padding: 5px 12px; - white-space: normal; - word-wrap: break-word; - } + .inspector-autocomplete-list(); } .loading-indicator { @@ -620,6 +625,10 @@ div.inspector-dictionary-container { } } +ul.autocomplete.dropdown-menu.inspector-autocomplete { + .inspector-autocomplete-list(); +} + .select2-dropdown { &.ocInspectorDropdown { font-size: 12px; diff --git a/modules/system/assets/ui/storm-min.js b/modules/system/assets/ui/storm-min.js index de97156dc..a40760eed 100644 --- a/modules/system/assets/ui/storm-min.js +++ b/modules/system/assets/ui/storm-min.js @@ -4806,6 +4806,190 @@ for(var i=0,len=arrayValue.length;i0){resultValue.push(currentValue)}}} this.inspector.setPropertyValue(this.propertyDefinition.property,resultValue)} $.oc.inspector.propertyEditors.stringList=StringListEditor}(window.jQuery);+function($){"use strict";var Base=$.oc.inspector.propertyEditors.popupBase,BaseProto=Base.prototype +var StringListAutocomplete=function(inspector,propertyDefinition,containerCell,group){this.items=null +Base.call(this,inspector,propertyDefinition,containerCell,group)} +StringListAutocomplete.prototype=Object.create(BaseProto) +StringListAutocomplete.prototype.constructor=Base +StringListAutocomplete.prototype.dispose=function(){BaseProto.dispose.call(this)} +StringListAutocomplete.prototype.init=function(){BaseProto.init.call(this)} +StringListAutocomplete.prototype.supportsExternalParameterEditor=function(){return false} +StringListAutocomplete.prototype.setLinkText=function(link,value){var value=value!==undefined?value:this.inspector.getPropertyValue(this.propertyDefinition.property) +if(value===undefined){value=this.propertyDefinition.default} +this.checkValueType(value) +if(!value){value=this.propertyDefinition.placeholder +$.oc.foundation.element.addClass(link,'placeholder') +if(!value){value='[]'} +link.textContent=value} +else{$.oc.foundation.element.removeClass(link,'placeholder') +link.textContent='['+value.join(', ')+']'}} +StringListAutocomplete.prototype.checkValueType=function(value){if(value&&Object.prototype.toString.call(value)!=='[object Array]'){this.throwError('The string list value should be an array.')}} +StringListAutocomplete.prototype.getPopupContent=function(){return'
\ + \ + \ + \ +
'} +StringListAutocomplete.prototype.configurePopup=function(popup){this.initAutocomplete() +this.buildItemsTable(popup.get(0)) +this.focusFirstInput()} +StringListAutocomplete.prototype.handleSubmit=function($form){return this.applyValues()} +StringListAutocomplete.prototype.buildItemsTable=function(popup){var table=popup.querySelector('table.inspector-dictionary-table'),tbody=document.createElement('tbody'),items=this.inspector.getPropertyValue(this.propertyDefinition.property) +if(items===undefined){items=this.propertyDefinition.default} +if(items===undefined||this.getValueKeys(items).length===0){var row=this.buildEmptyRow() +tbody.appendChild(row)} +else{for(var key in items){var row=this.buildTableRow(items[key]) +tbody.appendChild(row)}} +table.appendChild(tbody) +this.updateScrollpads()} +StringListAutocomplete.prototype.buildTableRow=function(value){var row=document.createElement('tr'),valueCell=document.createElement('td') +this.createInput(valueCell,value) +row.appendChild(valueCell) +return row} +StringListAutocomplete.prototype.buildEmptyRow=function(){return this.buildTableRow(null)} +StringListAutocomplete.prototype.createInput=function(container,value){var input=document.createElement('input'),controlContainer=document.createElement('div') +input.setAttribute('type','text') +input.setAttribute('class','form-control') +input.value=value +controlContainer.appendChild(input) +container.appendChild(controlContainer)} +StringListAutocomplete.prototype.setActiveCell=function(input){var activeCells=this.popup.querySelectorAll('td.active') +for(var i=activeCells.length-1;i>=0;i--){$.oc.foundation.element.removeClass(activeCells[i],'active')} +var activeCell=input.parentNode.parentNode +$.oc.foundation.element.addClass(activeCell,'active') +this.buildAutoComplete(input)} +StringListAutocomplete.prototype.createItem=function(){var activeRow=this.getActiveRow(),newRow=this.buildEmptyRow(),tbody=this.getTableBody(),nextSibling=activeRow?activeRow.nextElementSibling:null +tbody.insertBefore(newRow,nextSibling) +this.focusAndMakeActive(newRow.querySelector('input')) +this.updateScrollpads()} +StringListAutocomplete.prototype.deleteItem=function(){var activeRow=this.getActiveRow(),tbody=this.getTableBody() +if(!activeRow){return} +var nextRow=activeRow.nextElementSibling,prevRow=activeRow.previousElementSibling,input=this.getRowInputByIndex(activeRow,0) +if(input){this.removeAutocomplete(input)} +tbody.removeChild(activeRow) +var newSelectedRow=nextRow?nextRow:prevRow +if(!newSelectedRow){newSelectedRow=this.buildEmptyRow() +tbody.appendChild(newSelectedRow)} +this.focusAndMakeActive(newSelectedRow.querySelector('input')) +this.updateScrollpads()} +StringListAutocomplete.prototype.applyValues=function(){var tbody=this.getTableBody(),dataRows=tbody.querySelectorAll('tr'),link=this.getLink(),result=[] +for(var i=0,len=dataRows.length;i=0;i--){if(inputs[i]===activeElement){this.buildAutoComplete(inputs[i]) +return}}} +StringListAutocomplete.prototype.buildAutoComplete=function(input){if(this.items===null){return} +$(input).autocomplete({source:this.items,matchWidth:true,menu:'',bodyContainer:true})} +StringListAutocomplete.prototype.removeAutocomplete=function(input){var $input=$(input) +if(!$input.data('autocomplete')){return} +$input.autocomplete('destroy')} +StringListAutocomplete.prototype.prepareItems=function(items){var result={} +if($.isArray(items)){for(var i=0,len=items.length;i=0;i--){loadedItems[data.options[i].value]=data.options[i].title}} +this.items=this.prepareItems(loadedItems) +this.initializeAutocompleteForCurrentInput()} +StringListAutocomplete.prototype.removeAutocompleteFromAllRows=function(){var inputs=this.popup.querySelector('td input.form-control') +for(var i=inputs.length-1;i>=0;i--){this.removeAutocomplete(inputs[i])}} +StringListAutocomplete.prototype.onPopupShown=function(ev,link,popup){BaseProto.onPopupShown.call(this,ev,link,popup) +popup.on('focus.inspector','td input',this.proxy(this.onFocus)) +popup.on('blur.inspector','td input',this.proxy(this.onBlur)) +popup.on('keydown.inspector','td input',this.proxy(this.onKeyDown)) +popup.on('click.inspector','[data-cmd]',this.proxy(this.onCommand))} +StringListAutocomplete.prototype.onPopupHidden=function(ev,link,popup){popup.off('.inspector','td input') +popup.off('.inspector','[data-cmd]',this.proxy(this.onCommand)) +this.removeAutocompleteFromAllRows() +this.items=null +BaseProto.onPopupHidden.call(this,ev,link,popup)} +StringListAutocomplete.prototype.onFocus=function(ev){this.setActiveCell(ev.currentTarget)} +StringListAutocomplete.prototype.onBlur=function(ev){this.removeAutocomplete(ev.currentTarget)} +StringListAutocomplete.prototype.onCommand=function(ev){var command=ev.currentTarget.getAttribute('data-cmd') +switch(command){case'create-item':this.createItem() +break;case'delete-item':this.deleteItem() +break;}} +StringListAutocomplete.prototype.onKeyDown=function(ev){if(ev.keyCode==40){return this.navigateDown(ev)} +else if(ev.keyCode==38){return this.navigateUp(ev)}} +$.oc.inspector.propertyEditors.stringListAutocomplete=StringListAutocomplete}(window.jQuery);+function($){"use strict";var Base=$.oc.inspector.propertyEditors.popupBase,BaseProto=Base.prototype var DictionaryEditor=function(inspector,propertyDefinition,containerCell,group){this.keyValidationSet=null this.valueValidationSet=null Base.call(this,inspector,propertyDefinition,containerCell,group)} diff --git a/modules/system/assets/ui/storm.css b/modules/system/assets/ui/storm.css index f20bebe58..a90497b63 100644 --- a/modules/system/assets/ui/storm.css +++ b/modules/system/assets/ui/storm.css @@ -2460,7 +2460,7 @@ table.table.data tr.list-tree-level-10 td.list-cell-index-1{padding-left:125px} .inspector-fields td.text.active{background:#ffffff;border-left:1px solid #c8cccd} .inspector-fields td.autocomplete{padding:0} .inspector-fields td.autocomplete .autocomplete-container input[type=text]{padding:5px 12px} -.inspector-fields td.autocomplete .autocomplete-container ul.dropdown-menu{background:white;font-size:12px} +.inspector-fields td.autocomplete .autocomplete-container ul.dropdown-menu{background:white;font-size:12px;z-index:100000} .inspector-fields td.autocomplete .autocomplete-container ul.dropdown-menu li a{padding:5px 12px;white-space:normal;word-wrap:break-word} .inspector-fields td.autocomplete .autocomplete-container .loading-indicator span{margin-top:-12px;right:10px;left:auto} .inspector-fields td.trigger-cell{padding:0 !important} @@ -2538,6 +2538,8 @@ div.inspector-dictionary-container table.inspector-dictionary-table tbody tr:las .inspector-container:empty{display:none} .inspector-container .control-scrollpad{position:absolute} .inspector-field-comment:empty{display:none} +ul.autocomplete.dropdown-menu.inspector-autocomplete{background:white;font-size:12px;z-index:100000} +ul.autocomplete.dropdown-menu.inspector-autocomplete li a{padding:5px 12px;white-space:normal;word-wrap:break-word} .select2-dropdown.ocInspectorDropdown{font-size:12px;-webkit-border-radius:0 !important;-moz-border-radius:0 !important;border-radius:0 !important;border:none !important} .select2-dropdown.ocInspectorDropdown > .select2-results > .select2-results__options{font-size:12px} .select2-dropdown.ocInspectorDropdown > .select2-results > li > div{padding:5px 12px 5px} diff --git a/modules/system/assets/ui/storm.js b/modules/system/assets/ui/storm.js index 8c8ed3866..ad1dc8b12 100644 --- a/modules/system/assets/ui/storm.js +++ b/modules/system/assets/ui/storm.js @@ -68,6 +68,7 @@ =require js/inspector.editor.objectlist.js =require js/inspector.editor.object.js =require js/inspector.editor.stringlist.js +=require js/inspector.editor.stringlistautocomplete.js =require js/inspector.editor.dictionary.js =require js/inspector.editor.autocomplete.js =require js/inspector.helpers.js From 8cb5ec1c0eb3cd48f300c1edb94c503e1e483065 Mon Sep 17 00:00:00 2001 From: alekseybobkov Date: Fri, 29 Jan 2016 22:16:33 -0800 Subject: [PATCH 4/6] Fixed several minor bugs in Inspector. --- .../backend/traits/InspectableContainer.php | 18 ++++++- .../ui/js/inspector.editor.autocomplete.js | 2 +- .../assets/ui/js/inspector.editor.base.js | 12 +++++ .../assets/ui/js/inspector.editor.dropdown.js | 2 +- .../assets/ui/js/inspector.editor.set.js | 12 +++-- ...inspector.editor.stringlistautocomplete.js | 2 +- .../js/inspector.externalparametereditor.js | 20 +++++--- .../system/assets/ui/js/inspector.surface.js | 27 ++++++++-- modules/system/assets/ui/storm-min.js | 49 +++++++++++-------- 9 files changed, 103 insertions(+), 41 deletions(-) diff --git a/modules/backend/traits/InspectableContainer.php b/modules/backend/traits/InspectableContainer.php index 1376cb7e1..a3b5f5c70 100644 --- a/modules/backend/traits/InspectableContainer.php +++ b/modules/backend/traits/InspectableContainer.php @@ -33,7 +33,21 @@ trait InspectableContainer $obj = new $className(null); - $methodName = 'get'.ucfirst($property).'Options'; + // Nested properties have names like object.property. + // Convert them to Object.Property. + $propertyNameParts = explode('.', $property); + $propertyMethodName = ''; + foreach ($propertyNameParts as $part) { + $part = trim($part); + + if (!strlen($part)) { + continue; + } + + $propertyMethodName .= ucfirst($part); + } + + $methodName = 'get'.$propertyMethodName.'Options'; if (method_exists($obj, $methodName)) { $options = $obj->$methodName(); } @@ -53,4 +67,4 @@ trait InspectableContainer 'options' => $optionsArray ]; } -} +} \ No newline at end of file diff --git a/modules/system/assets/ui/js/inspector.editor.autocomplete.js b/modules/system/assets/ui/js/inspector.editor.autocomplete.js index c6a97733a..6e6215c09 100644 --- a/modules/system/assets/ui/js/inspector.editor.autocomplete.js +++ b/modules/system/assets/ui/js/inspector.editor.autocomplete.js @@ -200,7 +200,7 @@ return } - data['inspectorProperty'] = this.propertyDefinition.property + data['inspectorProperty'] = this.getPropertyPath() data['inspectorClassName'] = this.inspector.options.inspectorClass $form.request('onInspectableGetOptions', { diff --git a/modules/system/assets/ui/js/inspector.editor.base.js b/modules/system/assets/ui/js/inspector.editor.base.js index c0f0cd9f0..357404f77 100644 --- a/modules/system/assets/ui/js/inspector.editor.base.js +++ b/modules/system/assets/ui/js/inspector.editor.base.js @@ -81,6 +81,14 @@ BaseEditor.prototype.onInspectorPropertyChanged = function(property, value) { } + BaseEditor.prototype.notifyChildSurfacesPropertyChanged = function(property, value) { + if (!this.hasChildSurface()) { + return + } + + this.childInspector.notifyEditorsPropertyChanged(property, value) + } + BaseEditor.prototype.focus = function() { } @@ -92,6 +100,10 @@ return this.inspector.getRootSurface() } + BaseEditor.prototype.getPropertyPath = function() { + return this.inspector.getPropertyPath(this.propertyDefinition.property) + } + /** * 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.dropdown.js b/modules/system/assets/ui/js/inspector.editor.dropdown.js index ea666597f..09cdb29b0 100644 --- a/modules/system/assets/ui/js/inspector.editor.dropdown.js +++ b/modules/system/assets/ui/js/inspector.editor.dropdown.js @@ -323,7 +323,7 @@ this.saveDependencyValues() } - data['inspectorProperty'] = this.propertyDefinition.property + data['inspectorProperty'] = this.getPropertyPath() data['inspectorClassName'] = this.inspector.options.inspectorClass this.showLoadingIndicator() diff --git a/modules/system/assets/ui/js/inspector.editor.set.js b/modules/system/assets/ui/js/inspector.editor.set.js index 6bc6f759f..ea40eda94 100644 --- a/modules/system/assets/ui/js/inspector.editor.set.js +++ b/modules/system/assets/ui/js/inspector.editor.set.js @@ -164,7 +164,7 @@ $.oc.foundation.element.addClass(link, 'loading-indicator-container size-small') this.showLoadingIndicator() - data['inspectorProperty'] = this.propertyDefinition.property + data['inspectorProperty'] = this.getPropertyPath() data['inspectorClassName'] = this.inspector.options.inspectorClass $form.request('onInspectableGetOptions', { @@ -309,6 +309,10 @@ } SetEditor.prototype.setPropertyValue = function(checkboxValue, isChecked) { + // In this method the Set Editor mimics the Surface. + // It acts as a parent surface for the children checkboxes, + // watching changes in them and updating the link text. + var currentValue = this.getNormalizedValue() if (currentValue === undefined) { @@ -319,8 +323,10 @@ currentValue = [] } - var resultValue = [] - for (var itemValue in this.propertyDefinition.items) { + var resultValue = [], + items = this.getItemsSource() + + for (var itemValue in items) { if (itemValue !== checkboxValue) { if (currentValue.indexOf(itemValue) !== -1) { resultValue.push(itemValue) diff --git a/modules/system/assets/ui/js/inspector.editor.stringlistautocomplete.js b/modules/system/assets/ui/js/inspector.editor.stringlistautocomplete.js index d363d7a3e..0b3963a54 100644 --- a/modules/system/assets/ui/js/inspector.editor.stringlistautocomplete.js +++ b/modules/system/assets/ui/js/inspector.editor.stringlistautocomplete.js @@ -428,7 +428,7 @@ return } - data['inspectorProperty'] = this.propertyDefinition.property + data['inspectorProperty'] = this.getPropertyPath() data['inspectorClassName'] = this.inspector.options.inspectorClass $form.request('onInspectableGetOptions', { diff --git a/modules/system/assets/ui/js/inspector.externalparametereditor.js b/modules/system/assets/ui/js/inspector.externalparametereditor.js index d373a9dcb..bf4e4dfde 100644 --- a/modules/system/assets/ui/js/inspector.externalparametereditor.js +++ b/modules/system/assets/ui/js/inspector.externalparametereditor.js @@ -23,10 +23,11 @@ var Base = $.oc.foundation.base, BaseProto = Base.prototype - var ExternalParameterEditor = function(inspector, propertyDefinition, containerCell) { + var ExternalParameterEditor = function(inspector, propertyDefinition, containerCell, initialValue) { this.inspector = inspector this.propertyDefinition = propertyDefinition this.containerCell = containerCell + this.initialValue = initialValue Base.call(this) @@ -43,6 +44,7 @@ this.inspector = null this.propertyDefinition = null this.containerCell = null + this.initialValue = null BaseProto.dispose.call(this) } @@ -108,18 +110,16 @@ } ExternalParameterEditor.prototype.setInitialValue = function() { - var propertyValue = this.inspector.getPropertyValue(this.propertyDefinition.property) - - if (!propertyValue) { + if (!this.initialValue) { return } - if (typeof propertyValue !== 'string') { + if (typeof this.initialValue !== 'string') { return } var matches = [] - if (matches = propertyValue.match(/^\{\{([^\}]+)\}\}$/)) { + if (matches = this.initialValue.match(/^\{\{([^\}]+)\}\}$/)) { var value = $.trim(matches[1]) if (value.length > 0) { @@ -209,15 +209,21 @@ ExternalParameterEditor.prototype.toggleEditorVisibility = function(show) { var container = this.getContainer(), children = container.children, - height = 0 + height = 19 + + // Fixed value instead of trying to get the container cell height. + // If the editor is contained in initially hidden editor (collapsed group), + // the container cell will be unknown. 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++) { diff --git a/modules/system/assets/ui/js/inspector.surface.js b/modules/system/assets/ui/js/inspector.surface.js index f6bbee90d..07828bcae 100644 --- a/modules/system/assets/ui/js/inspector.surface.js +++ b/modules/system/assets/ui/js/inspector.surface.js @@ -235,6 +235,7 @@ // if (property.property) { row.setAttribute('data-property', property.property) + row.setAttribute('data-property-path', this.getPropertyPath(property.property)) } this.applyGroupIndexAttribute(property, row, group) @@ -364,7 +365,13 @@ var cell = row.querySelector('td'), propertyDefinition = this.findPropertyDefinition(property), - editor = new $.oc.inspector.externalParameterEditor(this, propertyDefinition, cell) + initialValue = this.getPropertyValue(property) + + if (initialValue === undefined) { + initialValue = propertyEditor.getUndefinedValue() + } + + var editor = new $.oc.inspector.externalParameterEditor(this, propertyDefinition, cell, initialValue) this.externalParameterEditors.push(editor) } @@ -536,7 +543,8 @@ this.markPropertyChanged(property, false) } - this.notifyEditorsPropertyChanged(property, value) + var propertyPath = this.getPropertyPath(property) + this.getRootSurface().notifyEditorsPropertyChanged(propertyPath, value) if (this.options.onChange !== null) { this.options.onChange(property, value) @@ -553,11 +561,19 @@ return value } - Surface.prototype.notifyEditorsPropertyChanged = function(property, value) { + Surface.prototype.notifyEditorsPropertyChanged = function(propertyPath, value) { + // Editors use this event to watch changes in properties + // they depend on. All editors should be notified, including + // editors in nested surfaces. The property name is passed as a + // path object.property (if the property is nested), so that + // property depenencies could be defined as + // ['property', 'object.property'] + for (var i = 0, len = this.editors.length; i < len; i++) { var editor = this.editors[i] - editor.onInspectorPropertyChanged(property, value) + editor.onInspectorPropertyChanged(propertyPath, value) + editor.notifyChildSurfacesPropertyChanged(propertyPath, value) } } @@ -573,7 +589,8 @@ } Surface.prototype.markPropertyChanged = function(property, changed) { - var row = this.tableContainer.querySelector('tr[data-property="'+property+'"]') + var propertyPath = this.getPropertyPath(property), + row = this.tableContainer.querySelector('tr[data-property-path="'+propertyPath+'"]') if (changed) { $.oc.foundation.element.addClass(row, 'changed') diff --git a/modules/system/assets/ui/storm-min.js b/modules/system/assets/ui/storm-min.js index a40760eed..060efc0ce 100644 --- a/modules/system/assets/ui/storm-min.js +++ b/modules/system/assets/ui/storm-min.js @@ -3416,7 +3416,8 @@ if(!this.parentSurface){this.focusFirstEditor()}} 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'),titleSpan=document.createElement('span'),description=this.buildPropertyDescription(property) -if(property.property){row.setAttribute('data-property',property.property)} +if(property.property){row.setAttribute('data-property',property.property) +row.setAttribute('data-property-path',this.getPropertyPath(property.property))} this.applyGroupIndexAttribute(property,row,group) $.oc.foundation.element.addClass(row,this.getRowCssClass(property,group)) this.applyHeadColspan(th,property) @@ -3462,7 +3463,9 @@ for(var i=0,len=rows.length;i-1} SetEditor.prototype.setPropertyValue=function(checkboxValue,isChecked){var currentValue=this.getNormalizedValue() if(currentValue===undefined){currentValue=this.propertyDefinition.default} if(!currentValue){currentValue=[]} -var resultValue=[] -for(var itemValue in this.propertyDefinition.items){if(itemValue!==checkboxValue){if(currentValue.indexOf(itemValue)!==-1){resultValue.push(itemValue)}} +var resultValue=[],items=this.getItemsSource() +for(var itemValue in items){if(itemValue!==checkboxValue){if(currentValue.indexOf(itemValue)!==-1){resultValue.push(itemValue)}} else{if(isChecked){resultValue.push(itemValue)}}} this.inspector.setPropertyValue(this.propertyDefinition.property,this.cleanUpValue(resultValue)) this.setLinkText(this.getLink())} @@ -4955,7 +4963,7 @@ return result} StringListAutocomplete.prototype.loadDynamicItems=function(){if(this.isDisposed()){return} var data=this.getRootSurface().getValues(),$form=$(this.popup).find('form') if(this.triggerGetItems(data)===false){return} -data['inspectorProperty']=this.propertyDefinition.property +data['inspectorProperty']=this.getPropertyPath() data['inspectorClassName']=this.inspector.options.inspectorClass $form.request('onInspectableGetOptions',{data:data,}).done(this.proxy(this.itemsRequestDone))} StringListAutocomplete.prototype.triggerGetItems=function(values){var $inspectable=this.getInspectableElement() @@ -5224,7 +5232,7 @@ var container=this.getContainer(),data=this.getRootSurface().getValues(),$form=$ $.oc.foundation.element.addClass(container,'loading-indicator-container size-small') this.showLoadingIndicator() if(this.triggerGetItems(data)===false){return} -data['inspectorProperty']=this.propertyDefinition.property +data['inspectorProperty']=this.getPropertyPath() data['inspectorClassName']=this.inspector.options.inspectorClass $form.request('onInspectableGetOptions',{data:data,}).done(this.proxy(this.itemsRequestDone)).always(this.proxy(this.hideLoadingIndicator))} AutocompleteEditor.prototype.triggerGetItems=function(values){var $inspectable=this.getInspectableElement() @@ -5364,9 +5372,10 @@ $.oc={} if($.oc.inspector===undefined) $.oc.inspector={} var Base=$.oc.foundation.base,BaseProto=Base.prototype -var ExternalParameterEditor=function(inspector,propertyDefinition,containerCell){this.inspector=inspector +var ExternalParameterEditor=function(inspector,propertyDefinition,containerCell,initialValue){this.inspector=inspector this.propertyDefinition=propertyDefinition this.containerCell=containerCell +this.initialValue=initialValue Base.call(this) this.init()} ExternalParameterEditor.prototype=Object.create(BaseProto) @@ -5376,6 +5385,7 @@ this.unregisterHandlers() this.inspector=null this.propertyDefinition=null this.containerCell=null +this.initialValue=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() @@ -5401,11 +5411,10 @@ 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} +ExternalParameterEditor.prototype.setInitialValue=function(){if(!this.initialValue){return} +if(typeof this.initialValue!=='string'){return} var matches=[] -if(matches=propertyValue.match(/^\{\{([^\}]+)\}\}$/)){var value=$.trim(matches[1]) +if(matches=this.initialValue.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)}}} @@ -5440,10 +5449,8 @@ 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)}} +ExternalParameterEditor.prototype.toggleEditorVisibility=function(show){var container=this.getContainer(),children=container.children,height=19 +if(!show){} for(var i=0,len=children.length;i Date: Sat, 30 Jan 2016 21:01:17 -0800 Subject: [PATCH 5/6] Minor fix in the tabs styles. --- modules/backend/assets/css/october.css | 8 ++++---- .../backend/assets/less/layout/fancylayout.less | 17 +++++++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/modules/backend/assets/css/october.css b/modules/backend/assets/css/october.css index 17d7ca8c2..99f947dce 100644 --- a/modules/backend/assets/css/october.css +++ b/modules/backend/assets/css/october.css @@ -791,16 +791,16 @@ html.csstransitions body.outer.preload .outer-form-container{-webkit-transform:s .fancy-layout .control-tabs.master-tabs.scroll-after:after,.fancy-layout.control-tabs.master-tabs.scroll-after:after{color:#ffffff} .fancy-layout .control-tabs.master-tabs > div > div.tabs-container,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container{background:#d35400;padding-left:20px;padding-right:20px} .fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs{margin-left:-8px} -.fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li{margin-left:-10px;top:1px} +.fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li{margin-left:-8px;top:1px} .fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li span.tab-close,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li span.tab-close{top:9px;right:-3px;left:auto;z-index:110} .fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li span.tab-close i,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li span.tab-close i{top:4px;right:1px;color:rgba(255,255,255,0.3) !important;font:14px bold "Helvetica Neue",Helvetica,Arial,sans-serif} .fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li span.tab-close i:hover,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li span.tab-close i:hover{color:#ffffff !important} -.fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a{border-bottom:none;background:transparent;font-size:14px;color:rgba(255,255,255,0.35);padding:6px 0 0 0;overflow:visible} +.fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a{border-bottom:none;background:transparent;font-size:14px;color:rgba(255,255,255,0.35);padding:6px 0 0 24px!important;overflow:visible} .fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a > span.title,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a > span.title{position:relative;display:inline-block;padding:8px 5px 9px 5px;height:31px;font-size:13px;z-index:100;background-color:#b9530f} .fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a > span.title:before,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a > span.title:before,.fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a > span.title:after,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a > span.title:after{content:' ';position:absolute;background:transparent url(../images/tab-shape.svg) no-repeat left -80px;width:20px;display:block;height:30px;top:0;z-index:100} .fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a > span.title:before,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a > span.title:before{left:-20px} .fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a > span.title:after,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a > span.title:after{right:-20px;background-position:-80px -80px} -.fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a:before,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a:before{z-index:110;position:relative;margin-right:-12px} +.fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a:before,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a:before{z-index:110;position:absolute;top:15px;left:22px} .fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a[class*=icon] > span.title,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li a[class*=icon] > span.title{padding-left:18px} .fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li.active a,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li.active a{z-index:107;color:#ffffff} .fancy-layout .control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li.active span.tab-close i,.fancy-layout.control-tabs.master-tabs > div > div.tabs-container > ul.nav-tabs > li.active span.tab-close i{color:#ffffff} @@ -858,7 +858,7 @@ html.csstransitions body.outer.preload .outer-form-container{-webkit-transform:s .fancy-layout .control-tabs.primary-tabs > .tab-content > .tab-pane.pane-compact,.fancy-layout.control-tabs.primary-tabs > .tab-content > .tab-pane.pane-compact{padding:0} .fancy-layout .control-tabs.primary-tabs > .tab-content > .tab-pane .form-control,.fancy-layout.control-tabs.primary-tabs > .tab-content > .tab-pane .form-control{border-width:1px} .fancy-layout .control-tabs.primary-tabs.collapsed,.fancy-layout.control-tabs.primary-tabs.collapsed{display:none} -.fancy-layout .control-tabs > div.tab-content,.fancy-layout.control-tabs > div.tab-content{background:#f9f9f9} +.fancy-layout .control-tabs.has-tabs > div.tab-content,.fancy-layout.control-tabs.has-tabs > div.tab-content{background:#f9f9f9} .fancy-layout .control-tabs > div.tab-content > div.tab-pane,.fancy-layout.control-tabs > div.tab-content > div.tab-pane{padding:0} .fancy-layout .control-tabs > div.tab-content > div.tab-pane.padded-pane,.fancy-layout.control-tabs > div.tab-content > div.tab-pane.padded-pane{padding:15px 15px 0 15px} .fancy-layout .form-tabless-fields{position:relative;background:#e67e22;padding:18px 23px 0 23px;-webkit-transition:all 0.5s;transition:all 0.5s} diff --git a/modules/backend/assets/less/layout/fancylayout.less b/modules/backend/assets/less/layout/fancylayout.less index d68220b7e..98a49abe2 100644 --- a/modules/backend/assets/less/layout/fancylayout.less +++ b/modules/backend/assets/less/layout/fancylayout.less @@ -54,7 +54,7 @@ > ul.nav-tabs { margin-left: -8px; > li { - margin-left: -10px; + margin-left: -8px; top: 1px; span.tab-close{ @@ -79,7 +79,7 @@ background: transparent; font-size: 14px; color: @color-fancy-master-tabs-inactive-text; - padding: 6px 0 0 0; + padding: 6px 0 0 24px!important; overflow: visible; > span.title { @@ -114,8 +114,9 @@ &:before { z-index: 110; - position: relative; - margin-right: -12px; + position: absolute; + top: 15px; + left: 22px; } &[class*=icon] > span.title { @@ -401,9 +402,13 @@ } } - > div.tab-content { - background: @body-bg; + &.has-tabs { + > div.tab-content { + background: @body-bg; + } + } + > div.tab-content { > div.tab-pane { padding: 0; From 241332e14a592152955c3955ff4359c44efbfe06 Mon Sep 17 00:00:00 2001 From: alekseybobkov Date: Thu, 4 Feb 2016 18:00:10 -0800 Subject: [PATCH 6/6] Minor style updates for the Inspector header area --- modules/system/assets/ui/less/inspector.less | 3 ++- modules/system/assets/ui/less/popover.less | 1 + modules/system/assets/ui/storm.css | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/system/assets/ui/less/inspector.less b/modules/system/assets/ui/less/inspector.less index 9872f0565..3dc39be97 100644 --- a/modules/system/assets/ui/less/inspector.less +++ b/modules/system/assets/ui/less/inspector.less @@ -585,7 +585,8 @@ div.inspector-dictionary-container { } } - span { + span, a { + text-decoration: none; position: absolute; top: 12px; float: none; diff --git a/modules/system/assets/ui/less/popover.less b/modules/system/assets/ui/less/popover.less index e29b81e51..30cadadd1 100644 --- a/modules/system/assets/ui/less/popover.less +++ b/modules/system/assets/ui/less/popover.less @@ -181,6 +181,7 @@ div.control-popover { float: none; color: #ffffff; cursor: pointer; + text-decoration: none; &:hover { .opacity(1); diff --git a/modules/system/assets/ui/storm.css b/modules/system/assets/ui/storm.css index a90497b63..0f2beee1c 100644 --- a/modules/system/assets/ui/storm.css +++ b/modules/system/assets/ui/storm.css @@ -1211,7 +1211,7 @@ div.control-popover .popover-head p{font-size:13px;font-weight:100;margin:10px 0 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 .popover-head .inspector-move-to-container{opacity:0.4;filter:alpha(opacity=40);position:absolute;top:12px;right:26px;float:none;color:#ffffff;cursor:pointer} +div.control-popover .popover-head .inspector-move-to-container{opacity:0.4;filter:alpha(opacity=40);position:absolute;top:12px;right:26px;float:none;color:#ffffff;cursor:pointer;text-decoration:none} div.control-popover .popover-head .inspector-move-to-container:hover{opacity:1;filter:alpha(opacity=100);color:#ffffff} div.control-popover .popover-head .inspector-move-to-container:before{-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)} 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} @@ -2531,8 +2531,8 @@ div.inspector-dictionary-container table.inspector-dictionary-table tbody tr:las .inspector-header h3{font-size:14px;font-weight:600;margin-top:0;margin-bottom:0;padding-right:15px;line-height:130%} .inspector-header p{font-size:13px;font-weight:100;margin:10px 0 0 0} .inspector-header p:empty{display:none} -.inspector-header span{position:absolute;top:12px;float:none;color:#ffffff;cursor:pointer;opacity:0.4;filter:alpha(opacity=40)} -.inspector-header span:hover{opacity:1;filter:alpha(opacity=100);color:#ffffff} +.inspector-header span,.inspector-header a{text-decoration:none;position:absolute;top:12px;float:none;color:#ffffff;cursor:pointer;opacity:0.4;filter:alpha(opacity=40)} +.inspector-header span:hover,.inspector-header a:hover{opacity:1;filter:alpha(opacity=100);color:#ffffff} .inspector-header .detach{right:26px} .inspector-header .close{right:11px;font-size:21px} .inspector-container:empty{display:none}