diff --git a/modules/backend/assets/js/october-min.js b/modules/backend/assets/js/october-min.js index cf8fa6de7..751afbc89 100644 --- a/modules/backend/assets/js/october-min.js +++ b/modules/backend/assets/js/october-min.js @@ -1260,9 +1260,12 @@ this.shown=false this.listen()} Autocomplete.prototype={constructor:Autocomplete,select:function(){var val=this.$menu.find('.active').attr('data-value') this.$element.val(this.updater(val)).change() -return this.hide()},updater:function(item){return item},show:function(){var pos=$.extend({},this.$element.position(),{height:this.$element[0].offsetHeight}),cssOptions={top:pos.top+pos.height,left:pos.left} +return this.hide()},updater:function(item){return item},show:function(){var offset=this.options.bodyContainer?this.$element.offset():this.$element.position(),pos=$.extend({},offset,{height:this.$element[0].offsetHeight}),cssOptions={top:pos.top+pos.height,left:pos.left} if(this.options.matchWidth){cssOptions.width=this.$element[0].offsetWidth} -this.$menu.insertAfter(this.$element).css(cssOptions).show() +this.$menu.css(cssOptions) +if(this.options.bodyContainer){$(document.body).append(this.$menu)} +else{this.$menu.insertAfter(this.$element)} +this.$menu.show() this.shown=true return this},hide:function(){this.$menu.hide() this.shown=false @@ -1339,7 +1342,7 @@ var old=$.fn.autocomplete $.fn.autocomplete=function(option){return this.each(function(){var $this=$(this),data=$this.data('autocomplete'),options=typeof option=='object'&&option if(!data)$this.data('autocomplete',(data=new Autocomplete(this,options))) if(typeof option=='string')data[option]()})} -$.fn.autocomplete.defaults={source:[],items:8,menu:'',item:'
  • ',minLength:1} +$.fn.autocomplete.defaults={source:[],items:8,menu:'',item:'
  • ',minLength:1,bodyContainer:false} $.fn.autocomplete.Constructor=Autocomplete $.fn.autocomplete.noConflict=function(){$.fn.autocomplete=old return this} diff --git a/modules/backend/assets/js/october.autocomplete.js b/modules/backend/assets/js/october.autocomplete.js index ff9bd3f74..3b65797fa 100644 --- a/modules/backend/assets/js/october.autocomplete.js +++ b/modules/backend/assets/js/october.autocomplete.js @@ -51,7 +51,8 @@ }, show: function () { - var pos = $.extend({}, this.$element.position(), { + var offset = this.options.bodyContainer ? this.$element.offset() : this.$element.position(), + pos = $.extend({}, offset, { height: this.$element[0].offsetHeight }), cssOptions = { @@ -63,10 +64,16 @@ cssOptions.width = this.$element[0].offsetWidth } - this.$menu - .insertAfter(this.$element) - .css(cssOptions) - .show() + this.$menu.css(cssOptions) + + if (this.options.bodyContainer) { + $(document.body).append(this.$menu) + } + else { + this.$menu.insertAfter(this.$element) + } + + this.$menu.show() this.shown = true return this @@ -347,7 +354,8 @@ items: 8, menu: '', item: '
  • ', - minLength: 1 + minLength: 1, + bodyContainer: false } $.fn.autocomplete.Constructor = Autocomplete diff --git a/modules/backend/widgets/Table.php b/modules/backend/widgets/Table.php index 2e5d5657b..cdcdd4cf4 100644 --- a/modules/backend/widgets/Table.php +++ b/modules/backend/widgets/Table.php @@ -197,4 +197,21 @@ class Table extends WidgetBase 'options' => $options ]; } + + public function onGetAutocompleteOptions() + { + $columnName = Input::get('column'); + $rowData = Input::get('rowData'); + + $eventResults = $this->fireEvent('table.getAutocompleteOptions', [$columnName, $rowData]); + + $options = []; + if (count($eventResults)) { + $options = $eventResults[0]; + } + + return [ + 'options' => $options + ]; + } } diff --git a/modules/backend/widgets/table/README.md b/modules/backend/widgets/table/README.md index eec6d0365..477b3cf16 100644 --- a/modules/backend/widgets/table/README.md +++ b/modules/backend/widgets/table/README.md @@ -161,6 +161,24 @@ Multiple fields are allowed as well: **Note:** Dependent drop-down should always be defined after their master columns. +### Autocomplete cell processor + +The autocomplete column type can load options from the column configuration or with AJAX. Example column configuration: + + color: + title: Color + type: autocomplete + options: + red: Red + green: Green + blue: Blue + +If the `options` element is not presented in the configuration, the options will be loaded with AJAX. + +**TODO:** Document the AJAX interface + +The editor can have the `dependsOn` property similar to the drop-down editor. + # Server-side table widget (Backend\Widgets\Table) ## Configuration diff --git a/modules/backend/widgets/table/assets/css/table.css b/modules/backend/widgets/table/assets/css/table.css index 4a8a574b7..2c15a9f7e 100644 --- a/modules/backend/widgets/table/assets/css/table.css +++ b/modules/backend/widgets/table/assets/css/table.css @@ -72,16 +72,16 @@ padding: 1px; } .control-table table.data td.active { - border-color: #5fb6f5 !important; + border-color: #4da7e8 !important; } .control-table table.data td.active .content-container { padding: 0; - border: 1px solid #5fb6f5; + border: 1px solid #4da7e8; } .control-table table.data td.active .content-container:before, .control-table table.data td.active .content-container:after { content: ' '; - background: #5fb6f5; + background: #4da7e8; position: absolute; left: -2px; top: -2px; @@ -189,7 +189,7 @@ color: #95a5a6; } .control-table .pagination ul li.active { - background: #5fb6f5; + background: #4da7e8; } .control-table .pagination ul li.active a { color: #ffffff; @@ -207,9 +207,10 @@ } } /* - * String editor + * String and autocomplete editors */ -.control-table td[data-column-type=string] input[type=text] { +.control-table td[data-column-type=string] input[type=text], +.control-table td[data-column-type=autocomplete] input[type=text] { width: 100%; height: 100%; display: block; @@ -217,6 +218,18 @@ border: none; padding: 5px 10px; } +ul.table-widget-autocomplete { + background: white; + font-size: 13px; + margin-top: 0; + border: 1px solid #808c8d; + border-top: 1px solid #ecf0f1; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +ul.table-widget-autocomplete li a { + padding: 5px 10px; +} /* * Checkbox editor */ @@ -254,7 +267,7 @@ top: -4px; } .control-table td[data-column-type=checkbox] div[data-checkbox-element]:focus { - border-color: #5fb6f5; + border-color: #4da7e8; outline: none; } /* @@ -366,6 +379,6 @@ html.cssanimations .control-table td[data-column-type=dropdown] [data-view-conta .table-control-dropdown-list li:hover, .table-control-dropdown-list li:focus, .table-control-dropdown-list li.selected { - background: #5fb6f5; + background: #4da7e8; color: white; } diff --git a/modules/backend/widgets/table/assets/js/build-min.js b/modules/backend/widgets/table/assets/js/build-min.js index f6a9f321d..7d24a15cb 100644 --- a/modules/backend/widgets/table/assets/js/build-min.js +++ b/modules/backend/widgets/table/assets/js/build-min.js @@ -863,6 +863,54 @@ DropdownProcessor.prototype.elementBelongsToProcessor=function(element){if(!this return false return this.tableObj.parentContainsElement(this.itemListElement,element)} $.oc.table.processor.dropdown=DropdownProcessor;}(window.jQuery);+function($){"use strict";if($.oc.table===undefined) +throw new Error("The $.oc.table namespace is not defined. Make sure that the table.js script is loaded.");if($.oc.table.processor===undefined) +throw new Error("The $.oc.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");var Base=$.oc.table.processor.string,BaseProto=Base.prototype +var AutocompleteProcessor=function(tableObj,columnName,columnConfiguration){this.cachedOptionPromises={} +Base.call(this,tableObj,columnName,columnConfiguration)} +AutocompleteProcessor.prototype=Object.create(BaseProto) +AutocompleteProcessor.prototype.constructor=AutocompleteProcessor +AutocompleteProcessor.prototype.dispose=function(){this.cachedOptionPromises=null +BaseProto.dispose.call(this)} +AutocompleteProcessor.prototype.onUnfocus=function(){if(!this.activeCell) +return +this.removeAutocomplete() +BaseProto.onUnfocus.call(this)} +AutocompleteProcessor.prototype.renderCell=function(value,cellContentContainer){BaseProto.renderCell.call(this,value,cellContentContainer)} +AutocompleteProcessor.prototype.buildEditor=function(cellElement,cellContentContainer,isClick){BaseProto.buildEditor.call(this,cellElement,cellContentContainer,isClick) +var self=this +this.fetchOptions(cellElement,function autocompleteFetchOptions(options){self.buildAutoComplete(options) +self=null})} +AutocompleteProcessor.prototype.fetchOptions=function(cellElement,onSuccess){if(this.columnConfiguration.options){if(onSuccess!==undefined){onSuccess(this.columnConfiguration.options)}}else{if(this.triggerGetOptions(onSuccess)===false){return} +var row=cellElement.parentNode,cachingKey=this.createOptionsCachingKey(row),viewContainer=this.getViewContainer(cellElement) +$.oc.foundation.element.addClass(viewContainer,'loading') +if(!this.cachedOptionPromises[cachingKey]){var requestData={column:this.columnName,rowData:this.tableObj.getRowData(row)},handlerName=this.tableObj.getAlias()+'::onGetAutocompleteOptions' +this.cachedOptionPromises[cachingKey]=this.tableObj.$el.request(handlerName,{data:requestData})} +this.cachedOptionPromises[cachingKey].done(function onAutocompleteLoadOptionsSuccess(data){if(onSuccess!==undefined){onSuccess(data.options)}}).always(function onAutocompleteLoadOptionsAlways(){$.oc.foundation.element.removeClass(viewContainer,'loading')})}} +AutocompleteProcessor.prototype.createOptionsCachingKey=function(row){var cachingKey='non-dependent',dependsOn=this.columnConfiguration.dependsOn +if(dependsOn){if(typeof dependsOn=='object'){for(var i=0,len=dependsOn.length;i',bodyContainer:true})} +AutocompleteProcessor.prototype.prepareItems=function(items){var result={} +if($.isArray(items)){for(var i=0,len=items.length;i', + bodyContainer: true + }) + } + + AutocompleteProcessor.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 + } + + AutocompleteProcessor.prototype.removeAutocomplete = function() { + var input = this.getInput() + + $(input).autocomplete('destroy') + } + + $.oc.table.processor.autocomplete = AutocompleteProcessor; +}(window.jQuery); \ No newline at end of file diff --git a/modules/backend/widgets/table/assets/js/table.processor.dropdown.js b/modules/backend/widgets/table/assets/js/table.processor.dropdown.js index 6ad9e08d9..e0c5fb683 100644 --- a/modules/backend/widgets/table/assets/js/table.processor.dropdown.js +++ b/modules/backend/widgets/table/assets/js/table.processor.dropdown.js @@ -212,7 +212,7 @@ if (!this.cachedOptionPromises[cachingKey]) { var requestData = { - column: this.columnName, + column: this.columnName, rowData: this.tableObj.getRowData(row) }, handlerName = this.tableObj.getAlias()+'::onGetDropdownOptions' diff --git a/modules/backend/widgets/table/assets/less/table.less b/modules/backend/widgets/table/assets/less/table.less index 3e696a070..8f152a1d0 100644 --- a/modules/backend/widgets/table/assets/less/table.less +++ b/modules/backend/widgets/table/assets/less/table.less @@ -265,11 +265,12 @@ } /* - * String editor + * String and autocomplete editors */ .control-table { - td[data-column-type=string] { + td[data-column-type=string], + td[data-column-type=autocomplete] { input[type=text] { width: 100%; height: 100%; @@ -281,6 +282,19 @@ } } +ul.table-widget-autocomplete { + background: white; + font-size: 13px; + margin-top: 0; + border: 1px solid #808c8d; + border-top: 1px solid #ecf0f1; + .border-bottom-radius(4px); + + li a { + padding: 5px 10px; + } +} + /* * Checkbox editor */