Skip to content

Commit be3a8f7

Browse files
committed
Add the scripts and dependencies for the agenda page back, and make them work.
- Legacy-Id: 19652
1 parent 273feec commit be3a8f7

13 files changed

Lines changed: 768 additions & 26 deletions

ietf/static/js/agenda_filter.js

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
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+
})();

ietf/static/js/agenda_materials.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright The IETF Trust 2021, All Rights Reserved
2+
3+
/*
4+
Javascript support for the materials modal rendered by session_agenda_include.html
5+
6+
Requires jquery be loaded
7+
*/
8+
9+
var agenda_materials; // public interface
10+
11+
(function() {
12+
'use strict';
13+
/**
14+
* Retrieve and display materials for a session
15+
*
16+
* If output_elt exists and has a "data-src" attribute, retrieves the document
17+
* from that URL and displays under output_elt. Handles text/plain, text/markdown,
18+
* and text/html.
19+
*
20+
* @param output_elt Element, probably a div, to hold the output
21+
*/
22+
function retrieve_session_materials(output_elt) {
23+
if (!output_elt) {return;}
24+
output_elt = $(output_elt);
25+
var data_src = output_elt.attr("data-src");
26+
if (!data_src) {
27+
output_elt.html("<p>Error: missing data-src attribute</p>");
28+
} else {
29+
output_elt.html("<p>Loading " + data_src + "...</p>");
30+
var outer_xhr = $.ajax({url:data_src,headers:{'Accept':'text/plain;q=0.8,text/html;q=0.9'}})
31+
outer_xhr.done(function(data, status, xhr) {
32+
var t = xhr.getResponseHeader("content-type");
33+
if (!t) {
34+
data = "<p>Error retrieving " + data_src
35+
+ ": Missing content-type in response header</p>";
36+
} else if (t.indexOf("text/plain") > -1) {
37+
data = "<pre class='agenda'>" + data + "</pre>";
38+
} else if (t.indexOf("text/markdown") > -1) {
39+
data = "<pre class='agenda'>" + data + "</pre>";
40+
} else if(t.indexOf("text/html") > -1) {
41+
// nothing to do here
42+
} else {
43+
data = "<p>Unknown type: " + xhr.getResponseHeader("content-type") + "</p>";
44+
}
45+
output_elt.html(data);
46+
}).fail(function() {
47+
output_elt.html("<p>Error retrieving " + data_src
48+
+ ": (" + outer_xhr.status.toString() + ") "
49+
+ outer_xhr.statusText + "</p>");
50+
})
51+
}
52+
}
53+
54+
/**
55+
* Retrieve contents of a session materials modal
56+
*
57+
* Expects output_elt to exist and have a "data-src" attribute. Retrieves the
58+
* contents of that URL, then attempts to populate the .agenda-frame and
59+
* .minutes-frame elements.
60+
*
61+
* @param output_elt Element, probably a div, to hold the output
62+
*/
63+
function retrieve_session_modal(output_elt) {
64+
if (!output_elt) {return;}
65+
output_elt = $(output_elt);
66+
var data_src = output_elt.attr("data-src");
67+
if (!data_src) {
68+
output_elt.html("<p>Error: missing data-src attribute</p>");
69+
} else {
70+
output_elt.html("<p>Loading...</p>");
71+
$.get(data_src).done(function(data) {
72+
output_elt.html(data);
73+
retrieve_session_materials(output_elt.find(".agenda-frame"));
74+
retrieve_session_materials(output_elt.find(".minutes-frame"));
75+
});
76+
}
77+
}
78+
79+
$(document).ready(function() {
80+
$(".modal").on("show.bs.modal", function () {
81+
retrieve_session_modal($(this).find(".session-materials"));
82+
});
83+
})
84+
})();

0 commit comments

Comments
 (0)