1+ window . agenda_filter ; // public interface
2+ window . agenda_filter_for_testing ; // methods to be accessed for automated testing
3+
4+ // closure to create private scope
5+ ( function ( ) {
6+ 'use strict'
7+
8+ /* n.b., const refers to the opts object itself, not its contents.
9+ * Use camelCase for easy translation into element.dataset keys,
10+ * which are automatically camel-cased from the data attribute name.
11+ * (e.g., data-always-show -> elt.dataset.alwaysShow) */
12+ const opts = {
13+ alwaysShow : false ,
14+ updateCallback : null // function(filter_params)
15+ } ;
16+
17+ /* Remove from list, if present */
18+ function remove_list_item ( list , item ) {
19+ var item_index = list . indexOf ( item ) ;
20+ if ( item_index !== - 1 ) {
21+ list . splice ( item_index , 1 )
22+ }
23+ }
24+
25+ /* Add to list if not present, remove if present
26+ *
27+ * Returns true if added to the list, otherwise false.
28+ */
29+ function toggle_list_item ( list , item ) {
30+ var item_index = list . indexOf ( item ) ;
31+ if ( item_index === - 1 ) {
32+ list . push ( item )
33+ return true ;
34+ } else {
35+ list . splice ( item_index , 1 )
36+ return false ;
37+ }
38+ }
39+
40+ function parse_query_params ( qs ) {
41+ var params = { }
42+ qs = decodeURI ( qs ) . replace ( / ^ \? / , '' ) . toLowerCase ( )
43+ if ( qs ) {
44+ var param_strs = qs . split ( '&' )
45+ for ( var ii = 0 ; ii < param_strs . length ; ii ++ ) {
46+ var toks = param_strs [ ii ] . split ( '=' , 2 )
47+ params [ toks [ 0 ] ] = toks [ 1 ] || true
48+ }
49+ }
50+ return params
51+ }
52+
53+ /* filt = 'show' or 'hide' */
54+ function get_filter_from_qparams ( qparams , filt ) {
55+ if ( ! qparams [ filt ] || ( qparams [ filt ] === true ) ) {
56+ return [ ] ;
57+ }
58+ var result = [ ] ;
59+ var qp = qparams [ filt ] . split ( ',' ) ;
60+
61+ for ( var ii = 0 ; ii < qp . length ; ii ++ ) {
62+ result . push ( qp [ ii ] . trim ( ) ) ;
63+ }
64+ return result ;
65+ }
66+
67+ function get_filter_params ( qparams ) {
68+ var enabled = opts . alwaysShow || qparams . show || qparams . hide ;
69+ return {
70+ enabled : enabled ,
71+ show : get_filter_from_qparams ( qparams , 'show' ) ,
72+ hide : get_filter_from_qparams ( qparams , 'hide' )
73+ }
74+ }
75+
76+ function get_keywords ( elt ) {
77+ var keywords = $ ( elt ) . attr ( 'data-filter-keywords' ) ;
78+ if ( keywords ) {
79+ return keywords . toLowerCase ( ) . split ( ',' ) ;
80+ }
81+ return [ ] ;
82+ }
83+
84+ function get_item ( elt ) {
85+ return $ ( elt ) . attr ( 'data-filter-item' ) ;
86+ }
87+
88+ // utility method - is there a match between two lists of keywords?
89+ function keyword_match ( list1 , list2 ) {
90+ for ( var ii = 0 ; ii < list1 . length ; ii ++ ) {
91+ if ( list2 . indexOf ( list1 [ ii ] ) !== - 1 ) {
92+ return true ;
93+ }
94+ }
95+ return false ;
96+ }
97+
98+ // Find the items corresponding to a keyword
99+ function get_items_with_keyword ( keyword ) {
100+ var items = [ ] ;
101+
102+ $ ( '.view button.pickview' ) . filter ( function ( index , elt ) {
103+ return keyword_match ( get_keywords ( elt ) , [ keyword ] ) ;
104+ } ) . each ( function ( index , elt ) {
105+ items . push ( get_item ( $ ( elt ) ) ) ;
106+ } ) ;
107+ return items ;
108+ }
109+
110+ function filtering_is_enabled ( filter_params ) {
111+ return filter_params . enabled ;
112+ }
113+
114+ // Update the filter / customization UI to match the current filter parameters
115+ function update_filter_ui ( filter_params ) {
116+ var buttons = $ ( '.pickview' ) ;
117+
118+ if ( ! filtering_is_enabled ( filter_params ) ) {
119+ // Not filtering - set to default and exit
120+ buttons . removeClass ( 'active' ) ;
121+ return ;
122+ }
123+
124+ update_href_querystrings ( filter_params_as_querystring ( filter_params ) )
125+
126+ // show the customizer - it will stay visible even if filtering is disabled
127+ const customizer = $ ( '#customize' ) ;
128+ if ( customizer . hasClass ( 'collapse' ) ) {
129+ customizer . collapse ( 'show' )
130+ }
131+
132+ // Update button state to match visibility
133+ buttons . each ( function ( index , elt ) {
134+ elt = $ ( elt ) ;
135+ var keywords = get_keywords ( elt ) ;
136+ keywords . push ( get_item ( elt ) ) ; // treat item as one of its keywords
137+ var hidden = keyword_match ( filter_params . hide , keywords ) ;
138+ var shown = keyword_match ( filter_params . show , keywords ) ;
139+ if ( shown && ! hidden ) {
140+ elt . addClass ( 'active' ) ;
141+ } else {
142+ elt . removeClass ( 'active' ) ;
143+ }
144+ } ) ;
145+ }
146+
147+ /* Update state of the view to match the filters
148+ *
149+ * Calling the individual update_* functions outside of this method will likely cause
150+ * various parts of the page to get out of sync.
151+ */
152+ function update_view ( ) {
153+ var filter_params = get_filter_params ( parse_query_params ( window . location . search ) )
154+ update_filter_ui ( filter_params )
155+ if ( opts . updateCallback ) {
156+ opts . updateCallback ( filter_params )
157+ }
158+ }
159+
160+ /* Trigger an update so the user will see the page appropriate for given filter_params
161+ *
162+ * Updates the URL to match filter_params, then updates the history / display to match
163+ * (if supported) or loads the new URL.
164+ */
165+ function update_filters ( filter_params ) {
166+ var new_url = replace_querystring (
167+ window . location . href ,
168+ filter_params_as_querystring ( filter_params )
169+ )
170+ update_href_querystrings ( filter_params_as_querystring ( filter_params ) )
171+ if ( window . history && window . history . replaceState ) {
172+ // Keep current origin, replace search string, no page reload
173+ history . replaceState ( { } , document . title , new_url )
174+ update_view ( )
175+ } else {
176+ // No window.history.replaceState support, page reload required
177+ window . location = new_url
178+ }
179+ }
180+
181+ /**
182+ * Update the querystring in the href filterable agenda links
183+ */
184+ function update_href_querystrings ( querystring ) {
185+ Array . from (
186+ document . getElementsByClassName ( 'agenda-link filterable' )
187+ ) . forEach (
188+ ( elt ) => elt . href = replace_querystring ( elt . href , querystring )
189+ )
190+ }
191+
192+ function filter_params_as_querystring ( filter_params ) {
193+ var qparams = [ ]
194+ if ( filter_params . show . length > 0 ) {
195+ qparams . push ( 'show=' + filter_params . show . join ( ) )
196+ }
197+ if ( filter_params . hide . length > 0 ) {
198+ qparams . push ( 'hide=' + filter_params . hide . join ( ) )
199+ }
200+ if ( qparams . length > 0 ) {
201+ return '?' + qparams . join ( '&' )
202+ }
203+ return ''
204+ }
205+
206+ function replace_querystring ( url , new_querystring ) {
207+ return url . replace ( / ( \? .* ) ? ( # .* ) ? $ / , new_querystring + window . location . hash )
208+ }
209+
210+ /* Helper for pick group/type button handlers - toggles the appropriate parameter entry
211+ * elt - the jquery element that was clicked
212+ */
213+ function handle_pick_button ( elt ) {
214+ var fp = get_filter_params ( parse_query_params ( window . location . search ) ) ;
215+ var item = get_item ( elt ) ;
216+
217+ /* Normally toggle in and out of the 'show' list. If this item is active because
218+ * one of its keywords is active, invert the sense and toggle in and out of the
219+ * 'hide' list instead. */
220+ var inverted = keyword_match ( fp . show , get_keywords ( elt ) ) ;
221+ var just_showed_item = false ;
222+ if ( inverted ) {
223+ toggle_list_item ( fp . hide , item ) ;
224+ remove_list_item ( fp . show , item ) ;
225+ } else {
226+ just_showed_item = toggle_list_item ( fp . show , item ) ;
227+ remove_list_item ( fp . hide , item ) ;
228+ }
229+
230+ /* If we just showed an item, remove its children from the
231+ * show/hide lists to keep things consistent. This way, selecting
232+ * an area will enable all items in the row as one would expect. */
233+ if ( just_showed_item ) {
234+ var children = get_items_with_keyword ( item ) ;
235+ $ . each ( children , function ( index , child ) {
236+ remove_list_item ( fp . show , child ) ;
237+ remove_list_item ( fp . hide , child ) ;
238+ } ) ;
239+ }
240+
241+ // If the show list is empty, clear the hide list because there is nothing to hide
242+ if ( fp . show . length === 0 ) {
243+ fp . hide = [ ] ;
244+ }
245+
246+ return fp ;
247+ }
248+
249+ function is_disabled ( elt ) {
250+ return elt . hasClass ( 'disabled' ) ;
251+ }
252+
253+ function register_handlers ( ) {
254+ $ ( '.pickview' ) . on ( "click" , function ( ) {
255+ if ( is_disabled ( $ ( this ) ) ) { return ; }
256+ var fp = handle_pick_button ( $ ( this ) ) ;
257+ update_filters ( fp ) ;
258+ } ) ;
259+ }
260+
261+ /**
262+ * Read options from the template
263+ */
264+ function read_template_options ( ) {
265+ const opts_elt = document . getElementById ( 'agenda-filter-options' ) ;
266+ opts . keys ( ) . forEach ( ( opt ) => {
267+ if ( opt in opts_elt . dataset ) {
268+ opts [ opt ] = opts_elt . dataset [ opt ] ;
269+ }
270+ } ) ;
271+ }
272+
273+ /* Entry point to filtering code when page loads
274+ *
275+ * This must be called if you are using the HTML template to provide a customization
276+ * button UI. Do not call if you only want to use the parameter parsing routines.
277+ */
278+ function enable ( ) {
279+ // ready handler fires immediately if document is already "ready"
280+ $ ( document ) . ready ( function ( ) {
281+ register_handlers ( ) ;
282+ update_view ( ) ;
283+ } )
284+ }
285+
286+ // utility method - filter a jquery set to those matching a keyword
287+ function rows_matching_filter_keyword ( rows , kw ) {
288+ return rows . filter ( function ( index , element ) {
289+ var row_kws = get_keywords ( element ) ;
290+ return keyword_match ( row_kws , [ kw . toLowerCase ( ) ] ) ;
291+ } ) ;
292+ }
293+
294+ // Make private functions available for unit testing
295+ agenda_filter_for_testing = {
296+ parse_query_params : parse_query_params ,
297+ toggle_list_item : toggle_list_item
298+ } ;
299+
300+ // Make public interface methods accessible
301+ agenda_filter = {
302+ enable : enable ,
303+ filtering_is_enabled : filtering_is_enabled ,
304+ get_filter_params : get_filter_params ,
305+ keyword_match : keyword_match ,
306+ parse_query_params : parse_query_params ,
307+ rows_matching_filter_keyword : rows_matching_filter_keyword ,
308+ set_update_callback : function ( cb ) { opts . updateCallback = cb }
309+ } ;
310+ } ) ( ) ;
0 commit comments