/** * digital ROCK jQuery based Javascript API * * @link http://www.digitalrock.de * @author Ralf Becker * @copyright 2010-18 by RalfBecker@digitalROCK.de * @version $Id: dr_api.js 1250 2015-06-18 06:49:01Z ralfbecker $ */ /** * Widgets defined in this file: * * - DrWidget: universal widget, which can display all data-types and implement reload-free / in-place navigation * - Resultlist: displays results and rankings, inherits from Startlist * - Startlist: displays startlists and implementes automatic scrolling and rotation through multiple results * - Results: displays top results of multiple categories and allows to change competition * - Starters: displays registration data * - Competitions: displays calendar, allows to change year and optional filter * - Profile: display profile of an athlete based on a html template * - ResultTemplate: displays result based on a html template * - Aggregated: displays an aggregated ranking: national team ranking, GER sektionenwertung or SUI regionalzentren * - DrBaseWidget: virtual base of all widgets implements json(p) loading of data * - DrTable: creates and updates a table from data and a column definition, used by most widgets * * In almost all cases you only need to use DrWidget as shown in following example: * * * * * *
* * * @link http://www.digitalrock.de/egroupware/ranking/README describes available parameters for json url * @link https://developers.google.com/webmasters/ajax-crawling/ describes supported ajax crawling scheme * @link http://svn.outdoor-training.de/repos/trunk/ranking/inc/class.ranking_widget.inc.php php class implementing ajax crawling */ /** * Example with multi-result scrolling (use c= and r= for cat and route) * * http://www.digitalrock.de/egroupware/ranking/sitemgr/digitalrock/eliste.html?comp=1251&cat=1&route=2&detail=0&rotate=c=1,r=2:c=2,r=2 * * You can also supply an optional parameter w= (think of German "Wettkampf" as "c" was already taken) to rotate though different competitions (the first of which is specified by the "comp" parameter in the original URL. * * Example https://www.digitalrock.de/egroupware/ranking/sitemgr/digitalrock/eliste.html?comp=1395&beamer=1&cat=1&route=0&rotate=w=1395,c=1,r=0:w=1396,c=1,r=0 * * The interesting part here is rotate=w=1395,c=1,r=0:w=1396,c=1,r=0 */ /** * Show nation specific footer * * @param {jQuery|String} selector where to show the footer * @param {String} nation */ function showFooterByNation(selector, nation) { var jelem = jQuery(selector); var footer = "/icc_footer.inc.php"; switch (nation) { case "GER": footer = "/dav_footer.inc.php"; break; case "SUI": footer = "/sac_footer.inc.php p:not(.register)"; break; } jelem.load(footer, function () { jelem.fadeIn("slow"); }); } /** * Show category specific footer * * @param {jQuery|String} selector where to show the footer * @param {Number|String} cat default read it from cat parameter of document */ function showFooterByCat(selector, cat) { if (!cat) cat = (document.location.href.match(/cat=([a-z]{3}|\d+)/i) || [])[1]; var national_cats = { GER: [ 212, 211, 207, 294, 205, 14, 202, 201, 296, 200, 206, 213, 4, 215, 13, 108, 12, 11, 66, 7, 226, 223, 217, 216, 214, 25, 54, 109, 107, 106, 67, 48, 49, 50, 51, 52, 53, 110, 111, 113, 114, 28, 27, 26, 112, 115, ], SUI: [ 47, 46, 44, 116, 30, 35, 69, 68, 43, 41, 209, 208, 219, 222, 31, 32, 33, 34, 36, 37, 38, 39, ], }; for (var nation in national_cats) { if ( cat.toUpperCase() === nation || national_cats[nation].indexOf(parseInt(cat)) >= 0 ) { showFooterByNation(selector, nation); return; } } showFooterByNation(selector); } /** * Baseclass for all widgets * * We only use jQuery() here (not $() or $j()!) to be able to run as well inside EGroupware as with stock jQuery from googleapis. */ var DrBaseWidget = (function () { /** * Constructor for all widgets from given json url * * Table get appended to specified _container * * @param _container * @param _json_url url for data to load */ function DrBaseWidget(_container, _json_url) { this.json_url = _json_url; this.container = jQuery( typeof _container == "string" ? "#" + _container : _container ); this.container.addClass(this.constructor.name); } /** * Install update method as popstate or hashchange handler */ DrBaseWidget.prototype.installPopState = function () { // add popstate or hashchange (IE8,9) event listener, to use browser back button for navigation // some browsers, eg. Chrome, generate a pop on inital page-load // to prevent loading page initially twice, we store initial location this.prevent_initial_pop = location.href; var initial_params = this.json_url.replace(/^.*(#!|#|\?)/, "") || ""; var that = this; jQuery(window).bind( window.history.pushState ? "popstate" : "hashchange", function (e) { if ( !that.prevent_initial_pop || that.prevent_initial_pop != location.href ) { that.update(location.hash || location.query || initial_params); } delete that.prevent_initial_pop; } ); }; /** * Update Widget from json_url * * To be able to cache jsonp requests in CDN, we have to use the same callback. * Using same callback leads to problems with concurrent requests: failed: parsererror (jsonp was not called) * To work around that we queue jsonp request, if there's already one running. * * Queue is maintained globally in DrBaseWidget.jsonp_queue, as requests come from different objects! * * @param {boolean} ignore_queue used internally to start next object in queue without requeing it */ DrBaseWidget.prototype.update = function (ignore_queue) { // remove our own parameters and current year from json url to improve caching var url = this.json_url .replace(/(detail|beamer|rotate|toc)=[^&]*(&|$)/, "") .replace(new RegExp("year=" + new Date().getFullYear() + "(&|$)"), "") .replace(/&$/, ""); // do we need a jsonp request var jsonp = this.json_url.indexOf("//") != -1 && this.json_url.split("/", 3) != location.href.split("/", 3); if (typeof DrBaseWidget.jsonp_queue == "undefined") DrBaseWidget.jsonp_queue = []; if (!ignore_queue && jsonp) { // add us to the queue DrBaseWidget.jsonp_queue.push(this); } // if there's only one in the queue (or no queueing necessary: no jsonp) --> send ajax request if (ignore_queue || !jsonp || DrBaseWidget.jsonp_queue.length == 1) { jQuery.ajax({ url: url, async: true, context: this, data: "", dataType: jsonp ? "jsonp" : "json", jsonpCallback: "jsonp", // otherwise jQuery generates a random name, not cachable by CDN cache: true, type: "GET", success: function (_data) { // if we are first object in queue, remove us if (DrBaseWidget.jsonp_queue[0] === this) DrBaseWidget.jsonp_queue.shift(); // if someone left in queue, run it's update ignore the queue if (DrBaseWidget.jsonp_queue.length) DrBaseWidget.jsonp_queue[0].update(true); this.handleResponse(_data); }, error: function (_xmlhttp, _err, _status) { // need same handling as success if (DrBaseWidget.jsonp_queue[0] === this) DrBaseWidget.jsonp_queue.shift(); if (DrBaseWidget.jsonp_queue.length) DrBaseWidget.jsonp_queue[0].update(true); //if (_err != 'timeout') alert('Ajax request to '+this.json_url+' failed: '+_err+(_status?' ('+_status+')':'')); // schedule update again after 60sec this.update_handle = window.setTimeout( jQuery.proxy(this.update, this, ignore_queue), 60000 ); }, }); } }; /** * Callback for loading data via ajax * * Virtual, need to be implemented in inheriting objects! * * @param _data */ DrBaseWidget.prototype.handleResponse = function (_data) { throw ( "No handleResponse implemented in " + this.constructor.name + " inheriting from DrBaseWidget!" ); }; /** * Add list with see also links, if not beamer or toc disabled * * @param _see_also */ DrBaseWidget.prototype.seeAlso = function (_see_also) { this.container.find("ul.seeAlso").remove(); if ( typeof _see_also != "undefined" && _see_also.length > 0 && !this.json_url.match(/toc=0/) && !this.json_url.match(/beamer=1/) ) { var ul = jQuery(document.createElement("ul")).attr("class", "seeAlso"); ul.prepend(document.createTextNode(this.lang("See also:"))); for (var i = 0; i < _see_also.length; ++i) { var tag = jQuery(document.createElement("li")); ul.append(tag); if (_see_also[i].url) { var a = jQuery(document.createElement("a")).attr( "href", _see_also[i].url ); tag.append(a); if (this.navigateTo) { a.click(this.navigateTo); } tag = a; } tag.text(_see_also[i].name); } this.container.append(ul); } }; /** * Replace attribute named from with one name to and value keeping the order of the attributes * * @param {object} obj * @param {string} from * @param {string} to * @param {*} value */ DrBaseWidget.prototype.replace_attribute = function (obj, from, to, value) { var found = false; for (var attr in obj) { if (!found) { if (attr == from) { found = true; delete obj[attr]; obj[to] = value; } } else { var val = obj[attr]; delete obj[attr]; obj[attr] = val; } } }; /** * Replace "nation" column with what's specified in "display_athlete" on competition * * @param {string} _display_athlete * @param {string} _nation */ DrBaseWidget.prototype.replace_nation = function (_display_athlete, _nation) { switch (_display_athlete) { case "none": delete this.columns.nation; break; case "city": case "pc_city": this.replace_attribute(this.columns, "nation", "city", "City"); break; case "federation": case "fed_and_parent": var fed_label = "Federation"; switch (_nation) { case "GER": fed_label = "DAV Sektion"; break; case "SUI": fed_label = "Sektion"; break; } this.replace_attribute(this.columns, "nation", "federation", fed_label); break; } }; /** * Format a date according to browser local format * * @param {string} _ymd yyyy-mm-dd string or everything understood by date constructor * @returns {string} */ DrBaseWidget.prototype.formatDate = function (_ymd) { if (!_ymd || typeof _ymd != "string") return ""; var date = new Date(_ymd); return date.toLocaleDateString().replace(/[0-9]+\./g, function (_match) { return _match.length <= 2 ? "0" + _match : _match; }); }; /** * Translate english phrase into user language * * @param {string} _msg * @returns {string} */ DrBaseWidget.prototype.lang = function (_msg) { if (_msg === null) { return ""; } if (typeof _msg !== "string" && _msg) { console.log("Cannot translate an object", _msg); return _msg; } var translation = _msg; if (typeof dr_translations !== "undefined") { if (typeof DrBaseWidget.user_lang === "undefined") { var language = (navigator.languages && navigator.languages[0]) || // Chrome / Firefox navigator.language || // All browsers navigator.userLanguage; // IE <= 10 DrBaseWidget.user_lang = language.replace(/-[A-Z]+/, ""); } if ( dr_translations[_msg] && dr_translations[_msg][DrBaseWidget.user_lang] ) { translation = dr_translations[_msg][DrBaseWidget.user_lang]; } } if (arguments.length == 1) return translation; if (arguments.length == 2) return translation.replace("%1", arguments[1]); // to cope with arguments containing '%2' (eg. an urlencoded path like a referer), // we first replace all placeholders '%N' with '|%N|' and then we replace all '|%N|' with arguments[N] translation = translation.replace(/%([0-9]+)/g, "|%$1|"); for (var i = 1; i < arguments.length; ++i) { translation = translation.replace("|%" + i + "|", arguments[i]); } return translation; }; return DrBaseWidget; })(); /** * DrTable helper to construct table from colum-defition and data */ var DrTable = (function () { /** * Constructor for table with given data and columns * * Table get appended to specified _container * * @param _data array with data for each participant * @param _columns hash with column name => header * @param _sort column name to sort by * @param _ascending * @param _quota quota if quota line should be drawn in result * @param _navigateTo click method for profiles * @param _showUnranked if true AND _sort=='result_rank' show participants without rank, default do NOT show them */ function DrTable( _data, _columns, _sort, _ascending, _quota, _navigateTo, _showUnranked ) { this.data = _data; this.columns = _columns; if (typeof _sort == "undefined") for (_sort in _columns) break; this.sort = _sort; if (typeof _ascending == "undefined") _ascending = true; this.ascending = _ascending; this.quota = parseInt(_quota); this.navigateTo = _navigateTo; this.showUnranked = _showUnranked ? true : false; // hash with PerId => tr containing athlete this.athletes = {}; this.sortData(); // header this.dom = document.createElement("table"); jQuery(this.dom).addClass("DrTable"); var thead = document.createElement("thead"); jQuery(this.dom).append(thead); var row = this.createRow(this.columns, "th"); jQuery(thead).append(row); // athletes var tbody = jQuery(document.createElement("tbody")); jQuery(this.dom).append(tbody); for (var i = 0; i < this.data.length; ++i) { var data = this.data[i]; if (Array.isArray(data.results)) { // category with result if (typeof this.column_count == "undefined") { this.column_count = 0; for (var c in this.columns) ++this.column_count; } if (typeof data.name != "undefined") { row = document.createElement("tr"); var th = jQuery(document.createElement("th")); th.attr("colspan", this.column_count); th.text(data.name); if (typeof data.url != "undefined") { var a = jQuery(document.createElement("a")); a.attr("href", data.url); a.text(this.lang("Complete Results")); if (this.navigateTo || typeof data.click != "undefined") a.click(this.navigateTo || data.click); th.append(a); } jQuery(row).append(th); tbody.append(row); } for (var j = 0; j < data.results.length; ++j) { tbody.append(this.createRow(data.results[j])); } } // single result row else { if ( this.sort == "result_rank" && ((typeof data.result_rank == "undefined" && !this.showUnranked) || data.result_rank < 1) ) { break; // no more ranked competitiors } tbody.append(this.createRow(data)); } } //console.log(this.athletes); } DrTable.prototype.lang = DrBaseWidget.prototype.lang; /** * Update table with new data, trying to re-use existing rows * * @param _data array with data for each participant * @param _quota quota if quota line should be drawn in result */ DrTable.prototype.update = function (_data, _quota) { this.data = _data; if (typeof _quota != "undefined") this.quota = parseInt(_quota); //console.log(this.data); this.sortData(); var tbody = this.dom.firstChild.nextSibling; var pos; // uncomment to test update: reverses the list on every call //if (this.data[0].PerId == tbody.firstChild.id) this.data.reverse(); var athletes = this.athletes; this.athletes = {}; for (var i = 0; i < this.data.length; ++i) { var data = this.data[i]; var row; if (data.PerId != "undefined") { row = athletes[data.PerId]; } else if (data.team_id != "undefined") { row = athletes[data.team_id]; } if ( this.sort == "result_rank" && (typeof data.result_rank == "undefined" || data.result_rank < 1) ) { break; // no more ranked competitiors } // search athlete in tbody if (typeof row != "undefined") { //jQuery(row).detach(); //this.updateRow(row,data); jQuery(row).remove(); } //else { row = this.createRow(data); } // no child in tbody --> append row if (typeof pos == "undefined") { jQuery(tbody).prepend(row); } else { jQuery(pos).after(row); } pos = row; } // remove further rows / athletes not in this.data if (typeof pos != "undefined" && typeof pos.nextSibling != "undefined") { jQuery("#" + pos.id + " ~ tr").remove(); } }; /** * Update given data-row with changed content * * @param {object} _row * @param {object} _data * @todo */ DrTable.prototype.updateRow = function (_row, _data) {}; /** * Create new data-row with all columns from this.columns * * @param {object} _data * @param {string} [_tag=td] */ DrTable.prototype.createRow = function (_data, _tag) { //console.log(_data); if (typeof _tag == "undefined") _tag = "td"; var row = document.createElement("tr"); if (typeof _data.PerId != "undefined" && _data.PerId > 0) { row.id = _data.PerId; if (_data.className) { row.className = _data.className; if (row.className.match(/hideNames/)) row.title = this.lang("Athlete asked not to show his name anymore."); } this.athletes[_data.PerId] = row; } else if (typeof _data.team_id != "undefined" && _data.team_id > 0) { row.id = _data.team_id; this.athletes[_data.team_id] = row; } var span = 1; for (var col in this.columns) { if (--span > 0) continue; var url = _data.url; // if object has a special getter func, call it var col_data; if (typeof this.columns[col] == "function") { col_data = this.columns[col].call(this, _data, _tag, col); } else { col_data = _data[col]; } // allow /-delemited expressions to index into arrays and objects if (typeof col_data == "undefined" && col.indexOf("/") != -1) { var parts = col.split("/"); col_data = _data; for (var p in parts) { col = parts[p]; if (col == "lastname" || col == "firstname") url = col_data.url; if (typeof col_data != "undefined") col_data = col_data[col]; } } else if (col.indexOf("/") != -1) col = col.substr(col.lastIndexOf("/") + 1); var tag = document.createElement(_tag); tag.className = col; jQuery(row).append(tag); // add pstambl link to name & vorname if ( typeof url != "undefined" && (col == "lastname" || col == "firstname") ) { var a = document.createElement("a"); a.href = url; a.target = "pstambl"; if (this.navigateTo && url.indexOf("#") != -1) jQuery(a).click(this.navigateTo); jQuery(tag).append(a); tag = a; } if ( typeof _data.fed_url != "undefined" && (col == "nation" || col == "federation") ) { var a = document.createElement("a"); a.href = _data.fed_url; a.target = "_blank"; jQuery(tag).append(a); tag = a; } if (typeof col_data == "object" && col_data) { if (typeof col_data.nodeName != "undefined") { jQuery(tag).append(col_data); } else { if (col_data.colspan > 1) tag.colSpan = span = col_data.colspan; if (col_data.className) tag.className = col_data.className; if (col_data.title) tag.title = col_data.title; if (col_data.url || col_data.click) { var a = document.createElement("a"); a.href = col_data.url || "#"; if (col_data.click || this.navigateTo) { jQuery(a).click(col_data.click || this.navigateTo); } jQuery(tag).append(a); tag = a; } if (col_data.nodes) { jQuery(tag).append(col_data.nodes); } else { jQuery(tag).text(col_data.label); } } } else { jQuery(tag).text(typeof col_data != "undefined" ? col_data : ""); span = 1; } } // add or remove quota line if ( this.sort == "result_rank" && this.quota && _data.result_rank && parseInt(_data.result_rank) >= 1 && parseInt(_data.result_rank) > this.quota ) { row.className = "quota_line"; delete this.quota; // to set quota line only once } return row; }; /** * Sort data according to sort criteria * * @todo get using this.sortNummeric callback working */ DrTable.prototype.sortData = function () { function sortResultRank(_a, _b) { var rank_a = _a["result_rank"]; if (typeof rank_a == "undefined" || rank_a < 1) rank_a = 9999; var rank_b = _b["result_rank"]; if (typeof rank_b == "undefined" || rank_b < 1) rank_b = 9999; var ret = rank_a - rank_b; if (!ret) ret = _a["lastname"] > _b["lastname"] ? 1 : -1; if (!ret) ret = _a["firstname"] > _b["firstname"] ? 1 : -1; return ret; } switch (this.sort) { case false: // dont sort break; case "result_rank": // not necessary as server returns them sorted this way //this.data.sort(sortResultRank); break; default: var sort = this.sort; this.data.sort(function (_a, _b) { var a = sort == "start_order" ? parseInt(_a[sort]) : _a[sort]; var b = sort == "start_order" ? parseInt(_b[sort]) : _b[sort]; return a == b ? 0 : a < b ? -1 : 1; //return _a[sort] == _b[sort] ? 0 : (_a[sort] < _b[sort] ? -1 : 1); }); break; } if (!this.ascending) this.data.reverse(); }; return DrTable; })(); /** * Startlist widget inheriting from DrBaseWidget */ var Startlist = (function () { /** * Constructor for startlist from given json url * * Table get appended to specified _container * * @param _container * @param _json_url url for data to load * @param {boolean} _no_navigation do NOT display TOC */ function Startlist(_container, _json_url, _no_navigation) { DrBaseWidget.prototype.constructor.call(this, _container, _json_url); this.no_navigation = _no_navigation; // do not continue, as constructor is called when inheriting from Startlist without parameters! if (typeof _container == "undefined") return; // Variables needed for scrolling in upDown // scroll speed this.scroll_by = 1; // scroll interval, miliseconds (will be changed on resized windows where scroll_by is increased, so a constant scrolling speed is maintained) this.scroll_interval = 20; // current scrolling direction. 1: down, -1: up this.scroll_dir = 1; // sleep on the borders for sleep_for seconds this.sleep_for = 4; // margin in which to reverse scrolling // CAUTION: At the beginning, we scroll pixelwise through the margin, one pixel each sleep_for seconds. Do not change the margin unless you know what you do. this.margin = 2; // helper variable var now = new Date(); this.sleep_until = now.getTime() + 10000; this.first_run = true; this.do_rotate = false; this.update(); if (this.json_url.match(/rotate=/)) { var list = this; // 20110716: This doesn't seem to be needed anymore. Comment it for now. //window.scrollBy(0, 20); this.scroll_interval_handle = window.setInterval(function () { list.upDown(); }, 20); } } // inherit from DrBaseWidget Startlist.prototype = new DrBaseWidget(); Startlist.prototype.constructor = Startlist; /** * Callback for loading data via ajax * * @param _data route data object */ Startlist.prototype.handleResponse = function (_data) { //console.log(_data); var detail = this.json_url.match(/detail=([^&]+)/); if (detail) detail = detail[1]; switch (_data.discipline) { case "speedrelay": this.startlist_cols = detail === null ? { // default detail start_order: this.lang("StartNr"), team_name: this.lang("Teamname"), "athletes/0/lastname": this.lang("Athlete #1"), "athletes/1/lastname": this.lang("Athlete #2"), "athletes/2/lastname": this.lang("Athlete #3"), } : detail ? { // detail=1 start_order: this.lang("StartNr"), team_name: this.lang("Teamname"), //'team_nation': 'Nation', "athletes/0/lastname": { label: this.lang("Athlete #1"), colspan: 3, }, "athletes/0/firstname": "", "athletes/0/result_time": "", "athletes/1/lastname": { label: this.lang("Athlete #2"), colspan: 3, }, "athletes/1/firstname": "", "athletes/1/result_time": "", "athletes/2/lastname": { label: this.lang("Athlete #3"), colspan: 3, }, "athletes/2/firstname": "", "athletes/2/result_time": "", } : { // detail=0 start_order: this.lang("StartNr"), team_name: this.lang("Teamname"), team_nation: this.lang("Nation"), }; break; case "combined": this.result_cols.final_points = this.lang("Final Points"); delete this.result_cols.start_number; // table is far too big anyway // fall through default: this.startlist_cols = { start_order: { label: this.lang("StartNr"), colspan: 2 }, start_number: "", lastname: { label: this.lang("Name"), colspan: 2 }, firstname: "", birthyear: this.lang("Birthyear"), nation: this.lang("Nation"), }; break; } // if quali_preselected and heat = 1||2, we have to use a function to get either start_order or text "preselected" if ( _data.quali_preselected && (_data.route_order == 0 || _data.route_order == 1) ) { var quali_preselected = _data.quali_preselected; var start_order = this.startlist_cols.start_order; this.startlist_cols.start_order = function (_data, _tag, col) { if (_tag == "th") return start_order; if (_data.ranking <= quali_preselected) return this.lang("Vorqualifiziert"); //'preselected'; return _data[col]; }; } var sort; // if we have no result columns or no ranked participant, show a startlist if ( typeof this.result_cols == "undefined" || (_data.participants[0] && !_data.participants[0].result_rank && _data.discipline != "ranking") ) { this.columns = this.startlist_cols; sort = "start_order"; this.container.attr("class", "Startlist"); } // if we are a result showing a startlist AND have now a ranked participant // --> switch back to result else { this.columns = this.result_cols; sort = "result_rank"; } this.replace_nation(_data.display_athlete, _data.nation); // fix route_names containing only one or two qualifications are send as array because index 0 and 1 if (Array.isArray(_data.route_names)) { var route_names = _data.route_names; delete _data.route_names; _data.route_names = {}; for (var i = 0; i < route_names.length; ++i) { _data.route_names[i] = route_names[i]; } } // keep route_names to detect additional routes on updates if (typeof this.route_names == "undefined") { this.route_names = _data.route_names; } // remove whole table, if the discipline is speed and the number of route_names changes if (_data.discipline == "speed" && this.json_url.match(/route=-1/)) { // && this.route_names != _data.route_names) for (var i = 2; i < 10; i++) { if (typeof _data.route_names[i] != typeof this.route_names[i]) { // there was an update of the route_names array this.route_names = _data.route_names; jQuery(this.container).empty(); delete this.table; break; } } } // remove whole table, if discipline or startlist/resultlist (detemined by sort) changed if ( (this.discipline && this.discipline != _data.discipline) || (this.sort && this.sort != sort) || _data.route_order != this.route_order || // switching heats (they can have different columns) detail !== this.detail || // switching detail on/off this.json_url != this.last_json_url ) { jQuery(this.container).empty(); delete this.table; } this.discipline = _data.discipline; this.sort = sort; this.route_order = _data.route_order; this.detail = detail; this.last_json_url = this.json_url; if (typeof this.table == "undefined") { // for general result use one column per heat if (this.columns.result && _data.route_names && _data.route_order == -1) { delete this.columns.result; // show final first and 2. quali behind 1. quali: eg. 3, 2, 0, 1 var routes = []; if (_data.route_names["1"]) routes.push("1"); for (var id in _data.route_names) { if (id != "-1" && id != "1") routes.push(id); } routes.reverse(); for (var i = 0; i < routes.length; ++i) { var route = routes[i]; // for ranking, we add link to results if (_data.discipline == "ranking") { var id = route.replace(/ $/, ""); // remove space append to force js to keep the order var comp_cat = id.split("_"); this.columns["result" + id] = { label: _data.route_names[route], url: "#!comp=" + comp_cat[0] + "&cat=" + (comp_cat[1] || _data.cat.GrpId), }; } else { this.columns["result" + route] = _data.route_names[route]; } } // evtl. add points column if (_data.participants[0] && _data.participants[0].quali_points) { this.columns["quali_points"] = this.lang("Points"); // delete single qualification results if (this.no_navigation) { delete this.columns.result0; delete this.columns.result1; if (_data.discipline == "combined") delete this.columns.result2; this.columns["quali_points"] = _data.discipline == "combined" ? this.lang("Quali.") : this.lang("Qualification"); } } if ( _data.discipline == "combined" && _data.participants[0] && typeof _data.participants[0].final_points == "undefined" ) { delete this.columns.final_points; } title_prefix = ""; } if ( this.columns.result && _data.participants[0] && _data.participants[0].rank_prev_heat && !this.json_url.match(/detail=0/) ) { this.columns["rank_prev_heat"] = this.lang("previous heat"); } // competition this.comp_header = jQuery(document.createElement("h1")); jQuery(this.container).append(this.comp_header); this.comp_header.addClass("compHeader"); // result date this.result_date = jQuery(document.createElement("h3")); jQuery(this.container).append(this.result_date); this.result_date.addClass("resultDate"); // route header this.header = jQuery(document.createElement("h1")); jQuery(this.container).append(this.header); this.header.addClass("listHeader"); // display a toc with all available heats, if not explicitly disabled (toc=0) or beamer this.displayToc(_data); // create new table if (!_data.error && _data.participants.length) { this.table = new DrTable( _data.participants, this.columns, this.sort, true, _data.route_result ? _data.route_quota : null, this.navigateTo, _data.discipline == "ranking" && (detail || !_data.participants[0].result_rank) ); if (_data.participants[0].result_rank) jQuery(this.table.dom).addClass(_data.discipline); jQuery(this.container).append(this.table.dom); } this.seeAlso(_data.see_also); } else { // update a toc with all available heats, if not explicitly disabled (toc=0) or beamer this.displayToc(_data); // update existing table this.table.update( _data.participants, _data.route_result ? _data.route_quota : null ); } // set/update header line this.setHeader(_data); // if route is NOT offical, update list every 10 sec, of category not offical update every 5min (to get new heats) if (!_data.category_offical && this.discipline != "ranking") { var list = this; this.update_handle = window.setTimeout(function () { list.update(); }, _data.expires * 1000); //console.log('setting up refresh in '+_data.expires+' seconds'); } }; /** * Create or update TOC (list of available routes for navigation) * * Can be disabled via "toc=0" or "beamer=1" in json_url. Always disabled for rankings. * * @param _data route data object */ Startlist.prototype.displayToc = function (_data) { if ( this.json_url.match(/toc=0/) || this.json_url.match(/beamer=1/) || this.discipline == "ranking" || this.no_navigation ) { return; // --> no toc } var toc = this.container.find("ul.listToc"); var new_toc = !toc.length; if (new_toc) toc = jQuery(document.createElement("ul")).addClass("listToc"); else toc.empty(); var href = location.href.replace(/\?(.*)#/, "#"); // prevent query and hash messing up navigation for (var r in _data.route_names) { if (r != this.route_order) { var li = jQuery(document.createElement("li")); var a = jQuery(document.createElement("a")); a.text(_data.route_names[r].replace(" - ", "-")); var reg_exp = /route=[^&]+/; var url = href.replace(reg_exp, "route=" + r); if (url.indexOf("route=") == -1) url += "&route=" + r; a.attr("href", url); if (this.navigateTo) { a.click(this.navigateTo); } else { var that = this; a.click(function (e) { that.json_url = that.json_url.replace( reg_exp, this.href.match(reg_exp)[0] ); if (that.json_url.indexOf("route=") == -1) that.json_url += "&route=" + r; that.update(); e.preventDefault(); }); } li.append(a); toc.prepend(li); } } // only add toc, if we have more then one route if (!new_toc) { // already added } else if (toc.children().length) { jQuery(this.container).append(toc); } else { toc.remove(); } // add category toc if (typeof _data.categorys == "undefined") return; var toc = this.container.find("ul.listCatToc"); var new_toc = !toc.length; if (new_toc) toc = jQuery(document.createElement("ul")).addClass("listCatToc"); else toc.empty(); var cats = this.shortenNames(_data.categorys, "name"); for (var i = 0; i < cats.length; ++i) { var cat = cats[i]; if (cat.GrpId != _data.GrpId) { var li = jQuery(document.createElement("li")); var a = jQuery(document.createElement("a")); a.text(cat.name); var reg_exp = /cat=[^&]+/; var url = href.replace(reg_exp, "cat=" + cat.GrpId); if (url.indexOf("cat=") == -1) url += "&cat=" + cat.GrpId; a.attr("href", url); if (this.navigateTo) { a.click(this.navigateTo); } else { var that = this; a.click(function (e) { that.json_url = that.json_url.replace( reg_exp, this.href.match(reg_exp)[0] ); if (that.json_url.indexOf("cat=") == -1) that.json_url += "cat=" + cat.GrpId; that.update(); e.preventDefault(); }); } li.append(a); toc.append(li); } } // only add toc, if we have more then one route if (!new_toc) { // already added } else if (toc.children().length) { jQuery(this.container).append(toc); } else { toc.remove(); } }; /** * Shorten several names by removing parts common to all and remove spacing (eg. "W O M E N" --> "WOMEN") * * shortenNames["M E N speed", "W O M E N speed"]) returns ["MEN", "WOMEN"] * * @param {array} names array of strings or objects with attribute attr * @param {string} attr attribute name to use or undefined * @return {array} */ Startlist.prototype.shortenNames = function (names, attr) { if (!jQuery.isArray(names) || !names.length) return names; var split_by_regexp = / +/; var spacing_regexp = /([A-Z]) ([A-Z])/; var strs = []; for (var i = 0; i < names.length; ++i) { var name = names[i]; if (attr) name = name[attr]; do { var n = name; name = name.replace(spacing_regexp, "$1$2"); } while (n != name); strs.push(name.split(split_by_regexp)); } var first = [].concat(strs[0]); for (var i = 0; i < first.length; ++i) { for (var j = 1; j < strs.length; ++j) { if (jQuery.inArray(first[i], strs[j]) == -1) { break; } } if (j == strs.length) { // in all strings --> remove first[i] from all strings for (var j = 0; j < strs.length; ++j) { strs[j].splice(jQuery.inArray(first[i], strs[j]), 1); } } } for (var j = 0; j < strs.length; ++j) { strs[j] = strs[j].join(" "); if (attr) { names[j][attr] = strs[j]; } else { names[j] = strs[j]; } } return names; }; /** * Set header with a (provisional) Result or Startlist prefix * * @param _data * @return */ Startlist.prototype.setHeader = function (_data) { var title_prefix = (this.sort == "start_order" ? this.lang("Startlist") : _data.route_result ? this.lang("Result") : this.lang("provisional Result")) + ": "; var header = _data.route_name; // if NOT detail=0 and not for general result, add prefix before route name if (!this.json_url.match(/detail=0/) && _data.route_order != -1) header = title_prefix + header; document.title = header; this.comp_header.empty(); this.comp_header.text(_data.comp_name); this.result_date.empty(); if (_data.error) { this.result_date.text(_data.error); this.result_date.removeClass("resultDate"); this.result_date.addClass("error"); } else if (_data.route_result) { this.result_date.text(_data.route_result); this.result_date.prepend( document.createTextNode(this.lang("As of") + " ") ); this.result_date.append( document.createTextNode(" " + this.lang("after")) ); } this.header.empty(); this.header.text(header); }; /** * Return the current scrolling position, which is the top of the current view. */ Startlist.prototype.currentTopPosition = function () { var y = 0; if (window.pageYOffset) { // all other browsers y = window.pageYOffset; } else if (document.body && document.body.scrollTop) { // IE y = document.body.scrollTop; } return y; }; Startlist.prototype.upDown = function () { // check whether to sleep var now = new Date(); var now_ms = now.getTime(); if (now_ms < this.sleep_until) { // sleep: in this case we do nothing return; } if (this.do_rotate) { // we scheduled a rotation. Do it and then return. this.rotateURL(); // wait for the page to build this.sleep_until = now.getTime() + 1000; this.first_run = true; this.do_rotate = false; // reset scroll_by and scroll_interval, which might have been changed when the windows was resized. this.scroll_by = 1; this.scroll_interval = 20; //console.log("reset scroll_by to " + this.scroll_by); return; } // Get current position var y = 0; var viewHeight = window.innerHeight; var pageHeight = document.body.offsetHeight; y = this.currentTopPosition(); // Do the scrolling window.scrollBy(0, this.scroll_by * this.scroll_dir); // Check, if scrolling worked var new_y = 0; new_y = this.currentTopPosition(); if (y == new_y) { this.scroll_by += 1; //console.log("increased scroll_by to " + this.scroll_by); // reconfigure the scroll interval to maintain a constant speed this.scroll_interval *= this.scroll_by; this.scroll_interval /= this.scroll_by - 1; //console.log("scroll_interval is now " + this.scroll_interval + " ms"); window.clearInterval(this.scroll_interval_handle); var list = this; this.scroll_interval_handle = window.setInterval(function () { list.upDown(); }, this.scroll_interval); } // Set scrolling and sleeping parameters accordingly var scrollTopPosition = y; var scrollBottomPosition = y + viewHeight; //alert("pageYOffset(y)="+pageYOffset+", innerHeight(wy)="+innerHeight+", offsetHeight(dy)="+document.body.offsetHeight); var do_sleep = 0; if (pageHeight <= viewHeight) { // No scrolling at all //console.log("Showing whole page"); do_sleep = 2; this.do_rotate = true; } else if ( this.scroll_dir != -1 && pageHeight - scrollBottomPosition <= this.margin ) { // UP this.scroll_dir = -1; this.first_run = false; do_sleep = 1; } else if (this.scroll_dir != 1 && scrollTopPosition <= this.margin) { // DOWN this.scroll_dir = 1; if (!this.first_run) { do_sleep = 1; this.do_rotate = true; } } // Arm the sleep timer //if (do_sleep > 0) { console.log("Sleeping for " + do_sleep * this.sleep_for + " seconds"); } this.sleep_until = now.getTime() + this.sleep_for * 1000 * do_sleep; }; Startlist.prototype.rotateURL = function () { var rotate_url_matches = this.json_url.match(/rotate=([^&]+)/); if (rotate_url_matches) { var urls = rotate_url_matches[1]; //console.log(urls); var current_comp = this.json_url.match(/comp=([^&]+)/)[1]; var current_cat = this.json_url.match(/cat=([^&]+)/)[1]; var current_route = this.json_url.match(/route=([^&]+)/)[1]; //console.log(current_cat); var next = urls.match( "(?:^|:|w=" + current_comp + ",)" + "c=" + current_cat + ",r=" + current_route + ":(?:w=([0-9_a-z]+),)?" + "c=([0-9_a-z]+),r=(-?[\\d]+)" ); //console.log(next); if (!next) { // at the end of the list, take the first argument next = urls.match( "^(?:w=([0-9_a-z]+),)?" + "c=([0-9_a-z]+),r=(-?[\\d]+)" ); //console.log("starting over"); //console.log(next); } // We might not find a next competition in the rotate parameter var next_comp = current_comp; if (next[1]) { next_comp = next[1]; } // Extract category and route var next_cat = next[2]; var next_route = next[3]; //console.log("current_cat = " + current_cat + ", current_route = " + current_route + ", next_cat = " + next_cat + ", next_route = " + next_route); this.json_url = this.json_url.replace( /comp=[0-9_a-z]+/, "comp=" + next_comp ); this.json_url = this.json_url.replace( /cat=[0-9_a-z]+/, "cat=" + next_cat ); this.json_url = this.json_url.replace( /route=[\d]+/, "route=" + next_route ); //console.log(this.json_url); // cancel the currently pending request before starting a new one. window.clearTimeout(this.update_handle); this.update(); } }; return Startlist; })(); /** * Resultlist widget inheriting from Startlist */ var Resultlist = (function () { /** * Constructor for result from given json url * * Table get appended to specified _container * * @param _container * @param _json_url url for data to load * @param {boolean} _no_navigation */ function Resultlist(_container, _json_url, _no_navigation) { Startlist.prototype.constructor.call( this, _container, _json_url, _no_navigation ); } // inherit from Startlist Resultlist.prototype = new Startlist(); Resultlist.prototype.constructor = Resultlist; /** * Callback for loading data via ajax * * Reimplemented to use different columns depending on discipline * * @param _data route data object */ Resultlist.prototype.handleResponse = function (_data) { var detail = this.json_url.match(/detail=([^&]+)/); switch (_data.discipline) { case "speedrelay": this.result_cols = !detail ? { // default detail result_rank: this.lang("Rank"), team_name: this.lang("Teamname"), "athletes/0/lastname": this.lang("Athlete #1"), "athletes/1/lastname": this.lang("Athlete #2"), "athletes/2/lastname": this.lang("Athlete #3"), result: this.lang("Sum"), } : detail[1] == "1" ? { // detail=1 result_rank: this.lang("Rank"), team_name: this.lang("Teamname"), //'team_nation': this.lang('Nation'), "athletes/0/lastname": { label: this.lang("Athlete #1"), colspan: 3, }, "athletes/0/firstname": "", "athletes/0/result_time": "", "athletes/1/lastname": { label: this.lang("Athlete #2"), colspan: 3, }, "athletes/1/firstname": "", "athletes/1/result_time": "", "athletes/2/lastname": { label: this.lang("Athlete #3"), colspan: 3, }, "athletes/2/firstname": "", "athletes/2/result_time": "", result: this.lang("Sum"), } : { // detail=0 result_rank: this.lang("Rank"), team_name: this.lang("Teamname"), team_nation: this.lang("Nation"), result: this.lang("Sum"), }; break; case "ranking": this.result_cols = { result_rank: this.lang("Rank"), lastname: { label: this.lang("Name"), colspan: 2 }, firstname: "", nation: this.lang("Nation"), points: this.lang("Points"), result: this.lang("Result"), }; // default columns for SUI ranking with NO details if ((!detail || detail[1] == "0") && _data.nation == "SUI") { this.result_cols = { result_rank: this.lang("Rank"), lastname: { label: this.lang("Name"), colspan: 2 }, firstname: "", birthyear: this.lang("Agegroup"), city: this.lang("City"), federation: "Sektion", rgz: "Regionalzentrum", points: this.lang("Points"), result: this.lang("Result"), }; } if ( (!detail || detail[1] == "0") && _data.participants[0] && _data.participants[0].result_rank ) { delete this.result_cols.result; // allow to click on points to show single results this.result_cols.points = { label: this.result_cols.points, url: location.href + "&detail=1", }; // add calculation to see-also links if (typeof _data.see_also == "undefined") _data.see_also = []; _data.see_also.push({ name: this.lang("calculation of this ranking"), url: location.href + "&detail=1", }); } break; default: // default columns for SUI ranking with NO details if ((!detail || detail[1] == "0") && _data.nation == "SUI") { this.result_cols = { result_rank: this.lang("Rank"), lastname: { label: this.lang("Name"), colspan: 2 }, firstname: "", birthyear: this.lang("Agegroup"), city: this.lang("City"), federation: this.lang("Sektion"), rgz: this.lang("Regionalzentrum"), result: this.lang("Result"), }; } else { this.result_cols = detail && detail[1] == "0" ? { result_rank: this.lang("Rank"), lastname: { label: this.lang("Name"), colspan: 2 }, firstname: "", nation: this.lang("Nation"), result: this.lang("Result"), } : _data.discipline == "speed" && _data.route_order == "0" ? { result_rank: this.lang("Rank"), lastname: { label: this.lang("Name"), colspan: 2 }, firstname: "", nation: this.lang("Nation"), start_number: this.lang("StartNr"), result: this.lang("Result"), result_l: this.lang("Result (A)"), result_r: this.lang("Result (B)"), } : { result_rank: this.lang("Rank"), lastname: { label: this.lang("Name"), colspan: 2 }, firstname: "", nation: this.lang("Nation"), start_number: this.lang("StartNr"), result: this.lang("Result"), }; } // for boulder heats use new display, but not for general result! if ( _data.discipline.substr(0, 7) == "boulder" && _data.route_order != -1 ) { delete this.result_cols.result; var that = this; var num_problems = parseInt(_data.route_num_problems); this.result_cols.boulder = function (_data, _tag) { return that.getBoulderResult.call(that, _data, _tag, num_problems); }; //Resultlist.prototype.getBoulderResult; if (!detail || detail[1] != 0) this.result_cols.result = this.lang("Sum"); } break; } // remove start-number column if no start-numbers used (determined on first participant only) if ( typeof this.result_cols.start_number != "undefined" && !_data.participants[0].start_number ) { delete this.result_cols.start_number; } Startlist.prototype.handleResponse.call(this, _data); align_td_nbsp("table.DrTable td"); if ( _data.discipline == "ranking" && !_data.error && ((detail && detail[1] == "1") || !_data.participants[0].result_rank) && (_data.max_comp || _data.max_disciplines) ) { var tfoot = jQuery(document.createElement("tfoot")); jQuery(this.table.dom).append(tfoot); var th = jQuery(document.createElement("th")); tfoot.append(jQuery(document.createElement("tr")).append(th)); var cols = 0; for (var c in this.result_cols) cols++; th.attr("colspan", cols); th.attr("class", "footer"); var max_disciplines = ""; if (_data.max_disciplines) { for (var discipline in _data.max_disciplines) { max_disciplines += (max_disciplines ? ", " : "") + discipline[0].toUpperCase() + discipline.slice(1) + ": " + _data.max_disciplines[discipline]; } } if (_data.nation) { th.html( (_data.max_comp ? "Für " + (_data.cup ? "den " + _data.cup.name : "die Rangliste") + " zählen die " + _data.max_comp + " besten Ergebnisse. " : "") + (max_disciplines ? " Maximal zählende Ergebnisse pro Disziplin: " + max_disciplines + ". " : "") + "Nicht zählende Ergebnisse sind eingeklammert. " + (_data.min_disciplines ? "
Teilnahme an mindestens " + _data.min_disciplines + " Disziplinen ist erforderlich. " : "") + (_data.drop_equally ? "Streichresultate erfolgen in allen Disziplinen gleichmäßig. " : "") ); } else { th.html( (_data.max_comp ? _data.max_comp + " best competition results are counting for " + (_data.cup ? _data.cup.name : "the ranking") + ". " : "") + (max_disciplines ? "Maximum number of counting results per discipline: " + max_disciplines + ". " : "") + "Not counting points are in brackets. " + (_data.min_disciplines ? "
Participation in at least " + _data.min_disciplines + " disciplines is required." : "") + (_data.drop_equally ? "Not counting results are selected from all disciplines equally." : "") ); } } if ( _data.statistics && (_data.discipline == "selfscore" || this.json_url.match("&stats=")) ) { if (!jQuery("#jqplot-css").length) { var ranking_url = this.json_url.replace(/json.php.*$/, ""); var jqplot_url = this.json_url.replace(/ranking\/json.php.*$/, "") + "vendor/npm-asset/as-jqplot/dist/"; jQuery("", { id: "jqplot-css", href: jqplot_url + "jquery.jqplot.min.css", type: "text/css", }).appendTo("head"); var load = [ jqplot_url + "jquery.jqplot.min.js", // not sure why bar-renderer does not work :( //jqplot_url+'plugins/jqplot.barRenderer.min.js', jqplot_url + "plugins/jqplot.highlighter.min.js", ranking_url + "js/dr_statistics.js?" + _data.dr_statistics, ]; for (var i = 0; i < load.length; ++i) { load[i] = jQuery.ajax({ url: load[i], dataType: "script", cache: true, // no cache buster! }); } var container = this.container; jQuery.when.apply(jQuery, load).done(function () { dr_statistics(container, _data); }); // dono why, but above done is not always executed, if files are already cached window.setTimeout(function () { typeof window.dr_statistics != "undefined" && dr_statistics(container, _data); }, 100); } else { dr_statistics(this.container, _data); } } }; /** * Get DOM nodes for display of graphical boulder-result * * @param _data * @param _tag 'th' for header, 'td' for data rows * @param _num_problems * @return DOM node */ Resultlist.prototype.getBoulderResult = function ( _data, _tag, _num_problems ) { if (_tag == "th") return "Result"; var tag = document.createElement("div"); for (var i = 1; i <= _num_problems; ++i) { var boulder = document.createElement("div"); var result = _data["boulder" + i]; if (result && result != "z0" && result != "b0") { var top_tries = result.match(/t([0-9]+)/); var bonus_tries = result.match(/(b|z)([0-9]+)/); if (top_tries) { boulder.className = "boulderTop"; var top_text = document.createElement("div"); top_text.className = "topTries"; jQuery(top_text).text(top_tries[1]); jQuery(boulder).append(top_text); } else { boulder.className = "boulderBonus"; } var bonus_text = document.createElement("div"); bonus_text.className = "bonusTries"; jQuery(bonus_text).text(bonus_tries[2]); jQuery(boulder).append(bonus_text); } else { boulder.className = result ? "boulderNone" : "boulder"; } jQuery(tag).append(boulder); } return tag; }; return Resultlist; })(); /** * Results widget inheriting from DrBaseWidget */ var Results = (function () { /** * Constructor for results from given json url * * Table get appended to specified _container * * @param _container * @param _json_url url for data to load * @param {boolean} _no_navigation do not show competition chooser */ function Results(_container, _json_url, _no_navigation) { DrBaseWidget.prototype.constructor.call(this, _container, _json_url); this.no_navigation = _no_navigation; this.update(); } // inherite from DrBaseWidget Results.prototype = new DrBaseWidget(); Results.prototype.constructor = Results; /** * Callback for loading data via ajax * * @param _data route data object */ Results.prototype.handleResponse = function (_data) { this.columns = { result_rank: this.lang("Rank"), lastname: { label: this.lang("Name"), colspan: 2 }, firstname: "", nation: this.lang("Nation"), }; this.replace_nation(_data.display_athlete, _data.nation); if (typeof this.table == "undefined") { // competition chooser if (!this.no_navigation) { this.comp_chooser = jQuery(document.createElement("select")); this.comp_chooser.addClass("compChooser"); this.container.append(this.comp_chooser); var that = this; this.comp_chooser.change(function (e) { that.json_url = that.json_url.replace( /comp=[^&]+/, "comp=" + this.value ); if (that.navigateTo) that.navigateTo(that.json_url); else that.update(); }); } // competition this.comp_header = jQuery(document.createElement("h1")); this.comp_header.addClass("compHeader"); this.container.append(this.comp_header); // result date this.comp_date = jQuery(document.createElement("h3")); this.comp_date.addClass("resultDate"); this.container.append(this.comp_date); } else { jQuery(this.table.dom).remove(); if (!this.no_navigation) this.comp_chooser.empty(); this.comp_header.empty(); this.comp_date.empty(); } // fill competition chooser if (!this.no_navigation) { var option = jQuery(document.createElement("option")); option.text(this.lang("Select another competition ...")); this.comp_chooser.append(option); for (var i = 0; i < _data.competitions.length; ++i) { var competition = _data.competitions[i]; if (_data.WetId == competition.WetId) continue; // we dont show current competition option = jQuery(document.createElement("option")); option.attr({ value: competition.WetId, title: competition.date_span }); option.text(competition.name); this.comp_chooser.append(option); } } this.comp_header.text(_data.name); this.comp_date.text(_data.date_span); for (var i = 0; i < _data.categorys.length; ++i) { var cat = _data.categorys[i]; var that = this; cat.click = function (e) { that.showCompleteResult(e); }; } // create new table this.table = new DrTable( _data.categorys, this.columns, "result_rank", true, null, this.navigateTo ); this.container.append(this.table.dom); this.seeAlso(_data.see_also); }; /** * Switch from Results (of all categories) to Resultlist (of a single category) * * @param e */ Results.prototype.showCompleteResult = function (e) { this.container.empty(); this.container.removeClass("Results"); new Resultlist( this.container, this.json_url.replace(/\?.*$/, e.target.href.match(/\?.*$/)[0]) ); e.preventDefault(); }; return Results; })(); /** * Starters / registration widget inheriting from DrBaseWidget */ var Starters = (function () { /** * Constructor for results from given json url * * Table get appended to specified _container * * @param _container * @param _json_url url for data to load */ function Starters(_container, _json_url) { DrBaseWidget.prototype.constructor.call(this, _container, _json_url); this.update(); } // inherite from DrBaseWidget Starters.prototype = new DrBaseWidget(); Starters.prototype.constructor = Starters; /** * Callback for loading data via ajax * * @param _data route data object */ Starters.prototype.handleResponse = function (_data) { this.data = _data; if (typeof this.table == "undefined") { // competition this.comp_header = jQuery(document.createElement("h1")); this.comp_header.addClass("compHeader"); this.container.append(this.comp_header); // result date this.comp_date = jQuery(document.createElement("h3")); this.comp_date.addClass("resultDate"); this.container.append(this.comp_date); } else { delete this.table; this.comp_header.empty(); this.comp_date.empty(); } this.comp_header.text(_data.name + " : " + _data.date_span); if (_data.deadline) this.comp_date.text(this.lang("Deadline") + ": " + _data.deadline); this.table = jQuery(document.createElement("table")).addClass("DrTable"); this.container.append(this.table); var thead = jQuery(document.createElement("thead")); this.table.append(thead); // create header row var row = jQuery(document.createElement("tr")); var th = jQuery(document.createElement("th")); th.text( typeof _data.federations != "undefined" ? this.lang("Federation") : this.lang("Nation") ); if (!this.json_url.match(/no_fed=1/)) row.append(th); var cats = {}; for (var i = 0; i < _data.categorys.length; ++i) { var th = jQuery(document.createElement("th")); th.addClass("category"); th.text(_data.categorys[i].name); row.append(th); cats[_data.categorys[i].GrpId] = i; } thead.append(row); var tbody = jQuery(document.createElement("tbody")); this.table.append(tbody); var fed; this.fed_rows = []; this.fed_rows_pos = []; var num_competitors = 0; for (var i = 0; i < _data.athletes.length; ++i) { var athlete = _data.athletes[i]; // evtl. create new row for federation/nation if ( (typeof fed == "undefined" || fed != athlete.reg_fed_id) && !this.json_url.match(/no_fed=1/) ) { this.fillUpFedRows(); // reset fed rows to empty this.fed_rows = []; this.fed_rows_pos = []; } // find rows with space in column of category var cat_col = cats[athlete.cat]; for (var r = 0; r < this.fed_rows.length; ++r) { if (this.fed_rows_pos[r] <= cat_col) break; } if (r == this.fed_rows.length) { // create a new fed-row row = jQuery(document.createElement("tr")); tbody.append(row); th = jQuery(document.createElement("th")); if (!this.json_url.match(/no_fed=1/)) row.append(th); this.fed_rows.push(row); this.fed_rows_pos.push(0); if (typeof fed == "undefined" || fed != athlete.reg_fed_id) { fed = athlete.reg_fed_id; th.text(this.federation(athlete.reg_fed_id)); th.addClass("federation"); } } this.fillUpFedRow(r, cat_col); // create athlete cell var td = jQuery(document.createElement("td")); td.addClass("athlete"); var lastname = jQuery(document.createElement("span")) .addClass("lastname") .text(athlete.lastname); var firstname = jQuery(document.createElement("span")) .addClass("firstname") .text(athlete.firstname); td.append(lastname).append(firstname); this.fed_rows[r].append(td); this.fed_rows_pos[r]++; // do not count if (athlete.cat != 120) num_competitors++; } this.fillUpFedRows(); var tfoot = jQuery(document.createElement("tfoot")); this.table.append(tfoot); var th = jQuery(document.createElement("th")); tfoot.append(jQuery(document.createElement("tr")).append(th)); th.attr("colspan", 1 + _data.categorys.length); th.text( this.lang( "Total of %1 athletes registered in all categories.", num_competitors ) ); }; /** * Fill a single fed-row up to a given position with empty td's * * @param {number} _r row-number * @param {number} _to column-number, default whole row */ Starters.prototype.fillUpFedRow = function (_r, _to) { if (typeof _to == "undefined") _to = this.data.categorys.length; while (this.fed_rows_pos[_r] < _to) { var td = jQuery(document.createElement("td")); this.fed_rows[_r].append(td); this.fed_rows_pos[_r]++; } }; /** * Fill up all fed rows with empty td's */ Starters.prototype.fillUpFedRows = function () { for (var r = 0; r < this.fed_rows.length; ++r) { this.fillUpFedRow(r); } }; /** * Get name of federation specified by given id * * @param _fed_id * @returns string with name */ Starters.prototype.federation = function (_fed_id) { if (typeof this.data.federations == "undefined") { return _fed_id; // nation of int. competition } for (var i = 0; i < this.data.federations.length; ++i) { var fed = this.data.federations[i]; if (fed.fed_id == _fed_id) return fed.shortcut || fed.name; } }; return Starters; })(); /** * Profile widget inheriting from DrBaseWidget */ var Profile = (function () { /** * Constructor for profile from given json url * * Table get appended to specified _container * * @param _container * @param _json_url url for data to load * @param _template optional string with html-template * @param _remove_leading_slash fix Joomla behavior of adding a slash to " + cat.name + "\n"; } select += "\n"; return select; } var parts = placeholder.split("/"); var data = _data; for (var i = 0; i < parts.length; ++i) { if (typeof data[parts[i]] == "undefined" || !data[parts[i]]) { return parts[i] === "N" ? match : ""; } data = data[parts[i]]; } switch (placeholder) { case "practice": data += " " + that.lang("years, since") + " " + (new Date().getFullYear() - data); break; case "height": data += " cm"; break; case "weight": data += " kg"; break; } return data; } ); // replace result data var bestResults = this.bestResults; var that = this; html = html.replace(/[\s]*\n?/g, function (match) { if (match.indexOf("$$results/N/") == -1) return match; // find and mark N best results var year = new Date().getFullYear(); var limits = []; for (var i = 0; i < _data.results.length; ++i) { var result = _data.results[i]; result.weight = result.rank / 2 + (year - parseInt(result.date)) + 4 * !result.nation; // maintain array of N best competitions (least weight) if ( limits.length < bestResults || result.weight < limits[limits.length - 1] ) { var limit = 0; for (var l = 0; l < limits.length; ++l) { limit = limits[l]; if (limit > result.weight) break; } if (limit < result.weight && l == limits.length - 1) l = limits.length; limits = limits .slice(0, l) .concat([result.weight]) .concat(limits.slice(l, bestResults - 1 - l)); } } var weight_limit = limits.pop(); var rows = ""; var l = 0; for (var i = 0; i < _data.results.length; ++i) { var result = _data.results[i]; if ( match.indexOf("$$results/N/weightClass$$") >= 0 && (result.weight > weight_limit || ++l > bestResults) ) { result.weightClass = "profileResultHidden"; } rows += match.replace( that.pattern_results, function (match, placeholder) { switch (placeholder) { case "cat_name+name": return ( (result.GrpId != _data.GrpId ? result.cat_name + ": " : "") + result.name ); case "date": return that.formatDate(result.date); default: return typeof result[placeholder] != "undefined" ? result[placeholder] : ""; } } ); } return rows; }); this.container.html(html); // remove links with empty href this.container.find('a[href=""]').replaceWith(function () { return jQuery(this).contents(); }); // remove images with empty src this.container.find('img[src=""]').remove(); // hide rows with profileHideRowIfEmpty, if ALL td.profileHideRowIfEmpty are empty this.container.find("tr.profileHideRowIfEmpty").each(function (index, row) { var tds = jQuery(row).children("td.profileHideRowIfEmpty"); if (tds.length == tds.filter(":empty").length) { jQuery(row).hide(); } }); // install click handler from DrWidget if (this.navigateTo) this.container .find('.profileData a:not(a[href^="javascript:"])') .click(this.navigateTo); // bind chooseCategory handler (works with multiple templates) var that = this; this.container.find("select.chooseCategory").change(function (e) { that.chooseCategory.call(that, this.value); e.stopImmediatePropagation(); return false; }); }; /** * toggle between best results and all results */ Profile.prototype.toggleResults = function () { var hidden_rows = this.container.find("tr.profileResultHidden"); var display = hidden_rows.length ? jQuery(hidden_rows[0]).css("display") : "none"; hidden_rows.css("display", display == "none" ? "table-row" : "none"); }; /** * choose a given category for rankings * * @param {string} GrpId */ Profile.prototype.chooseCategory = function (GrpId) { var cat_regexp = /([#&])cat=([^&]+)/; function replace_cat(str, GrpId) { if (str.match(cat_regexp)) { return str.replace(cat_regexp, "$1cat=" + GrpId); } return str + "&cat=" + GrpId; } location.hash = replace_cat(location.hash, GrpId); this.json_url = replace_cat(this.json_url, GrpId); this.update(); }; /** * Default template for Profile widget */ Profile.prototype.template = "
\n" + '\n' + " \n" + " \n" + ' \n' + " \n" + ' \n' + " \n" + " \n" + "
\n" + '

\n' + ' $$firstname$$\n' + ' $$lastname$$\n' + "

\n" + '

$$nation$$

\n' + '

$$federation$$

\n' + "
\n" + '\n' + " \n" + " \n" + " \n" + ' \n' + " \n" + ' \n' + " \n" + ' \n' + ' \n' + " \n" + ' \n' + " \n" + ' \n' + " \n" + ' \n' + " \n" + ' \n' + " \n" + ' \n' + " \n" + ' \n' + ' \n' + " \n" + ' \n' + ' \n' + ' \n' + " \n" + ' \n' + ' \n' + ' \n' + " \n" + ' \n' + ' \n' + ' \n' + " \n" + ' \n' + ' \n' + " \n" + ' \n' + ' \n' + " \n" + " \n" + ' \n' + ' \n' + ' \n' + ' \n' + " \n" + " \n" + ' \n' + ' \n' + ' \n' + ' \n' + " \n" + ' \n' + ' \n' + " \n" + " \n" + " \n" + ' \n' + ' \n' + ' \n' + ' \n' + " \n" + " \n" + "
age:$$age$$year of birth:$$birthdate$$
place of birth:$$birthplace$$
height:$$height$$weight:$$weight$$
address:$$postcode$$ $$city$$$$street$$
practicing climbing for:$$practice$$
professional climber (if not, profession):$$professional$$
other sports practiced:$$other_sports$$
$$freetext$$
Category: $$categoryChooser$$
$$rankings/0/name$$:$$rankings/0/rank$$$$rankings/1/name$$:$$rankings/1/rank$$
$$rankings/2/name$$:$$rankings/2/rank$$$$rankings/3/name$$:$$rankings/3/rank$$
best results / all results:
$$results/N/rank$$$$results/N/cat_name+name$$$$results/N/date$$
\n" + "
\n"; return Profile; })(); /** * ResultTemplate widget inheriting from DrBaseWidget */ var ResultTemplate = (function () { /** * Constructor for ResultTemplate from given json url * * Table get appended to specified _container * * @param _container * @param _json_url url for data to load * @param _template optional string with html-template */ function ResultTemplate(_container, _json_url, _template) { DrBaseWidget.prototype.constructor.call(this, _container, _json_url); if (_template) this.template = _template; else this.template = this.container.html(); this.container.empty(); this.update(); } // inherite from DrBaseWidget ResultTemplate.prototype = new DrBaseWidget(); ResultTemplate.prototype.constructor = ResultTemplate; /** * Callback for loading data via ajax * * @param _data route data object */ ResultTemplate.prototype.handleResponse = function (_data) { // if route is NOT offical, update list every 10 sec if (!_data.route_result && typeof this.update_handle == "undefined") { var list = this; this.update_handle = window.setInterval(function () { list.update(); }, 10000); } // if route is offical stop reload else if (_data.route_result && this.update_handle) { window.clearInterval(this.update_handle); delete this.update_handle; } // replace non-result data var pattern = /\$\$([^$]+)\$\$/g; var html = this.template.replace(pattern, function (match, placeholder) { var parts = placeholder.split("/"); var data = _data; for (var i = 0; i < parts.length; ++i) { if (typeof data[parts[i]] == "undefined" || !data[parts[i]]) { return parts[i] === "N" ? match : ""; } data = data[parts[i]]; } switch (placeholder) { } return data; }); // replace result data pattern = /\$\$participants\/N\/([^$]+)\$\$/g; html = html.replace(/[\s]*\n?/g, function (match) { if (match.indexOf("$$participants/N/") == -1) return match; var rows = ""; for (var i = 0; i < _data.participants.length; ++i) { var result = _data.participants[i]; rows += match.replace(pattern, function (match, placeholder) { switch (placeholder) { default: return typeof result[placeholder] != "undefined" ? result[placeholder] : ""; } }); } return rows; }); // replace container this.container.html(html); }; return ResultTemplate; })(); /** * Competitions / calendar widget inheriting from DrBaseWidget */ var Competitions = (function () { /** * Constructor for ompetitions / calendar from given json url * * Table get appended to specified _container * * @param _container * @param _json_url url for data to load * @param _filters object with filters and optional _comp_url * {string} _filters._comp_url url to use as link with added WetId for competition name */ function Competitions(_container, _json_url, _filters) { DrBaseWidget.prototype.constructor.call(this, _container, _json_url); if (typeof _filters != "undefined") { this.filters = _filters; if (typeof _filters._comp_url != "undefined") { this.comp_url = _filters._comp_url; delete this.filters._comp_url; } if (typeof _filters._comp_url_label != "undefined") { this.comp_url_label = _filters._comp_url_label; delete this.filters._comp_url_label; } } this.year_regexp = /([&?])year=(\d+)/; this.update(); } // inherite from DrBaseWidget Competitions.prototype = new DrBaseWidget(); Competitions.prototype.constructor = Competitions; /** * Callback for loading data via ajax * * @param _data route data object */ Competitions.prototype.handleResponse = function (_data) { this.container.empty(); var year = this.json_url.match(this.year_regexp); year = year ? parseInt(year[2]) : new Date().getFullYear(); var h1 = jQuery(document.createElement("h1")).text( this.lang("Calendar") + " " + year ); this.container.append(h1); var filter = jQuery(document.createElement("div")).addClass("filter"); var select = jQuery(document.createElement("select")).attr("name", "year"); var years = _data.years || [year + 1, year, year - 1]; for (var i = 0; i < years.length; ++i) { var y = years[i]; var option = jQuery(document.createElement("option")).attr("value", y); option.text(y); if (year == y) option.attr("selected", "selected"); select.append(option); } var that = this; select.change(function (e) { that.changeYear(this.value); }); select.attr("style", "margin-right: 5px"); filter.append(select); if ( typeof this.filters != "undefied" && !jQuery.isEmptyObject(this.filters) ) { select = jQuery(document.createElement("select")).attr("name", "filter"); for (var f in this.filters) { var option = jQuery(document.createElement("option")).attr( "value", this.filters[f] ); option.text(f); if (decodeURI(this.json_url).indexOf(this.filters[f]) != -1) option.attr("selected", "selected"); select.append(option); } select.change(function (e) { that.changeFilter(this.value); }); filter.append(select); } this.container.append(filter); var competitions = jQuery(document.createElement("div")).addClass( "competitions" ); this.container.append(competitions); var now = new Date(); // until incl. Wednesday (=3) we display competitions from last week first, after that from this week var week_to_display = now.getWeek() - (now.getDay() <= 3 ? 1 : 0); var closest, closest_dist; for (var i = 0; i < _data.competitions.length; ++i) { var competition = _data.competitions[i]; var comp_div = jQuery(document.createElement("div")).addClass( "competition" ); var title = jQuery(document.createElement("div")) .addClass("title") .text(competition.name); if (this.comp_url) { title = jQuery(document.createElement("a")) .attr({ href: this.comp_url + competition.WetId }) .append(title); } comp_div.append(title); comp_div.append( jQuery(document.createElement("div")) .addClass("date") .text(competition.date_span) ); var cats_ul = jQuery(document.createElement("ul")).addClass("cats"); var have_cats = false; var links = { homepage: this.lang("Event Website"), info: this.lang("Regulation"), info2: this.lang("Info Sheet"), startlist: this.lang("Startlist"), result: this.lang("Result"), }; // add comp_url as first link with given label if (this.comp_url && this.comp_url_label) { links = jQuery.extend({ comp_url: this.comp_url_label }, links); competition.comp_url = this.comp_url + competition.WetId; } if (typeof competition.cats == "undefined") competition.cats = []; for (var c = 0; c < competition.cats.length; ++c) { var cat = competition.cats[c]; var url = ""; if (typeof cat.status != "undefined") { switch (cat.status) { case 4: // registration links.starters = this.lang("Starters"); competition.starters = "#!type=starters&comp=" + competition.WetId; break; case 2: // startlist in result-service case 1: // result in result-service case 0: // result in ranking (ToDo: need extra export, as it might not be in result-service) url = "#!comp=" + competition.WetId + "&cat=" + cat.GrpId; break; } } var cat_li = jQuery(document.createElement("li")); if (url != "") { var a = jQuery(document.createElement("a")).attr("href", url); a.text(cat.name); if (this.navigateTo) a.click(this.navigateTo); cat_li.append(a); } else { cat_li.text(cat.name); } cats_ul.append(cat_li); have_cats = true; } var links_ul = jQuery(document.createElement("ul")).addClass("links"); var have_links = false; for (var l in links) { if (typeof competition[l] == "undefined" || competition[l] === null) continue; var a = jQuery(document.createElement("a")); a.attr("href", competition[l]); if (l == "comp_url" && this.comp_url[0] == "/"); else if (l != "starters") a.attr("target", "_blank"); else if (this.navigateTo) a.click(this.navigateTo); a.text(links[l]); links_ul.append( jQuery(document.createElement("li")) .addClass(l + "Link") .append(a) ); have_links = true; } if (have_links) comp_div.append(links_ul); if (have_cats) comp_div.append(cats_ul); competitions.append(comp_div); var dist = Math.abs( new Date(competition.date).getWeek() - week_to_display ); if (typeof closest_dist == "undefined" || dist < closest_dist) { closest_dist = dist; closest = comp_div[0]; } } if (closest && year == new Date().getFullYear()) { // need to delay scrolling a bit, layout seems to need some time window.setTimeout(function () { closest.parentElement.scrollTop = closest.offsetTop + closest.parentElement.offsetTop; }, 100); } }; Competitions.prototype.changeYear = function (year) { if (this.json_url.match(this.year_regexp)) { this.json_url = this.json_url.replace(this.year_regexp, "$1year=" + year); } else { if (this.json_url.substr(-1) != "?") this.json_url += this.json_url.indexOf("?") == -1 ? "?" : "&"; this.json_url += "year=" + year; } if (this.navigateTo) { this.navigateTo(this.json_url); } else { this.update(); } }; Competitions.prototype.changeFilter = function (filter) { if (this.json_url.indexOf("?") == -1) { this.json_url += "?" + filter; } else { var year = this.json_url.match(this.year_regexp); this.json_url = this.json_url.replace( /\?.*$/, "?" + (year && year[2] ? "year=" + year[2] + "&" : "") + filter ); } if (this.navigateTo) { this.navigateTo(this.json_url); } else { this.update(); } }; return Competitions; })(); /** * Aggregated rankings widget inheriting from DrBaseWidget */ var Aggregated = (function () { /** * Constructor for aggregated rankings (nat. team ranking, sektionenwertung, ...) from given json url * * Table get appended to specified _container * * @param _container * @param _json_url url for data to load */ function Aggregated(_container, _json_url) { DrBaseWidget.prototype.constructor.call(this, _container, _json_url); this.update(); } // inherite from DrBaseWidget Aggregated.prototype = new DrBaseWidget(); Aggregated.prototype.constructor = Aggregated; /** * Callback for loading data via ajax * * @param _data route data object */ Aggregated.prototype.handleResponse = function (_data) { var that = this; // if we are not controlled by DrWidget, install our own navigation if (!this.navigateTo) { this.navigateTo = function (e) { document.location.hash = this.href.replace(/^.*#!/, ""); e.preventDefault(); }; this.update = function () { that.json_url = that.json_url.replace(/&(cup|comp|cat)=[^&]+/, "") + "&" + document.location.hash.substr(1); DrBaseWidget.prototype.update.call(that); }; this.installPopState(); } this.columns = { rank: this.lang("Rank"), nation: { label: _data.aggregated_name, colspan: 2 }, name: _data.aggregated_name, points: { label: this.lang("Points") }, }; if (location.hash.indexOf("detail=1") == -1) { this.columns.points.click = function (e) { var hidden_cols = that.container.find(".result,.calculationHidden"); var display = hidden_cols.length ? jQuery(hidden_cols[0]).css("display") : "none"; hidden_cols.css("display", display == "none" ? "table-cell" : "none"); location.hash += "&detail=1"; e.preventDefault(); }; // add calculation to see-also links if (typeof _data.see_also == "undefined") _data.see_also = []; _data.see_also.push({ name: "calculation of this ranking", url: location.href + "&detail=1", }); } if (_data.aggregate_by != "nation") delete this.columns.nation; if (typeof this.table == "undefined") { this.ranking_name = jQuery(document.createElement("h1")).addClass( "rankingName" ); this.container.append(this.ranking_name); // cup or competition this.header = jQuery(document.createElement("h2")).addClass( "rankingHeader" ); this.container.append(this.header); // category names this.header2 = jQuery(document.createElement("h3")).addClass( "rankingHeader2" ); this.container.append(this.header2); } else { jQuery(this.table.dom).remove(); this.header.empty(); this.header2.empty(); } this.ranking_name.text(_data.name); this.header.text(_data.cup_name || _data.comp_name); // if we filter by cat display categories as 2. header if (_data.cat_filter) { if (_data.cat_name) { // category name given, use it but upcase and space it this.header2.text(_data.cat_name.toUpperCase().split("").join(" ")); } else { var names = ""; for (var i in _data.categorys) { names += (names ? ", " : "") + _data.categorys[i].name; } this.header2.text(names); } } // make _data available to other methods this.data = _data; var comps = []; for (var c in _data.competitions) comps.push(_data.competitions[c]); // use competition columns for more then one comp. and international or SUI if (comps.length > 1 && (!_data.nation || _data.nation == "SUI")) { comps.sort(function (a, b) { return a.date < b.date ? 1 : -1; }); for (var c = 0; c < comps.length; ++c) { this.columns["result" + comps[c].WetId] = function ( _data, _tag, _name ) { return that.comp_column.call(that, _data, _tag, _name); }; } } // otherwise use category header else { var cats = []; for (var c in _data.categorys) cats.push(_data.categorys[c]); cats.sort(function (a, b) { return a.name < b.name ? -1 : 1; }); for (var c = 0; c < cats.length; ++c) { this.columns["result" + cats[c].GrpId] = function (_data, _tag, _name) { return that.cat_column.call(that, _data, _tag, _name); }; } } if (!_data.use_cup_points) { // display all ranking points with 2 digits for (var f = 0; f < _data.federations.length; ++f) { _data.federations[f].points = _data.federations[f].points.toFixed(2); } } // create new table this.table = new DrTable( _data.federations, this.columns, false, true, null, this.navigateTo ); // add table footer with note about how many results are counting var tfoot = jQuery(document.createElement("tfoot")); jQuery(this.table.dom).append(tfoot); var th = jQuery(document.createElement("th")).addClass("result"); if (this.json_url.indexOf("detail=1") == -1) th.addClass("calculationHidden"); tfoot.append(jQuery(document.createElement("tr")).append(th)); var cols = 0; for (var c in this.columns) cols++; th.attr("colspan", cols); th.text( this.lang( "For %1 %2 best results per competition and category are counting.", _data.name, _data.best_results ) + " " + this.lang("Not counting results are in brackets.") ); this.container.append(this.table.dom); this.seeAlso(_data.see_also); }; /** * Display a competition column * * @param _data * @param _tag tag 'th' for header or 'td' for data row * @param _name column-name */ Aggregated.prototype.comp_column = function (_data, _tag, _name) { var id = _name.substr(6); var ret = { className: "result" }; if (this.json_url.indexOf("detail=1") == -1) ret.className += " calculationHidden"; if (_tag == "th") { // use comp. shortcut plus date as column header ret.label = (this.data.competitions[id].short || this.data.competitions[id].name.replace(/^.* - /, "")) + "\n" + this.formatDate(this.data.competitions[id].date); // add comp to url evtl. replacing cup ret.url = location.href.indexOf("cup=") == -1 ? location.href + (location.href.indexOf("#!") == -1 ? "#!" : "&") + "comp=" + id : location.href.replace(/(cup)=[^&]+/, "comp=" + id); // nat. team ranking selects a cat, if none given, need to add it to not get a different selected if (this.data.cat_filter && ret.url.indexOf("cat=") == -1) ret.url += "&cat=" + this.data.cat_filter; // keep in detailed view if (ret.url.indexOf("detail=1") == -1) ret.url += "&detail=1"; ret.title = this.data.competitions[id].name; } else if ( this.data.cat_filter && this.data.cat_filter.indexOf(",") == -1 ) { ret.label = ""; ret.nodes = this.results(id, "WetId", _data.counting); } else { ret.label = _data.comps[id]; } return ret; }; /** * Display a category column * * @param _data * @param _tag tag 'th' for header or 'td' for data row * @param _name column-name */ Aggregated.prototype.cat_column = function (_data, _tag, _name) { var id = _name.substr(6); var ret = { className: "result" }; if (this.json_url.indexOf("detail=1") == -1) ret.className += " calculationHidden"; if (_tag == "th") { ret.label = this.data.categorys[id].name; // get less wide headers by inserting a newline ret.label = ret.label .replace(/(lead|speed|boulder)/, "\n$1") .replace(/(männliche|weibliche|male|female) */, "$1\n"); if (!this.data.comp_filter && !this.data.cat_filter) { ret.url = location.href + "&cat=" + id; // keep in detailed view if (ret.url.indexOf("detail=1") == -1) ret.url += "&detail=1"; } } else if (!this.data.comp_filter && !this.data.cat_filter) { // sektionenwertung var points = 0.0; for (var r = 0; r < _data.counting.length; ++r) { var result = _data.counting[r]; if (result.GrpId == id) { points += result.points; } } ret.label = points.toFixed(2); } else { ret.label = ""; ret.nodes = this.results(id, "GrpId", _data.counting); } return ret; }; Aggregated.prototype.results = function (_id, _attr, _results) { var cols = ["rank", "lastname", "firstname", "points"]; var nodes; for (var r = 0; r < _results.length; ++r) { var result = _results[r]; if (result[_attr] == _id) { if (typeof nodes == "undefined") { nodes = jQuery(document.createElement("div")).addClass("resultRows"); } var div = jQuery(document.createElement("div")).addClass("resultRow"); for (var c = 0; c < cols.length; ++c) { var col = cols[c]; var span = jQuery(document.createElement("span")).addClass(col); span.text( col != "points" || this.data.use_cup_points ? result[col] : result[col].toFixed(2) ); div.append(span); } nodes.append(div); } } return nodes; }; return Aggregated; })(); /** * Universal widget to display all data specified by _json_url or location */ var DrWidget = (function () { /** * call appropriate widget to display data specified by _json_url or location * * @param _container * @param _json_url url for data to load * @param _arg3 object with widget specific 3. argument, eg. { Competitions: {filters}, Profile: 'template-id' } */ function DrWidget(_container, _json_url, _arg3) { DrBaseWidget.prototype.constructor.call(this, _container, _json_url); this.arg3 = _arg3 || {}; var matches = this.json_url.match(/\?.*$/); this.update(matches ? matches[0] : null); // install this.update as PopState handler this.installPopState(); } // inherit from DrBaseWidget DrWidget.prototype = new DrBaseWidget(); DrWidget.prototype.constructor = DrWidget; /** * Navigate to a certain result-page * * @param _params default if not specified first location.hash then location.search * @param {DrBaseWidget} _widget to clear evtl. pending update timer */ DrWidget.prototype.navigateTo = function (_params, _widget) { // clear pending update timer of widget, to stay on new widget we navigate to now if (_widget && _widget.update_handle) { window.clearTimeout(_widget.update_handle); delete _widget.update_handle; } delete this.prevent_initial_pop; // check if we have an ordinary link without hash or query, eg. result PDF if (!_params.match(/(#|\?)/)) { document.location = _params; } var params = "!" + _params.replace(/^.*(#!|#|\?)/, ""); // update location hash, to reflect current page-content if (document.location.hash != "#" + params) document.location.hash = params; }; DrWidget.prototype.update = function (_params) { var params = _params || location.hash || location.search; params = params.replace(/^.*(#!|#|\?)/, ""); this.json_url = this.json_url.replace(/\?.*$/, "") + "?" + params; // check which widget is needed to render requested content function hasParam(_param, _value) { if (typeof _value == "undefined") _value = ""; return params.indexOf(_param + "=" + _value) != -1; } var widget; if ( hasParam("type", "nat_team_ranking") || hasParam("type", "sektionenwertung") || hasParam("type", "regionalzentren") ) { widget = "Aggregated"; } else if (hasParam("person")) { widget = "Profile"; } else if (hasParam("cat") && !hasParam("comp")) { widget = "Resultlist"; // ranking uses Resultlist! } else if (hasParam("nation") || !hasParam("comp")) { widget = "Competitions"; } else if (hasParam("comp") && hasParam("type", "starters")) { widget = "Starters"; } else if ((hasParam("comp") && !hasParam("cat")) || hasParam("filter")) { widget = "Results"; } else if (hasParam("type", "startlist")) { widget = "Startlist"; } else { widget = "Resultlist"; } // check if widget is currently instancated and only need to update or need to be instancated if ( typeof this.widget == "undefined" || this.widget.constructor != window[widget] ) { this.container.html(""); // following .empty() does NOT work in IE8 Grrrr this.container.empty(); // do a new of objects whos name is stored in widget this.widget = Object.create(window[widget].prototype); // Object.create, does NOT call constructor, so do it now manually window[widget].call( this.widget, this.container, this.json_url, this.arg3[widget] ); var that = this; this.widget.navigateTo = function (e) { if (typeof e == "string") { that.navigateTo(e, that.widget); } else { that.navigateTo(this.href, that.widget); e.preventDefault(); } }; } else { this.widget.json_url = this.json_url; this.widget.update(); } }; return DrWidget; })(); /** * Widget to let user choose a competition and category to show its result * * Chooser selectboxes stay visible, when user navigates in result eg. to show a profile */ var ResultChooser = (function () { /** * call appropriate widget to display data specified by _json_url or location * * @param _container * @param _json_url url for data to load * @param {object} _arg3 object with 3. parameter to pass to widget used to display results */ function ResultChooser(_container, _json_url, _arg3) { DrBaseWidget.prototype.constructor.call( this, _container, _json_url.replace(/&cat=[^&]+/, "") ); this.arg3 = _arg3 || { Results: true, // show NO own navigation Startlist: true, Resultlist: true, }; var matches = this.json_url.match(/\?.*$/); this.update(matches ? matches[0] : null); this.comp_chooser = this.cat_chooser = undefined; var matches = _json_url.match(/&cat=([^&]+)/); this.cat = matches ? matches[1] : undefined; this.widget = undefined; // install this.update as PopState handler //this.installPopState(); } // inherit from DrBaseWidget ResultChooser.prototype = new DrBaseWidget(); ResultChooser.prototype.constructor = ResultChooser; /** * Callback for loading data via ajax * * @param _data route data object */ ResultChooser.prototype.handleResponse = function (_data) { if (!this.comp_chooser) { // competition chooser this.comp_chooser = jQuery(document.createElement("select")); this.comp_chooser.addClass("compChooser"); this.container.append(this.comp_chooser); var that = this; this.comp_chooser.change(function (e) { that.json_url = that.json_url .replace(/comp=[^&]+/, "comp=" + this.value) .replace(/&cat=[^&]+/, ""); that.update(); }); this.cat_chooser = jQuery(document.createElement("select")); this.cat_chooser.addClass("catChooser"); this.container.append(this.cat_chooser); this.cat_chooser.change(function (e) { that.cat = this.value; that.json_url = that.json_url.replace( /comp=[^&]+/, "comp=" + that.comp_chooser.val() ); if (!that.cat) that.json_url = that.json_url.replace(/&cat=[^&]+/, ""); else if (that.json_url.search("cat=") == -1) that.json_url += "&cat=" + this.value; else that.json_url = that.json_url.replace( /cat=[^&]+/, "cat=" + this.value ); that.widget.navigateTo(that.json_url); }); } else { this.comp_chooser.empty(); this.cat_chooser.empty(); } // fill competition chooser for (var i = 0; i < _data.competitions.length; ++i) { var competition = _data.competitions[i]; var option = jQuery(document.createElement("option")); option.attr({ value: competition.WetId, title: competition.date_span }); option.text(competition.name); if (competition.WetId == _data.WetId) option.attr("selected", true); this.comp_chooser.append(option); } // fill category chooser var option = jQuery(document.createElement("option")); option.attr("value", ""); option.text(this.lang("Select a single category to show ...")); this.cat_chooser.append(option); var cat_found = false; for (var i = 0; i < _data.categorys.length; ++i) { var cat = _data.categorys[i]; option = jQuery(document.createElement("option")); option.attr("value", cat.GrpId); option.text(cat.name); if (cat.GrpId == this.cat) { option.attr("selected", true); cat_found = true; } this.cat_chooser.append(option); } if (!cat_found) this.cat = undefined; if (!this.widget) { var widget_container = jQuery(document.createElement("div")).appendTo( this.container ); this.widget = new DrWidget( widget_container, this.json_url + (this.cat ? "&cat=" + this.cat : ""), this.arg3 ); } else { this.widget.navigateTo( this.json_url + (this.cat ? "&cat=" + this.cat : "") ); } }; return ResultChooser; })(); /** * Dynamically load a css file * * @param href url to css file */ function load_css(href) { //Get the head node and append a new link node with the stylesheet url to it var headID = document.getElementsByTagName("head")[0]; var cssnode = document.createElement("link"); cssnode.type = "text/css"; cssnode.rel = "stylesheet"; cssnode.href = href; headID.appendChild(cssnode); } /** * Align non-breaking-space separated parts of text in a td by wrapping them in equally sized spans * * @param {string|jQuery} elems */ function align_td_nbsp(elems) { jQuery(elems).replaceWith(function () { var parts = jQuery(this) .contents() .text() .split(/\u00A0+/); if (parts.length == 1) return jQuery(this).clone(); var prefix = '
'; var postfix = "
"; return jQuery( "" + prefix + parts.join(postfix + prefix) + postfix + "" ).addClass(this.className); }); } /** * Some compatibilty functions to cope with older javascript implementations */ if (!Array.isArray) { Array.isArray = function (vArg) { return Object.prototype.toString.call(vArg) === "[object Array]"; }; } if (!Object.create) { Object.create = function (o) { function F() {} F.prototype = o; return new F(); }; } if (!Date.getWeek) { Date.prototype.getWeek = function () { var onejan = new Date(this.getFullYear(), 0, 1); return Math.ceil(((this - onejan) / 86400000 + onejan.getDay() + 1) / 7); }; }