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
);