PageSourceSearch

https://dascna.org/wp-content/plugins/bmlt-wordpress-satellite-plu…/table_display.js?ver=1774500678

js dascna.org collected 2026-10-06 15:05:16 UTC 58,252 bytes, 1,061 lines download raw bytes

1/****************************************************************************************//**
2  \file table_display.js                                                                    *
3  \brief Javascript functions for the basic table display.                                  *
4                                                                                           * \
5                                                                                           *
6    This file contains a function/object that implements the Fast Table Shortcode. Upon     *
7    instantiation, it uses the page DOM to create a tabular display of meetings that are    *
8    returned by a focused semantic search. The meetings are segregated by weekday, and only *
9    one weekday is populated at a time. Clicking on another weekday will start an AJAX      *
10    query to the server, fetching the meetings for that weekday (and only that weekday).    *
11    Search results are cached.                                                              *
12                                                                                           *
13    The displayed meetings can be sorted by clicking on the column headers (with the sort   *
14    direction being changed by repeated clicks). Changing the sort flushes all cached data. *
15                                                                                           *
16    This class was designed to be highly self-contained. It can be instantiated multiple    *
17    times per page, with each instance reflecting a different set of settings from the      *
18    BMLT Administration setup (Theme, Week Start, Time Format and Root Server). The basic   *
19    search parameters are passed in upon instantiation. These define the dataset to be      *
20    fetched and displayed. The dataset is retrieved through a standard JSON semantic query  *
21    to the instance's Root Server.                                                          *
22                                                                                           *
23    The weekday tabs tend to be the "hubs" of the functionality. JSON search results are    *
24    attached to the weekday tabs, and this data is used to create the weekday search        *
25    display. The formats JSON is held by the main class, as that is used throughout the     *
26    class. Otherwise, we try to encapsulate data and functionality as much as possible.     *
27                                                                                           *
28    This should only be used on Root Server version 2.7.8 or above, because the town sort   *
29    will not work properly on older versions (but this will generally work, anyway).        *
30                                                                                           *
31    SOME NOTES ON CONVENTIONS:                                                              *
32    You will notice an abundance of semicolons. This is because this file is passed through *
33    an optimizer that crunches the entire file into one line. In this case, semicolons are  *
34    absolutely vital.                                                                       *
35                                                                                           *
36    We use a lot of completion block functions, which is fairly standard for JavaScript.    *
37    These are for things like mouseovers and click handlers.                                *
38                                                                                           *
39    We use ul elements, as opposed to table elements, in order to maximize the potential    *
40    for CSS modification of the displayed results (such as for responsive design).          *
41                                                                                           *
42    This file is part of the BMLT Common Satellite Base Class Project. The project GitHub   *
43    page is available here: https://github.com/MAGSHARE/BMLT-Common-CMS-Plugin-Class        *
44                                                                                           *
45    This file is part of the Basic Meeting List Toolbox (BMLT).                             *
46                                                                                           *
47    Find out more at: https://bmlt.app                                              *
48                                                                                           *
49    BMLT is free software: you can redistribute it and/or modify                            *
50    it under the terms of the GNU General Public License as published by                    *
51    the Free Software Foundation, either version 3 of the License, or                       *
52    (at your option) any later version.                                                     *
53                                                                                           *
54    BMLT is distributed in the hope that it will be useful,                                 *
55    but WITHOUT ANY WARRANTY; without even the implied warranty of                          *
56    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                           *
57    GNU General Public License for more details.                                            *
58                                                                                           *
59    You should have received a copy of the GNU General Public License                       *
60    along with this code.  If not, see <http://www.gnu.org/licenses/>.                      *
61                                                                                           ********************************************************************************************/
62
63/****************************************************************************************//**
64    \brief  This class function implements the [[bmlt_table]] shortcode. Most of the shortcode
65            functionality is JavaScript. This function builds a new DOM tree that contains
66            a <div> element that will, in turn, contain a couple of lists that are warped
67            using CSS into a semblance of a table.
68    \param in_display_id The element DOM ID of the container div.
69    \param in_settings_id The ID of the settings used for this table.
70    \param in_theme_name The name of the styling theme.
71    \param in_ajax_base_uri The base URI for AJAX callbacks.
72    \param in_start_weekday The weekday that starts the week. 0 is Sunday, 6 is Saturday.
73    \param in_is_military_time True, if the time is to be displayed as military.
74    \param in_search_params The query parameters for the base search.
75                                                                                           ********************************************************************************************/
76function TableSearchDisplay( in_display_id, in_settings_id, in_theme_name, in_ajax_base_uri, in_start_weekday, in_is_military_time, in_search_params )
77{
78    /****************************************************************************************
79     *                                   INSTANCE DATA MEMBERS                               *
80     ****************************************************************************************/
81
82    // These are the parameters passed in.
83    var my_settings_id;             ///\var my_settings_id This is the unique ID of the BMLT settings that we use to reference default values.
84    var my_ajax_base_uri;           ///\var my_ajax_base_uri This is the base URI for our AJAX calls.
85    var my_theme_name;              ///\var my_theme_name This is the name of our styling theme.
86    var my_search_query_params;     ///\var my_search_query_params These are the search parameters that we use to populate the search.
87    var my_start_weekday;           ///\var my_start_weekday The day of week that starts the header (0 is Sunday, 6 is Saturday).
88    var my_military_time;           ///\var my_military_time True, if the start time is military.
89    
90    // These are DOM elements that we track.
91    var my_container_object;        ///\var my_container_object This is the div block that surrounds this table.
92    var my_header_container;        ///\var my_header_container The div element that will contain the weekday tabs.
93    var my_body_container;          ///\var my_body_container The table element that will contain the search results.
94    var my_body_container_header;   ///\var my_body_container_header The table thead element that will contain the search results.
95    var my_body_container_body;     ///\var my_body_container_body The table tbody element that will contain the search results.
96    var my_weekday_links;           ///\var my_weekday_links An array of li elements, containing the weekday tabs
97    var my_selected_tab;            ///\var my_selected_tab This will be the selected tab object.
98    
99    var my_selected_sort_key;       ///\var my_selected_sort_key The currently selected sort key.
100    var my_sort_dir;                ///\var my_sort_dir The currently selected sort direction.
101    
102    var my_format_data;             ///\var my_format_data This is the JSON object that contains the format data for the search. It is loaded at startup, and not changed afterwards.
103    
104    // These are basically constants. They are the sort keys.
105    var my_sort_key_time;           ///\var my_sort_key_time The sort keys for sorting by start time.
106    var my_sort_key_meeting_name;   ///\var my_sort_key_meeting_name The sort keys for sorting by meeting name.
107    var my_sort_key_town;           ///\var my_sort_key_town The sort keys for sorting by borough/town.
108    var my_sort_key_address;        ///\var my_sort_key_address The sort keys for sorting by street address/location name.
109
110    this.my_sort_key_time = 'start_time,location_nation,location_province,location_sub_province,location_city_subsection,location_municipality,location_neighborhood,meeting_name';
111    this.my_sort_key_meeting_name = 'meeting_name,start_time,location_city_subsection,location_municipality';
112    this.my_sort_key_town = 'location_city_subsection,location_municipality,start_time,meeting_name';
113    this.my_sort_key_address = 'location_text,location_street,location_city_subsection,location_municipality,start_time,meeting_name';
114    
115    /****************************************************************************************
116    *################################### MAIN FUNCTION CODE ################################*
117    ****************************************************************************************/
118    
119    // Start off by getting our container.
120    this.my_container_object = document.getElementById(in_display_id);
121    this.my_container_object.handlerObject = this;  // Attach a context object (this function instance).
122    
123    // Set up the parameters passed in.
124    this.my_settings_id = in_settings_id;
125    this.my_ajax_base_uri = in_ajax_base_uri;
126    this.my_theme_name = in_theme_name;
127    this.my_start_weekday = in_start_weekday;
128    this.my_search_query_params = in_search_params;
129    this.my_military_time = in_is_military_time;
130    
131    // Initial values.
132    this.my_selected_sort_key = this.my_sort_key_time;
133    this.my_sort_dir = 'asc';
134    
135    // This makes the elements visible (This means that non-JS browsers see nothing but the <noscript> element).
136    this.my_container_object.style.display = 'block';
137    
138    // Fire off the first shot -get the formats, which will cascade to the first search.
139    this.domBuilder_CreateHeader();
140};
141
142/****************************************************************************************
143 * ############################### CLASS PROTOTYPE FUNCTIONS #############################*
144 ****************************************************************************************/
145
146/****************************************************************************************
147 * ############################### COMMUNICATIONS ROUTINES ###############################*
148 ****************************************************************************************/
149/************************************************************************************//**
150                                      AJAX HANDLER                                      *
151                                                                                       ****************************************************************************************/
152
153/************************************************************************************//**
154    \brief A simple, generic AJAX request function.
155    \param url The URI to be called. This should include all parameters in GET form (even if this is POST).
156    \param callback The success callback
157    \param method The method ('get', 'GET', 'post' or 'POST')
158    \param extra_data If supplied, extra data to be delivered to the callback.
159    \returns a new XMLHTTPRequest object.
160                                                                                       ****************************************************************************************/
161TableSearchDisplay.prototype.comms_AjaxRequest = function ( url, callback, method, extra_data ) {
162    /********************************************************************************//**
163                                                                                       *   \brief Create a generic XMLHTTPObject.
164                                                                                       *   This will account for the various flavors imposed by different browsers.
165                                                                                       *   \returns a new XMLHTTPRequest object.
166                                                                                       ************************************************************************************/
167
168    function createXMLHTTPObject()
169    {
170        // Various systems will produce one or more instances of these classes.
171        var XMLHttpArray = [
172            function () {
173                return new XMLHttpRequest()},                   // Your average browser.
174            function () {
175                return new ActiveXObject("Msxml2.XMLHTTP")},    // These are for Microsoft browsers.
176            function () {
177                return new ActiveXObject("Msxml2.XMLHTTP")},
178            function () {
179                return new ActiveXObject("Microsoft.XMLHTTP")}
180            ];
181        
182        var xmlhttp = false;
183        
184        // Go through the array until you hit a dispatcher that was properly instantiated.
185        // Once you find it, make that our dispatcher.
186        for ( var i=0; i < XMLHttpArray.length; i++ )
187            {
188            try
189                {
190                xmlhttp = XMLHttpArray[i]();
191            }
192            catch(e)
193                {
194                continue;
195            };
196            break;
197        };
198    
199        return xmlhttp;
200    };
201    
202    // Set up a dispatcher.
203    var req = createXMLHTTPObject();
204    req.finalCallback = callback;   // Point to the given callback.
205    var sVars = null;
206    method = method.toString().toUpperCase();   // Clean the input method to all uppercase.
207
208    // Split the URL up, if this is a POST.
209    if (method == "POST" ) {
210        var rmatch = /^([^\?]*)\?(.*)$/.exec(url);
211        url = rmatch[1];
212        sVars = rmatch[2];
213        // This horrible, horrible kludge, is because Drupal insists on having its q parameter in the GET list only.
214        var rmatch_kludge = /(q=admin\/settings\/bmlt)&?(.*)/.exec(rmatch[2]);
215        if (rmatch_kludge && rmatch_kludge[1] ) {
216            url += '?'+rmatch_kludge[1];
217            sVars = rmatch_kludge[2];
218        };
219    };
220    
221    // If we are passing some extra data to the callback, we do so here.
222    if (extra_data ) {
223        req.extra_data = extra_data;
224    };
225    
226    // Create the HTTPRequest
227    req.open(method, url, true);
228    
229    // In the case of POST, our data is separate from the URI.
230    if (method == "POST" ) {
231        req.setRequestHeader("Method", "POST "+url+" HTTP/1.1");
232        req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
233    };
234    
235    // Set the function to be called as the request does its thing.
236    req.onreadystatechange = function ( ) {
237        if (req.readyState != 4 ) { return;  // We only care about the end.
238        }
239        if(req.status != 200 ) { return;     // If we failed, we drop out.
240        }
241        callback(req, req.extra_data);   // If we made it this far, we call our callback.
242        req = null;                         // Kill the request.
243    };
244    
245    req.send(sVars); // Throw the switch, Igor!
246
247    return req;
248};
249
250/************************************************************************************//**
251    \brief  Called to load all the format objects for the search.
252            This will give us the "used formats" for the given filtered search. This will
253            request a JSON response from the server. It does not use switcher=GetFormats.
254            Instead, it requests the formats only rom switcher=GetSearchResults. This will
255            allow the number of formats to be limited to only those used for the search results.
256            This uses the filter established by the query parameters passed in on instantiation.
257            This function initiates an AJAX call that will return and call the class function
258            comms_callback_loadloadFormatData.
259                                                                                       ****************************************************************************************/
260TableSearchDisplay.prototype.comms_loadFormatData = function () {
261    // Since loading the formats happens, regardless of the weekday, we set all the spinners going.
262    for ( var i = 0; i < 7; i++ )
263        {
264        var tab_object = this.my_weekday_links[i];
265        tab_object.className = 'bmlt_table_header_weekday_list_element is_loading';
266    };
267    
268    // Ask for the formats used by the search set (not every format in the database).
269    // Tell the formats to come alone, bearing small, unmarked bills.
270    var uri = encodeURI(this.my_search_query_params + '&bmlt_settings_id=' + this.my_settings_id + '&get_formats_only=1').toString();
271    uri =  this.my_ajax_base_uri + encodeURI('switcher=GetSearchResults&') + ur
271i;
272    this.m_ajax_request = this.comms_AjaxRequest(uri, this.comms_callback_loadloadFormatData, 'get', this);
273};
274
275/************************************************************************************//**
276    \brief  Called to load a weekday.
277            This is called when the tab doesn't yet have a cache. It requests a JSON response
278            from the Root server for switcher=GetSearchResults.
279            This uses the filter established by the query parameters passed in on instantiation.
280            This function initiates an AJAX call that will return and call the class function
281            comms_callback_loadloadWeekdayData.
282    \param in_tab_object The tab DOM object.
283                                                                                       ****************************************************************************************/
284TableSearchDisplay.prototype.comms_loadWeekdayData = function ( in_tab_object ) {
285    // We append the weekday, sort and sort order parameters to the URI.
286    var uri = encodeURI(this.my_search_query_params + '&sort_keys=' + this.my_selected_sort_key  + ((this.my_sort_dir == 'desc') ? ',desc' : '') +'&bmlt_settings_id=' + this.my_settings_id + '&weekdays[]=' + (parseInt(in_tab_object.index) + 1).toString()).toString();
287    uri =  this.my_ajax_base_uri + encodeURI('switcher=GetSearchResults&') + uri;
288    this.m_ajax_request = this.comms_AjaxRequest(uri, this.comms_callback_loadloadWeekdayData, 'get', in_tab_object);
289};
290
291/****************************************************************************************
292 * ##################################### AJAX CALLBACKS ##################################*
293 ****************************************************************************************/
294
295/************************************************************************************//**
296    \brief  Called when the format data is loaded.
297            This loads the function object with a cache of the retrieved JSON data. This
298            data is loaded once, then used to construct the format objects in the table.
299            This is called once, at the start. After the format data has been loaded, the
300            callback then initiates a new search for the current weekday.
301    \param in_response_object The HTTPRequest object for this AJAX call.
302    \param in_context_object The TableSearchDisplay object that initiated the call.
303                                                                                       ****************************************************************************************/
304TableSearchDisplay.prototype.comms_callback_loadloadFormatData = function ( in_response_object, in_context_object ) {
305    for ( var i = 0; i < 7; i++ )
306        {
307        var tab_object = in_context_object.my_weekday_links[i];
308        tab_object.className = 'bmlt_table_header_weekday_list_element';
309    };
310
311    eval("var format_data = " + in_response_object.responseText + ";");    // Extract the JSON object with the returned data.
312    in_context_object.my_format_data = format_data.formats;
313
314    // Now that we have the formats, get the meeting list for today.
315    var d = new Date();
316    in_context_object.my_header_container.selectTab(d.getDay());
317};
318
319/************************************************************************************//**
320    \brief  Called when the weekday data is loaded.
321            This loads the tab object with a cache of the retrieved JSON data, and has
322            the tab object select itself.
323    \param in_response_object The HTTPRequest object for this AJAX call.
324    \param in_tab_object The Tab object that denotes the chosen weekday.
325                                                                                       ****************************************************************************************/
326TableSearchDisplay.prototype.comms_callback_loadloadWeekdayData = function ( in_response_object, in_tab_object ) {
327    eval("var json_response = " + in_response_object.responseText + ";");    // Extract the JSON object with the returned data.
328    in_tab_object.weekday_json_data = json_response;
329    in_tab_object.select();
330};
331
332/****************************************************************************************
333 * #################################### UTILITY ROUTINES #################################*
334 ****************************************************************************************/
335
336/************************************************************************************//**
337    \brief  This turns military time to ante meridian format or cleaned military time.
338            If the time is midnight or noon, the localized string for that is returned.
339    \param in_time_string A string with the time in military format.
340    \returns A string, with the properly formatted time ("Noon", "Midnight", "HH:MM" or "HH:MM AM|PM").
341                                                                                       ****************************************************************************************/
342TableSearchDisplay.prototype.utility_convertTime = function ( in_time_string ) {
343    var ret = in_time_string;
344    var time_array = in_time_string.split(":");
345    var hours = parseInt(time_array[0]);
346    var minutes = parseInt(time_array[1]);
347    
348    // We use "Noon" and "Midnight" strings for those times.
349    // Kludge, to make "Midnight" late, as the computer wants it to be early.
350    // Anything over 23:54:59 is "Midnight."
351    if (((0 == hours) && (0 == minutes)) || ((hours == 23) && (minutes >= 55)) ) {
352        ret = g_table_ampm_array[3];    // Midnight
353    }
354    else if ((12 == hours) && (0 == minutes) ) {
355        ret = g_table_ampm_array[2];    // Noon
356    }
357    else if (!this.my_military_time ) {
358        var ampm = ( hours >= 12 ) ? g_table_ampm_array[1] : g_table_ampm_array[0];
359        
360        // Subtract 12 from afternoon.
361        if (hours > 12 ) {
362            hours -= 12;
363        };
364        
365        // Padding with a leading 0.
366        if (minutes < 10 ) {
367            minutes = "0" + minutes.toString();
368        };
369        
370        ret = hours.toString() + ':' + minutes + ' ' + ampm;
371    }
372    else
373        {
374        // Padding with a leading 0.
375        if (minutes < 10 ) {
376            minutes = "0" + minutes.toString();
377        };
378        
379        
380        ret = hours.toString() + ':' + minutes.toString();
381    };
382    
383    return ret;
384};
385
386/************************************************************************************//**
387    \brief  This builds a readable aggregated street address from the components in the JSON.
388            Very simply, we just do the location name and street address.
389    \param in_json_data The JSON data for one meeting.
390    \returns a string, with the address HTML.
391                                                                                       ****************************************************************************************/
392TableSearchDisplay.prototype.utility_createStreetAddress = function ( in_json_data ) {
393    var ret = '';
394
395    if (in_json_data.location_text && in_json_data.location_text.toString() ) {
396        ret = '<span class="bmlt_table_location_text_span">';  // This allows folks to style the location name.
397        ret += in_json_data.location_text.toString();
398        if ((ret != '') && in_json_data.location_street && in_json_data.location_street.toString() ) {
399            ret += '<span class="bmlt_table_location_text_comma_span">, </span>';
400        };
401        ret += '</span>';
402    };
403    
404    if (in_json_data.location_street && in_json_data.location_street.toString() ) {
405        ret += '<span class="bmlt_table_location_street_span">';  // This allows folks to style the street.
406        ret += in_json_data.location_street.toString();
407        ret += '</span>';
408    };
409        
410    return ret;
411};
412
413/************************************************************************************//**
414    \brief  This builds an aggregated town from the components in the JSON.
415    \param in_json_data The JSON data for one meeting.
416    \returns a string, with the town HTML.
417                                                                                       ****************************************************************************************/
418TableSearchDisplay.prototype.utility_createAddressTown = function ( in_json_data ) {
419    var hasTown = false;
420    var ret = '<span class="bmlt_table_town_span">';  // This allows folks to style the town.
421    
422    // We prefer boroughs, as folks think of them as "towns."
423    if (in_json_data.location_city_subsection && in_json_data.location_city_subsection.toString() ) {
424        ret += in_json_data.location_city_subsection.toString();
425        hasTown = true;
426    }
427    else
428        {
429        if (in_json_data.location_municipality && in_json_data.location_municipality.toString() ) {
430            ret += in_json_data.location_municipality.toString();
431            hasTown = true;
432        };
433    };
434    
435    if (hasTown && in_json_data.location_province && in_json_data.location_province.toString() ) {
436        ret += '<span class="bmlt_table_town_comma_span">, </span>';
437    };
438    
439    ret += '</span>';
440    
441    // Add the state.
442    if (in_json_data.location_province && in_json_data.location_province.toString() ) {
443        ret += '<span class="bmlt_table_state_span">';  // This allows folks to style the state.            
444        ret += in_json_data.location_province.toString();
445        ret += '</span>';
446    };
447    
448    return ret;
449};
450
451/****************************************************************************************
452 * #################################### THIRD-PARTY CODE #################################*
453 ****************************************************************************************/
454/**
455    \param in_pattern   The printf pattern to use
456    \param ... any variables needed to satisfy the pattern.
457sprintf() for JavaScript 0.6
458
459Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
460All rights reserved.
461
462Redistribution and use in source and binary forms, with or without
463modification, are permitted provided that the following conditions are met:
464 * Redistributions of source code must retain the above copyright
465      notice, this list of conditions and the following disclaimer.
466 * Redistributions in binary form must reproduce the above copyright
467      notice, this list of conditions and the following disclaimer in the
468      documentation and/or other materials provided with the distribution.
469 * Neither the name of sprintf() for JavaScript nor the
470      names of its contributors may be used to endorse or promote products
471      derived from this software without specific prior written permission.
472
473THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
474ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
475WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
476DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY
477DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
478(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
479LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
480ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
481(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
482SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
483
484
485Changelog:
4862007.04.03 - 0.1:
487 - initial release
4882007.09.11 - 0.2:
489 - feature: added argument swapping
4902007.09.17 - 0.3:
491 - bug fix: no longer throws exception on empty paramenters (Hans Pufal)
4922007.10.21 - 0.4:
493 - unit test and patch (David Baird)
4942010.05.09 - 0.5:
495 - bug fix: 0 is now preceeded with a + sign
496 - bug fix: the sign was not at the right position on padded results (Kamal Abdali)
497 - switched from GPL to BSD license
4982010.05.22 - 0.6:
499 - reverted to 0.4 and fixed the bug regarding the sign of the number 0
500 Note:
501 Thanks to Raphael Pigulla <raph (at] n3rd [dot) org> (http://www.n3rd.org/)
502 who warned me about a bug in 0.5, I discovered that the last update was
503 a regress. I appologize for that.
504 */
505TableSearchDisplay.prototype.utility_sprintf = function () {
506    function str_repeat(   i,
507        m
508    ) {
509        for (var o = []; m > 0; o[--m] = i);
510        return o.join('');
511    };
512    
513    var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = '';
514    
515    while (f)
516        {
517        if (m = /^[^\x25]+/.exec(f)) {
518            o.push(m[0]);
519        }
520        else if (m = /^\x25{2}/.exec(f)) {
521            o.push('%');
522        }
523        else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
524            if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) {
525                throw('Too few arguments.');
526            };
527            
528            if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) {
529                throw('Expecting number but found ' + typeof(a));
530            };
531            
532            switch (m[7])
533                {
534            case 'b': a = a.toString(2); break;
535            case 'c': a = String.fromCharCode(a); break;
536            case 'd': a = parseInt(a,10); break;
537            case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
538            case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
539            case 'o': a = a.toString(8); break;
540            case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
541            case 'u': a = Math.abs(a); break;
542            case 'x': a = a.toString(16); break;
543            case 'X': a = a.toString(16).toUpperCase(); break;
544            };
545            
546            a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+' + a : a);
547            c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
548            x = m[5] - String(a).length - s.length;
549            p = m[5] ? str_repeat(c, x) : '';
550            o.push(s + (m[4] ? a + p : p + a));
551        }
552        else
553            {
554            throw('Huh ?!');
555        };
556        
557        f = f.substring(m[0].length);
558    };
559    
560    return o.join('');
561};
562
563/****************************************************************************************
564 * ################################## DOM BUILDER ROUTINES ###############################*
565 * ****************************************************************************************
566    These routines all construct the DOM tree for the shortcode. The weekday header is
567    displayed all the time (with the selected tab changing), but the table data (and the
568    header) is reconstructed from scratch each time.
569 *************************************************************************************//**
570    \brief Creates the weekday selection header.
571    \returns the instantiated DOM object.
572                                                                                        ****************************************************************************************/
573TableSearchDisplay.prototype.domBuilder_CreateHeader = function () {
574    this.my_weekday_links = new Array();
575    this.my_header_container = document.createElement('ul');   // Create the header container.
576    this.my_header_container.handler = this;
577    this.my_header_container.className = 'bmlt_table_header_weekday_list';
578    
579    this.my_container_object.appendChild(this.my_header_container);
580
581    var startingIndex = this.my_start_weekday;  // Allows us to start from different weekdays.
582    
583    // Build the list of weekday tab objects.
584    for ( var i = 0; i < 7; i++ )
585        {
586        this.my_weekday_links[i] = this.domBuilder_CreateOneWeekday(startingIndex++);
587        if (startingIndex == 7 ) {
588            startingIndex = 0;
589        };
590    
591        this.my_header_container.appendChild(this.my_weekday_links[i]);
592    };
593    
594    // This inline function tells the header to select the indexed (0-based) tab.
595    this.my_header_container.selectTab = function ( in_weekday  ///< The day of the week (0 is Sunday, 6 is Saturday)
596    ) {
597        for ( var i = 0; i < 7; i++ )
598            {
599            if (this.handler && this.handler.my_weekday_links && this.handler.my_weekday_links[i] ) {
600                // If this is the day we are looking at, we select it.
601                if (this.handler.my_weekday_links[i].index == in_weekday ) {
602                    this.handler.my_weekday_links[i].select();
603                };
604            };
605        };
606    };
607
608    this.comms_loadFormatData();
609};
610
611/************************************************************************************//**
612    \brief Creates one weekday button for the header.
613    \param in_weekday_index The index of the weekday to be created. 0 is Sunday, 6 is Saturday.
614    \returns the instantiated DOM object.
615                                                                                       ****************************************************************************************/
616TableSearchDisplay.prototype.domBuilder_CreateOneWeekday = function ( in_weekday_index ) {
617    // First set up the weekday tab element. It will anchor the context for the data.
618    var weekdayElement = document.createElement('li');        // Create the basic list element.
619    weekdayElement.handler = this;  // We will do this a lot. It allows anyone to grab the object context from the element.
620    weekdayElement.index = in_weekday_index;    // This is the 0-based weekday index.
621    weekdayElement.rest_className = 'bmlt_table_header_weekday_list_element';
622    weekdayElement.className = weekdayElement.rest_className;
623    
624    // Today is a great day!
625    var d = new Date();
626    if (d.getDay() == in_weekday_index ) {
627        weekdayElement.rest_className += ' is_today';
628    };
629    
630    // If we click, we select.
631    weekdayElement.onclick = function () {
632        this.select()};
633    
634    // This inline function will force a reload of the search for the given weekday.
635    weekdayElement.reload = function () {
636        if (this.className != (this.rest_className + ' is_loading') ) {
637            if (this.handler.my_body_container )   // Clear any preexisting condition.
638                {
639                this.handler.my_body_container.parentNode.removeChild(this.handler.my_body_container);
640                this.handler.my_body_container.innerHTML = '';
641                this.handler.my_body_container = null;
642            };
643        
644            this.weekday_json_data = null;
645            this.className = this.rest_className + ' is_loading';
646            
647            // Set a title, saying what is happening.
648            this.setAttribute('title', this.handler.utility_sprintf(g_table_header_tab_loading_format, g_table_weekday_long_name_array[this.index]));
649            
650            this.handler.comms_loadWeekdayData(this);
651        };
652    };
653    
654    // This inline function will read the data from the server if it is not already cached.
655    // If the tab has cached data, then it deselects all the other tabs, selects itself, and populates the main area with the data in its cache.
656    weekdayElement.select = function () {
657        if (!this.weekday_json_data ) {
658            this.reload();
659        }
660        else
661            {
662            if (this.handler.my_body_container )   // Clear any preexisting condition.
663                {
664                this.handler.my_body_container.parentNode.removeChild(this.handler.my_body_container);
665                this.handler.my_body_container.innerHTML = '';
666                this.handler.my_body_container = null;
667            };
668
669            for ( var i = 0; i < 7; i++ )
670                {
671                if (this.handler && this.handler.my_weekday_links && this.handler.my_weekday_links[i] ) {
672                    var tabObject = this.handler.my_weekday_links[i];
673                    // If the indexed object is not ours, then we deselect it.
674                    if (tabObject.index != this.index ) {
675                        // This is not selected or loading.
676                        tabObject.className = tabObject.rest_className;
677                        // Set a title, saying what will happen when this is clicked.
678                        tabObject.setAttribute('title', this.handler.utility_sprintf(g_table_header_tab_format, g_table_weekday_long_name_array[tabObject.index]));
679                    };
680                };
681            };
682        
683            this.handler.my_selected_tab = this;
684            this.sort_key = this.handler.my_selected_sort_key;
685            this.sort_dir = this.handler.my_sort_dir;
686            this.handler.domBuilder_PopulateWeekday(this.weekday_json_data, this.index, this.sort_key, this.sort_dir, this);
687        };
688    };
689    
690    // This will be the span element that holds the weekday name.
691    var textNode = document.createElement('span');
692    textNode.appendChild(document.createTextNode(g_table_weekday_name_array[in_weekday_index]));
693    textNode.rest_className = 'bmlt_table_weekday_span';
694    textNode.className = textNode.rest_className;
695    
696    // We add these functions to handle changing the class when the cursor moves over the tab.
697    textNode.onmouseover = function () {
698        if (this.parentNode.className != (this.parentNode.rest_className + ' is_loading') ) {
699            if (this.parentNode.handler.my_selected_tab != this.parentNode ) {
700                this.parentNode.className = this.parentNode.rest_className + ' bmlt_table_mouseover';
701            };
702        };
703    };
704    
705    // We restore the class when we mouse out. However, we ignore it if we are loading.
706    textNode.onmouseout = function () {
707        if (!this.parentNode.className.match(/is_loading/) ) {
708            this.parentNode.className = this.parentNode.rest_className;
709            if (this.parentNode.handler.my_selected_tab == this.parentNode ) {
710                this.parentNode.className += ' is_selected';
711            };
712        };
713    };
714    
715    weekdayElement.appendChild(textNode);
716    
717    // This is the throbber image. It will be hidden unless the weekday is loading.
718    var throbberImage = document.createElement('img');
719    // We replace the "default" with our theme.
720    var regex = /themes\x2Fdefault\x2F/;
721    throbberImage.src = g_table_throbber_img_src.replace(regex, 'themes/' + this.my_theme_name.toString() + '/');
722    throbberImage.className = 'bmlt_table_throbber_image';
723    weekdayElement.appendChild(throbberImage);
724    
725    return weekdayElement;
726};
727
728/************************************************************************************//**
729    \brief Populates the meeting result table for the given data.
730    \param in_search_results_json A JSON object with the meeting search results.
731    \param in_index The 0-based weekday index.
732    \param in_sort_key The sort key, for the data sort.
733    \param in_sort_dir The sort direction, for the data sort. It will be 'asc' or 'desc'.
734    \param in_tabObject The tab object, which will be set to selected when the weekday is done.
735    \returns the instantiated DOM object.
736                                                                                       ****************************************************************************************/
737TableSearchDisplay.prototype.domBuilder_PopulateWeekday = function ( in_search_results_json, in_index, in_sort_key, in_sort_dir, in_tabObject ) {
738    if (null != this.my_body_container )   // Clear any preexisting condition.
739        {
740        this.my_body_container.parentNode.removeChild(this.my_body_container);
741        this.my_body_container.innerHTML = '';
742        this.my_body_container = null;
743    };
744    
745    this.my_body_container = document.createElement('div');        // Create the main display container element.
746    this.my_body_container.rest_className = 'bmlt_table_div';
747    this.my_body_container.rest_className += (' bmlt_table_' + ((in_sort_dir == 'asc') ? 'sort_asc' : 'sort_desc') + '_div');
748    this.my_body_container.rest_className += (' bmlt_table_sort_' + in_sort_key.replace(/,/g, '_') + '_div');
749    this.my_body_container.className = this.my_body_container.rest_className;
750    this.my_body_container_header = null;
751    this.my_body_container_body = null;
752    
753    // Attach all the various DOM elements.
754    this.my_container_object.appendChild(this.my_body_container);        
755    this.my_body_container.appendChild(this.domBuilder_PopulateWeekdayHeader(in_index, in_sort_key, in_sort_dir));
756    this.my_body_container.appendChild(this.domBuilder_PopulateWeekdayBody(in_search_results_json));
757    
758    var div = document.createElement('div');
759    div.className = 'bmlt_breaker_div';
760    this.my_body_container.appendChild(div);
761
762    in_tabObject.className = in_tabObject.rest_className + ' is_selected';
763};
764
765/************************************************************************************//**
766    \brief Populates the header for the meetings table.
767    \param in_index The 0-based weekday index.
768    \param in_sort_key The sort key, for the data sort.
769    \param in_sort_dir The sort direction, for the data sort. It will be 'asc' or 'desc'.
770    \returns the instantiated DOM object.
771                                                                                       ****************************************************************************************/
772TableSearchDisplay.prototype.domBuilder_PopulateWeekdayHeader = function ( in_index, in_sort_key, in_sort_dir ) {
773    if (null != this.my_body_container_header )   // Clear any preexisting condition.
774        {
775        this.my_body_container_header.parentNode.removeChild(this.my_body_container_header);
776        this.my_body_container_header.innerHTML = '';
777        this.my_body_container_header = null;
778    };
779    
780    this.my_body_container_header = document.createElement('ul');        // Create the header element.
781    this.my_body_container_header.className = 'bmlt_table_header_ul';
782    
783    var theElement = this.domBuilder_PopulateWeekdayHeaderColumn(g_table_time_header_text, this.my_sort_key_time, ((in_sort_key == this.my_sort_key_time) ? in_sort_dir : 'asc'), in_sort_key == this.my_sort_key_time);
784    theElement.className = 'bmlt_table_header_li bmlt_table_header_li_time';
785    this.my_body_container_header.appendChild(theElement);
786    
787    theElement = this.domBuilder_PopulateWeekdayHeaderColumn(g_table_town_header_text, this.my_sort_key_town, ((in_
787sort_key == this.my_sort_key_town) ? in_sort_dir : 'asc'), in_sort_key == this.my_sort_key_town);
788    theElement.className = 'bmlt_table_header_li bmlt_table_header_li_town';
789    this.my_body_container_header.appendChild(theElement);
790    
791    theElement = this.domBuilder_PopulateWeekdayHeaderColumn(g_table_name_header_text, this.my_sort_key_meeting_name, ((in_sort_key == this.my_sort_key_meeting_name) ? in_sort_dir : 'asc'), in_sort_key == this.my_sort_key_meeting_name);
792    theElement.className = 'bmlt_table_header_li bmlt_table_header_li_name';
793    this.my_body_container_header.appendChild(theElement);
794    
795    theElement = this.domBuilder_PopulateWeekdayHeaderColumn(g_table_address_header_text, this.my_sort_key_address, ((in_sort_key == this.my_sort_key_address) ? in_sort_dir : 'asc'), in_sort_key == this.my_sort_key_address);
796    theElement.className = 'bmlt_table_header_li bmlt_table_header_li_address';
797    this.my_body_container_header.appendChild(theElement);
798    
799    theElement = this.domBuilder_PopulateWeekdayHeaderColumn(g_table_format_header_text, null, null, false);
800    theElement.className = 'bmlt_table_header_li bmlt_table_header_li_format';
801    this.my_body_container_header.appendChild(theElement);
802    
803    return this.my_body_container_header;
804};
805
806/************************************************************************************//**
807    \brief Populates the header for the meetings table.
808    \param in_name The text name for the column.
809    \param in_sort_key The sort key, for the data sort.
810    \param in_sort_dir The sort direction, for the data sort. It will be 'asc' or 'desc'.
811    \param in_is_selected A boolean value. True, if this is the sort key.
812    \returns the instantiated DOM object.
813                                                                                       ****************************************************************************************/
814TableSearchDisplay.prototype.domBuilder_PopulateWeekdayHeaderColumn = function ( in_name, in_sort_key, in_sort_dir, in_is_selected ) {
815    var theElement =  document.createElement('li');       // Create the column element
816    theElement.key = in_sort_key;                            // This will be the sort key for this column.
817    var textNode = document.createElement('span');
818    textNode.appendChild(document.createTextNode(in_name));
819    textNode.is_selected = false;
820    textNode.rest_className = 'bmlt_table_header_span';
821    textNode.className = textNode.rest_className;
822    textNode.sort_dir = in_sort_dir;
823    textNode.sort_key = in_sort_key;
824    textNode.handler = this;
825    
826    if (in_sort_key ) {
827        if (in_is_selected ) {
828            textNode.rest_className = 'bmlt_table_header bmlt_table_header_selected' + ' bmlt_table_header_selected_sort_direction_' + ((in_sort_dir == 'asc') ? 'asc' : 'desc');
829            textNode.className = textNode.rest_className;
830            textNode.is_selected = true;
831        };
832    
833        textNode.onmouseover = function () {
834            this.className = this.rest_className + ' bmlt_table_mouseover';
835        };
836    
837        textNode.onmouseout = function () {
838            this.className = this.rest_className;
839        };
840
841        textNode.onclick = function () {
842            if (this.handler.my_selected_sort_key == this.sort_key ) {
843                this.sort_dir = ((this.sort_dir == 'asc') ? 'desc' : 'asc');
844            };
845        
846            this.handler.my_selected_sort_key = this.sort_key;
847            this.handler.my_sort_dir = this.sort_dir;
848            for ( var i = 0; i < 7; i++ )
849                {
850                this.handler.my_weekday_links[i].weekday_json_data = null;
851            };
852            
853            this.handler.my_selected_tab.reload();
854        };
855    }
856    else
857        {
858        textNode.rest_className = 'bmlt_header_formats_span';
859        textNode.className = textNode.rest_className;
860    };
861    
862    theElement.appendChild(textNode);
863
864    return theElement;
865};
866
867/************************************************************************************//**
868    \brief Populates the main table for the meetings table.
869    \param in_json_data The JSON data for all the meetings.
870    \returns the instantiated DOM object.
871                                                                                       ****************************************************************************************/
872TableSearchDisplay.prototype.domBuilder_PopulateWeekdayBody = function ( in_json_data ) {
873    if (null != this.my_body_container_body )   // Clear any preexisting condition.
874        {
875        this.my_body_container_body.parentNode.removeChild(this.my_body_container_body);
876        this.my_body_container_body.innerHTML = '';
877        this.my_body_container_body = null;
878    };
879    
880    this.my_body_container_body = document.createElement('ul');
881    this.my_body_container_body.className = 'bmlt_table_data_ul';
882    
883    if (in_json_data.length ) {
884        for ( var i = 0; i < in_json_data.length; i++ )
885            {
886            var listElement = document.createElement('li');
887            listElement.className = 'bmlt_table_data_ul_li';
888            listElement.className += (' bmlt_row' + ((i % 2) ? '_odd' : '_even'));
889            var meeting_json = in_json_data[i];
890            var meeting_object = this.domBuilder_PopulateWeekdayBody_one_meeting(meeting_json);
891            listElement.appendChild(meeting_object);
892            this.my_body_container_body.appendChild(listElement);
893        };
894    }
895    else
896        {
897        var listElement = document.createElement('li');
898        listElement.className = 'bmlt_table_data_ul_no_meetings_li';
899        listElement.innerHTML = '<span class="bmlt_table_no_meetings_span">' + this.utility_sprintf(g_table_no_meetings_format, g_table_weekday_long_name_array[this.my_selected_tab.index]) + '</span>';
900        this.my_body_container_body.appendChild(listElement);
901    };
902    
903    return this.my_body_container_body;
904};
905
906/************************************************************************************//**
907    \brief  Populates one row (meeting)
908    \param in_json_data The JSON data for the meeting.
909    \returns the instantiated DOM object.
910                                                                                       ****************************************************************************************/
911TableSearchDisplay.prototype.domBuilder_PopulateWeekdayBody_one_meeting = function ( in_json_data ) {
912    var rowElement = document.createElement('ul');
913    rowElement.className = 'bmlt_table_data_ul_li_ul';
914    var comments = null;
915    
916    if (in_json_data.comments ) {
917        comments = in_json_data.comments;
918    };
919    if (in_json_data.location_info ) {
920        location_info = in_json_data.location_info;
921    };
922    
923    var latitude = in_json_data.latitude;
924    var longitude = in_json_data.longitude;
925
926    var time_column = this.domBuilder_PopulateWeekdayBody_one_column('time', this.utility_convertTime(in_json_data.start_time.toString()), 0, 0);
927    var town_column = this.domBuilder_PopulateWeekdayBody_one_column('town', this.utility_createAddressTown(in_json_data), 0, 0);
928    var name_column = this.domBuilder_PopulateWeekdayBody_one_column('name', in_json_data.meeting_name.toString(), 0, 0);
929    var address_column = this.domBuilder_PopulateWeekdayBody_one_column('address', this.utility_createStreetAddress(in_json_data), latitude, longitude);
930    var format_column = this.domBuilder_PopulateWeekdayBody_one_column('formats', in_json_data.formats.toString(), 0, 0);
931    
932    rowElement.appendChild(time_column);
933    rowElement.appendChild(town_column);
934    rowElement.appendChild(name_column);
935    rowElement.appendChild(address_column);
936    rowElement.appendChild(format_column);
937    
938    if (in_json_data.comments ) {
939        var comments_column = this.domBuilder_PopulateWeekdayBody_one_column('comments', in_json_data.comments.toString(), 0, 0);
940        rowElement.appendChild(comments_column);
941    };
942    if (in_json_data.location_info ) {
943        var location_info_column = this.domBuilder_PopulateWeekdayBody_one_column('location_info', in_json_data.location_info.toString(), 0, 0);
944        rowElement.appendChild(location_info_column);
945    };
946    
947    var div = document.createElement('div');
948    div.className = 'bmlt_breaker_div';
949    rowElement.appendChild(div);
950    
951    return rowElement;
952};
953
954/************************************************************************************//**
955    \brief Populates one column of a meeting row.
956    \param in_tag The tag used as a key for this column
957    \param in_string The name of the column (displayed as a string)
958    \param in_latitude if supplied, the longitude of a meeting (is usually null)
959    \param in_longitude if supplied, the latitude of a meeting (is usually null)
960    \returns the instantiated DOM object.
961                                                                                       ****************************************************************************************/
962TableSearchDisplay.prototype.domBuilder_PopulateWeekdayBody_one_column = function ( in_tag, in_string, in_latitude, in_longitude ) {
963    var columnElement = document.createElement('li');
964    columnElement.className = 'bmlt_table_data_ul_li_ul_li bmlt_table_data_ul_li_ul_li_' + in_tag;
965
966    if (in_tag == 'formats' ) {
967        var formats_div = this.domBuilder_createFormats(in_string);
968        if (formats_div ) {
969            columnElement.appendChild(formats_div);
970        };
971    }
972    else if (in_latitude && in_longitude ) {
973        var textNode = document.createElement('span');
974        textNode.className = 'bmlt_table_data_ul_li_ul_li_span bmlt_table_data_ul_li_ul_li_span_' + in_tag;
975        var anchorNode = document.createElement('a');
976        textNode.className = 'bmlt_table_data_ul_li_ul_li_span_a bmlt_table_data_ul_li_ul_li_span_a_' + in_tag;
977        anchorNode.href= this.utility_sprintf(g_table_map_link_uri_format, parseFloat(in_latitude), parseFloat(in_longitude));
978        anchorNode.innerHTML = in_string;
979        textNode.appendChild(anchorNode);
980        columnElement.appendChild(textNode);
981    }
982    else
983        {
984        var textNode = document.createElement('span');
985        textNode.className = 'bmlt_table_data_ul_li_ul_li_span bmlt_table_data_ul_li_ul_li_span_' + in_tag;
986        textNode.innerHTML = in_string ? in_string : '&nbsp;';
987        columnElement.appendChild(textNode);
988    };
989        
990    return columnElement;
991};
992
993/************************************************************************************//**
994    \brief  This builds a div element for the given formats. The div is filled with spans; one for each format.
995    \param in_formats_string The JSON data for one meeting.
996    \returns the instantiated DOM object.
997                                                                                       ****************************************************************************************/
998TableSearchDisplay.prototype.domBuilder_createFormats = function ( in_formats_string ) {
999    var ret = document.createElement('div');
1000    ret.className = 'bmlt_formats_div';
1001    var formatsArray = in_formats_string.split(',');
1002    
1003    for ( var i = 0; i < formatsArray.length; i++ )
1004        {
1005        var format_string = formatsArray[i].toString();
1006        var span = this.domBuilder_createOneFormatSpan(format_string);
1007        if (span ) {
1008            ret.appendChild(span);
1009        };
1010    };
1011    
1012    if (ret.childNodes.length == 0 ) {
1013        ret = null;
1014    }
1015    else
1016        {
1017        var div = document.createElement('div');
1018        div.className = 'bmlt_breaker_div';
1019        ret.appendChild(div);
1020    };
1021    
1022    return ret;
1023};
1024
1025/************************************************************************************//**
1026    \brief  This builds a span element for the given format.
1027    \param in_format_string format key.
1028    \returns the instantiated DOM object.
1029                                                                                       ****************************************************************************************/
1030TableSearchDisplay.prototype.domBuilder_createOneFormatSpan = function ( in_format_string ) {
1031    if (in_format_string ) {
1032        var ret = document.createElement('span');
1033        ret.rest_className = 'bmlt_format_span bmlt_format_span_' + in_format_string;
1034        ret.className = ret.rest_className;
1035        ret.appendChild(document.createTextNode(in_format_string));
1036        for ( var i = 0; i < this.my_format_data.length; i++ )
1037            {
1038            var format_object = this.my_format_data[i];
1039            
1040            if (format_object.key_string == in_format_string ) {
1041                var title_string = format_object.name_string;
1042                ret.setAttribute('title', title_string);
1043                var description_string = format_object.description_string;
1044                ret.onclick = function () {
1045                    alert(description_string);
1046                };
1047            
1048                ret.onmouseover = function () {
1049                    this.className = this.rest_className + ' bmlt_table_mouseover';
1050                };
1051    
1052                ret.onmouseout = function () {
1053                    this.className = this.rest_className;
1054                };
1055            };
1056        };
1057        return ret;
1058    };
1059    
1060    return null;
1061};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.