Merged in [19183] from jennifer@painless-security.com:

Add 'Select Sessions' tab to agenda pages.
 - Legacy-Id: 19185
Note: SVN reference [19183] has been migrated to Git commit 82ad0402f8
This commit is contained in:
Robert Sparks 2021-07-02 00:58:34 +00:00
commit 4cb03a24cc
14 changed files with 962 additions and 180 deletions

View file

@ -166,6 +166,16 @@ def get_schedule_by_name(meeting, owner, name):
return meeting.schedule_set.filter(name = name).first()
def preprocess_assignments_for_agenda(assignments_queryset, meeting, extra_prefetches=()):
"""Add computed properties to assignments
For each assignment a, adds
a.start_timestamp
a.end_timestamp
a.session.historic_group
a.session.historic_parent
a.session.rescheduled_to (if rescheduled)
a.session.prefetched_active_materials
"""
assignments_queryset = assignments_queryset.prefetch_related(
'timeslot', 'timeslot__type', 'timeslot__meeting',
'timeslot__location', 'timeslot__location__floorplan', 'timeslot__location__urlresource_set',
@ -264,9 +274,9 @@ def filter_keywords_for_session(session):
if group.state_id == 'bof':
keywords.add('bof')
keywords.add(group.acronym.lower())
token = session.docname_token_only_for_multiple()
if token is not None:
keywords.add(group.acronym.lower() + "-" + token)
specific_kw = filter_keyword_for_specific_session(session)
if specific_kw is not None:
keywords.add(specific_kw)
area = getattr(group, 'historic_parent', group.parent)
# Only sessions belonging to "regular" groups should respond to the
@ -280,6 +290,18 @@ def filter_keywords_for_session(session):
keywords.update(['officehours', session.name.lower().replace(' ', '')])
return sorted(list(keywords))
def filter_keyword_for_specific_session(session):
"""Get keyword that identifies a specific session
Returns None if the session cannot be selected individually.
"""
group = getattr(session, 'historic_group', session.group)
if group is None:
return None
kw = group.acronym.lower() # start with this
token = session.docname_token_only_for_multiple()
return kw if token is None else '{}-{}'.format(kw, token)
def read_session_file(type, num, doc):
# XXXX FIXME: the path fragment in the code below should be moved to
# settings.py. The *_PATH settings should be generalized to format()

View file

@ -3,6 +3,7 @@
from django import template
from django.urls import reverse
register = template.Library()
@ -60,3 +61,10 @@ def args(obj, arg):
obj.__callArg += [arg]
return obj
@register.simple_tag(name='webcal_url', takes_context=True)
def webcal_url(context, viewname, *args, **kwargs):
"""webcal URL for a view"""
return 'webcal://{}{}'.format(
context.request.get_host(),
reverse(viewname, args=args, kwargs=kwargs)
)

View file

@ -1355,7 +1355,65 @@ class AgendaTests(IetfSeleniumTestCase):
wait.until(in_iframe_href('tz=america/halifax', 'weekview'))
except:
self.fail('iframe href not updated to contain selected time zone')
def test_agenda_session_selection(self):
wait = WebDriverWait(self.driver, 2)
url = self.absreverse('ietf.meeting.views.agenda_personalize', kwargs={'num': self.meeting.number})
self.driver.get(url)
# Verify that elements are all updated when the filters change. That the correct elements
# have the appropriate classes is a separate test.
elements_to_check = self.driver.find_elements_by_css_selector('.agenda-link.filterable')
self.assertGreater(len(elements_to_check), 0, 'No elements with agenda links to update were found')
self.assertFalse(
any(checkbox.is_selected()
for checkbox in self.driver.find_elements_by_css_selector(
'input.checkbox[name="selected-sessions"]')),
'Sessions were selected before being clicked',
)
mars_checkbox = self.driver.find_element_by_css_selector('input[type="checkbox"][name="selected-sessions"][data-filter-item="mars"]')
break_checkbox = self.driver.find_element_by_css_selector('input[type="checkbox"][name="selected-sessions"][data-filter-item="secretariat-sessb"]')
registration_checkbox = self.driver.find_element_by_css_selector('input[type="checkbox"][name="selected-sessions"][data-filter-item="secretariat-sessa"]')
secretariat_button = self.driver.find_element_by_css_selector('button[data-filter-item="secretariat"]')
mars_checkbox.click() # select mars session
try:
wait.until(
lambda driver: all('?show=mars' in el.get_attribute('href') for el in elements_to_check)
)
except TimeoutException:
self.fail('Some agenda links were not updated when mars session was selected')
self.assertTrue(mars_checkbox.is_selected(), 'mars session checkbox was not selected after being clicked')
self.assertFalse(break_checkbox.is_selected(), 'break checkbox was selected without being clicked')
self.assertFalse(registration_checkbox.is_selected(), 'registration checkbox was selected without being clicked')
mars_checkbox.click() # deselect mars session
try:
wait.until(
lambda driver: not any('?show=mars' in el.get_attribute('href') for el in elements_to_check)
)
except TimeoutException:
self.fail('Some agenda links were not updated when mars session was de-selected')
self.assertFalse(mars_checkbox.is_selected(), 'mars session checkbox was still selected after being clicked')
self.assertFalse(break_checkbox.is_selected(), 'break checkbox was selected without being clicked')
self.assertFalse(registration_checkbox.is_selected(), 'registration checkbox was selected without being clicked')
secretariat_button.click() # turn on all secretariat sessions
break_checkbox.click() # also select the break
try:
wait.until(
lambda driver: all(
'?show=secretariat&hide=secretariat-sessb' in el.get_attribute('href')
for el in elements_to_check
))
except TimeoutException:
self.fail('Some agenda links were not updated when secretariat group but not break was selected')
self.assertFalse(mars_checkbox.is_selected(), 'mars session checkbox was unexpectedly selected')
self.assertFalse(break_checkbox.is_selected(), 'break checkbox was unexpectedly selected')
self.assertTrue(registration_checkbox.is_selected(), 'registration checkbox was expected to be selected')
@ifSeleniumEnabled
class WeekviewTests(IetfSeleniumTestCase):
@ -1693,7 +1751,10 @@ class InterimTests(IetfSeleniumTestCase):
self.assert_upcoming_view_filter_matches_ics_filter(querystring)
# Check the ical links
simplified_querystring = querystring.replace(' ', '%20') # encode spaces'
simplified_querystring = querystring.replace(' ', '') # remove spaces
if simplified_querystring in ['?show=', '?hide=', '?show=&hide=']:
simplified_querystring = '' # these empty querystrings will be dropped (not an exhaustive list)
ics_link = self.driver.find_element_by_link_text('Download as .ics')
self.assertIn(simplified_querystring, ics_link.get_attribute('href'))
webcal_link = self.driver.find_element_by_link_text('Subscribe with webcal')

View file

@ -35,6 +35,7 @@ from ietf.meeting.helpers import can_approve_interim_request, can_view_interim_r
from ietf.meeting.helpers import send_interim_approval_request
from ietf.meeting.helpers import send_interim_meeting_cancellation_notice, send_interim_session_cancellation_notice
from ietf.meeting.helpers import send_interim_minutes_reminder, populate_important_dates, update_important_dates
from ietf.meeting.helpers import filter_keyword_for_specific_session
from ietf.meeting.models import Session, TimeSlot, Meeting, SchedTimeSessAssignment, Schedule, SessionPresentation, SlideSubmission, SchedulingEvent, Room, Constraint, ConstraintName
from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting, make_interim_test_data
from ietf.meeting.utils import finalize, condition_slide_order
@ -375,6 +376,63 @@ class MeetingTests(TestCase):
self.assertEqual(r_with_tz.status_code,200)
self.assertEqual(r.content, r_with_tz.content)
def test_agenda_personalize(self):
"""Session selection page should have a checkbox for each session with appropriate keywords"""
meeting = make_meeting_test_data()
url = urlreverse("ietf.meeting.views.agenda_personalize",kwargs=dict(num=meeting.number))
r = self.client.get(url)
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
for assignment in SchedTimeSessAssignment.objects.filter(
schedule__in=[meeting.schedule, meeting.schedule.base],
timeslot__type__private=False,
):
row = q('#row-{}'.format(assignment.slug()))
self.assertIsNotNone(row, 'No row for assignment {}'.format(assignment))
checkboxes = row('input[type="checkbox"][name="selected-sessions"]')
self.assertEqual(len(checkboxes), 1,
'Row for assignment {} does not have a checkbox input'.format(assignment))
checkbox = checkboxes.eq(0)
self.assertEqual(
checkbox.attr('data-filter-item'),
filter_keyword_for_specific_session(assignment.session),
)
def test_agenda_personalize_updates_urls(self):
"""The correct URLs should be updated when filter settings change on the personalize agenda view
Tests that the expected elements have the necessary classes. The actual update of these fields
is tested in the JS tests
"""
meeting = make_meeting_test_data()
url = urlreverse("ietf.meeting.views.agenda_personalize",kwargs=dict(num=meeting.number))
r = self.client.get(url)
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
# Find all the elements expected to be updated
expected_elements = []
nav_tab_anchors = q('ul.nav.nav-tabs > li > a')
for anchor in nav_tab_anchors.items():
text = anchor.text().strip()
if text in ['Agenda', 'UTC Agenda', 'Select Sessions']:
expected_elements.append(anchor)
for btn in q('.buttonlist a.btn').items():
text = btn.text().strip()
if text in ['View customized agenda', 'Download as .ics', 'Subscribe with webcal']:
expected_elements.append(btn)
# Check that all the expected elements have the correct classes
for elt in expected_elements:
self.assertTrue(elt.has_class('agenda-link'))
self.assertTrue(elt.has_class('filterable'))
# Finally, check that there are no unexpected elements marked to be updated.
# If there are, they should be added to the test above.
self.assertEqual(len(expected_elements),
len(q('.agenda-link.filterable')),
'Unexpected elements updated')
@override_settings(MEETING_MATERIALS_SERVE_LOCALLY=False, MEETING_DOC_HREFS = settings.MEETING_DOC_CDN_HREFS)
def test_materials_through_cdn(self):
meeting = make_meeting_test_data(create_interims=True)

View file

@ -47,6 +47,7 @@ type_ietf_only_patterns = [
url(r'^agenda/by-type$', views.agenda_by_type),
url(r'^agenda/by-type/(?P<type>[a-z]+)$', views.agenda_by_type),
url(r'^agenda/by-type/(?P<type>[a-z]+)/ics$', views.agenda_by_type_ics),
url(r'^agenda/personalize', views.agenda_personalize),
url(r'^agendas/list$', views.list_schedules),
url(r'^agendas/edit$', RedirectView.as_view(pattern_name='ietf.meeting.views.list_schedules', permanent=True)),
url(r'^agendas/diff/$', views.diff_schedules),

View file

@ -65,7 +65,7 @@ from ietf.meeting.helpers import get_wg_list, find_ads_for_meeting
from ietf.meeting.helpers import get_meeting, get_ietf_meeting, get_current_ietf_meeting_num
from ietf.meeting.helpers import get_schedule, schedule_permissions, is_regular_agenda_filter_group
from ietf.meeting.helpers import preprocess_assignments_for_agenda, read_agenda_file
from ietf.meeting.helpers import filter_keywords_for_session, tag_assignments_with_filter_keywords
from ietf.meeting.helpers import filter_keywords_for_session, tag_assignments_with_filter_keywords, filter_keyword_for_specific_session
from ietf.meeting.helpers import convert_draft_to_pdf, get_earliest_session_date
from ietf.meeting.helpers import can_view_interim_request, can_approve_interim_request
from ietf.meeting.helpers import can_edit_interim_request
@ -1464,6 +1464,119 @@ def session_materials(request, session_id):
assignment = assignments[0]
return render(request, 'meeting/session_materials.html', dict(item=assignment))
def get_assignments_for_agenda(schedule):
"""Get queryset containing assignments to show on the agenda"""
return SchedTimeSessAssignment.objects.filter(
schedule__in=[schedule, schedule.base],
timeslot__type__private=False,
)
def extract_groups_hierarchy(prepped_assignments):
"""Extract groups hierarchy for agenda display
It's a little bit complicated because we can be dealing with historic groups.
"""
seen = set()
groups = [a.session.historic_group for a in prepped_assignments
if a.session
and a.session.historic_group
and is_regular_agenda_filter_group(a.session.historic_group)
and a.session.historic_group.historic_parent]
group_parents = []
for g in groups:
if g.historic_parent.acronym not in seen:
group_parents.append(g.historic_parent)
seen.add(g.historic_parent.acronym)
seen = set()
for p in group_parents:
p.group_list = []
for g in groups:
if g.acronym not in seen and g.historic_parent.acronym == p.acronym:
p.group_list.append(g)
seen.add(g.acronym)
p.group_list.sort(key=lambda g: g.acronym)
return group_parents
def prepare_filter_keywords(tagged_assignments, group_parents):
#
# The agenda_filter template expects a list of categorized header buttons, each
# with a list of children. Make two categories: the IETF areas and the other parent groups.
# We also pass a list of 'extra' buttons - currently Office Hours and miscellaneous filters.
# All but the last of these are additionally used by the agenda.html template to make
# a list of filtered ical buttons. The last group is ignored for this.
area_group_filters = []
other_group_filters = []
extra_filters = []
for p in group_parents:
new_filter = dict(
label=p.acronym.upper(),
keyword=p.acronym.lower(),
children=[
dict(
label=g.acronym,
keyword=g.acronym.lower(),
is_bof=g.is_bof(),
) for g in p.group_list
]
)
if p.type.slug == 'area':
area_group_filters.append(new_filter)
else:
other_group_filters.append(new_filter)
office_hours_labels = set()
for a in tagged_assignments:
suffix = ' office hours'
if a.session.name.lower().endswith(suffix):
office_hours_labels.add(a.session.name[:-len(suffix)].strip())
if len(office_hours_labels) > 0:
# keyword needs to match what's tagged in filter_keywords_for_session()
extra_filters.append(dict(
label='Office Hours',
keyword='officehours',
children=[
dict(
label=label,
keyword=label.lower().replace(' ', '')+'officehours',
is_bof=False,
) for label in office_hours_labels
]
))
# Keywords that should appear in 'non-area' column
non_area_labels = [
'BoF', 'EDU', 'Hackathon', 'IEPG', 'IESG', 'IETF', 'Plenary', 'Secretariat', 'Tools',
]
# Remove any unused non-area keywords
non_area_filters = [
dict(label=label, keyword=label.lower(), is_bof=False)
for label in non_area_labels if any([
label.lower() in assignment.filter_keywords
for assignment in tagged_assignments
])
]
if len(non_area_filters) > 0:
extra_filters.append(dict(
label=None,
keyword=None,
children=non_area_filters,
))
area_group_filters.sort(key=lambda p:p['label'])
other_group_filters.sort(key=lambda p:p['label'])
filter_categories = [category
for category in [area_group_filters, other_group_filters, extra_filters]
if len(category) > 0]
return filter_categories, non_area_labels
@ensure_csrf_cookie
def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""):
base = base if base else 'agenda'
@ -1486,6 +1599,7 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""
else:
raise Http404("No such meeting")
# Select the schedule to show
if name is None:
schedule = get_schedule(meeting, name)
else:
@ -1497,112 +1611,23 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""
return render(request, "meeting/no-"+base+ext, {'meeting':meeting }, content_type=mimetype[ext])
updated = meeting.updated()
filtered_assignments = SchedTimeSessAssignment.objects.filter(
schedule__in=[schedule, schedule.base],
timeslot__type__private=False,
# Select and prepare sessions that should be included
filtered_assignments = preprocess_assignments_for_agenda(
get_assignments_for_agenda(schedule),
meeting
)
filtered_assignments = preprocess_assignments_for_agenda(filtered_assignments, meeting)
tag_assignments_with_filter_keywords(filtered_assignments)
# Done processing for CSV output
if ext == ".csv":
return agenda_csv(schedule, filtered_assignments)
# extract groups hierarchy, it's a little bit complicated because
# we can be dealing with historic groups
seen = set()
groups = [a.session.historic_group for a in filtered_assignments
if a.session
and a.session.historic_group
and is_regular_agenda_filter_group(a.session.historic_group)
and a.session.historic_group.historic_parent]
group_parents = []
for g in groups:
if g.historic_parent.acronym not in seen:
group_parents.append(g.historic_parent)
seen.add(g.historic_parent.acronym)
seen = set()
for p in group_parents:
p.group_list = []
for g in groups:
if g.acronym not in seen and g.historic_parent.acronym == p.acronym:
p.group_list.append(g)
seen.add(g.acronym)
p.group_list.sort(key=lambda g: g.acronym)
# Groups gathered and processed. Now arrange for the filter UI.
#
# The agenda_filter template expects a list of categorized header buttons, each
# with a list of children. Make two categories: the IETF areas and the other parent groups.
# We also pass a list of 'extra' buttons - currently Office Hours and miscellaneous filters.
# All but the last of these are additionally used by the agenda.html template to make
# a list of filtered ical buttons. The last group is ignored for this.
area_group_filters = []
other_group_filters = []
extra_filters = []
for p in group_parents:
new_filter = dict(
label=p.acronym.upper(),
keyword=p.acronym.lower(),
children=[
dict(
label=g.acronym,
keyword=g.acronym.lower(),
is_bof=g.is_bof(),
) for g in p.group_list
]
)
if p.type.slug == 'area':
area_group_filters.append(new_filter)
else:
other_group_filters.append(new_filter)
office_hours_labels = set()
for a in filtered_assignments:
suffix = ' office hours'
if a.session.name.lower().endswith(suffix):
office_hours_labels.add(a.session.name[:-len(suffix)].strip())
if len(office_hours_labels) > 0:
# keyword needs to match what's tagged in filter_keywords_for_session()
extra_filters.append(dict(
label='Office Hours',
keyword='officehours',
children=[
dict(
label=label,
keyword=label.lower().replace(' ', '')+'officehours',
is_bof=False,
) for label in office_hours_labels
]
))
# Keywords that should appear in 'non-area' column
non_area_labels = [
'BoF', 'EDU', 'Hackathon', 'IEPG', 'IESG', 'IETF', 'Plenary', 'Secretariat', 'Tools',
]
# Remove any unused non-area keywords
non_area_filters = [
dict(label=label, keyword=label.lower(), is_bof=False)
for label in non_area_labels if any([
label.lower() in assignment.filter_keywords
for assignment in filtered_assignments
])
]
if len(non_area_filters) > 0:
extra_filters.append(dict(
label=None,
keyword=None,
children=non_area_filters,
))
area_group_filters.sort(key=lambda p:p['label'])
other_group_filters.sort(key=lambda p:p['label'])
filter_categories = [category
for category in [area_group_filters, other_group_filters, extra_filters]
if len(category) > 0]
# Now prep the filter UI
filter_categories, non_area_labels = prepare_filter_keywords(
filtered_assignments,
extract_groups_hierarchy(filtered_assignments),
)
is_current_meeting = (num is None) or (num == get_current_ietf_meeting_num())
@ -1756,6 +1781,45 @@ def agenda_by_type_ics(request,num=None,type=None):
updated = meeting.updated()
return render(request,"meeting/agenda.ics",{"schedule":schedule,"updated":updated,"assignments":assignments},content_type="text/calendar")
def agenda_personalize(request, num):
meeting = get_ietf_meeting(num) # num may be None, which requests the current meeting
if meeting is None or meeting.schedule is None:
raise Http404('No such meeting')
# Select and prepare sessions that should be included
filtered_assignments = preprocess_assignments_for_agenda(
get_assignments_for_agenda(meeting.schedule),
meeting
)
tag_assignments_with_filter_keywords(filtered_assignments)
for assignment in filtered_assignments:
# may be None for some sessions
assignment.session_keyword = filter_keyword_for_specific_session(assignment.session)
# Now prep the filter UI
filter_categories, non_area_labels = prepare_filter_keywords(
filtered_assignments,
extract_groups_hierarchy(filtered_assignments),
)
is_current_meeting = (num is None) or (num == get_current_ietf_meeting_num())
return render(
request,
"meeting/agenda_personalize.html",
{
'schedule': meeting.schedule,
'updated': meeting.updated(),
'filtered_assignments': filtered_assignments,
'filter_categories': filter_categories,
'non_area_labels': non_area_labels,
'timezone': meeting.time_zone,
'is_current_meeting': is_current_meeting,
'cache_time': 150 if is_current_meeting else 3600,
}
)
def session_draft_list(num, acronym):
try:
agendas = Document.objects.filter(type="agenda",

View file

@ -5,7 +5,14 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
(function () {
'use strict'
var update_callback; // function(filter_params)
/* n.b., const refers to the opts object itself, not its contents.
* Use camelCase for easy translation into element.dataset keys,
* which are automatically camel-cased from the data attribute name.
* (e.g., data-always-show -> elt.dataset.alwaysShow) */
const opts = {
alwaysShow: false,
updateCallback: null // function(filter_params)
};
/* Remove from list, if present */
function remove_list_item (list, item) {
@ -58,7 +65,7 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
}
function get_filter_params (qparams) {
var enabled = !!(qparams.show || qparams.hide);
var enabled = opts.alwaysShow || qparams.show || qparams.hide;
return {
enabled: enabled,
show: get_filter_from_qparams(qparams, 'show'),
@ -114,8 +121,13 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
return;
}
update_href_querystrings(filter_params_as_querystring(filter_params))
// show the customizer - it will stay visible even if filtering is disabled
$('#customize').collapse('show')
const customizer = $('#customize');
if (customizer.hasClass('collapse')) {
customizer.collapse('show')
}
// Update button state to match visibility
buttons.each(function (index, elt) {
@ -140,8 +152,8 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
function update_view () {
var filter_params = get_filter_params(parse_query_params(window.location.search))
update_filter_ui(filter_params)
if (update_callback) {
update_callback(filter_params)
if (opts.updateCallback) {
opts.updateCallback(filter_params)
}
}
@ -151,20 +163,11 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
* (if supported) or loads the new URL.
*/
function update_filters (filter_params) {
var qparams = []
var search = ''
if (filter_params.show.length > 0) {
qparams.push('show=' + filter_params.show.join())
}
if (filter_params.hide.length > 0) {
qparams.push('hide=' + filter_params.hide.join())
}
if (qparams.length > 0) {
search = '?' + qparams.join('&')
}
// strip out the search / hash, then add back
var new_url = window.location.href.replace(/(\?.*)?(#.*)?$/, search + window.location.hash)
var new_url = replace_querystring(
window.location.href,
filter_params_as_querystring(filter_params)
)
update_href_querystrings(filter_params_as_querystring(filter_params))
if (window.history && window.history.replaceState) {
// Keep current origin, replace search string, no page reload
history.replaceState({}, document.title, new_url)
@ -175,6 +178,35 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
}
}
/**
* Update the querystring in the href filterable agenda links
*/
function update_href_querystrings(querystring) {
Array.from(
document.getElementsByClassName('agenda-link filterable')
).forEach(
(elt) => elt.href = replace_querystring(elt.href, querystring)
)
}
function filter_params_as_querystring(filter_params) {
var qparams = []
if (filter_params.show.length > 0) {
qparams.push('show=' + filter_params.show.join())
}
if (filter_params.hide.length > 0) {
qparams.push('hide=' + filter_params.hide.join())
}
if (qparams.length > 0) {
return '?' + qparams.join('&')
}
return ''
}
function replace_querystring(url, new_querystring) {
return url.replace(/(\?.*)?(#.*)?$/, new_querystring + window.location.hash)
}
/* Helper for pick group/type button handlers - toggles the appropriate parameter entry
* elt - the jquery element that was clicked
*/
@ -225,7 +257,19 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
update_filters(fp);
});
}
/**
* Read options from the template
*/
function read_template_options() {
const opts_elt = document.getElementById('agenda-filter-options');
opts.keys().forEach((opt) => {
if (opt in opts_elt.dataset) {
opts[opt] = opts_elt.dataset[opt];
}
});
}
/* Entry point to filtering code when page loads
*
* This must be called if you are using the HTML template to provide a customization
@ -261,6 +305,6 @@ var agenda_filter_for_testing; // methods to be accessed for automated testing
keyword_match: keyword_match,
parse_query_params: parse_query_params,
rows_matching_filter_keyword: rows_matching_filter_keyword,
set_update_callback: function (cb) {update_callback = cb}
set_update_callback: function (cb) {opts.updateCallback = cb}
};
})();

View file

@ -0,0 +1,82 @@
// Copyright The IETF Trust 2021, All Rights Reserved
/**
* Agenda personalization JS methods
*
* Requires agenda_timezone.js and timezone.js be included.
*/
const agenda_personalize = (
function () {
'use strict';
let meeting_timezone = document.getElementById('initial-data').dataset.timezone;
let selection_inputs;
/**
* Update the checkbox state to match the filter parameters
*/
function updateAgendaCheckboxes(filter_params) {
selection_inputs.forEach((inp) => {
const item_keywords = inp.dataset.filterKeywords.toLowerCase().split(',');
if (
agenda_filter.keyword_match(item_keywords, filter_params.show)
&& !agenda_filter.keyword_match(item_keywords, filter_params.hide)
) {
inp.checked = true;
} else {
inp.checked = false;
}
});
}
function handleFilterParamUpdate(filter_params) {
updateAgendaCheckboxes(filter_params);
}
function handleTableClick(event) {
if (event.target.name === 'selected-sessions') {
// hide the tooltip after clicking on a checkbox
const jqElt = jQuery(event.target);
if (jqElt.tooltip) {
jqElt.tooltip('hide');
}
}
}
window.addEventListener('load', function () {
// Methods/variables here that are not in ietf_timezone or agenda_filter are from agenda_timezone.js
// First, initialize_moments(). This must be done before calling any of the update methods.
// It does not need timezone info, so safe to call before initializing ietf_timezone.
initialize_moments(); // fills in moments in the agenda data
// Now set up callbacks related to ietf_timezone. This must happen before calling initialize().
// In particular, set_current_tz_cb() must be called before the update methods are called.
set_current_tz_cb(ietf_timezone.get_current_tz); // give agenda_timezone access to this method
ietf_timezone.set_tz_change_callback(function (newtz) {
update_times(newtz);
}
);
// With callbacks in place, call ietf_timezone.initialize(). This will call the tz_change callback
// after setting things up.
ietf_timezone.initialize(meeting_timezone);
// Now make other setup calls from agenda_timezone.js
add_tooltips();
init_timers();
selection_inputs = document.getElementsByName('selected-sessions');
agenda_filter.set_update_callback(handleFilterParamUpdate);
agenda_filter.enable();
document.getElementById('agenda-table')
.addEventListener('click', handleTableClick);
}
);
// export public interface
return { meeting_timezone };
}
)();

View file

@ -116,7 +116,7 @@
{% endfor %}
<div style="display:inline-block">
<a class="btn btn-default" href="{% url "ietf.meeting.views.agenda_ical" num=schedule.meeting.number %}?show={{ non_area_keywords|join:',' }}">Non-area events</a>
<a id="ical-link" class="hidden btn btn-primary" href="{% url "ietf.meeting.views.agenda_ical" num=schedule.meeting.number %}">Customized schedule</a>
<a id="ical-link" class="hidden btn btn-primary agenda-link filterable" href="{% url "ietf.meeting.views.agenda_ical" num=schedule.meeting.number %}">Customized schedule</a>
</div>
</p>
@ -421,14 +421,7 @@
function update_ical_links(filter_params) {
var ical_link = $("#ical-link");
if (agenda_filter.filtering_is_enabled(filter_params)) {
// Replace the query string in the ical link
var orig_link_href = ical_link.attr("href").split("?")[0];
ical_link.attr("href", orig_link_href+window.location.search);
ical_link.removeClass("hidden");
} else {
ical_link.addClass("hidden");
}
ical_link.toggleClass("hidden", !agenda_filter.filtering_is_enabled(filter_params));
}
function update_weekview(filter_params) {

View file

@ -1,14 +1,28 @@
{% comment %}
Required parameters:
filter_categories - filter description structure (see agenda view for example)
Optional parameters:
always_show - if False or absent, menu closes when not in use and "Customize" button is shown
customize_button_text - text to show on the "Customize" button (defaults to "Customize...")
{% endcomment %}
{% load agenda_filter_tags %}
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
{% if not always_show %}
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#customize">
<span class="fa fa-caret-down"></span> {% firstof customize_button_text "Customize..."%}
</a>
</h4>
</div>
<div id="customize" class="panel-collapse collapse">
{% endif %}
<div id="customize" class="{% if not always_show %}panel-collapse collapse{% endif %}">
<span hidden {# options to pass to the JS - correspond to keys in opts object #}
id="agenda-filter-options"
data-always-show="{% firstof always_show False %}">
</span>
<div class="panel-body">
<p>

View file

@ -0,0 +1,405 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2021, All Rights Reserved #}
{% load origin %}
{% load static %}
{% load ietf_filters %}
{% load textfilters %}
{% load htmlfilters %}
{% block title %}
IETF {{ schedule.meeting.number }} meeting agenda personalization
{% endblock %}
{% block morecss %}
tr:not(:first-child) th.gap {
height: 3em !important;
background-color: inherit !important;
border: none !important;
}
tr:first-child th.gap {
height: 0 !important;
background-color: inherit !important;
border: none !important;
}
div.tz-display {
margin-bottom: 0.5em;
margin-top: 1em;
text-align: right;
}
.tz-display a {
cursor: pointer;
}
.tz-display label {
font-weight: normal;
}
.tz-display select {
min-width: 15em;
}
#affix .nav li.tz-display {
padding: 4px 20px;
}
#affix .nav li.tz-display a {
display: inline;
padding: 0;
}
{% endblock %}
{% block bodyAttrs %}data-spy="scroll" data-target="#affix"{% endblock %}
{% block content %}
{% origin %}
<div class="row">
<div class="col-md-12">
{% include "meeting/meeting_heading.html" with meeting=schedule.meeting updated=updated selected="select-sessions" title_extra="" %}
</div>
</div>
<div class="row">
<div class="col-md-10">
{# cache this part -- it takes 3-6 seconds to generate #}
{% load cache %}
{% cache cache_time ietf_meeting_agenda_personalize schedule.meeting.number request.path %}
<div class="row">
<div class="col-xs-6"><h1>Session Selection</h1></div>
<div class="col-xs-6">
<div class="tz-display">
<div><small>
<label for="timezone-select">Time zone:</label>
<a id="meeting-timezone" onclick="ietf_timezone.use('{{ timezone }}')">Meeting</a> |
<a id="local-timezone" onclick="ietf_timezone.use('local')">Local</a> |
<a id="utc-timezone" onclick="ietf_timezone.use('UTC')">UTC</a>
</small></div>
<select id="timezone-select" class="tz-select">
{# Avoid blank while loading. JavaScript replaces the option list after init. #}
<option selected>{{ timezone }}</option>
</select>
</div>
</div>
</div>
{% if is_current_meeting %}
<p class="alert alert-info">
<b>Note:</b> IETF agendas are subject to change, up to and during a meeting.
</p>
{% endif %}
{% include "meeting/agenda_filter.html" with filter_categories=filter_categories always_show=True %}
{% include "meeting/agenda_personalize_buttonlist.html" with meeting=schedule.meeting only %}
<h2>
Individual Sessions
</h2>
<p>
Check boxes below to select individual sessions.
</p>
<table id="agenda-table" class="table table-condensed table-striped">
{% for item in filtered_assignments %}
{% ifchanged item.timeslot.time|date:"Y-m-d" %}
<tr>
<th class="gap" colspan="7"></th>
</tr>
<tr class="warning">
<th colspan="7">
{# The anchor here needs to be in a div, not in the th, in order for the anchor-target margin hack to work #}
<div class="anchor-target" id="{{ item.timeslot.time|slugify }}"></div>
{{ item.timeslot.time|date:"l, F j, Y" }}
</th>
</tr>
{% endifchanged %}
{% if item.timeslot.type_id == 'regular' %}
{% ifchanged %}
<tr class="info session-label-row"
data-slot-start-ts="{{ item.start_timestamp }}"
data-slot-end-ts="{{ item.end_timestamp }}">
<td class="leftmarker"></td>
<th class="text-nowrap text-right">
<span class="hidden-xs">
{% include "meeting/timeslot_start_end.html" %}
</span>
</th>
<th colspan="4">
<span class="hidden-sm hidden-md hidden-lg">
{% include "meeting/timeslot_start_end.html" %}
</span>
{{ item.timeslot.time|date:"l" }}
{{ item.timeslot.name|capfirst_allcaps }}
</th>
<td class="rightmarker"></td>
</tr>
{% endifchanged %}
{% endif %}
{% if item.timeslot.type.slug == 'break' or item.timeslot.type.slug == 'reg' or item.timeslot.type.slug == 'other' %}
<tr id="row-{{ item.slug }}"
data-slot-start-ts="{{ item.start_timestamp }}"
data-slot-end-ts="{{ item.end_timestamp }}">
<td class="leftmarker">
{% if item.session_keyword %}
<input
type="checkbox"
class="pickview"
title="Select session"
name="selected-sessions"
value="{{ item.session_keyword }}"
data-filter-keywords="{{ item.filter_keywords|join:',' }}"
data-filter-item="{{ item.session_keyword }}">
{% endif %}
</td>
<td class="text-nowrap text-right">
<span class="hidden-xs">
{% include "meeting/timeslot_start_end.html" %}
</span>
</td>
<td colspan="3">
<span class="hidden-sm hidden-md hidden-lg">
{% include "meeting/timeslot_start_end.html" %}
</span>
{% if item.timeslot.show_location and item.timeslot.get_html_location %}
{% if schedule.meeting.number|add:"0" < 96 %}
<a
href="https://tools.ietf.org/agenda/{{ schedule.meeting.number }}/venue/?room={{ item.timeslot.get_html_location|xslugify }}">{{ item.timeslot.get_html_location }}</a>
{% elif item.timeslot.location.floorplan %}
<a
href="{% url 'ietf.meeting.views.floor_plan' num=schedule.meeting.number %}?room={{ item.timeslot.get_html_location|xslugify }}">{{ item.timeslot.get_html_location }}</a>
{% else %}
{{ item.timeslot.get_html_location }}
{% endif %}
{% with item.timeslot.location.floorplan as floor %}
{% if item.timeslot.location.floorplan %}
<span class="hidden-xs">
<a
href="{% url 'ietf.meeting.views.floor_plan' num=schedule.meeting.number %}#{{ floor.name|xslugify }}"
class="pull-right" title="{{ floor.name }}"><span
class="label label-blank label-wide">{{ floor.short }}</span></a>
</span>
{% endif %}
{% endwith %}
{% endif %}
</td>
<td>
{% if item.session.agenda %}
<a href="{{ item.session.agenda.get_href }}">
{{ item.timeslot.name }}
</a>
{% else %}
{{ item.timeslot.name }}
{% endif %}
{% if item.session.current_status == 'canceled' %}
<span class="label label-danger pull-right">CANCELLED</span>
{% endif %}
</td>
<td class="rightmarker"></td>
</tr>
{% endif %}
{% if item.timeslot.type_id == 'regular' or item.timeslot.type.slug == 'plenary' %}
{% if item.session.historic_group %}
<tr id="row-{{ item.slug }}"
{% if item.timeslot.type.slug == 'plenary' %}class="{{ item.timeslot.type.slug }}danger"{% endif %}
data-slot-start-ts="{{ item.start_timestamp }}"
data-slot-end-ts="{{ item.end_timestamp }}">
<td class="leftmarker">
{% if item.session_keyword %}
<input
type="checkbox"
class="pickview"
title="Select session"
name="selected-sessions"
value="{{ item.session_keyword }}"
data-filter-keywords="{{ item.filter_keywords|join:',' }}"
data-filter-item="{{ item.session_keyword }}">
{% endif %}
</td>
{% if item.timeslot.type.slug == 'plenary' %}
<th class="text-nowrap text-right">
<span class="hidden-xs">
{% include "meeting/timeslot_start_end.html" %}
</span>
</th>
<td colspan="3">
<span class="hidden-sm hidden-md hidden-lg">
{% include "meeting/timeslot_start_end.html" %}
</span>
{% if item.timeslot.show_location and item.timeslot.get_html_location %}
{% if schedule.meeting.number|add:"0" < 96 %}
<a
href="https://tools.ietf.org/agenda/{{ schedule.meeting.number }}/venue/?room={{ item.timeslot.get_html_location|xslugify }}">{{ item.timeslot.get_html_location }}</a>
{% elif item.timeslot.location.floorplan %}
<a
href="{% url 'ietf.meeting.views.floor_plan' num=schedule.meeting.number %}?room={{ item.timeslot.get_html_location|xslugify }}">{{ item.timeslot.get_html_location }}</a>
{% else %}
{{ item.timeslot.get_html_location }}
{% endif %}
{% endif %}
</td>
{% else %}
<td>
{% with item.timeslot.location.floorplan as floor %}
{% if item.timeslot.location.floorplan %}
<span class="hidden-xs">
<a
href="{% url 'ietf.meeting.views.floor_plan' num=schedule.meeting.number %}#{{ floor.name|xslugify }}"
class="pull-right" title="{{ floor.name }}"><span
class="label label-blank">{{ floor.short }}</span></a>
</span>
{% endif %}
{% endwith %}
</td>
<td>
{% if item.timeslot.show_location and item.timeslot.get_html_location %}
{% if schedule.meeting.number|add:"0" < 96 %}
<a
href="https://tools.ietf.org/agenda/{{ schedule.meeting.number }}/venue/?room={{ item.timeslot.get_html_location|xslugify }}">{{ item.timeslot.get_html_location }}</a>
{% elif item.timeslot.location.floorplan %}
<a
href="{% url 'ietf.meeting.views.floor_plan' num=schedule.meeting.number %}?room={{ item.timeslot.get_html_location|xslugify }}">{{ item.timeslot.get_html_location }}</a>
{% else %}
{{ item.timeslot.get_html_location }}
{% endif %}
{% endif %}
</td>
<td><span class="hidden-xs">{{ item.session.historic_group.historic_parent.acronym }}</span></td>
<td>
{% if item.session.historic_group %}
<a
href="{% url 'ietf.group.views.group_about' acronym=item.session.historic_group.acronym %}">{{ item.session.historic_group.acronym }}</a>
{% else %}
{{ item.session.historic_group.acronym }}
{% endif %}
</td>
{% endif %}
<td>
{% if item.session.agenda %}
<a href="{{ item.session.agenda.get_href }}">
{% endif %}
{% if item.timeslot.type.slug == 'plenary' %}
{{ item.timeslot.name }}
{% else %}
{{ item.session.historic_group.name }}
{% endif %}
{% if item.session.agenda %}
</a>
{% endif %}
{% if item.session.current_status == 'canceled' %}
<span class="label label-danger pull-right">CANCELLED</span>
{% endif %}
{% if item.session.historic_group.state_id == "bof" %}
<span class="label label-success pull-right">BOF</span>
{% endif %}
{% if item.session.current_status == 'resched' %}
<span class="label label-danger pull-right">
RESCHEDULED
{% if item.session.rescheduled_to %}
TO
<span class="timetooltip reschedtimetooltip"><span class="time"
data-start-time="{{ item.session.rescheduled_to.utc_start_time|date:"U" }}"
data-end-time="{{ item.session.rescheduled_to.utc_end_time|date:"U" }}"
{% if item.timeslot.time|date:"l" != item.session.rescheduled_to.time|date:"l" %}
weekday="1"{% endif %}>
{% if "-utc" in request.path %}
{{ item.session.rescheduled_to.utc_start_time|date:"l G:i"|upper }}-
{{ item.session.rescheduled_to.utc_end_time|date:"G:i" }}
{% else %}
{{ item.session.rescheduled_to.time|date:"l G:i"|upper }}-
{{ item.session.rescheduled_to.end_time|date:"G:i" }}
{% endif %}
</span></span>
{% endif %}
</span>
{% endif %}
{% if item.session.agenda_note|first_url|conference_url %}
<br>
<a href={{ item.session.agenda_note|first_url }}>{{ item.session.agenda_note|slice:":23" }}</a>
{% elif item.session.agenda_note %}
<br><span class="text-danger">{{ item.session.agenda_note }}</span>
{% endif %}
</td>
<td class="rightmarker"></td>
</tr>
{% endif %}
{% endif %}
{% endfor %}
</table>
{% include "meeting/agenda_personalize_buttonlist.html" with meeting=schedule.meeting only %}
</div>
<div class="col-md-2 hidden-print bs-docs-sidebar" id="affix">
<ul class="nav nav-pills nav-stacked small" data-spy="affix">
<li><a href="#now">Now</a></li>
{% for item in filtered_assignments %}
{% ifchanged item.timeslot.time|date:"Y-m-d" %}
<li><a href="#{{ item.timeslot.time|slugify }}">{{ item.timeslot.time|date:"l, F j, Y" }}</a></li>
{% endifchanged %}
{% endfor %}
<li>
<hr/>
</li>
<li class="tz-display">Showing <span class="current-tz">{{ timezone }}</span> time</li>
<li class="tz-display"><span> {# span avoids applying nav link styling to these shortcuts #}
<a onclick="ietf_timezone.use('{{ timezone }}')">Meeting time</a> |
<a onclick="ietf_timezone.use('local')">Local time</a> |
<a onclick="ietf_timezone.use('UTC')">UTC</a></span>
</li>
{% if settings.DEBUG and settings.DEBUG_AGENDA %}
<li>
<hr/>
</li>
<li><span id="current-time"></span></li>
{% endif %}
</ul>
</div>
</div>
{% endcache %}
{# make the timezone available to JS #}
<span id="initial-data" hidden data-timezone="{{ timezone }}"></span>
{% endblock %}
{% block js %}
<script src="{% static 'moment/min/moment.min.js' %}"></script>
<script src="{% static 'moment-timezone/builds/moment-timezone-with-data-10-year-range.min.js' %}"></script>
<script src="{% static 'ietf/js/agenda/timezone.js' %}"></script>
<script src="{% static 'ietf/js/agenda/agenda_timezone.js' %}"></script>
<script src="{% static 'ietf/js/agenda/agenda_filter.js' %}"></script>
<script src="{% static 'ietf/js/agenda/agenda_personalize.js' %}"></script>
<script>
{% if settings.DEBUG and settings.DEBUG_AGENDA %}
speedup = +$.urlParam('speedup')
if (speedup < 1) {
speedup = 1
}
start_time = moment().utc()
if ($.urlParam('date')) {
offset_time = moment.tz(decodeURIComponent($.urlParam('date')), 'UTC')
} else {
offset_time = start_time
}
if (speedup > 1 || offset_time != start_time) {
moment.now = function () {
return (+new Date() - start_time) * speedup + offset_time
}
}
{% else %}
speedup = 1
{% endif %}
/* pull this from the agenda_personalize js module to make available to agenda_timezone */
meeting_timezone = agenda_personalize.meeting_timezone;
</script>
{% endblock %}

View file

@ -0,0 +1,20 @@
{% comment %}
Buttons for the agenda_personalize.html template
Required parameter: meeting - meeting being displayed
{% endcomment %}
{% load agenda_custom_tags %}
<div class="buttonlist">
<a class="btn btn-default agenda-link filterable"
href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}">
View customized agenda
</a>
<a class="btn btn-default agenda-link filterable"
href="{% url 'ietf.meeting.views.agenda_ical' num=meeting.number %}">
Download as .ics
</a>
<a class="btn btn-default agenda-link filterable"
href="{% webcal_url 'ietf.meeting.views.agenda_ical' num=meeting.number %}">
Subscribe with webcal
</a>
</div>

View file

@ -28,30 +28,57 @@
</small>
</h1>
{# <a> tags with the agenda-link filterable classes will be updated with show/hide parameters #}
<ul class="nav nav-tabs" role="tablist">
<li {% if selected == "agenda" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}">Agenda</a></li>
<li {% if selected == "agenda-utc" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.agenda' num=meeting.number utc='-utc' %}">UTC Agenda</a></li>
{% if user|has_role:"Secretariat,Area Director,IAB" %}
<li {% if selected == "agenda" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}"
class="agenda-link filterable">
Agenda
</a>
</li>
<li {% if selected == "agenda-utc" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.agenda' num=meeting.number utc='-utc' %}"
class="agenda-link filterable">
UTC Agenda
</a>
</li>
{% if user|has_role:"Secretariat,Area Director,IAB" %}
{% if schedule != meeting.schedule %}
<li {% if selected == "by-room" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.agenda_by_room' num=meeting.number name=schedule.name owner=schedule.owner.email %}">by Room</a></li>
<li {% if selected == "by-type" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.agenda_by_type' num=meeting.number name=schedule.name owner=schedule.owner.email %}">by Type</a></li>
<li {% if selected == "room-view" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.room_view' num=meeting.number name=schedule.name owner=schedule.owner.email %}">Room grid</a></li>
{% else %}
<li {% if selected == "by-room" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.agenda_by_room' num=meeting.number %}">by Room</a></li>
<li {% if selected == "by-type" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.agenda_by_type' num=meeting.number %}">by Type</a></li>
<li {% if selected == "room-view" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.room_view' num=meeting.number %}">Room grid</a></li>
{% endif %}
{% endif %}
<li {% if selected == "floor-plan" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.floor_plan' num=meeting.number %}">Floor plan</a></li>
<li><a href="{% url 'ietf.meeting.views.agenda' num=meeting.number ext='.txt' %}">Plaintext</a></li>
<li><a href="https://tools.ietf.org/agenda/{{meeting.number}}/">Tools-style &raquo;</a></li>
<li {% if selected == "by-room" %}class="active"{% endif %}>
<a
href="{% url 'ietf.meeting.views.agenda_by_room' num=meeting.number name=schedule.name owner=schedule.owner.email %}">by
Room</a></li>
<li {% if selected == "by-type" %}class="active"{% endif %}>
<a
href="{% url 'ietf.meeting.views.agenda_by_type' num=meeting.number name=schedule.name owner=schedule.owner.email %}">by
Type</a></li>
<li {% if selected == "room-view" %}class="active"{% endif %}>
<a
href="{% url 'ietf.meeting.views.room_view' num=meeting.number name=schedule.name owner=schedule.owner.email %}">Room
grid</a></li>
{% else %}
<li {% if selected == "by-room" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.agenda_by_room' num=meeting.number %}">by Room</a></li>
<li {% if selected == "by-type" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.agenda_by_type' num=meeting.number %}">by Type</a></li>
<li {% if selected == "room-view" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.room_view' num=meeting.number %}">Room grid</a></li>
{% endif %}
{% endif %}
<li {% if selected == "floor-plan" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.floor_plan' num=meeting.number %}">Floor plan</a></li>
<li>
<a href="{% url 'ietf.meeting.views.agenda' num=meeting.number ext='.txt' %}">
Plaintext
</a>
</li>
<li {% if selected == "select-sessions" %}class="active"{% endif %}>
<a href="{% url 'ietf.meeting.views.agenda_personalize' num=meeting.number %}"
class="agenda-link filterable">
Select Sessions
</a>
</li>
<li>
<a href="https://tools.ietf.org/agenda/{{ meeting.number }}/">Tools-style &raquo;</a>
</li>
</ul>

View file

@ -83,8 +83,7 @@
{% if menu_actions %}
<div id="menu-actions" class="buttonlist">
{% for action in menu_actions %}
<a class="btn btn-default"
data-append-filter="{{ action.append_filter }}"
<a class="btn btn-default {% if action.append_filter %}agenda-link filterable{% endif %}"
href="{{ action.url }}">{{ action.label }}</a>
{% endfor %}
</div>
@ -314,24 +313,8 @@
});
}
function update_links(filter_params) {
var filtered_links = $("#menu-actions [data-append-filter='True']");
var filtering_enabled = agenda_filter.filtering_is_enabled(filter_params);
filtered_links.each(function(index, elt) {
var orig_link_href = $(elt).attr("href").split("?")[0];
if (filtering_enabled) {
// append new querystring
$(elt).attr("href", orig_link_href+window.location.search);
} else {
// remove querystring
$(elt).attr("href", orig_link_href);
}
});
}
function update_view(filter_params) {
update_meeting_display(filter_params);
update_links(filter_params);
update_calendar(current_tz, filter_params);
}