mirror of
https://github.com/wintercms/winter.git
synced 2024-06-28 05:33:29 +02:00
Added autocomplete column type to the Table widget
This commit is contained in:
parent
e7a51a75f4
commit
0e2e18af7f
9
modules/backend/assets/js/october-min.js
vendored
9
modules/backend/assets/js/october-min.js
vendored
@ -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:'<ul class="autocomplete dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1}
|
||||
$.fn.autocomplete.defaults={source:[],items:8,menu:'<ul class="autocomplete dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1,bodyContainer:false}
|
||||
$.fn.autocomplete.Constructor=Autocomplete
|
||||
$.fn.autocomplete.noConflict=function(){$.fn.autocomplete=old
|
||||
return this}
|
||||
|
@ -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: '<ul class="autocomplete dropdown-menu"></ul>',
|
||||
item: '<li><a href="#"></a></li>',
|
||||
minLength: 1
|
||||
minLength: 1,
|
||||
bodyContainer: false
|
||||
}
|
||||
|
||||
$.fn.autocomplete.Constructor = Autocomplete
|
||||
|
@ -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
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<len;i++)
|
||||
cachingKey+=dependsOn[i]+this.tableObj.getRowCellValueByColumnName(row,dependsOn[i])}else
|
||||
cachingKey=dependsOn+this.tableObj.getRowCellValueByColumnName(row,dependsOn)}
|
||||
return cachingKey}
|
||||
AutocompleteProcessor.prototype.triggerGetOptions=function(callback){var tableElement=this.tableObj.getElement()
|
||||
if(!tableElement){return}
|
||||
var optionsEvent=$.Event('autocompleteitems.oc.table'),values={}
|
||||
$(tableElement).trigger(optionsEvent,[{values:values,callback:callback,column:this.columnName,columnConfiguration:this.columnConfiguration}])
|
||||
if(optionsEvent.isDefaultPrevented()){return false}
|
||||
return true}
|
||||
AutocompleteProcessor.prototype.getInput=function(){if(!this.activeCell){return null}
|
||||
return this.activeCell.querySelector('.string-input')}
|
||||
AutocompleteProcessor.prototype.buildAutoComplete=function(items){if(!this.activeCell){return}
|
||||
var input=this.getInput()
|
||||
if(!input){return}
|
||||
if(items===undefined){items=[]}
|
||||
$(input).autocomplete({source:this.prepareItems(items),matchWidth:true,menu:'<ul class="autocomplete dropdown-menu table-widget-autocomplete"></ul>',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);+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.validator===undefined)
|
||||
$.oc.table.validator={}
|
||||
var Base=function(options){this.options=options}
|
||||
|
@ -15,6 +15,7 @@
|
||||
=require table.processor.string.js
|
||||
=require table.processor.checkbox.js
|
||||
=require table.processor.dropdown.js
|
||||
=require table.processor.autocomplete.js
|
||||
=require table.validator.base.js
|
||||
=require table.validator.required.js
|
||||
=require table.validator.basenumber.js
|
||||
|
@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Autocomplete cell processor for the table control.
|
||||
*/
|
||||
|
||||
+function ($) { "use strict";
|
||||
|
||||
// NAMESPACE CHECK
|
||||
// ============================
|
||||
|
||||
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.");
|
||||
|
||||
// CLASS DEFINITION
|
||||
// ============================
|
||||
|
||||
var Base = $.oc.table.processor.string,
|
||||
BaseProto = Base.prototype
|
||||
|
||||
var AutocompleteProcessor = function(tableObj, columnName, columnConfiguration) {
|
||||
//
|
||||
// State properties
|
||||
//
|
||||
|
||||
this.cachedOptionPromises = {}
|
||||
|
||||
//
|
||||
// Parent constructor
|
||||
//
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
/*
|
||||
* Forces the processor to hide the editor when the user navigates
|
||||
* away from the cell. Processors can update the sell value in this method.
|
||||
* Processors must clear the reference to the active cell in this method.
|
||||
*/
|
||||
AutocompleteProcessor.prototype.onUnfocus = function() {
|
||||
if (!this.activeCell)
|
||||
return
|
||||
|
||||
this.removeAutocomplete()
|
||||
|
||||
BaseProto.onUnfocus.call(this)
|
||||
}
|
||||
|
||||
/*
|
||||
* Renders the cell in the normal (no edit) mode
|
||||
*/
|
||||
AutocompleteProcessor.prototype.renderCell = function(value, cellContentContainer) {
|
||||
BaseProto.renderCell.call(this, value, cellContentContainer)
|
||||
|
||||
// this.fetchOptions(cellContentContainer.parentNode)
|
||||
}
|
||||
|
||||
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 options are not provided and not found in the cache,
|
||||
// request them from the server. For dependent autocomplete editors
|
||||
// the caching key contains the master column values.
|
||||
|
||||
if (this.triggerGetOptions(onSuccess) === false) {
|
||||
return
|
||||
}
|
||||
|
||||
var row = cellElement.parentNode,
|
||||
cachingKey = this.createOptionsCachingKey(row),
|
||||
viewContainer = this.getViewContainer(cellElement)
|
||||
|
||||
// Request options from the server. When the table widget builds,
|
||||
// multiple cells in the column could require loading the options.
|
||||
// The AJAX promises are cached here so that we have a single
|
||||
// request per caching key.
|
||||
|
||||
$.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 < len; i++ )
|
||||
cachingKey += dependsOn[i] + this.tableObj.getRowCellValueByColumnName(row, dependsOn[i])
|
||||
} else
|
||||
cachingKey = dependsOn + this.tableObj.getRowCellValueByColumnName(row, dependsOn)
|
||||
}
|
||||
|
||||
return cachingKey
|
||||
}
|
||||
|
||||
AutocompleteProcessor.prototype.triggerGetOptions = function(callback) {
|
||||
var tableElement = this.tableObj.getElement()
|
||||
if (!tableElement) {
|
||||
return
|
||||
}
|
||||
|
||||
var optionsEvent = $.Event('autocompleteitems.oc.table'),
|
||||
values = {} // TODO - implement loading values from the current row.
|
||||
|
||||
$(tableElement).trigger(optionsEvent, [{
|
||||
values: values,
|
||||
callback: callback,
|
||||
column: this.columnName,
|
||||
columnConfiguration: this.columnConfiguration
|
||||
}])
|
||||
|
||||
if (optionsEvent.isDefaultPrevented()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
AutocompleteProcessor.prototype.getInput = function() {
|
||||
if (!this.activeCell) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.activeCell.querySelector('.string-input')
|
||||
}
|
||||
|
||||
AutocompleteProcessor.prototype.buildAutoComplete = function(items) {
|
||||
if (!this.activeCell) {
|
||||
return
|
||||
}
|
||||
|
||||
var input = this.getInput()
|
||||
if (!input) {
|
||||
return
|
||||
}
|
||||
|
||||
if (items === undefined) {
|
||||
items = []
|
||||
}
|
||||
|
||||
$(input).autocomplete({
|
||||
source: this.prepareItems(items),
|
||||
matchWidth: true,
|
||||
menu: '<ul class="autocomplete dropdown-menu table-widget-autocomplete"></ul>',
|
||||
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);
|
@ -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'
|
||||
|
@ -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
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user