2 ( function ( jQuery, global ) {
7 "selector": "select[data-filterable]",
8 "host": window.location.protocol + "//" + window.location.host,
10 "datasource": function ( select ) {
12 var type = select.attr( 'data-type' ),
13 root = settings.host + _site_domain + _index_page;
15 return root + '/listview/fetch_ajax?query=[' + type + 's] all&columns[]=key&limit=1000000';
19 "collector": function ( select, data ) {
22 for ( var i = 0; i < data.data.length; i++ ) {
23 names.push( data.data[ i ].key );
25 select.filterable( names );
31 error: function( xhr ) {
32 console.log( xhr.responseText );
38 var getBoxing = function ( filtered, multi ) {
41 return $( '<div class="jq-filterable-box">' ).append(
42 $( '<div class="jq-filterable-left">' ).append(
43 $( '<input type="text" class="jq-filterable-filter jq-filterable-larger" placeholder="Search...">' ),
44 $( '<input type="button" value="➤" class="jq-filterable-move" title="Use objects matching search">' ),
47 .addClass( "jq-filterable-list" ),
49 $( '<div class="jq-filterable-stats jq-filterable-larger">' )
50 ),$( '<div class="jq-filterable-right">' ).append(
51 $( '<select multiple class="jq-filterable-results">' ),
53 $( '<div class="jq-filterable-result-stats jq-filterable-larger">' ).append( "No items selected..." )
57 return $( '<div class="jq-filterable-box">' ).append(
58 $( '<div class="jq-filterable-left">' ).append(
59 $( '<input type="text" class="jq-filterable-filter" placeholder="Search...">' ),
61 .addClass( "jq-filterable-list" ),
63 $( '<div class="jq-filterable-stats jq-filterable-largest">' )
70 var Filterable = function Filterable ( filtered, data ) {
74 if ( filtered.find( 'option' ).length > 0 ) {
75 filtered.find( 'option' ).each( function ( i, e ) {
76 defaults.push( e.value );
84 if ( filtered.attr( "multiple" ) ) {
85 this.box = getBoxing( filtered, true );
88 this.box = getBoxing( filtered, false );
89 this.multiple = false;
92 if (!this.multiple && !filtered.attr('data-required')) {
96 this.results = new Set();
97 this.memory = new Set();
98 this.data = new Set( data );
100 this.filter = this.box.find( ".jq-filterable-filter" );
101 this.filtered = this.box.find( ".jq-filterable-list" );
103 this.statusbar = this.box.find( '.jq-filterable-stats' );
104 this.selected = this.box.find( '.jq-filterable-results' );
105 this.resultstats = this.box.find( '.jq-filterable-result-stats' );
106 this.mover = this.box.find( '.jq-filterable-move' );
108 this.form = filtered.closest("form");
109 this.form.on( "submit", function ( e ) {
110 self.selected.find( "option" ).attr( "selected", true );
113 if ( this.multiple ) {
115 this.selected.attr( "id", this.filtered.attr( "id" ) );
116 this.selected.attr( "name", this.filtered.attr( "name" ) );
118 this.filtered.attr("id", this.selected.attr("id").replace('[', '_tmp['));
119 this.filtered.removeAttr( "name" );
123 filtered.replaceWith( this.box );
125 var _default = filtered.find( ':selected' );
127 if ( _default.length > 0 ) {
128 this.search( this.filter.val(), _default.val() );
129 if ( this.multiple && defaults.length > 0 ) {
130 defaults = new Set( defaults );
131 this.add( defaults );
134 this.search( this.filter.val() );
135 if ( defaults.length > 0 ) {
136 defaults = new Set( defaults );
137 this.add( defaults );
141 // Add relevant events
143 var key_timeout = null;
144 this.box.on( "keyup", ".jq-filterable-filter", function ( e ) {
146 if ( $.inArray( e.which, [ 37, 38, 39, 40 ] ) >= 0 ) return;
147 else if ( e.which == 13 ) {
149 clearTimeout( key_timeout );
150 self.search( self.filter.val() );
154 clearTimeout( key_timeout );
155 key_timeout = setTimeout( function () {
156 self.search( self.filter.val() );
163 this.box.on('click', '.deselect_all', function( e ) {
168 if ( this.multiple ) {
170 this.box.on( "change", ".jq-filterable-list, .jq-filterable-results", function ( e ) {
172 var parent = $( e.target ),
175 if ( parent.is( "option" ) ) {
176 parent = parent.closest( 'select' );
179 values = parent.val();
180 values = new Set( values );
182 if ( parent[0] == self.selected[0] )
183 self.remove( values );
184 else self.add( values );
188 this.mover.on( "click", function () {
190 var values = self.search( self.filter.val(), null, true );
199 Filterable.prototype.batcher = function batcher ( set ) {
201 var iterator = new SetIterator( set ),
204 this.selected.empty();
208 var fragment = document.createDocumentFragment(),
213 while ( index = iterator.next() ) {
215 opt = document.createElement( 'option' );
216 opt.innerHTML = index;
219 fragment.appendChild( opt );
222 if ( counter > 1000 ) break;
226 self.selected.append( fragment );
227 return ( counter < 1000 );
233 Filterable.prototype.add = function add ( set ) {
238 this.memory = set.union( this.memory );
240 this.filtered.attr( 'disabled', 'disabled' );
241 this.box.addClass( 'jq-filterable-working' );
243 this.form.find( 'input[type="submit"]' )
244 .attr( 'disabled', 'disabled' );
246 var batch = this.batcher( this.memory ),
248 interval = setInterval( function () {
253 clearInterval( interval );
255 self.filtered.attr( 'disabled', false );
256 self.form.find( 'input[type="submit"]' )
257 .attr( 'disabled', false );
259 self.box.removeClass( 'jq-filterable-working' );
260 self.search( self.filter.val() );
267 Filterable.prototype.remove = function remove ( set ) {
269 var iterator = new SetIterator( set ),
270 index = null, i = null;
272 while ( index = iterator.next() ) {
273 i = this.memory.find( index );
274 if ( i >= 0 ) this.memory.remove( i );
275 this.selected.find( 'option[value="' + index + '"]' ).remove();
278 this.search( this.filter.val() );
282 Filterable.prototype.note = function note ( message, type ) {
284 this.statusbar.html( message );
286 if ( type && type == "error" )
287 this.statusbar.attr( "data-state", "error" );
288 else if ( type && type == "warning" )
289 this.statusbar.attr( "data-state", "warning" );
290 else this.statusbar.attr( "data-state", "info" );
294 Filterable.prototype.error = function error ( message ) {
296 this.filter.css( { "border-color": "#f40" } );
297 this.note( message, "error" );
301 Filterable.prototype.reset = function reset () {
304 this.selected.empty();
305 this.search( this.filter.val() );
309 Filterable.prototype.update_labels = function update_labels ( ) {
311 if ( this.matching >= settings.limit ) {
312 this.note( "Not all items shown; " + this.matching + "/" + this.data.size() );
314 this.note( this.matching + " Items" );
317 // Fixes IE 9 error with dynamic options
318 this.selected.css( 'width', '0px' );
319 this.selected.css( 'width', '' );
321 if( this.memory.size() > 0 ) {
322 this.resultstats.html( this.memory.size() + " items selected. <a href='#' class='deselect_all'>Deselect all</a>" );
324 this.resultstats.text( "No items selected..." );
329 /** method search ( string term )
331 * Searches the data array for regexp matches
332 * against term, then runs method populate.
335 * @param boolean respond
338 Filterable.prototype.search = function search ( term, source, respond ) {
341 this.results = new Set();
344 term = new RegExp( term, "i" );
346 this.error( "Invalid search ( " + e.message + " ) " );
350 var iterator = new SetIterator( this.data ),
353 while ( ( index = iterator.next() ) != null ) {
354 if ( index.match( term ) )
355 this.results.push( index );
359 this.results.reset();
361 this.results = this.results.diff( this.memory );
362 this.matching = this.results.size();
365 this.results.reset();
368 this.results.shrink( 0, settings.limit );
369 this.populate( source );
374 /** method populate ( string array data )
376 * Searches the data array for regexp matches
377 * against term, then runs method populate.
382 Filterable.prototype.populate = function populate ( source ) {
384 var fragment = document.createDocumentFragment(),
389 iterator = new SetIterator( this.results );
391 while ( ( index = iterator.next() ) != null ) {
393 opt = document.createElement( 'option' );
394 opt.innerHTML = index;
397 fragment.appendChild( opt );
401 this.filtered.empty();
402 this.filtered.append( fragment );
403 this.update_labels();
406 this.filtered.val( source );
409 if ( this.multiple ) {
410 this.filtered.val([]);
415 var Filterables = [];
416 var FilterableFactory = function FilterableFactory ( data ) {
418 var F = ( new Filterable( this, data ) );
419 Filterables.push( F );
424 jQuery.filterable_settings = function ( key, value ) {
425 if ( settings[ key ] ) {
426 settings[ key ] = value;
430 jQuery.fn.filterable = FilterableFactory;
431 jQuery.fn.filterable.find = function ( element ) {
433 for ( var i = 0; i < Filterables.length; i++ ) {
434 if ( Filterables[ i ].selected[ 0 ] == element[ 0 ] ) {
435 return Filterables[ i ];
443 function selectload ( index, element ) {
445 var select = $( element );
447 if ( select.attr( 'data-type' ) ) {
449 settings.ajax.success = function ( data ) {
450 settings.collector( select, data );
453 settings.ajax.url = settings.datasource( select );
454 $.ajax( settings.ajax );
456 } else if (select.length) {
458 var options = $.map( select.children(), function( option ) {
462 select.children().each( function() {
463 if (!$(this).attr('selected')) {
464 select.removeOption(this.text);
468 select.filterable( options );
474 $( document ).ready( function () {
475 var selects = $( settings.selector );
476 selects.each( selectload );
479 } ) ( jQuery, window );