1/** 2 * jquery.lw-widget.js 3 * 4 * Usage: 5 * basic: $(selector).lwWidget(options); 6 * methods: $(selector).lwWidget('method_name', arg1, arg2, etc); 7 * 8 * Description: 9 * This plugin inserts a widget on an external site 10 */ 11 12(function($) { 13 var defaults = { 14 include_default_styles: false, 15 group_by_date: false, 16 format: 'json' 17 }; 18 19 var disable_timezone = false; 20 var User = livewhale.lib.DateUser; 21 var Timezone = livewhale.lib.DateTimezone; 22 23 function getDefaultResults(opts, format_args, data) { 24 var result = ''; 25 26 // for each widget result 27 $.each(data.data, function(key, val) { 28 var output = data.format, 29 has_image = false, 30 item; 31 32 // format each result 33 $.each(format_args, function(key2, val2) { 34 // make title a link if url exists 35 if (val2 === 'title' && val.title && val.url) { 36 val.title = '<a href="' + val.url + '">' + val.title + '</a>'; 37 } 38 39 // 40 if (val2 === 'thumbnail' && val.thumbnail) { 41 var img_alt = (val.title) ? encodeURIComponent(val.title) : 'Thumbnail Image'; 42 val.thumbnail = '<span class="lw_item_thumb">' + (val.url ? '<a href="' + val.url + '">' : '') 43 + '<img src="' + val.thumbnail + '" alt="' + img_alt + '"/>' + (val.url ? '</a>' : '') + '</span>'; 44 has_image = true; 45 } 46 output = output.replace('{' + val2 + '}', (val[val2] ? val[val2] : '')); 47 }); 48 result += '<div class="lw_widget_item' + (has_image ? ' lw_has_image' : '') + '">' + output + '</div>'; 49 }); 50 return result; 51 } 52 53 function getEventsResults(opts, format_args, data) { 54 var results = {}, 55 result = ''; 56 57 // set default timezone and formats based on widget group 58 Timezone.time_format[data.group.timezone_format] = data.group.time_format; 59 Timezone.date_format[data.group.timezone_format] = data.group.date_format; 60 Timezone.setUserFormatZone(data.group.timezone_format); 61 62 // strip PHP % in datetime formats; reassign to TZ formats if present 63 $.each(['date_format', 'time_format'], function(i, format) { 64 if (typeof data[format] === 'string') { 65 data[format] = data[format].replace(/(^|[^%]{1})%/g, '$1'); 66 if (data[format] && typeof Timezone[format][data.group.timezone_format] !== 'undefined') { 67 Timezone[format][data.group.timezone_format] = data[format]; 68 } 69 } 70 }); 71 72 function getItem(val) { 73 var output = ((val.is_starred === 1) ? data.format_starred : data.format), 74 has_image = false, 75 start_time = new Timezone(val.date_ts, val.tz_offset), 76 end_time = (val.date2_ts) ? new Timezone(val.date2_ts, val.tz_offset) : null, 77 event_url; 78 79 if (val.title) { 80 val.title_clean = val.title; 81 } else { 82 val.title_clean = ''; 83 } 84 85 // handle different landing page url 86 if (val.url && val.url.length > 0) { 87 event_url = val.url; 88 } else { 89 event_url = null; 90 } 91 if (data.type === 'events' && val.id && data.group.directory && data.group.directory !== opts.host + '/') { 92 event_url = data.group.directory + '#!event_id/' + val.id + '/view/event'; 93 } 94 95 // format each result 96 $.each(format_args, function(key2, val2) { 97 // show date range if multi-day event 98 if (val2 === 'date' && end_time && start_time.getShortDateString() !== end_time.getShortDateString()) { 99 if (start_time.getMonth() === end_time.getMonth()) { 100 val[val2] = val[val2] + '-' + end_time.getDate(); 101 } else { 102 val[val2] = val[val2] + ' - ' + end_time.getFormattedDate('F d'); 103 } 104 } 105 106 // make title a link if url exists 107 if (val2 === 'title' && val.title && event_url) { 108 val.title = '<a href="' + event_url + '" class="lw_events_title">' + val.title + '</a>'; 109 } 110 111 // handle image, image_src 112 if (val2 === 'image' && val.image) { 113 val.image = val.image.replace(/(href|src)="(?!(http|\/\/))(.*?)"/ig, '$1="' + opts.host + '$3"'); 114 if (val.image_src) { 115 val.image_src = opts.host + val.image_src; 116 } 117 has_image = true; 118 } 119 120 // handle thumbnail 121 if (val2 === 'thumbnail' && val.thumbnail) { 122 var img_alt = (val.title) ? encodeURIComponent(val.title) : 'Thumbnail Image'; 123 val.thumbnail = '<span class="lw_item_thumb">' + (event_url ? '<a href="' + event_url + '">' : '') + '<img src="' + val.image_src + '" alt="' + img_alt + '"/>
123' + (event_url ? '</a>' : '') + '</span>'; 124 has_image = true; 125 } 126 127 // handle links in summary/description 128 if ((val2 === 'summary' || val2 === 'description') && val[val2] && val[val2].match(/<[^>]+(href|src)=[^>]+>/i)) { 129 val[val2] = val[val2].replace(/(href|src)="(?!(http|\/\/))(.*?)"/ig, '$1="' + opts.host + '$3"'); 130 } 131 132 // make time available, showing only original timezone value 133 if (val2 === 'time') { // change to 'time' when this is completed 134 if (val.is_all_day && (val.is_all_day === true || val.is_all_day === '1' || val.is_all_day === 1)) { 135 val[val2] = 'All Day'; 136 } else if (val.date_ts) { 137 val[val2] = start_time.getTimeString(); // and output the formatted string 138 if (end_time) { 139 if (start_time.getShortDateString() === end_time.getShortDateString()) { // if same day 140 if (start_time.getTimeString() !== end_time.getTimeString()) { // append end time if different 141 val[val2] += ' – ' + end_time.getTimeString(); 142 } 143 } else { // show date with end time 144 val[val2] += ' until ' + end_time.getShortDateString() + ', ' + end_time.getTimeString(); 145 } 146 } 147 } 148 } 149 output = output.replace('{' + val2 + '}', (val[val2] ? val[val2] : '')); 150 }); 151 152 return '<div class="lw_widget_item' + (has_image ? ' lw_has_image' : '') + (val.is_starred ? ' lw_is_starred' : '') + '">' + output + '</div>'; 153 } 154 155 if (opts.group_by_date) { 156 $.each(data.data, function(index, item) { 157 var start_time = new Timezone(item.date_ts, item.tz_offset), 158 date = item.date, 159 end_time; 160 161 if (item.date2_ts) { 162 end_time = new Timezone(item.date2_ts, item.tz_offset); 163 // show date range if multi-day event 164 if (start_time.getShortDateString() !== end_time.getShortDateString()) { 165 if (start_time.getMonth() === end_time.getMonth()) { 166 date += '-' + end_time.getDate(); 167 } else { 168 date += ' - ' + end_time.getFormattedDate('F d'); 169 } 170 } 171 } 172 if (!$.isArray(results[date])) { 173 results[date] = []; 174 } 175 results[date].push(item); 176 }); 177 $.each(results, function(date, date_results) { 178 result += '<h3>' + date + '</h3>'; 179 $.each(date_results, function(i, item) { 180 result += getItem(item); 181 }); 182 }); 183 } else { 184 // add each widget result 185 $.each(data.data, function(index, item) { 186 result += getItem(item); 187 }); 188 } 189 return result; 190 } 191 192 // public methods 193 var methods = { 194 init: function(options) { 195 var that = this, 196 plugin = this, 197 opts = $.extend({}, defaults, options), 198 $body = $('body'), 199 user = new User(); 200 201 return this.each(function() { 202 var $widget = $(this), 203 liveurl, url, request_opts; 204 205 function replaceCalendarLinks() { 206 if (!opts.link_to_calendar) return; 207 208 $widget.find('a[href]').each(function() { 209 var $a = $(this), 210 href = $a.attr('href'), 211 id_match = href.match(/\/(\d+)[^\/]*$/), 212 widget_host = opts.host.replace(/^https?:\/\//, '').replace(/\/$/, ''), 213 link_host_match = href.match(/^.*:\/\/([A-Za-z0-9\-\.]+)/); 214 215 if (id_match && link_host_match && link_host_match.length === 2 && link_host_match[1] === widget_host) { 216 $a.attr('href', opts.link_to_calendar + '/#!view/event/event_id/' + id_match[1]); 217 } 218 }); 219 } 220 221 function includeDefaultStyles(type, theme) { 222 var css_url; 223 theme = theme || 'core'; 224 225 if (opts.theme) { 226 theme = opts.theme; 227 } 228 229 css_url = opts.host + liveurl + '/resource/css/%5Clivewhale%5Ctheme%5C' + theme 230 + '%5Cstyles%5C' + type + '-embed-widget.css'; 231 232 // href must be set last, and after appended to work in IE8 233 $('<link>').appendTo($('head')).attr({ 234 type: 'text/css', 235 rel: 'stylesheet' 236 }).attr('href', css_url); 237 } 238 239 $widget.addClass('loading'); 240 241 // return if no valid host 242 if (!opts.host || !opts.host.match(/[^\.]+\.[^\.]+/gi)) { 243 return this; 244 } 245 246 // return if no widget id 247 if (!opts.id) { 248 return this; 249 } 250 251 // trim trailing slash from host 252 opts.host = opts.host.replace(/\/$/, ''); 253 254 if (!opts.host.match(/^http/)) { 255 opts.host = document.location.protocol + '//' + opts.host; 256 } 257 258 liveurl = opts.liveurl_dir || '/live'; 259 url = opts.host + liveurl + '/widget/' + opts.id; 260 261 // correct argument syntax 262 // early clients used the plural form 263 if (opts.tags) { 264 opts.tag = opts.tags; 265 delete opts.tags; 266 } 267 if (opts.categories) { 268 opts.category = opts.categories; 269 delete opts.categories; 270 } 271 if (opts.groups) { 272 opts.group = opts.groups; 273 delete opts.groups; 274 } 275 276 // build table of option properties to exclude from the ajax request 277 var exclude_from_request = { 278 host: 1, 279 group_by_date: 1, 280 link_to_calendar: 1, 281 theme: 1, 282 id: 1, 283 liveurl_dir: 1, 284 include_default_styles: 1, 285 format: 1 286 }; 287
288 // add all options not in the exclude table to the ajax request 289 $.each(opts, function(key, vals) { 290 // skip keys in exclude table 291 if (exclude_from_request[key]) { 292 return true; 293 } 294 295 // add everything else to the url 296 vals=(key!=='filter' ? vals.split('|') : [vals]); 297 $.each(vals, function(i, val) { 298 // replace slashes with dashes if this is a date field 299 if ('start_date' === key || 'end_date' === key) { 300 val = val.replace(/\//g, '-'); 301 } 302 url += '/' + encodeURIComponent(key) + '/' + encodeURIComponent($.trim(val)); 303 }); 304 }); 305 306 console.log('url', url); 307 308 request_opts = { 309 type: 'GET', 310 timeout: 30000, 311 crossDomain: true 312 }; 313 314 if ('json' === opts.format) { 315 url += '@JSON'; 316 317 request_opts.data = { external_widget: 1 }; 318 request_opts.dataType = 'jsonp'; 319 request_opts.success = function(data) { 320 var format_args = [], 321 results = {}, 322 result, css_url, $wrapper; 323 324 $body.trigger('beforeWidgetLoad.lw'); 325 326 // parse format args from format 327 $.each((data.format + data.format_starred).match(/\{[^\}]+\}/gi), function(key, val) { 328 // remove "{" and "}" chars from format args 329 val = val.replace('{', '').replace('}', ''); 330 if ($.inArray(val, format_args)) { 331 format_args.push(val); 332 } 333 }); 334 335 // add default styles if specified in options 336 if (opts.include_default_styles) { 337 includeDefaultStyles(data.type, data.theme); 338 } 339 340 // output results 341 $wrapper = $('<div class="lw_widget lw_widget_' + data.type + '"/>'); 342 343 // add custom classes 344 if (!$.isArray(data['class'])) { 345 data['class'] = []; 346 } 347 if (opts.group_by_date) { 348 data['class'].push('lw_widget_group_by_date'); 349 } 350 $.each(data['class'], function(i, class_name) { 351 $wrapper.addClass(class_name); 352 }); 353 354 if (data.data.length) { 355 // we changed the names of a few data points to make them consistent with non-external data points 356 // this copies the new names to the old properties to preserve backwards compatibility 357 $.each(data.data, function(index, obj) { 358 if ($.isPlainObject(obj)) { 359 data.data[index].href = obj.url; 360 data.data[index].group_title = obj.group; 361 } 362 }); 363 364 if (data.type === 'events') { 365 result = getEventsResults(opts, format_args, data); 366 } else { 367 result = getDefaultResults(opts, format_args, data); 368 } 369 } else { 370 result = data.no_results; 371 } 372 $wrapper.append(result); 373 $widget.html($wrapper); 374 375 if (opts.link_to_calendar) { 376 replaceCalendarLinks(); 377 } 378 379 $widget.removeClass('loading'); 380 $body.trigger('widgetLoad.lw'); 381 }; 382 } else { 383 request_opts.dataType = 'html'; 384 request_opts.success = function(data) { 385 $body.trigger('beforeWidgetLoad.lw'); 386 387 $widget.html(data); 388 389 // handler for opening a new window when widget link has target="_blank". This fixes a bug 390 // in Safari that caused a cached versions of window.location.hash to override the actual 391 // hash in the url 392 $widget.on('click', 'a[target="_blank"]', function(e) { 393 e.preventDefault(); 394 window.open($(this).attr('href'), 'lw_calendar_event'); 395 return true; 396 }); 397 398 if (opts.link_to_calendar) { 399 replaceCalendarLinks(); 400 } 401 402 $widget.removeClass('loading'); 403 $body.trigger('widgetLoad.lw'); 404 }; 405 } 406 407 // get format arg 408 $.ajax(url, request_opts); 409 410 $widget.on('click', '.lw_paginate a', function(e) { 411 e.preventDefault(); 412 var href = $(this).attr('href'); 413 var opts = {}; 414 var page; 415 416 $widget.addClass('loading'); 417 418 href = href.substring(href.lastIndexOf('?') + 1); 419 var pairs = href.split('&'); 420 var all = false; 421 422 $.each(pairs, function(i, pair) { 423 var arg = pair.split('='); 424 if (arg.length !== 2) { 425 return true; 426 } 427 if (arg[0] === 'page') { 428 page = arg[1]; 429 } 430 if (arg[0] === 'all' && arg[1]) { 431 all = true; 432 } 433 }); 434 435 if (all) { 436 $.extend(true, opts, request_opts, { 437 data: { 438 'all': '1' 439 } 440 }); 441 } else if (page) { 442 $.extend(true, opts, request_opts, { 443 data: { 444 'page': page 445 } 446 }); 447 } 448 $.ajax(url, opts); 449 return true; 450 }); 451 452 // make sure the element isn't hidden 453 $widget.show(); 454 455 // we put this here to override code on U Calgary's site that 456 // was hiding the widget after load. We decided to add it to 457 // the core code because it doesn't negatively impact widgets 458 // on other sites. 459 setTimeout(function() { 460 $widget.show(); 461 }, 200); 462 }); 463 } 464 }; 465 466 $.fn.lwWidget = function(method) { 467 if (methods[method]) { 468 return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 469 } else if (typeof method === 'object' || !method) { 470 return methods.init.apply(this, arguments); 471 } else {
472 console.log('Method ' + method + ' does not exist on jQuery.lwWidget'); 473 return false; 474 } 475 }; 476}(livewhale.jQuery || jQuery));
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.