annotate static/js/jplayer/add-on/jquery.jplayer.inspector.js @ 947:49646a78e965

Update requirements_dev.txt for Django 1.7.7 upgrade.
author Brian Neal <bgneal@gmail.com>
date Sat, 16 May 2015 14:36:31 -0500
parents a747215e9a5a
children
rev   line source
bgneal@488 1 /*
bgneal@488 2 * jPlayerInspector Plugin for jPlayer (2.0.0+) Plugin for jQuery JavaScript Library
bgneal@488 3 * http://www.happyworm.com/jquery/jplayer
bgneal@488 4 *
bgneal@488 5 * Copyright (c) 2009 - 2011 Happyworm Ltd
bgneal@488 6 * Dual licensed under the MIT and GPL licenses.
bgneal@488 7 * - http://www.opensource.org/licenses/mit-license.php
bgneal@488 8 * - http://www.gnu.org/copyleft/gpl.html
bgneal@488 9 *
bgneal@488 10 * Author: Mark J Panaghiston
bgneal@488 11 * Version: 1.0.3
bgneal@488 12 * Date: 7th August 2011
bgneal@488 13 *
bgneal@488 14 * For use with jPlayer Version: 2.0.29
bgneal@488 15 *
bgneal@488 16 * Note: Declare inspector instances after jPlayer instances. ie., Otherwise the jPlayer instance is nonsense.
bgneal@488 17 */
bgneal@488 18
bgneal@488 19 (function($, undefined) {
bgneal@488 20 $.jPlayerInspector = {};
bgneal@488 21 $.jPlayerInspector.i = 0;
bgneal@488 22 $.jPlayerInspector.defaults = {
bgneal@488 23 jPlayer: undefined, // The jQuery selector of the jPlayer instance to inspect.
bgneal@488 24 idPrefix: "jplayer_inspector_",
bgneal@488 25 visible: false
bgneal@488 26 };
bgneal@488 27
bgneal@488 28 var methods = {
bgneal@488 29 init: function(options) {
bgneal@488 30 var self = this;
bgneal@488 31 var $this = $(this);
bgneal@488 32
bgneal@488 33 var config = $.extend({}, $.jPlayerInspector.defaults, options);
bgneal@488 34 $(this).data("jPlayerInspector", config);
bgneal@488 35
bgneal@488 36 config.id = $(this).attr("id");
bgneal@488 37 config.jPlayerId = config.jPlayer.attr("id");
bgneal@488 38
bgneal@488 39 config.windowId = config.idPrefix + "window_" + $.jPlayerInspector.i;
bgneal@488 40 config.statusId = config.idPrefix + "status_" + $.jPlayerInspector.i;
bgneal@488 41 config.configId = config.idPrefix + "config_" + $.jPlayerInspector.i;
bgneal@488 42 config.toggleId = config.idPrefix + "toggle_" + $.jPlayerInspector.i;
bgneal@488 43 config.eventResetId = config.idPrefix + "event_reset_" + $.jPlayerInspector.i;
bgneal@488 44 config.updateId = config.idPrefix + "update_" + $.jPlayerInspector.i;
bgneal@488 45 config.eventWindowId = config.idPrefix + "event_window_" + $.jPlayerInspector.i;
bgneal@488 46
bgneal@488 47 config.eventId = {};
bgneal@488 48 config.eventJq = {};
bgneal@488 49 config.eventTimeout = {};
bgneal@488 50 config.eventOccurrence = {};
bgneal@488 51
bgneal@488 52 $.each($.jPlayer.event, function(eventName,eventType) {
bgneal@488 53 config.eventId[eventType] = config.idPrefix + "event_" + eventName + "_" + $.jPlayerInspector.i;
bgneal@488 54 config.eventOccurrence[eventType] = 0;
bgneal@488 55 });
bgneal@488 56
bgneal@488 57 var structure =
bgneal@488 58 '<p><a href="#" id="' + config.toggleId + '">' + (config.visible ? "Hide" : "Show") + '</a> jPlayer Inspector</p>'
bgneal@488 59 + '<div id="' + config.windowId + '">'
bgneal@488 60 + '<div id="' + config.statusId + '"></div>'
bgneal@488 61 + '<div id="' + config.eventWindowId + '" style="padding:5px 5px 0 5px;background-color:#eee;border:1px dotted #000;">'
bgneal@488 62 + '<p style="margin:0 0 10px 0;"><strong>jPlayer events that have occurred over the past 1 second:</strong>'
bgneal@488 63 + '<br />(Backgrounds: <span style="padding:0 5px;background-color:#eee;border:1px dotted #000;">Never occurred</span> <span style="padding:0 5px;background-color:#fff;border:1px dotted #000;">Occurred before</span> <span style="padding:0 5px;background-color:#9f9;border:1px dotted #000;">Occurred</span> <span style="padding:0 5px;background-color:#ff9;border:1px dotted #000;">Multiple occurrences</span> <a href="#" id="' + config.eventResetId + '">reset</a>)</p>';
bgneal@488 64
bgneal@488 65 // MJP: Would use the next 3 lines for ease, but the events are just slapped on the page.
bgneal@488 66 // $.each($.jPlayer.event, function(eventName,eventType) {
bgneal@488 67 // structure += '<div id="' + config.eventId[eventType] + '" style="float:left;">' + eventName + '</div>';
bgneal@488 68 // });
bgneal@488 69
bgneal@488 70 var eventStyle = "float:left;margin:0 5px 5px 0;padding:0 5px;border:1px dotted #000;";
bgneal@488 71 // MJP: Doing it longhand so order and layout easier to control.
bgneal@488 72 structure +=
bgneal@488 73 '<div id="' + config.eventId[$.jPlayer.event.ready] + '" style="' + eventStyle + '"></div>'
bgneal@488 74 + '<div id="' + config.eventId[$.jPlayer.event.flashreset] + '" style="' + eventStyle + '"></div>'
bgneal@488 75 + '<div id="' + config.eventId[$.jPlayer.event.resize] + '" style="' + eventStyle + '"></div>'
bgneal@488 76 + '<div id="' + config.eventId[$.jPlayer.event.repeat] + '" style="' + eventStyle + '"></div>'
bgneal@488 77 + '<div id="' + config.eventId[$.jPlayer.event.click] + '" style="' + eventStyle + '"></div>'
bgneal@488 78 + '<div id="' + config.eventId[$.jPlayer.event.error] + '" style="' + eventStyle + '"></div>'
bgneal@488 79 + '<div id="' + config.eventId[$.jPlayer.event.warning] + '" style="' + eventStyle + '"></div>'
bgneal@488 80
bgneal@488 81 + '<div id="' + config.eventId[$.jPlayer.event.loadstart] + '" style="clear:left;' + eventStyle + '"></div>'
bgneal@488 82 + '<div id="' + config.eventId[$.jPlayer.event.progress] + '" style="' + eventStyle + '"></div>'
bgneal@488 83 + '<div id="' + config.eventId[$.jPlayer.event.timeupdate] + '" style="' + eventStyle + '"></div>'
bgneal@488 84 + '<div id="' + config.eventId[$.jPlayer.event.volumechange] + '" style="' + eventStyle + '"></div>'
bgneal@488 85
bgneal@488 86 + '<div id="' + config.eventId[$.jPlayer.event.play] + '" style="clear:left;' + eventStyle + '"></div>'
bgneal@488 87 + '<div id="' + config.eventId[$.jPlayer.event.pause] + '" style="' + eventStyle + '"></div>'
bgneal@488 88 + '<div id="' + config.eventId[$.jPlayer.event.waiting] + '" style="' + eventStyle + '"></div>'
bgneal@488 89 + '<div id="' + config.eventId[$.jPlayer.event.playing] + '" style="' + eventStyle + '"></div>'
bgneal@488 90 + '<div id="' + config.eventId[$.jPlayer.event.seeking] + '" style="' + eventStyle + '"></div>'
bgneal@488 91 + '<div id="' + config.eventId[$.jPlayer.event.seeked] + '" style="' + eventStyle + '"></div>'
bgneal@488 92 + '<div id="' + config.eventId[$.jPlayer.event.ended] + '" style="' + eventStyle + '"></div>'
bgneal@488 93
bgneal@488 94 + '<div id="' + config.eventId[$.jPlayer.event.loadeddata] + '" style="clear:left;' + eventStyle + '"></div>'
bgneal@488 95 + '<div id="' + config.eventId[$.jPlayer.event.loadedmetadata] + '" style="' + eventStyle + '"></div>'
bgneal@488 96 + '<div id="' + config.eventId[$.jPlayer.event.canplay] + '" style="' + eventStyle + '"></div>'
bgneal@488 97 + '<div id="' + config.eventId[$.jPlayer.event.canplaythrough] + '" style="' + eventStyle + '"></div>'
bgneal@488 98
bgneal@488 99 + '<div id="' + config.eventId[$.jPlayer.event.suspend] + '" style="clear:left;' + eventStyle + '"></div>'
bgneal@488 100 + '<div id="' + config.eventId[$.jPlayer.event.abort] + '" style="' + eventStyle + '"></div>'
bgneal@488 101 + '<div id="' + config.eventId[$.jPlayer.event.emptied] + '" style="' + eventStyle + '"></div>'
bgneal@488 102 + '<div id="' + config.eventId[$.jPlayer.event.stalled] + '" style="' + eventStyle + '"></div>'
bgneal@488 103 + '<div id="' + config.eventId[$.jPlayer.event.ratechange] + '" style="' + eventStyle + '"></div>'
bgneal@488 104 + '<div id="' + config.eventId[$.jPlayer.event.durationchange] + '" style="' + eventStyle + '"></div>'
bgneal@488 105
bgneal@488 106 + '<div style="clear:both"></div>';
bgneal@488 107
bgneal@488 108 // MJP: Would like a check here in case we missed an event.
bgneal@488 109
bgneal@488 110 // MJP: Check fails, since it is not on the page yet.
bgneal@488 111 /* $.each($.jPlayer.event, function(eventName,eventType) {
bgneal@488 112 if($("#" + config.eventId[eventType])[0] === undefined) {
bgneal@488 113 structure += '<div id="' + config.eventId[eventType] + '" style="clear:left;' + eventStyle + '">' + eventName + '</div>';
bgneal@488 114 }
bgneal@488 115 });
bgneal@488 116 */
bgneal@488 117 structure +=
bgneal@488 118 '</div>'
bgneal@488 119 + '<p><a href="#" id="' + config.updateId + '">Update</a> jPlayer Inspector</p>'
bgneal@488 120 + '<div id="' + config.configId + '"></div>'
bgneal@488 121 + '</div>';
bgneal@488 122 $(this).html(structure);
bgneal@488 123
bgneal@488 124 config.windowJq = $("#" + config.windowId);
bgneal@488 125 config.statusJq = $("#" + config.statusId);
bgneal@488 126 config.configJq = $("#" + config.configId);
bgneal@488 127 config.toggleJq = $("#" + config.toggleId);
bgneal@488 128 config.eventResetJq = $("#" + config.eventResetId);
bgneal@488 129 config.updateJq = $("#" + config.updateId);
bgneal@488 130
bgneal@488 131 $.each($.jPlayer.event, function(eventName,eventType) {
bgneal@488 132 config.eventJq[eventType] = $("#" + config.eventId[eventType]);
bgneal@488 133 config.eventJq[eventType].text(eventName + " (" + config.eventOccurrence[eventType] + ")"); // Sets the text to the event name and (0);
bgneal@488 134
bgneal@488 135 config.jPlayer.bind(eventType + ".jPlayerInspector", function(e) {
bgneal@488 136 config.eventOccurrence[e.type]++;
bgneal@488 137 if(config.eventOccurrence[e.type] > 1) {
bgneal@488 138 config.eventJq[e.type].css("background-color","#ff9");
bgneal@488 139 } else {
bgneal@488 140 config.eventJq[e.type].css("background-color","#9f9");
bgneal@488 141 }
bgneal@488 142 config.eventJq[e.type].text(eventName + " (" + config.eventOccurrence[e.type] + ")");
bgneal@488 143 // The timer to handle the color
bgneal@488 144 clearTimeout(config.eventTimeout[e.type]);
bgneal@488 145 config.eventTimeout[e.type] = setTimeout(function() {
bgneal@488 146 config.eventJq[e.type].css("background-color","#fff");
bgneal@488 147 }, 1000);
bgneal@488 148 // The timer to handle the occurences.
bgneal@488 149 setTimeout(function() {
bgneal@488 150 config.eventOccurrence[e.type]--;
bgneal@488 151 config.eventJq[e.type].text(eventName + " (" + config.eventOccurrence[e.type] + ")");
bgneal@488 152 }, 1000);
bgneal@488 153 if(config.visible) { // Update the status, if inspector open.
bgneal@488 154 $this.jPlayerInspector("updateStatus");
bgneal@488 155 }
bgneal@488 156 });
bgneal@488 157 });
bgneal@488 158
bgneal@488 159 config.jPlayer.bind($.jPlayer.event.ready + ".jPlayerInspector", function(e) {
bgneal@488 160 $this.jPlayerInspector("updateConfig");
bgneal@488 161 });
bgneal@488 162
bgneal@488 163 config.toggleJq.click(function() {
bgneal@488 164 if(config.visible) {
bgneal@488 165 $(this).text("Show");
bgneal@488 166 config.windowJq.hide();
bgneal@488 167 config.statusJq.empty();
bgneal@488 168 config.configJq.empty();
bgneal@488 169 } else {
bgneal@488 170 $(this).text("Hide");
bgneal@488 171 config.windowJq.show();
bgneal@488 172 config.updateJq.click();
bgneal@488 173 }
bgneal@488 174 config.visible = !config.visible;
bgneal@488 175 $(this).blur();
bgneal@488 176 return false;
bgneal@488 177 });
bgneal@488 178
bgneal@488 179 config.eventResetJq.click(function() {
bgneal@488 180 $.each($.jPlayer.event, function(eventName,eventType) {
bgneal@488 181 config.eventJq[eventType].css("background-color","#eee");
bgneal@488 182 });
bgneal@488 183 $(this).blur();
bgneal@488 184 return false;
bgneal@488 185 });
bgneal@488 186
bgneal@488 187 config.updateJq.click(function() {
bgneal@488 188 $this.jPlayerInspector("updateStatus");
bgneal@488 189 $this.jPlayerInspector("updateConfig");
bgneal@488 190 return false;
bgneal@488 191 });
bgneal@488 192
bgneal@488 193 if(!config.visible) {
bgneal@488 194 config.windowJq.hide();
bgneal@488 195 } else {
bgneal@488 196 // config.updateJq.click();
bgneal@488 197 }
bgneal@488 198
bgneal@488 199 $.jPlayerInspector.i++;
bgneal@488 200
bgneal@488 201 return this;
bgneal@488 202 },
bgneal@488 203 destroy: function() {
bgneal@488 204 $(this).data("jPlayerInspector") && $(this).data("jPlayerInspector").jPlayer.unbind(".jPlayerInspector");
bgneal@488 205 $(this).empty();
bgneal@488 206 },
bgneal@488 207 updateConfig: function() { // This displays information about jPlayer's configuration in inspector
bgneal@488 208
bgneal@488 209 var jPlayerInfo = "<p>This jPlayer instance is running in your browser where:<br />"
bgneal@488 210
bgneal@488 211 for(i = 0; i < $(this).data("jPlayerInspector").jPlayer.data("jPlayer").solutions.length; i++) {
bgneal@488 212 var solution = $(this).data("jPlayerInspector").jPlayer.data("jPlayer").solutions[i];
bgneal@488 213 jPlayerInfo += "&nbsp;jPlayer's <strong>" + solution + "</strong> solution is";
bgneal@488 214 if($(this).data("jPlayerInspector").jPlayer.data("jPlayer")[solution].used) {
bgneal@488 215 jPlayerInfo += " being <strong>used</strong> and will support:<strong>";
bgneal@488 216 for(format in $(this).data("jPlayerInspector").jPlayer.data("jPlayer")[solution].support) {
bgneal@488 217 if($(this).data("jPlayerInspector").jPlayer.data("jPlayer")[solution].support[format]) {
bgneal@488 218 jPlayerInfo += " " + format;
bgneal@488 219 }
bgneal@488 220 }
bgneal@488 221 jPlayerInfo += "</strong><br />";
bgneal@488 222 } else {
bgneal@488 223 jPlayerInfo += " <strong>not required</strong><br />";
bgneal@488 224 }
bgneal@488 225 }
bgneal@488 226 jPlayerInfo += "</p>";
bgneal@488 227
bgneal@488 228 if($(this).data("jPlayerInspector").jPlayer.data("jPlayer").html.active) {
bgneal@488 229 if($(this).data("jPlayerInspector").jPlayer.data("jPlayer").flash.active) {
bgneal@488 230 jPlayerInfo += "<strong>Problem with jPlayer since both HTML5 and Flash are active.</strong>";
bgneal@488 231 } else {
bgneal@488 232 jPlayerInfo += "The <strong>HTML5 is active</strong>.";
bgneal@488 233 }
bgneal@488 234 } else {
bgneal@488 235 if($(this).data("jPlayerInspector").jPlayer.data("jPlayer").flash.active) {
bgneal@488 236 jPlayerInfo += "The <strong>Flash is active</strong>.";
bgneal@488 237 } else {
bgneal@488 238 jPlayerInfo += "No solution is currently active. jPlayer needs a setMedia().";
bgneal@488 239 }
bgneal@488 240 }
bgneal@488 241 jPlayerInfo += "</p>";
bgneal@488 242
bgneal@488 243 var formatType = $(this).data("jPlayerInspector").jPlayer.data("jPlayer").status.formatType;
bgneal@488 244 jPlayerInfo += "<p><code>status.formatType = '" + formatType + "'</code><br />";
bgneal@488 245 if(formatType) {
bgneal@488 246 jPlayerInfo += "<code>Browser canPlay('" + $.jPlayer.prototype.format[formatType].codec + "')</code>";
bgneal@488 247 } else {
bgneal@488 248 jPlayerInfo += "</p>";
bgneal@488 249 }
bgneal@488 250
bgneal@488 251 jPlayerInfo += "<p><code>status.src = '" + $(this).data("jPlayerInspector").jPlayer.data("jPlayer").status.src + "'</code></p>";
bgneal@488 252
bgneal@488 253 jPlayerInfo += "<p><code>status.media = {<br />";
bgneal@488 254 for(prop in $(this).data("jPlayerInspector").jPlayer.data("jPlayer").status.media) {
bgneal@488 255 jPlayerInfo += "&nbsp;" + prop + ": " + $(this).data("jPlayerInspector").jPlayer.data("jPlayer").status.media[prop] + "<br />"; // Some are strings
bgneal@488 256 }
bgneal@488 257 jPlayerInfo += "};</code></p>"
bgneal@488 258
bgneal@488 259 + "<p>Raw browser test for HTML5 support. Should equal a function if HTML5 is available.<br />";
bgneal@488 260 if($(this).data("jPlayerInspector").jPlayer.data("jPlayer").html.audio.available) {
bgneal@488 261 jPlayerInfo += "<code>htmlElement.audio.canPlayType = " + (typeof $(this).data("jPlayerInspector").jPlayer.data("jPlayer").htmlElement.audio.canPlayType) +"</code><br />"
bgneal@488 262 }
bgneal@488 263 if($(this).data("jPlayerInspector").jPlayer.data("jPlayer").html.video.available) {
bgneal@488 264 jPlayerInfo += "<code>htmlElement.video.canPlayType = " + (typeof $(this).data("jPlayerInspector").jPlayer.data("jPlayer").htmlElement.video.canPlayType) +"</code>";
bgneal@488 265 }
bgneal@488 266 jPlayerInfo += "</p>";
bgneal@488 267
bgneal@488 268 jPlayerInfo += "<p>This instance is using the constructor options:<br />"
bgneal@488 269 + "<code>$('#" + $(this).data("jPlayerInspector").jPlayer.data("jPlayer").internal.self.id + "').jPlayer({<br />"
bgneal@488 270
bgneal@488 271 + "&nbsp;swfPath: '" + $(this).data("jPlayerInspector").jPlayer.jPlayer("option", "swfPath") + "',<br />"
bgneal@488 272
bgneal@488 273 + "&nbsp;solution: '" + $(this).data("jPlayerInspector").jPlayer.jPlayer("option", "solution") + "',<br />"
bgneal@488 274
bgneal@488 275 + "&nbsp;supplied: '" + $(this).data("jPlayerInspector").jPlayer.jPlayer("option", "supplied") + "',<br />"
bgneal@488 276
bgneal@488 277 + "&nbsp;preload: '" + $(this).data("jPlayerInspector").jPlayer.jPlayer("option", "preload") + "',<br />"
bgneal@488 278
bgneal@488 279 + "&nbsp;volume: " + $(this).data("jPlayerInspector").jPlayer.jPlayer("option", "volume") + ",<br />"
bgneal@488 280
bgneal@488 281 + "&nbsp;muted: " + $(this).data("jPlayerInspector").jPlayer.jPlayer("option", "muted") + ",<br />"
bgneal@488 282
bgneal@488 283 + "&nbsp;backgroundColor: '" + $(this).data("jPlayerInspector").jPlayer.jPlayer("option", "backgroundColor") + "',<br />"
bgneal@488 284
bgneal@488 285 + "&nbsp;cssSelectorAncestor: '" + $(this).data("jPlayerInspector").jPlayer.jPlayer("option", "cssSelectorAncestor") + "',<br />"
bgneal@488 286
bgneal@488 287 + "&nbsp;cssSelector: {";
bgneal@488 288
bgneal@488 289 var cssSelector = $(this).data("jPlayerInspector").jPlayer.jPlayer("option", "cssSelector");
bgneal@488 290 for(prop in cssSelector) {
bgneal@488 291
bgneal@488 292 // jPlayerInfo += "<br />&nbsp;&nbsp;" + prop + ": '" + cssSelector[prop] + "'," // This works too of course, but want to use option method for deep keys.
bgneal@488 293 jPlayerInfo += "<br />&nbsp;&nbsp;" + prop + ": '" + $(this).data("jPlayerInspector").jPlayer.jPlayer("option", "cssSelector." + prop) + "',"
bgneal@488 294 }
bgneal@488 295
bgneal@488 296 jPlayerInfo = jPlayerInfo.slice(0, -1); // Because the sloppy comma was bugging me.
bgneal@488 297
bgneal@488 298 jPlayerInfo += "<br />&nbsp;},<br />"
bgneal@488 299
bgneal@488 300 + "&nbsp;errorAlerts: " + $(this).data("jPlayerInspector").jPlayer.jPlayer("option", "errorAlerts") + ",<br />"
bgneal@488 301
bgneal@488 302 + "&nbsp;warningAlerts: " + $(this).data("jPlayerInspector").jPlayer.jPlayer("option", "warningAlerts") + "<br />"
bgneal@488 303
bgneal@488 304 + "});</code></p>";
bgneal@488 305 $(this).data("jPlayerInspector").configJq.html(jPlayerInfo);
bgneal@488 306 return this;
bgneal@488 307 },
bgneal@488 308 updateStatus: function() { // This displays information about jPlayer's status in the inspector
bgneal@488 309 $(this).data("jPlayerInspector").statusJq.html(
bgneal@488 310 "<p>jPlayer is " +
bgneal@488 311 ($(this).data("jPlayerInspector").jPlayer.data("jPlayer").status.paused ? "paused" : "playing") +
bgneal@488 312 " at time: " + Math.floor($(this).data("jPlayerInspector").jPlayer.data("jPlayer").status.currentTime*10)/10 + "s." +
bgneal@488 313 " (d: " + Math.floor($(this).data("jPlayerInspector").jPlayer.data("jPlayer").status.duration*10)/10 + "s" +
bgneal@488 314 ", sp: " + Math.floor($(this).data("jPlayerInspector").jPlayer.data("jPlayer").status.seekPercent) + "%" +
bgneal@488 315 ", cpr: " + Math.floor($(this).data("jPlayerInspector").jPlayer.data("jPlayer").status.currentPercentRelative) + "%" +
bgneal@488 316 ", cpa: " + Math.floor($(this).data("jPlayerInspector").jPlayer.data("jPlayer").status.currentPercentAbsolute) + "%)</p>"
bgneal@488 317 );
bgneal@488 318 return this;
bgneal@488 319 }
bgneal@488 320 };
bgneal@488 321 $.fn.jPlayerInspector = function( method ) {
bgneal@488 322 // Method calling logic
bgneal@488 323 if ( methods[method] ) {
bgneal@488 324 return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
bgneal@488 325 } else if ( typeof method === 'object' || ! method ) {
bgneal@488 326 return methods.init.apply( this, arguments );
bgneal@488 327 } else {
bgneal@488 328 $.error( 'Method ' + method + ' does not exist on jQuery.jPlayerInspector' );
bgneal@488 329 }
bgneal@488 330 };
bgneal@488 331 })(jQuery);