diff --git a/modules/system/assets/ui/js/inspector.editor.dictionary.js b/modules/system/assets/ui/js/inspector.editor.dictionary.js new file mode 100644 index 000000000..31ea21a9a --- /dev/null +++ b/modules/system/assets/ui/js/inspector.editor.dictionary.js @@ -0,0 +1,398 @@ +/* + * Inspector text editor class. + */ ++function ($) { "use strict"; + + var Base = $.oc.inspector.propertyEditors.popupBase, + BaseProto = Base.prototype + + var DictionaryEditor = function(inspector, propertyDefinition, containerCell, group) { + Base.call(this, inspector, propertyDefinition, containerCell, group) + } + + DictionaryEditor.prototype = Object.create(BaseProto) + DictionaryEditor.prototype.constructor = Base + + // + // Popup editor methods + // + + DictionaryEditor.prototype.setLinkText = function(link, value) { + var value = value !== undefined ? value + : this.inspector.getPropertyValue(this.propertyDefinition.property) + + if (value === undefined) { + value = this.propertyDefinition.default + } + + if (value === undefined || $.isEmptyObject(value)) { + var placeholder = this.propertyDefinition.placeholder + + if (placeholder !== undefined) { + $.oc.foundation.element.addClass(link, 'placeholder') + link.textContent = placeholder + } + else { + link.textContent = 'Items: 0' + } + } + else { + var itemCount = 0 + + if (typeof value !== 'object') { + throw new Error('Object list value should be an object. Property: ' + this.propertyDefinition.property) + } + + itemCount = this.getValueKeys(value).length + + $.oc.foundation.element.removeClass(link, 'placeholder') + link.textContent = 'Items: ' + itemCount + } + } + + DictionaryEditor.prototype.getPopupContent = function() { + return '
\ + \ + \ + \ +
' + } + + DictionaryEditor.prototype.configurePopup = function(popup) { + this.buildItemsTable(popup.get(0)) + + this.focusFirstInput() + } + + DictionaryEditor.prototype.handleSubmit = function($form) { + return this.applyValues() +// TODO: validate here + } + + // + // Building and row management + // + + DictionaryEditor.prototype.buildItemsTable = function(popup) { + var table = popup.querySelector('table.inspector-dictionary-table'), + tbody = document.createElement('tbody'), + items = this.inspector.getPropertyValue(this.propertyDefinition.property), + titleProperty = this.propertyDefinition.titleProperty + + 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(key, items[key]) + + tbody.appendChild(row) + } + } + + table.appendChild(tbody) + this.updateScrollpads() + } + + DictionaryEditor.prototype.buildTableRow = function(key, value) { + var row = document.createElement('tr'), + keyCell = document.createElement('td'), + valueCell = document.createElement('td') + + this.createInput(keyCell, key) + this.createInput(valueCell, value) + + row.appendChild(keyCell) + row.appendChild(valueCell) + + return row + } + + DictionaryEditor.prototype.buildEmptyRow = function() { + return this.buildTableRow(null, null) + } + + DictionaryEditor.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) + } + + DictionaryEditor.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') + } + + DictionaryEditor.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() + } + + DictionaryEditor.prototype.deleteItem = function() { + var activeRow = this.getActiveRow(), + tbody = this.getTableBody() + + if (!activeRow) { + return + } + + var nextRow = activeRow.nextElementSibling, + prevRow = activeRow.previousElementSibling + + tbody.removeChild(activeRow) + + var newSelectedRow = nextRow ? nextRow : prevRow + + if (!newSelectedRow) { + newSelectedRow = this.buildEmptyRow() + tbody.appendChild(newSelectedRow) + } + + this.focusAndMakeActive(newSelectedRow .querySelector('input')) + this.updateScrollpads() + } + + DictionaryEditor.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], + keyInput = this.getRowInputByIndex(dataRow, 0), + valueInput = this.getRowInputByIndex(dataRow, 1), + key = $.trim(keyInput.value), + value = $.trim(valueInput.value) + + if (key.length == 0 && value.length == 0) { + continue + } + + if (key.length == 0) { + $.oc.flashMsg({text: 'The key cannot be empty.', 'class': 'error', 'interval': 3}) + this.focusAndMakeActive(keyInput) + return false + } + + if (value.length == 0) { + $.oc.flashMsg({text: 'The value cannot be empty.', 'class': 'error', 'interval': 3}) + this.focusAndMakeActive(valueInput) + return false + } + + if (result[key] !== undefined) { + $.oc.flashMsg({text: 'Keys should be unique.', 'class': 'error', 'interval': 3}) + this.focusAndMakeActive(keyInput) + return false + } + + result[key] = value + } + + this.inspector.setPropertyValue(this.propertyDefinition.property, result) + this.setLinkText(link, result) + } + + // + // Helpers + // + + DictionaryEditor.prototype.getValueKeys = function(value) { + var result = [] + + for (var key in value) { + result.push(key) + } + + return result + } + + DictionaryEditor.prototype.getActiveRow = function() { + var activeCell = this.popup.querySelector('td.active') + + if (!activeCell) { + return null + } + + return activeCell.parentNode + } + + DictionaryEditor.prototype.getTableBody = function() { + return this.popup.querySelector('table.inspector-dictionary-table tbody') + } + + DictionaryEditor.prototype.updateScrollpads = function() { + $('.control-scrollpad', this.popup).scrollpad('update') + } + + DictionaryEditor.prototype.focusFirstInput = function() { + var input = this.popup.querySelector('td input') + + if (input) { + input.focus() + this.setActiveCell(input) + } + } + + DictionaryEditor.prototype.getEditorCell = function(cell) { + return cell.parentNode.parentNode // cell / div / td + } + + DictionaryEditor.prototype.getEditorRow = function(cell) { + return cell.parentNode.parentNode.parentNode // cell / div / td / tr + } + + DictionaryEditor.prototype.focusAndMakeActive = function(input) { + input.focus() + this.setActiveCell(input) + } + + DictionaryEditor.prototype.getRowInputByIndex = function(row, index) { + return row.cells[index].querySelector('input') + } + + // + // Navigation + // + + DictionaryEditor.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) + } + + DictionaryEditor.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) + } + + // + // Event handlers + // + + DictionaryEditor.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('keydown.inspector', 'td input', this.proxy(this.onKeyDown)) + popup.on('click.inspector', '[data-cmd]', this.proxy(this.onCommand)) + } + + DictionaryEditor.prototype.onPopupHidden = function(ev, link, popup) { + popup.off('.inspector', 'td input') + popup.off('.inspector', '[data-cmd]', this.proxy(this.onCommand)) + + BaseProto.onPopupHidden.call(this, ev, link, popup) + } + + DictionaryEditor.prototype.onFocus = function(ev) { + this.setActiveCell(ev.currentTarget) + } + + DictionaryEditor.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; + } + } + + 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); \ No newline at end of file diff --git a/modules/system/assets/ui/js/inspector.editor.popupbase.js b/modules/system/assets/ui/js/inspector.editor.popupbase.js index bbfcae622..c49e64031 100644 --- a/modules/system/assets/ui/js/inspector.editor.popupbase.js +++ b/modules/system/assets/ui/js/inspector.editor.popupbase.js @@ -7,6 +7,8 @@ BaseProto = Base.prototype var PopupBase = function(inspector, propertyDefinition, containerCell, group) { + this.popup = null + Base.call(this, inspector, propertyDefinition, containerCell, group) } @@ -15,6 +17,7 @@ PopupBase.prototype.dispose = function() { this.unregisterHandlers() + this.popup = null BaseProto.dispose.call(this) } @@ -105,17 +108,22 @@ PopupBase.prototype.onPopupShown = function(ev, link, popup) { $(popup).on('submit.inspector', 'form', this.proxy(this.onSubmit)) + this.popup = popup.get(0) + this.configurePopup(popup) } PopupBase.prototype.onPopupHidden = function(ev, link, popup) { - $(popup).off('.inspector', this.proxy(this.onSubmit)) + $(popup).off('.inspector', 'form', this.proxy(this.onSubmit)) + this.popup = null } PopupBase.prototype.onSubmit = function(ev) { ev.preventDefault() - this.handleSubmit($(ev.target)) + if (this.handleSubmit($(ev.target)) === false) { + return false + } this.setLinkText(this.getLink()) this.hidePopup()