2 ( function ( jQuery
, global
) {
7 "selector": "select[data-filterable]",
8 "host": window
.location
.protocol
+ "//" + window
.location
.host
,
10 "datasource": function ( select
) {
11 var type
= select
.attr( 'data-type' );
12 return settings
.host
+ '/api/status/' + type
15 "collector": function ( select
, data
) {
17 for ( var i
= 0; i
< data
.length
; i
++ )
18 names
.push( data
[ i
].name
);
19 select
.filterable( names
);
23 data
: { 'format': 'json', csrf_token
: _csrf_token
, 'auth_method': 'ninja' },
25 error: function( xhr
) {
26 console
.log( xhr
.responseText
);
32 var getBoxing = function ( filtered
, multi
) {
35 return $( '<div class="jq-filterable-box">' ).append(
36 $( '<div class="jq-filterable-left">' ).append(
37 $( '<input type="text" class="jq-filterable-filter jq-filterable-larger" placeholder="Search...">' ),
38 $( '<input type="button" value="➤" class="jq-filterable-move" title="Use objects matching search">' ),
41 .addClass( "jq-filterable-list" ),
43 $( '<div class="jq-filterable-stats jq-filterable-larger">' )
44 ),$( '<div class="jq-filterable-right">' ).append(
45 $( '<select multiple class="jq-filterable-results">' ),
47 $( '<div class="jq-filterable-result-stats jq-filterable-larger">' ).append( "No items selected..." )
51 return $( '<div class="jq-filterable-box">' ).append(
52 $( '<div class="jq-filterable-left">' ).append(
53 $( '<input type="text" class="jq-filterable-filter" placeholder="Search...">' ),
55 .addClass( "jq-filterable-list" ),
57 $( '<div class="jq-filterable-stats jq-filterable-largest">' )
64 var Filterable
= function Filterable ( filtered
, data
) {
68 if ( filtered
.find( 'option' ).length
> 0 ) {
69 filtered
.find( 'option' ).each( function ( i
, e
) {
70 defaults
.push( e
.value
);
78 if ( filtered
.attr( "multiple" ) ) {
79 this.box
= getBoxing( filtered
, true );
82 this.box
= getBoxing( filtered
, false );
83 this.multiple
= false;
86 if (!this.multiple
&& !filtered
.attr('data-required')) {
90 this.results
= new Set();
91 this.memory
= new Set();
92 this.data
= new Set( data
);
94 this.filter
= this.box
.find( ".jq-filterable-filter" );
95 this.filtered
= this.box
.find( ".jq-filterable-list" );
97 this.statusbar
= this.box
.find( '.jq-filterable-stats' );
98 this.selected
= this.box
.find( '.jq-filterable-results' );
99 this.resultstats
= this.box
.find( '.jq-filterable-result-stats' );
100 this.mover
= this.box
.find( '.jq-filterable-move' );
102 this.form
= filtered
.closest("form");
103 this.form
.on( "submit", function ( e
) {
104 self
.selected
.find( "option" ).attr( "selected", true );
107 if ( this.multiple
) {
109 this.selected
.attr( "id", this.filtered
.attr( "id" ) );
110 this.selected
.attr( "name", this.filtered
.attr( "name" ) );
112 this.filtered
.attr("id", this.selected
.attr("id").replace('[', '_tmp['));
113 this.filtered
.removeAttr( "name" );
117 filtered
.replaceWith( this.box
);
119 var _default
= filtered
.find( ':selected' );
121 if ( _default
.length
> 0 ) {
122 this.search( this.filter
.val(), _default
.val() );
123 if ( this.multiple
&& defaults
.length
> 0 ) {
124 defaults
= new Set( defaults
);
125 this.add( defaults
);
128 this.search( this.filter
.val() );
129 if ( defaults
.length
> 0 ) {
130 defaults
= new Set( defaults
);
131 this.add( defaults
);
135 // Add relevant events
137 var key_timeout
= null;
138 this.box
.on( "keyup", ".jq-filterable-filter", function ( e
) {
140 if ( $.inArray( e
.which
, [ 37, 38, 39, 40 ] ) >= 0 ) return;
141 else if ( e
.which
== 13 ) {
143 clearTimeout( key_timeout
);
144 self
.search( self
.filter
.val() );
148 clearTimeout( key_timeout
);
149 key_timeout
= setTimeout( function () {
150 self
.search( self
.filter
.val() );
157 this.box
.on('click', '.deselect_all', function( e
) {
162 if ( this.multiple
) {
164 this.box
.on( "change", ".jq-filterable-list, .jq-filterable-results", function ( e
) {
166 var parent
= $( e
.target
),
169 if ( parent
.is( "option" ) ) {
170 parent
= parent
.closest( 'select' );
173 values
= parent
.val();
174 values
= new Set( values
);
176 if ( parent
[0] == self
.selected
[0] )
177 self
.remove( values
);
178 else self
.add( values
);
182 this.mover
.on( "click", function () {
184 var values
= self
.search( self
.filter
.val(), null, true );
193 Filterable
.prototype.batcher
= function batcher ( set ) {
195 var iterator
= new SetIterator( set ),
198 this.selected
.empty();
202 var fragment
= document
.createDocumentFragment(),
207 while ( index
= iterator
.next() ) {
209 opt
= document
.createElement( 'option' );
210 opt
.innerHTML
= index
;
213 fragment
.appendChild( opt
);
216 if ( counter
> 1000 ) break;
220 self
.selected
.append( fragment
);
221 return ( counter
< 1000 );
227 Filterable
.prototype.add
= function add ( set ) {
232 this.memory
= set.union( this.memory
);
234 this.filtered
.attr( 'disabled', 'disabled' );
235 this.box
.addClass( 'jq-filterable-working' );
237 this.form
.find( 'input[type="submit"]' )
238 .attr( 'disabled', 'disabled' );
240 var batch
= this.batcher( this.memory
),
242 interval
= setInterval( function () {
247 clearInterval( interval
);
249 self
.filtered
.attr( 'disabled', false );
250 self
.form
.find( 'input[type="submit"]' )
251 .attr( 'disabled', false );
253 self
.box
.removeClass( 'jq-filterable-working' );
254 self
.search( self
.filter
.val() );
261 Filterable
.prototype.remove
= function remove ( set ) {
263 var iterator
= new SetIterator( set ),
264 index
= null, i
= null;
266 while ( index
= iterator
.next() ) {
267 i
= this.memory
.find( index
);
268 if ( i
>= 0 ) this.memory
.remove( i
);
269 this.selected
.find( 'option[value="' + index
+ '"]' ).remove();
272 this.search( this.filter
.val() );
276 Filterable
.prototype.note
= function note ( message
, type
) {
278 this.statusbar
.html( message
);
280 if ( type
&& type
== "error" )
281 this.statusbar
.attr( "data-state", "error" );
282 else if ( type
&& type
== "warning" )
283 this.statusbar
.attr( "data-state", "warning" );
284 else this.statusbar
.attr( "data-state", "info" );
288 Filterable
.prototype.error
= function error ( message
) {
290 this.filter
.css( { "border-color": "#f40" } );
291 this.note( message
, "error" );
295 Filterable
.prototype.reset
= function reset () {
298 this.selected
.empty();
299 this.search( this.filter
.val() );
303 Filterable
.prototype.update_labels
= function update_labels ( ) {
305 if ( this.matching
>= settings
.limit
) {
306 this.note( "Not all items shown; " + this.matching
+ "/" + this.data
.size() );
308 this.note( this.matching
+ " Items" );
311 // Fixes IE 9 error with dynamic options
312 this.selected
.css( 'width', '0px' );
313 this.selected
.css( 'width', '' );
315 if( this.memory
.size() > 0 ) {
316 this.resultstats
.html( this.memory
.size() + " items selected. <a href='#' class='deselect_all'>Deselect all</a>" );
318 this.resultstats
.text( "No items selected..." );
323 /** method search ( string term )
325 * Searches the data array for regexp matches
326 * against term, then runs method populate.
329 * @param boolean respond
332 Filterable
.prototype.search
= function search ( term
, source
, respond
) {
335 this.results
= new Set();
338 term
= new RegExp( term
, "i" );
340 this.error( "Invalid search ( " + e
.message
+ " ) " );
344 var iterator
= new SetIterator( this.data
),
347 while ( ( index
= iterator
.next() ) != null ) {
348 if ( index
.match( term
) )
349 this.results
.push( index
);
353 this.results
.reset();
355 this.results
= this.results
.diff( this.memory
);
356 this.matching
= this.results
.size();
359 this.results
.reset();
362 this.results
.shrink( 0, settings
.limit
);
363 this.populate( source
);
368 /** method populate ( string array data )
370 * Searches the data array for regexp matches
371 * against term, then runs method populate.
376 Filterable
.prototype.populate
= function populate ( source
) {
378 var fragment
= document
.createDocumentFragment(),
383 iterator
= new SetIterator( this.results
);
385 while ( ( index
= iterator
.next() ) != null ) {
387 opt
= document
.createElement( 'option' );
388 opt
.innerHTML
= index
;
391 fragment
.appendChild( opt
);
395 this.filtered
.empty();
396 this.filtered
.append( fragment
);
397 this.update_labels();
400 this.filtered
.val( source
);
403 if ( this.multiple
) {
404 this.filtered
.val([]);
409 var Filterables
= [];
410 var FilterableFactory
= function FilterableFactory ( data
) {
412 var F
= ( new Filterable( this, data
) );
413 Filterables
.push( F
);
418 jQuery
.fn
.filterable
= FilterableFactory
;
419 jQuery
.fn
.filterable
.find = function ( element
) {
421 for ( var i
= 0; i
< Filterables
.length
; i
++ ) {
422 if ( Filterables
[ i
].selected
[ 0 ] == element
[ 0 ] ) {
423 return Filterables
[ i
];
431 function selectload ( index
, element
) {
433 var select
= $( element
);
435 if ( select
.attr( 'data-type' ) ) {
437 settings
.ajax
.success = function ( data
) {
438 settings
.collector( select
, data
);
441 settings
.ajax
.url
= settings
.datasource( select
);
442 $.ajax( settings
.ajax
);
444 } else if (select
.length
) {
446 var options
= $.map( select
.children(), function( option
) {
450 select
.children().each( function() {
451 if (!$(this).attr('selected')) {
452 select
.removeOption(this.text
);
456 select
.filterable( options
);
462 $( document
).ready( function () {
463 var selects
= $( settings
.selector
);
464 selects
.each( selectload
);
467 } ) ( jQuery
, window
);