if (typeof left_nav === 'undefined') { var left_nav; //IE doesn't follow the scoping rule that variables used within a function that are not declared locally should be treated globally. Instead, it throws a runtime error and quits. } $(document).ready(function(){ if (typeof json_url === 'undefined') { json_url = '/site/leftnav_category_headings/json/category_headings.json'; } if (typeof cat_key === 'undefined') { try { var breadcrumb = get_toplevel_breadcrumb(); cat_key = breadcrumb.short_name; } catch (e) { if (e.code === 'no_links') { cat_key = e.short_name; } } } if (cat_key) { // var left_nav; //allow left_nav variable to be scoped globally so hook functions can access it. (note: all local variables are named "__left_nav" because IE appears to confuse the scoping.) left_nav = new category_headings(cat_key, json_url); left_nav.bootstrap(); } }); function get_toplevel_breadcrumb() { var container = $('#slb_bread_crumb'); var result, error; if (container.length == 0) { error = { "code": "no_crumbs" }; throw( error ); } var first_link = container.find('a.crumb:first'); if (first_link.length == 0) { error = { "code": "no_links", "short_name": $.trim( container.text().toLowerCase() ).replace(/[\r\n]/g,'').replace(/[^a-zA-Z0-9]/g, '_').replace(/_*$/,''), "long_name": $.trim( container.text() ).replace(/[\r\n]/g,'').replace(/[>\s]*$/,''), "url": document.URL }; throw( error ); } result = { "short_name": $.trim( first_link.text().toLowerCase() ).replace(/[\r\n]/g,'').replace(/[^a-zA-Z0-9]/g, '_'), "long_name": $.trim( first_link.text() ).replace(/[\r\n]/g,'').replace(/[>\s]*$/,''), "url": first_link.attr('href') }; return result; } function category_headings(cat_key, json_url) { var __this__ = this; var $ = jQuery; this.cat_key = cat_key; this.json_url = json_url; this.json_data; this.bootstrap = function() { $.getJSON(__this__.json_url, function(data) { if (typeof leftnav_category_headings_hook___post_json_download === 'function') { leftnav_category_headings_hook___post_json_download(data); } var key = __this__.cat_key; var alias; while (key) { if (typeof data[key] === 'undefined') { __this__.no_heading_action(); key = null; } else { if ( (typeof data[key]['__is_alias__'] === 'undefined') || (data[key]['__is_alias__'] == false) ) { __this__.json_data = data[key]; cat_key = key; //set global variable so hook functions can access it. if (typeof leftnav_category_headings_hook___post_bootstrap === 'function') { leftnav_category_headings_hook___post_bootstrap(__this__.json_data); } __this__.initialize(); key = null; } else { //an alias key is defined and non-empty. (since this is done in a loop, we can alias other aliases.) key = data[key]['__is_alias__']; } } } }); } // ============================================================================================ // no data found: this.no_heading_action = function() { var __left_nav = $('#SL_sideBar .navigationList table tr.portletHandle table').find('tr:first').closest('table'); var __left_offset = __left_nav.find('td:first').width(); __left_nav.css('margin-left', '-' +__left_offset+ 'px'); } // ============================================================================================ // process data: this.initialize = function() { var __left_nav = $('#SL_sideBar .navigationList table tr.portletHandle table').find('tr:first').parent(); var headings = {}; var most_recent_heading = null; __left_nav.find('tr').each(function(){ var tr_tag = $(this); var td_tag_count = tr_tag.find('td').length; //1st table cell always contains padding ( tag) => by not removing this cell or including padding in the rows being added.. the new section headings will appear top-level. //2nd table cell contains an anchor tag (if top level), or an img tag (if expanded into a child category) //3 or more cells only exist when expanded into a child category if (td_tag_count <= 1) {return true;} if (td_tag_count == 2) { var top_level_category = tr_tag.find('td:first').next().find('a:first'); var found_heading = false; if (top_level_category.length == 1) { var label_txt = $.trim( top_level_category.text().toLowerCase() ).replace(/[\r\n]/g,'').replace(/[^a-zA-Z0-9]/g, '_'); $.each(__this__.json_data, function(heading_name, txt_labels){ if (heading_name.substring(0,2) == '__') { if ( (heading_name === '__category_blacklist__') && ($.inArray(label_txt, txt_labels) >= 0) ) { tr_tag.remove(); } return true; } if ($.inArray(label_txt, txt_labels) >= 0) { if (typeof headings[heading_name] === 'undefined') { headings[heading_name] = []; } headings[heading_name].push(tr_tag); most_recent_heading = heading_name; found_heading = true; return false; } }) } if (!found_heading) { most_recent_heading = null; } } if ( (td_tag_count >= 3) && (most_recent_heading != null) ) { //subcategory of a top-level category that belongs to: most_recent_heading headings[most_recent_heading].push(tr_tag); } }); if (typeof leftnav_category_headings_hook___pre_row_assembly === 'function') { leftnav_category_headings_hook___pre_row_assembly(headings); } var new_rows = []; // need a buffer to prepend in reverse order when in prepend_mode (default behavior). Doing so allows categories not declared as being elements of any section with a heading to follow afterward with no change to their normal formatting. Appending is useful when existing categories are left unmodified, but we would like to add additional sections to the left nav. var prepend_mode = ( (typeof __this__.json_data['__insertion_method__'] != 'undefined') && (__this__.json_data['__insertion_method__'] === 'append') )? false : true; if ( (typeof __this__.json_data['__heading_sort_order__'] != 'undefined') && (__this__.json_data['__heading_sort_order__'].length > 0) ) { $.each(__this__.json_data['__heading_sort_order__'], function(i, heading_name){ if (typeof headings[heading_name] != 'undefined') { var rows = headings[heading_name]; assemble_rows(heading_name, rows, prepend_mode, new_rows); } }); } else { $.each(headings, function(heading_name, rows){ assemble_rows(heading_name, rows, prepend_mode, new_rows) }); } if (prepend_mode) { $.each(new_rows.reverse(), function(i, new_row){ __left_nav.prepend(new_row); }); } else { $.each(new_rows, function(i, new_row){ __left_nav.append(new_row); }); } } function assemble_rows(heading_name, rows, prepend_mode, row_array) { var new_row_html = ''; var new_row; if (!prepend_mode) { new_row = $(new_row_html); new_row.find('td.leftnav_category_heading').html(' '); //empty row row_array.push(new_row); } new_row = $(new_row_html); new_row.find('td.leftnav_category_heading').html(heading_name); //category heading (unmodified) row_array.push(new_row); $.each(rows, function(i, tr_tag){ tr_tag.detach(); row_array.push(tr_tag); }); if (prepend_mode) { new_row = $(new_row_html); new_row.find('td.leftnav_category_heading').html(' '); //empty row row_array.push(new_row); } } // ============================================================================================ public utility functions for usage by external hook functions this.hook_helper___verify_sort_order_exists = function(json_data) { if (typeof json_data["__heading_sort_order__"] === 'undefined') { json_data["__heading_sort_order__"] = []; $.each(json_data, function(heading_name){ if (heading_name.substring(0,2) === '__') {return true;} json_data["__heading_sort_order__"].push(heading_name); }); } } this.hook_helper___get_last_heading_position_index_in_sort_order = function(json_data) { if (typeof json_data["__heading_sort_order__"] === 'undefined') {return null;} return json_data["__heading_sort_order__"].length - 1; } this.hook_helper___inject_heading_into_sort_order = function(json_data, new_heading, new_heading_position_index) { this.hook_helper___verify_sort_order_exists(json_data); if (new_heading_position_index === 'first') { new_heading_position_index = 0; } if (new_heading_position_index === 'last') { new_heading_position_index = json_data["__heading_sort_order__"].length; } json_data["__heading_sort_order__"].splice(new_heading_position_index, 0, new_heading); } this.hook_helper___assemble_row = function(url, description, depth, attributes) { // a top-level category has a depth of 1. [depth, attributes] are both optional. if (typeof depth === 'undefined') {depth = 1;} var colspan = 11 - depth; var td_padding = ''; var row_html = ''; var new_row = $(row_html); var anchor_tag = new_row.find('a'); anchor_tag.attr('href', url); if (typeof attributes != 'undefined') { $.each(attributes, function(key, val){ anchor_tag.attr(key, val); }); } anchor_tag.addClass('smalltext'); anchor_tag.text(description); anchor_tag.closest('td').attr('colspan', colspan); for (var i=0; i