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