1/*
2 * jQuery Pines Notify (pnotify) Plugin 1.2.0
3 *
4 * http://pinesframework.org/pnotify/
5 * Copyright (c) 2009-2012 Hunter Perrin
6 *
7 * Triple license under the GPL, LGPL, and MPL:
8 *	  http://www.gnu.org/licenses/gpl.html
9 *	  http://www.gnu.org/licenses/lgpl.html
10 *	  http://www.mozilla.org/MPL/MPL-1.1.html
11 */
12
13(function($) {
14	var history_handle_top,
15		timer,
16		body,
17		jwindow = $(window),
18		styling = {
19			jqueryui: {
20				container: "ui-widget ui-widget-content ui-corner-all",
21				notice: "ui-state-highlight",
22				// (The actual jQUI notice icon looks terrible.)
23				notice_icon: "ui-icon ui-icon-info",
24				info: "",
25				info_icon: "ui-icon ui-icon-info",
26				success: "ui-state-default",
27				success_icon: "ui-icon ui-icon-circle-check",
28				error: "ui-state-error",
29				error_icon: "ui-icon ui-icon-alert",
30				closer: "ui-icon ui-icon-close",
31				pin_up: "ui-icon ui-icon-pin-w",
32				pin_down: "ui-icon ui-icon-pin-s",
33				hi_menu: "ui-state-default ui-corner-bottom",
34				hi_btn: "ui-state-default ui-corner-all",
35				hi_btnhov: "ui-state-hover",
36				hi_hnd: "ui-icon ui-icon-grip-dotted-horizontal"
37			},
38			bootstrap: {
39				container: "alert",
40				notice: "",
41				notice_icon: "icon-exclamation-sign",
42				info: "alert-info",
43				info_icon: "icon-info-sign",
44				success: "alert-success",
45				success_icon: "icon-ok-sign",
46				error: "alert-error",
47				error_icon: "icon-warning-sign",
48				closer: "icon-remove",
49				pin_up: "icon-pause",
50				pin_down: "icon-play",
51				hi_menu: "well",
52				hi_btn: "btn",
53				hi_btnhov: "",
54				hi_hnd: "icon-chevron-down"
55			}
56		};
57	// Set global variables.
58	var do_when_ready = function(){
59		body = $("body");
60		jwindow = $(window);
61		// Reposition the notices when the window resizes.
62		jwindow.bind('resize', function(){
63			if (timer)
64				clearTimeout(timer);
65			timer = setTimeout($.pnotify_position_all, 10);
66		});
67	};
68	if (document.body)
69		do_when_ready();
70	else
71		$(do_when_ready);
72	$.extend({
73		pnotify_remove_all: function () {
74			var notices_data = jwindow.data("pnotify");
75			/* POA: Added null-check */
76			if (notices_data && notices_data.length) {
77				$.each(notices_data, function(){
78					if (this.pnotify_remove)
79						this.pnotify_remove();
80				});
81			}
82		},
83		pnotify_position_all: function () {
84			// This timer is used for queueing this function so it doesn't run
85			// repeatedly.
86			if (timer)
87				clearTimeout(timer);
88			timer = null;
89			// Get all the notices.
90			var notices_data = jwindow.data("pnotify");
91			if (!notices_data || !notices_data.length)
92				return;
93			// Reset the next position data.
94			$.each(notices_data, function(){
95				var s = this.opts.stack;
96				if (!s) return;
97				s.nextpos1 = s.firstpos1;
98				s.nextpos2 = s.firstpos2;
99				s.addpos2 = 0;
100				s.animation = true;
101			});
102			$.each(notices_data, function(){
103				this.pnotify_position();
104			});
105		},
106		pnotify: function(options) {
107			// Stores what is currently being animated (in or out).
108			var animating;
109
110			// Build main options.
111			var opts;
112			if (typeof options != "object") {
113				opts = $.extend({}, $.pnotify.defaults);
114				opts.text = options;
115			} else {
116				opts = $.extend({}, $.pnotify.defaults, options);
117			}
118			// Translate old pnotify_ style options.
119			for (var i in opts) {
120				if (typeof i == "string" && i.match(/^pnotify_/))
121					opts[i.replace(/^pnotify_/, "")] = opts[i];
122			}
123
124			if (opts.before_init) {
125				if (opts.before_init(opts) === false)
126					return null;
127			}
128
129			// This keeps track of the last element the mouse was over, so
130			// mouseleave, mouseenter, etc can be called.
131			var nonblock_last_elem;
132			// This is used to pass events through the notice if it is non-blocking.
133			var nonblock_pass = function(e, e_name){
134				pnotify.css("display", "none");
135				var element_below = document.elementFromPoint(e.clientX, e.clientY);
136				pnotify.css("display", "block");
137				var jelement_below = $(element_below);
138				var cursor_style = jelement_below.css("cursor");
139				pnotify.css("cursor", cursor_style != "auto" ? cursor_style : "default");
140				// If the element changed, call mouseenter, mouseleave, etc.
141				if (!nonblock_last_elem || nonblock_last_elem.get(0) != element_below) {
142					if (nonblock_last_elem) {
143						dom_event.call(nonblock_last_elem.get(0), "mouseleave", e.originalEvent);
144						dom_event.call(nonblock_last_elem.get(0), "mouseout", e.originalEvent);
145					}
146					dom_event.call(element_below, "mouseenter", e.originalEvent);
147					dom_event.call(element_below, "mouseover", e.originalEvent);
148				}
149				dom_event.call(element_below, e_name, e.originalEvent);
150				// Remember the latest element the mouse was over.
151				nonblock_last_elem = jelement_below;
152			};
153
154			// Get our styling object.
155			var styles = styling[opts.styling];
156
157			// Create our widget.
158			// Stop animation, reset the removal timer, and show the close
159			// button when the user mouses over.
160			var pnotify = $("<div />", {
161				"class": "ui-pnotify "+opts.addclass,
162				"css": {"display": "none"},
163				"mouseenter": function(e){
164					if (opts.nonblock) e.stopPropagation();
165					if (opts.mouse_reset && animating == "out") {
166						// If it's animating out, animate back in really quickly.
167						pnotify.stop(true);
168						animating = "in";
169						pnotify.css("height", "auto").animate({"width": opts.width, "opacity": opts.nonblock ? opts.nonblock_opacity : opts.opacity}, "fast");
170					}
171					if (opts.nonblock) {
172						// If it's non-blocking, animate to the other opacity.
173						pnotify.animate({"opacity": opts.nonblock_opacity}, "fast");
174					}
175					// Stop the close timer.
176					if (opts.hide && opts.mouse_reset) pnotify.pnotify_cancel_remove();
177					// Show the buttons.
178					if (opts.sticker && !opts.nonblock) pnotify.sticker.trigger("pnotify_icon").css("visibility", "visible");
179					if (opts.closer && !opts.nonblock) pnotify.closer.css("visibility", "visible");
180				},
181				"mouseleave": function(e){
182					if (opts.nonblock) e.stopPropagation();
183					nonblock_last_elem = null;
184					pnotify.css("cursor", "auto");
185					// Animate back to the normal opacity.
186					if (opts.nonblock && animating != "out")
187						pnotify.animate({"opacity": opts.opacity}, "fast");
188					// Start the close timer.
189					if (opts.hide && opts.mouse_reset) pnotify.pnotify_queue_remove();
190					// Hide the buttons.
191					if (opts.sticker_hover)
192						pnotify.sticker.css("visibility", "hidden");
193					if (opts.closer_hover)
194						pnotify.closer.css("visibility", "hidden");
195					$.pnotify_position_all();
196				},
197				"mouseover": function(e){
198					if (opts.nonblock) e.stopPropagation();
199				},
200				"mouseout": function(e){
201					if (opts.nonblock) e.stopPropagation();
202				},
203				"mousemove": function(e){
204					if (opts.nonblock) {
205						e.stopPropagation();
206						nonblock_pass(e, "onmousemove");
207					}
208				},
209				"mousedown": function(e){
210					if (opts.nonblock) {
211						e.stopPropagation();
212						e.preventDefault();
213						nonblock_pass(e, "onmousedown");
214					}
215				},
216				"mouseup": function(e){
217					if (opts.nonblock) {
218						e.stopPropagation();
219						e.preventDefault();
220						nonblock_pass(e, "onmouseup");
221					}
222				},
223				"click": function(e){
224					if (opts.nonblock) {
225						e.stopPropagation();
226						nonblock_pass(e, "onclick");
227					}
228				},
229				"dblclick": function(e){
230					if (opts.nonblock) {
231						e.stopPropagation();
232						nonblock_pass(e, "ondblclick");
233					}
234				}
235			});
236			pnotify.opts = opts;
237			// Create a container for the notice contents.
238			pnotify.container = $("<div />", {"class": styles.container+" ui-pnotify-container "+(opts.type == "error" ? styles.error : (opts.type == "info" ? styles.info : (opts.type == "success" ? styles.success : styles.notice)))})
239			.appendTo(pnotify);
240			if (opts.cornerclass != "")
241				pnotify.container.removeClass("ui-corner-all").addClass(opts.cornerclass);
242			// Create a drop shadow.
243			if (opts.shadow)
244				pnotify.container.addClass("ui-pnotify-shadow");
245
246			// The current version of Pines Notify.
247			pnotify.pnotify_version = "1.2.0";
248
249			// This function is for updating the notice.
250			pnotify.pnotify = function(options) {
251				// Update the notice.
252				var old_opts = opts;
253				if (typeof options == "string")
254					opts.text = options;
255				else
256					opts = $.extend({}, opts, options);
257				// Translate old pnotify_ style options.
258				for (var i in opts) {
259					if (typeof i == "string" && i.match(/^pnotify_/))
260						opts[i.replace(/^pnotify_/, "")] = opts[i];
261				}
262				pnotify.opts = opts;
263				// Update the corner class.
264				if (opts.cornerclass != old_opts.cornerclass)
265					pnotify.container.removeClass("ui-corner-all").addClass(opts.cornerclass);
266				// Update the shadow.
267				if (opts.shadow != old_opts.shadow) {
268					if (opts.shadow)
269						pnotify.container.addClass("ui-pnotify-shadow");
270					else
271						pnotify.container.removeClass("ui-pnotify-shadow");
272				}
273				// Update the additional classes.
274				if (opts.addclass === false)
275					pnotify.removeClass(old_opts.addclass);
276				else if (opts.addclass !== old_opts.addclass)
277					pnotify.removeClass(old_opts.addclass).addClass(opts.addclass);
278				// Update the title.
279				if (opts.title === false)
280					pnotify.title_container.slideUp("fast");
281				else if (opts.title !== old_opts.title) {
282					if (opts.title_escape)
283						pnotify.title_container.text(opts.title).slideDown(200);
284					else
285						pnotify.title_container.html(opts.title).slideDown(200);
286				}
287				// Update the text.
288				if (opts.text === false) {
289					pnotify.text_container.slideUp("fast");
290				} else if (opts.text !== old_opts.text) {
291					if (opts.text_escape)
292						pnotify.text_container.text(opts.text).slideDown(200);
293					else
294						pnotify.text_container.html(opts.insert_brs ? String(opts.text).replace(/\n/g, "<br />") : opts.text).slideDown(200);
295				}
296				// Update values for history menu access.
297				pnotify.pnotify_history = opts.history;
298				pnotify.pnotify_hide = opts.hide;
299				// Change the notice type.
300				if (opts.type != old_opts.type)
301					pnotify.container.removeClass(styles.error+" "+styles.notice+" "+styles.success+" "+styles.info).addClass(opts.type == "error" ? styles.error : (opts.type == "info" ? styles.info : (opts.type == "success" ? styles.success : styles.notice)));
302				if (opts.icon !== old_opts.icon || (opts.icon === true && opts.type != old_opts.type)) {
303					// Remove any old icon.
304					pnotify.container.find("div.ui-pnotify-icon").remove();
305					if (opts.icon !== false) {
306						// Build the new icon.
307						$("<div />", {"class": "ui-pnotify-icon"})
308						.append($("<span />", {"class": opts.icon === true ? (opts.type == "error" ? styles.error_icon : (opts.type == "info" ? styles.info_icon : (opts.type == "success" ? styles.success_icon : styles.notice_icon))) : opts.icon}))
309						.prependTo(pnotify.container);
310					}
311				}
312				// Update the width.
313				if (opts.width !== old_opts.width)
314					pnotify.animate({width: opts.width});
315				// Update the minimum height.
316				if (opts.min_height !== old_opts.min_height)
317					pnotify.container.animate({minHeight: opts.min_height});
318				// Update the opacity.
319				if (opts.opacity !== old_opts.opacity)
320					pnotify.fadeTo(opts.animate_speed, opts.opacity);
321				// Update the sticker and closer buttons.
322				if (!opts.closer || opts.nonblock)
323					pnotify.closer.css("display", "none");
324				else
325					pnotify.closer.css("display", "block");
326				if (!opts.sticker || opts.nonblock)
327					pnotify.sticker.css("display", "none");
328				else
329					pnotify.sticker.css("display", "block");
330				// Update the sticker icon.
331				pnotify.sticker.trigger("pnotify_icon");
332				// Update the hover status of the buttons.
333				if (opts.sticker_hover)
334					pnotify.sticker.css("visibility", "hidden");
335				else if (!opts.nonblock)
336					pnotify.sticker.css("visibility", "visible");
337				if (opts.closer_hover)
338					pnotify.closer.css("visibility", "hidden");
339				else if (!opts.nonblock)
340					pnotify.closer.css("visibility", "visible");
341				// Update the timed hiding.
342				if (!opts.hide)
343					pnotify.pnotify_cancel_remove();
344				else if (!old_opts.hide)
345					pnotify.pnotify_queue_remove();
346				pnotify.pnotify_queue_position();
347				return pnotify;
348			};
349
350			// Position the notice. dont_skip_hidden causes the notice to
351			// position even if it's not visible.
352			pnotify.pnotify_position = function(dont_skip_hidden){
353				// Get the notice's stack.
354				var s = pnotify.opts.stack;
355				if (!s) return;
356				if (!s.nextpos1)
357					s.nextpos1 = s.firstpos1;
358				if (!s.nextpos2)
359					s.nextpos2 = s.firstpos2;
360				if (!s.addpos2)
361					s.addpos2 = 0;
362				var hidden = pnotify.css("display") == "none";
363				// Skip this notice if it's not shown.
364				if (!hidden || dont_skip_hidden) {
365					var curpos1, curpos2;
366					// Store what will need to be animated.
367					var animate = {};
368					// Calculate the current pos1 value.
369					var csspos1;
370					switch (s.dir1) {
371						case "down":
372							csspos1 = "top";
373							break;
374						case "up":
375							csspos1 = "bottom";
376							break;
377						case "left":
378							csspos1 = "right";
379							break;
380						case "right":
381							csspos1 = "left";
382							break;
383					}
384					curpos1 = parseInt(pnotify.css(csspos1));
385					if (isNaN(curpos1))
386						curpos1 = 0;
387					// Remember the first pos1, so the first visible notice goes there.
388					if (typeof s.firstpos1 == "undefined" && !hidden) {
389						s.firstpos1 = curpos1;
390						s.nextpos1 = s.firstpos1;
391					}
392					// Calculate the current pos2 value.
393					var csspos2;
394					switch (s.dir2) {
395						case "down":
396							csspos2 = "top";
397							break;
398						case "up":
399							csspos2 = "bottom";
400							break;
401						case "left":
402							csspos2 = "right";
403							break;
404						case "right":
405							csspos2 = "left";
406							break;
407					}
408					curpos2 = parseInt(pnotify.css(csspos2));
409					if (isNaN(curpos2))
410						curpos2 = 0;
411					// Remember the first pos2, so the first visible notice goes there.
412					if (typeof s.firstpos2 == "undefined" && !hidden) {
413						s.firstpos2 = curpos2;
414						s.nextpos2 = s.firstpos2;
415					}
416					// Check that it's not beyond the viewport edge.
417					if ((s.dir1 == "down" && s.nextpos1 + pnotify.height() > jwindow.height()) ||
418						(s.dir1 == "up" && s.nextpos1 + pnotify.height() > jwindow.height()) ||
419						(s.dir1 == "left" && s.nextpos1 + pnotify.width() > jwindow.width()) ||
420						(s.dir1 == "right" && s.nextpos1 + pnotify.width() > jwindow.width()) ) {
421						// If it is, it needs to go back to the first pos1, and over on pos2.
422						s.nextpos1 = s.firstpos1;
423						s.nextpos2 += s.addpos2 + (typeof s.spacing2 == "undefined" ? 25 : s.spacing2);
424						s.addpos2 = 0;
425					}
426					// Animate if we're moving on dir2.
427					if (s.animation && s.nextpos2 < curpos2) {
428						switch (s.dir2) {
429							case "down":
430								animate.top = s.nextpos2+"px";
431								break;
432							case "up":
433								animate.bottom = s.nextpos2+"px";
434								break;
435							case "left":
436								animate.right = s.nextpos2+"px";
437								break;
438							case "right":
439								animate.left = s.nextpos2+"px";
440								break;
441						}
442					} else
443						pnotify.css(csspos2, s.nextpos2+"px");
444					// Keep track of the widest/tallest notice in the column/row, so we can push the next column/row.
445					switch (s.dir2) {
446						case "down":
447						case "up":
448							if (pnotify.outerHeight(true) > s.addpos2)
449								s.addpos2 = pnotify.height();
450							break;
451						case "left":
452						case "right":
453							if (pnotify.outerWidth(true) > s.addpos2)
454								s.addpos2 = pnotify.width();
455							break;
456					}
457					// Move the notice on dir1.
458					if (s.nextpos1) {
459						// Animate if we're moving toward the first pos.
460						if (s.animation && (curpos1 > s.nextpos1 || animate.top || animate.bottom || animate.right || animate.left)) {
461							switch (s.dir1) {
462								case "down":
463									animate.top = s.nextpos1+"px";
464									break;
465								case "up":
466									animate.bottom = s.nextpos1+"px";
467									break;
468								case "left":
469									animate.right = s.nextpos1+"px";
470									break;
471								case "right":
472									animate.left = s.nextpos1+"px";
473									break;
474							}
475						} else
476							pnotify.css(csspos1, s.nextpos1+"px");
477					}
478					// Run the animation.
479					if (animate.top || animate.bottom || animate.right || animate.left)
480						pnotify.animate(animate, {duration: 500, queue: false});
481					// Calculate the next dir1 position.
482					switch (s.dir1) {
483						case "down":
484						case "up":
485							s.nextpos1 += pnotify.height() + (typeof s.spacing1 == "undefined" ? 25 : s.spacing1);
486							break;
487						case "left":
488						case "right":
489							s.nextpos1 += pnotify.width() + (typeof s.spacing1 == "undefined" ? 25 : s.spacing1);
490							break;
491					}
492				}
493			};
494
495			// Queue the positiona all function so it doesn't run repeatedly and
496			// use up resources.
497			pnotify.pnotify_queue_position = function(milliseconds){
498				if (timer)
499					clearTimeout(timer);
500				if (!milliseconds)
501					milliseconds = 10;
502				timer = setTimeout($.pnotify_position_all, milliseconds);
503			};
504
505			// Display the notice.
506			pnotify.pnotify_display = function() {
507				// If the notice is not in the DOM, append it.
508				if (!pnotify.parent().length)
509					pnotify.appendTo(body);
510				// Run callback.
511				if (opts.before_open) {
512					if (opts.before_open(pnotify) === false)
513						return;
514				}
515				// Try to put it in the right position.
516				if (opts.stack.push != "top")
517					pnotify.pnotify_position(true);
518				// First show it, then set its opacity, then hide it.
519				if (opts.animation == "fade" || opts.animation.effect_in == "fade") {
520					// If it's fading in, it should start at 0.
521					pnotify.show().fadeTo(0, 0).hide();
522				} else {
523					// Or else it should be set to the opacity.
524					if (opts.opacity != 1)
525						pnotify.show().fadeTo(0, opts.opacity).hide();
526				}
527				pnotify.animate_in(function(){
528					if (opts.after_open)
529						opts.after_open(pnotify);
530
531					pnotify.pnotify_queue_position();
532
533					// Now set it to hide.
534					if (opts.hide)
535						pnotify.pnotify_queue_remove();
536				});
537			};
538
539			// Remove the notice.
540			pnotify.pnotify_remove = function() {
541				if (pnotify.timer) {
542					window.clearTimeout(pnotify.timer);
543					pnotify.timer = null;
544				}
545				// Run callback.
546				if (opts.before_close) {
547					if (opts.before_close(pnotify) === false)
548						return;
549				}
550				pnotify.animate_out(function(){
551					if (opts.after_close) {
552						if (opts.after_close(pnotify) === false)
553							return;
554					}
555					pnotify.pnotify_queue_position();
556					// If we're supposed to remove the notice from the DOM, do it.
557					if (opts.remove)
558						pnotify.detach();
559				});
560			};
561
562			// Animate the notice in.
563			pnotify.animate_in = function(callback){
564				// Declare that the notice is animating in. (Or has completed animating in.)
565				animating = "in";
566				var animation;
567				if (typeof opts.animation.effect_in != "undefined")
568					animation = opts.animation.effect_in;
569				else
570					animation = opts.animation;
571				if (animation == "none") {
572					pnotify.show();
573					callback();
574				} else if (animation == "show")
575					pnotify.show(opts.animate_speed, callback);
576				else if (animation == "fade")
577					pnotify.show().fadeTo(opts.animate_speed, opts.opacity, callback);
578				else if (animation == "slide")
579					pnotify.slideDown(opts.animate_speed, callback);
580				else if (typeof animation == "function")
581					animation("in", callback, pnotify);
582				else
583					pnotify.show(animation, (typeof opts.animation.options_in == "object" ? opts.animation.options_in : {}), opts.animate_speed, callback);
584			};
585
586			// Animate the notice out.
587			pnotify.animate_out = function(callback){
588				// Declare that the notice is animating out. (Or has completed animating out.)
589				animating = "out";
590				var animation;
591				if (typeof opts.animation.effect_out != "undefined")
592					animation = opts.animation.effect_out;
593				else
594					animation = opts.animation;
595				if (animation == "none") {
596					pnotify.hide();
597					callback();
598				} else if (animation == "show")
599					pnotify.hide(opts.animate_speed, callback);
600				else if (animation == "fade")
601					pnotify.fadeOut(opts.animate_speed, callback);
602				else if (animation == "slide")
603					pnotify.slideUp(opts.animate_speed, callback);
604				else if (typeof animation == "function")
605					animation("out", callback, pnotify);
606				else
607					pnotify.hide(animation, (typeof opts.animation.options_out == "object" ? opts.animation.options_out : {}), opts.animate_speed, callback);
608			};
609
610			// Cancel any pending removal timer.
611			pnotify.pnotify_cancel_remove = function() {
612				if (pnotify.timer)
613					window.clearTimeout(pnotify.timer);
614			};
615
616			// Queue a removal timer.
617			pnotify.pnotify_queue_remove = function() {
618				// Cancel any current removal timer.
619				pnotify.pnotify_cancel_remove();
620				pnotify.timer = window.setTimeout(function(){
621					pnotify.pnotify_remove();
622				}, (isNaN(opts.delay) ? 0 : opts.delay));
623			};
624
625			// Provide a button to close the notice.
626			pnotify.closer = $("<div />", {
627				"class": "ui-pnotify-closer",
628				"css": {"cursor": "pointer", "visibility": opts.closer_hover ? "hidden" : "visible"},
629				"click": function(){
630					pnotify.pnotify_remove();
631					pnotify.sticker.css("visibility", "hidden");
632					pnotify.closer.css("visibility", "hidden");
633				}
634			})
635			.append($("<span />", {"class": styles.closer}))
636			.appendTo(pnotify.container);
637			if (!opts.closer || opts.nonblock)
638				pnotify.closer.css("display", "none");
639
640			// Provide a button to stick the notice.
641			pnotify.sticker = $("<div />", {
642				"class": "ui-pnotify-sticker",
643				"css": {"cursor": "pointer", "visibility": opts.sticker_hover ? "hidden" : "visible"},
644				"click": function(){
645					opts.hide = !opts.hide;
646					if (opts.hide)
647						pnotify.pnotify_queue_remove();
648					else
649						pnotify.pnotify_cancel_remove();
650					$(this).trigger("pnotify_icon");
651				}
652			})
653			.bind("pnotify_icon", function(){
654				$(this).children().removeClass(styles.pin_up+" "+styles.pin_down).addClass(opts.hide ? styles.pin_up : styles.pin_down);
655			})
656			.append($("<span />", {"class": styles.pin_up}))
657			.appendTo(pnotify.container);
658			if (!opts.sticker || opts.nonblock)
659				pnotify.sticker.css("display", "none");
660
661			// Add the appropriate icon.
662			if (opts.icon !== false) {
663				$("<div />", {"class": "ui-pnotify-icon"})
664				.append($("<span />", {"class": opts.icon === true ? (opts.type == "error" ? styles.error_icon : (opts.type == "info" ? styles.info_icon : (opts.type == "success" ? styles.success_icon : styles.notice_icon))) : opts.icon}))
665				.prependTo(pnotify.container);
666			}
667
668			// Add a title.
669			pnotify.title_container = $("<h4 />", {
670				"class": "ui-pnotify-title"
671			})
672			.appendTo(pnotify.container);
673			if (opts.title === false)
674				pnotify.title_container.hide();
675			else if (opts.title_escape)
676				pnotify.title_container.text(opts.title);
677			else
678				pnotify.title_container.html(opts.title);
679
680			// Add text.
681			pnotify.text_container = $("<div />", {
682				"class": "ui-pnotify-text"
683			})
684			.appendTo(pnotify.container);
685			if (opts.text === false)
686				pnotify.text_container.hide();
687			else if (opts.text_escape)
688				pnotify.text_container.text(opts.text);
689			else
690				pnotify.text_container.html(opts.insert_brs ? String(opts.text).replace(/\n/g, "<br />") : opts.text);
691
692			// Set width and min height.
693			if (typeof opts.width == "string")
694				pnotify.css("width", opts.width);
695			if (typeof opts.min_height == "string")
696				pnotify.container.css("min-height", opts.min_height);
697
698			// The history variable controls whether the notice gets redisplayed
699			// by the history pull down.
700			pnotify.pnotify_history = opts.history;
701			// The hide variable controls whether the history pull down should
702			// queue a removal timer.
703			pnotify.pnotify_hide = opts.hide;
704
705			// Add the notice to the notice array.
706			var notices_data = jwindow.data("pnotify");
707			if (notices_data == null || typeof notices_data != "object")
708				notices_data = [];
709			if (opts.stack.push == "top")
710				notices_data = $.merge([pnotify], notices_data);
711			else
712				notices_data = $.merge(notices_data, [pnotify]);
713			jwindow.data("pnotify", notices_data);
714			// Now position all the notices if they are to push to the top.
715			if (opts.stack.push == "top")
716				pnotify.pnotify_queue_position(1);
717
718			// Run callback.
719			if (opts.after_init)
720				opts.after_init(pnotify);
721
722			if (opts.history) {
723				// If there isn't a history pull down, create one.
724				var history_menu = jwindow.data("pnotify_history");
725				if (typeof history_menu == "undefined") {
726					history_menu = $("<div />", {
727						"class": "ui-pnotify-history-container "+styles.hi_menu,
728						"mouseleave": function(){
729							history_menu.animate({top: "-"+history_handle_top+"px"}, {duration: 100, queue: false});
730						}
731					})
732					.append($("<div />", {"class": "ui-pnotify-history-header", "text": "Redisplay"}))
733					.append($("<button />", {
734							"class": "ui-pnotify-history-all "+styles.hi_btn,
735							"text": "All",
736							"mouseenter": function(){
737								$(this).addClass(styles.hi_btnhov);
738							},
739							"mouseleave": function(){
740								$(this).removeClass(styles.hi_btnhov);
741							},
742							"click": function(){
743								// Display all notices. (Disregarding non-history notices.)
744								$.each(notices_data, function(){
745									if (this.pnotify_history) {
746										if (this.is(":visible")) {
747											if (this.pnotify_hide)
748												this.pnotify_queue_remove();
749										} else if (this.pnotify_display)
750											this.pnotify_display();
751									}
752								});
753								return false;
754							}
755					}))
756					.append($("<button />", {
757							"class": "ui-pnotify-history-last "+styles.hi_btn,
758							"text": "Last",
759							"mouseenter": function(){
760								$(this).addClass(styles.hi_btnhov);
761							},
762							"mouseleave": function(){
763								$(this).removeClass(styles.hi_btnhov);
764							},
765							"click": function(){
766								// Look up the last history notice, and display it.
767								var i = -1;
768								var notice;
769								do {
770									if (i == -1)
771										notice = notices_data.slice(i);
772									else
773										notice = notices_data.slice(i, i+1);
774									if (!notice[0])
775										break;
776									i--;
777								} while (!notice[0].pnotify_history || notice[0].is(":visible"));
778								if (!notice[0])
779									return false;
780								if (notice[0].pnotify_display)
781									notice[0].pnotify_display();
782								return false;
783							}
784					}))
785					.appendTo(body);
786
787					// Make a handle so the user can pull down the history tab.
788					var handle = $("<span />", {
789						"class": "ui-pnotify-history-pulldown "+styles.hi_hnd,
790						"mouseenter": function(){
791							history_menu.animate({top: "0"}, {duration: 100, queue: false});
792						}
793					})
794					.appendTo(history_menu);
795
796					// Get the top of the handle.
797					history_handle_top = handle.offset().top + 2;
798					// Hide the history pull down up to the top of the handle.
799					history_menu.css({top: "-"+history_handle_top+"px"});
800					// Save the history pull down.
801					jwindow.data("pnotify_history", history_menu);
802				}
803			}
804
805			// Mark the stack so it won't animate the new notice.
806			opts.stack.animation = false;
807
808			// Display the notice.
809			pnotify.pnotify_display();
810
811			return pnotify;
812		}
813	});
814
815	// Some useful regexes.
816	var re_on = /^on/,
817		re_mouse_events = /^(dbl)?click$|^mouse(move|down|up|over|out|enter|leave)$|^contextmenu$/,
818		re_ui_events = /^(focus|blur|select|change|reset)$|^key(press|down|up)$/,
819		re_html_events = /^(scroll|resize|(un)?load|abort|error)$/;
820	// Fire a DOM event.
821	var dom_event = function(e, orig_e){
822		var event_object;
823		e = e.toLowerCase();
824		if (document.createEvent && this.dispatchEvent) {
825			// FireFox, Opera, Safari, Chrome
826			e = e.replace(re_on, '');
827			if (e.match(re_mouse_events)) {
828				// This allows the click event to fire on the notice. There is
829				// probably a much better way to do it.
830				$(this).offset();
831				event_object = document.createEvent("MouseEvents");
832				event_object.initMouseEvent(
833					e, orig_e.bubbles, orig_e.cancelable, orig_e.view, orig_e.detail,
834					orig_e.screenX, orig_e.screenY, orig_e.clientX, orig_e.clientY,
835					orig_e.ctrlKey, orig_e.altKey, orig_e.shiftKey, orig_e.metaKey, orig_e.button, orig_e.relatedTarget
836				);
837			} else if (e.match(re_ui_events)) {
838				event_object = document.createEvent("UIEvents");
839				event_object.initUIEvent(e, orig_e.bubbles, orig_e.cancelable, orig_e.view, orig_e.detail);
840			} else if (e.match(re_html_events)) {
841				event_object = document.createEvent("HTMLEvents");
842				event_object.initEvent(e, orig_e.bubbles, orig_e.cancelable);
843			}
844			if (!event_object) return;
845			this.dispatchEvent(event_object);
846		} else {
847			// Internet Explorer
848			if (!e.match(re_on)) e = "on"+e;
849			event_object = document.createEventObject(orig_e);
850			this.fireEvent(e, event_object);
851		}
852	};
853
854	$.pnotify.defaults = {
855		// The notice's title.
856		title: false,
857		// Whether to escape the content of the title. (Not allow HTML.)
858		title_escape: false,
859		// The notice's text.
860		text: false,
861		// Whether to escape the content of the text. (Not allow HTML.)
862		text_escape: false,
863		// What styling classes to use. (Can be either jqueryui or bootstrap.)
864		styling: "bootstrap",
865		// Additional classes to be added to the notice. (For custom styling.)
866		addclass: "",
867		// Class to be added to the notice for corner styling.
868		cornerclass: "",
869		// Create a non-blocking notice. It lets the user click elements underneath it.
870		nonblock: false,
871		// The opacity of the notice (if it's non-blocking) when the mouse is over it.
872		nonblock_opacity: .2,
873		// Display a pull down menu to redisplay previous notices, and place the notice in the history.
874		history: true,
875		// Width of the notice.
876		width: "300px",
877		// Minimum height of the notice. It will expand to fit content.
878		min_height: "16px",
879		// Type of the notice. "notice", "info", "success", or "error".
880		type: "notice",
881		// Set icon to true to use the default icon for the selected style/type, false for no icon, or a string for your own icon class.
882		icon: true,
883		// The animation to use when displaying and hiding the notice. "none", "show", "fade", and "slide" are built in to jQuery. Others require jQuery UI. Use an object with effect_in and effect_out to use different effects.
884		animation: "fade",
885		// Speed at which the notice animates in and out. "slow", "def" or "normal", "fast" or number of milliseconds.
886		animate_speed: "slow",
887		// Opacity of the notice.
888		opacity: 1,
889		// Display a drop shadow.
890		shadow: true,
891		// Provide a button for the user to manually close the notice.
892		closer: true,
893		// Only show the closer button on hover.
894		closer_hover: true,
895		// Provide a button for the user to manually stick the notice.
896		sticker: true,
897		// Only show the sticker button on hover.
898		sticker_hover: true,
899		// After a delay, remove the notice.
900		hide: true,
901		// Delay in milliseconds before the notice is removed.
902		delay: 8000,
903		// Reset the hide timer if the mouse moves over the notice.
904		mouse_reset: true,
905		// Remove the notice's elements from the DOM after it is removed.
906		remove: true,
907		// Change new lines to br tags.
908		insert_brs: true,
909		// The stack on which the notices will be placed. Also controls the direction the notices stack.
910		stack: {"dir1": "down", "dir2": "left", "push": "bottom", "spacing1": 25, "spacing2": 25}
911	};
912})(jQuery);