Commit 9c5a59ce authored by Sven Franck's avatar Sven Franck

refactor of renderjs, first pass (working, not cleaned)

parent 25bd1e4d
# files
RENDERJS = renderjs.js
RENDERJS_MIN = renderjs.min.js
# npm install uglify-js
UGLIFY_CMD = $(shell which uglifyjs || echo node ~/node_modules/uglify-js/bin/uglifyjs)
# npm install jslint
LINT_CMD = $(shell which jslint || echo node ~/node_modules/jslint/bin/jslint.js) --terse
YUIDOC_CMD = $(shell which yuidoc)
auto: build
build: uglify
uglify: $(RENDERJS_MIN)
$(RENDERJS_MIN): $(RENDERJS)
$(UGLIFY_CMD) "$<" > "$@"
lint: $(RENDERJS)
$(LINT_CMD) "$<"
doc:
$(YUIDOC_CMD) .
clean:
rm -f $(RENDERJS_MIN)
What is RenderJs?
RenderJs is a JavaScript library which uses jQuery and RequireJS.
RenderJs provides an easy way to define gadgets (aka mashups) in pure HTML5 and does not require application server.
It handles dependencies through RequireJS, caching and interaction.
It is suitable for the development of mobile applications, desktop applications. It is used by OfficeJS, ERP5.
Documentation
Documentation is available at http://www.renderjs.org/
Developer
> git clone https://git.erp5.org/repos/renderjs.git
> make (will produce a minified version for now, requires nodejs installed as well its uglifyjs module)
> make lint (will produce a jslint report, requires nodejs installed as well its jslint module)
\ No newline at end of file
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>content</title>
<!-- API add service LINK (src optional) -->
<!-- <link rel="multiply" type="math" src="http://www.somewhere.com/else"> -->
<link rel="multiply" type="service/math">
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script type="text/javascript" src="../../renderjs.js"></script>
</head>
<body>
<table style="border: 1px solid black; font-size: 9px">
<tr>
<td colspan="2" style="text-align:center;"><b>content.html</b></td>
<tr>
<td><b>Loaded</b></td>
<td>Ajax (via addGadget())</td>
</tr>
<tr>
<td><b>Location</b></td>
<td>inside content_loader.html</td>
</tr>
<tr>
<td><b>Service</b></td>
<td>Multiply</td>
</tr>
<tr>
<td><b>Domain</b></td>
<td>same</td>
</tr>
</table>
<p>Loaded via AJAX, Service: Multiply, same domain</p>
<p>Hello, I provide a service</p>
<!-- API add service HTML (src optional) -->
<div data-service = '[{"rel":"multiply", "type":"service/math"}]'></div>
<script type="text/javascript">
//<![CDATA[
// service multiplication
var multiply = function (a, b) {
try {
return parseFloat(a)*parseFloat(b);
}
catch (e) {
return "not possible to multiply";
}
};
// API add service JS (src optional)
$(this).addService({"type": "service/math", "rel": "multiply"});
//]]>
</script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>content_loader</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script type="text/javascript" src="../../renderjs.js"></script>
<body>
<table style="border: 1px solid black; font-size: 9px">
<tr>
<td colspan="2" style="text-align:center;"><b>content_loader.html</b></td>
<tr>
<td><b>Loaded</b></td>
<td>iFrame (from HTML source)</td>
</tr>
<tr>
<td><b>Location</b></td>
<td>inside index.html</td>
</tr>
<tr>
<td><b>Service</b></td>
<td>None</td>
</tr>
<tr>
<td><b>Domain</b></td>
<td>same</td>
</tr>
</table>
<p>Hello, I'm the gadge text</p>
<div class="foo"></div>
<script type="text/javascript">
$('.foo').addGadget({"src":"content.html"});
// $('.foo').addGadget({"src":"content.html", "iframe":"true"});
</script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>content</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="../../renderjs.js"></script>
</head>
<body>
<table style="border: 1px solid black; font-size: 9px">
<tr>
<td colspan="2" style="text-align:center;"><b>content_menu.html</b></td>
<tr>
<td><b>Loaded</b></td>
<td>iFrame (from HTML source)</td>
</tr>
<tr>
<td><b>Location</b></td>
<td>inside index.html</td>
</tr>
<tr>
<td><b>Service</b></td>
<td>None</td>
</tr>
<tr>
<td><b>Domain</b></td>
<td>same</td>
</tr>
</table>
<ul>
<li><button class="a" data-foo="5" data-bar="6">Button A</button></li>
<li><button class="b" data-foo="4" data-bar="1">Button B</button></li>
<li><button class="c" data-foo="8" data-bar="2">Button C</button></li>
</ul>
<script type="text/javascript">
$(this.document).addGadget({"src":"http://domain1/sven/newRenderjs/examples/gadgets/deep_menu.html", "iframe":"true"});
// $(this.document).addGadget({"src":"deep_menu.html", "iframe":"true"});
</script>
<script type="text/javascript">
(function () {
//<![CDATA[
var document = this.document;
// // retrieve parameters set by renderJs
// setTimeout(function(){
// console.log("settimeout");
// console.log($("body")[0])
// console.log($("body")[0].config);
// },0);
}());
//]]>
</script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>content</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="../../renderjs.js"></script>
</head>
<body>
<table style="border: 1px solid black; font-size: 9px">
<tr>
<td colspan="2" style="text-align:center;"><b>menu_deep.html</b></td>
<tr>
<td><b>Loaded</b></td>
<td>Nested iFrame (via addGadget())</td>
</tr>
<tr>
<td><b>Location</b></td>
<td>inside content_menu.html</td>
</tr>
<tr>
<td><b>Service</b></td>
<td>substract</td>
</tr>
<tr>
<td><b>Domain</b></td>
<td>CROSS DOMAIN</td>
</tr>
</table>
<p>Hello, I'm a cross domain gadget, providing Substract Service</p>
<ul>
<li><button class="e" data-foo="1" data-bar="1">Button D</button></li>
<li><button class="f" data-foo="11" data-bar="2">Button E</button></li>
<li><button class="g" data-foo="111" data-bar="3">Button F</button></li>
</ul>
<script type="text/javascript">
//<![CDATA[
var document = this.document;
$(this.document).find('button').on('click', function(e, target) {
// var parent;
// if ( window.self === window.top || self == top ) {
// // parent is root
// parent = window.location.href;
// } else {
// // inside an iFrame
// parent = window.parent.location.href;
// }
// console.log("DEEP");
// console.log(window);
// console.log("posting to:");
// console.log(parent);
// window.postMessage(e.target.getAttribute("data-foo"), parent);
});
// service substraction
var subtract = function (a, b) {
try {
return a-b;
}
catch (e) {
return "not possible to subtract";
}
};
// API add service JS (src optional)
$(this).addService({"type": "service/math", "rel": "subtract"});
//]]>
</script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>menu</title>
<link rel="google" type="search" src="google.com/index">
<link rel="bing" type="search" src="bing.com/index">
<link rel="duckduck" type="search" src="duckduckgo.com/index">
<link rel="word" type="office" src="somewhere.com/word">
<link rel="word" type="excel" src="somewhere.com/excel">
<link rel="powerpoint" type="excel" src="somewhere.com/powerpoint">
<script type="text/javascript">
(function () {
//<![CDATA[
// console.log("I'm the menu gadget, I provide a bunch of links to display");
}());
//]]>
</script>
</head>
<body>
<p>Hello world from the menu</p>
<div data-gadget="content.html"
data-id="content"
data-iframe="true"></div>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>nestedPlainText</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script type="text/javascript" src="../renderjs.js"></script>
</head>
<body>
<table style="border: 1px solid black; font-size: 9px">
<tr>
<td colspan="2" style="text-align:center;"><b>nestedplaintext.html</b></td>
<tr>
<td><b>Loaded</b></td>
<td>Ajax (HTML hardcoded)</td>
</tr>
<tr>
<td><b>Location</b></td>
<td>inside plaintext.html</td>
</tr>
<tr>
<td><b>Service</b></td>
<td>None</td>
</tr>
<tr>
<td><b>Domain</b></td>
<td>same</td>
</tr>
</table>
<p>Just some text to see if adding gadgets recursively works on index.html</p>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>plaintext</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script type="text/javascript" src="../renderjs.js"></script>
</head>
<body>
<table style="border: 1px solid black; font-size: 9px">
<tr>
<td colspan="2" style="text-align:center;"><b>plaintext.html</b></td>
<tr>
<td><b>Loaded</b></td>
<td>Ajax (from addGadget())</td>
</tr>
<tr>
<td><b>Location</b></td>
<td>inside index.html</td>
</tr>
<tr>
<td><b>Service</b></td>
<td>None</td>
</tr>
<tr>
<td><b>Domain</b></td>
<td>same</td>
</tr>
</table>
<p>We try to calculate!</p>
<div>
<input style="width: 20px;" type="number" value="2" name="a" id="a" />
<input style="width: 20px;" type="number" value="2" name="b" id="b" />
<input style="width: 50px;" type="number" value="" name="c" id="c" />
<button id="multiply">Multiply</button>
<button id="subtract" disabled="disabled">Subtract</button>
</div>
<div data-gadget="gadgets/nestedplaintext.html"></div>
<script type="text/javascript">
$(this.document).find('button').on('click', function(e, target) {
var options = {
"service" : $(this).attr('id'),
"parameters": [$('#a').val(), $('#b').val()]
};
// this is not perfect, because I cannot call $("button").requestService...
$("#c").val(renderJs.requestService(options));
});
</script>
</body>
</html>
\ No newline at end of file
<html>
<head>
<script data-main="../../require-renderjs.js"
type="text/javascript"
src="../../lib/require/require.js"></script>
<head>
<body>
<div id="say-hello"
data-gadget="say-hello.html"
data-gadget-cacheable="0"
data-gadget-cache-id="say-hello"></div>
</body>
</html>
\ No newline at end of file
<html>
<head></head>
<body>
Hello from the gadget!
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>first</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="../renderjs.js"></script>
</head>
<body>
<div data-gadget="gadgets/content_menu.html"
data-iframe="true"
data-param='{
"_links": {
"self": {"href": "/menu"},
"app_foo": {"href": "/menu/foo"},
"app_bar": {"href": "/menu/bar"},
"app_baz": {"href": "/menu/baz"}
},
"filter": "search"
}'></div>
<div data-gadget="gadgets/content_loader.html" data-iframe="true"></div>
<script type="text/javascript">
$(this).addGadget({"src":"gadgets/plaintext.html"});
</script>
</body>
</html>
\ No newline at end of file
<html>
<head>
<script data-main="../../require-renderjs.js"
type="text/javascript"
src="../../lib/require/require.js"></script>
<head>
<body>
<div id="init-gadget"
data-gadget="init-gadget.html"
data-gadget-cacheable="0"
data-gadget-cache-id="init-gadget"
data-gadget-property="{&quot;name&quot;: &quot;Ivan&quot;}"></div>
</body>
</html>
\ No newline at end of file
<html>
<head></head>
<body>
<p>Hello to <span id="name"></span> from the gadget which can be initialized via data-gadget-property attribute from parent gadget!</p>
<script type="text/javascript" language="javascript">
//<![CDATA[
name = RenderJs.GadgetIndex.getGadgetById("init-gadget").name;
$("#name").html(name);
//]]>
</script>
</body>
</html>
\ No newline at end of file
<div>
A (gadget)
<button onclick="RenderJs.GadgetIndex.getGadgetById('A').jsCall1()">A.jsCall1 -> B.jsCall1</button>
<button onclick="$('#A').trigger('htmlEvent1')">HTML event 1</button>
<button onclick="$('#A').trigger('htmlEvent2')">HTML event 2</button>
<button onclick="$('#main-interactor').trigger('htmlEvent3')">
A.htmlEvent3 -> InteractionGadget.htmlEvent3 -> A.htmlEvent3 AND B.htmlEvent3
</button>
<p id="hide" style="display:none;"> hidden text on myownHTMlEvent1</p>
</div>
<script type="text/javascript" language="javascript">
//<![CDATA[
$(document).ready(function() {
gadget = RenderJs.GadgetIndex.getGadgetById("A");
gadget.jsCall1 = function (){alert("A.jscall1");};
gadget.jsCall2 = function (){alert("A.jscall2");};
gadget.htmlEvent3 = function (){alert("A.htmlEvent3");};
gadget.myOwnHtmlEvent1 = function (){
$("#hide").toggle();
alert("A.myOwnHtmlEvent1");
};
});
//]]>
</script>
\ No newline at end of file
<div>
B (gadget)
<button onclick="RenderJs.GadgetIndex.getGadgetById('B').jsCall2();">B.jsCall2 -> A.jsCall2</button>
</div>
<script type="text/javascript" language="javascript">
//<![CDATA[
$(document).ready(function() {
gadget = RenderJs.GadgetIndex.getGadgetById("B");
gadget.jsCall1 = function (){alert("B.jscall1");};
gadget.jsCall2 = function (){alert("B.jscall2");};
gadget.htmlEvent1 = function (){alert("B.htmlEvent1");};
gadget.htmlEvent2 = function (){alert("B.htmlEvent2");};
gadget.htmlEvent3 = function (){alert("B.htmlEvent3");};
});
//]]>
</script>
\ No newline at end of file
<html>
<head>
<script data-main="../../require-renderjs.js"
type="text/javascript"
src="../../lib/require/require.js"></script>
<head>
<body>
<div id="A"
data-gadget="A.html"
data-gadget-cacheable="0"
data-gadget-cache-id="A"></div>
<div id="B"
data-gadget="B.html"
data-gadget-cacheable="0"
data-gadget-cache-id="B"></div>
<div data-gadget=""
id="main-interactor"
data-gadget-connection="[
{&quot;source&quot;: &quot;A.jsCall1&quot;, &quot;destination&quot;: &quot;B.jsCall1&quot;},
{&quot;source&quot;: &quot;A.htmlEvent1&quot;, &quot;destination&quot;: &quot;B.htmlEvent1&quot;},
{&quot;source&quot;: &quot;A.htmlEvent1&quot;, &quot;destination&quot;: &quot;A.myOwnHtmlEvent1&quot;},
{&quot;source&quot;: &quot;A.htmlEvent2&quot;, &quot;destination&quot;: &quot;B.htmlEvent2&quot;},
{&quot;source&quot;: &quot;B.jsCall2&quot;, &quot;destination&quot;: &quot;A.jsCall2&quot;},
{&quot;source&quot;: &quot;main-interactor.htmlEvent3&quot;, &quot;destination&quot;: &quot;A.htmlEvent3&quot;},
{&quot;source&quot;: &quot;main-interactor.htmlEvent3&quot;, &quot;destination&quot;: &quot;B.htmlEvent3&quot;}]">
</div>
</body>
</html>
<html>
<head></head>
<body>
<div data-role="page">
<div data-role="header">
<h1>Jquery mobile and RenderJs test</h1>
</div>
<div data-role="content">
<h2>Some documentation for this gadget.</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>My Page</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.3.0-rc.1/jquery.mobile-1.3.0-rc.1.min.css" />
<link rel="stylesheet" href="jqm-gadget.css" />
<script data-main="require-renderjs_jqm.js"
type="text/javascript"
src="../../lib/require/require.js"></script>
</head>
<body>
<div data-role="page">
<div data-role="header">
<h1>Jquery mobile and RenderJs test</h1>
</div>
<div data-role="content">
<div id="jqm-gadget"
data-gadget="jqm-gadget.html"></div>
<div id="jqm-details-gadget"
data-gadget="jqm-details-gadget.html"></div>
<div id="jqm-doc-gadget"
data-gadget="jqm-doc-gadget.html"></div>
</div>
</div>
</body>
</html>
<html>
<head></head>
<body>
Country of origin:<span id="country"> ...</span>
<script type="text/javascript" language="javascript">
//<![CDATA[
$(document).ready(function() {
gadget = RenderJs.GadgetIndex.getGadgetById("jqm-details-gadget");
gadget.updateCountry = function (country) {
$("#country").html(country);
};
});
//]]>
</script>
</body>
</html>
<html>
<head></head>
<body>
<ul data-role="listview" data-inset="true" data-filter="false">
<li><a href="documentation.html" class="ui-link-inherit">Link</a></li>
</ul>
</body>
</html>
#jqm-gadget,
#jqm-details-gadget{
width:45%;
float:left;
margin-left:10px;
}
#country{
font-weight: bold;
}
#jqm-doc-gadget{
width:100%;
float:left;
}
\ No newline at end of file
<html>
<head></head>
<body>
<ul data-role="listview" data-inset="true" data-filter="true" id="mylist">
<li onclick="RenderJs.GadgetIndex.getGadgetById('jqm-details-gadget').updateCountry('Germany');"><a href="#">Audi</a></li>
<li onclick="RenderJs.GadgetIndex.getGadgetById('jqm-details-gadget').updateCountry('USA');"><a href="#">Cadillac</a></li>
<li onclick="RenderJs.GadgetIndex.getGadgetById('jqm-details-gadget').updateCountry('Italy');"><a href="#">Ferrari</a></li>
<li onclick="RenderJs.GadgetIndex.getGadgetById('jqm-details-gadget').updateCountry('Japan');"><a href="#">Toyota</a></li>
<li onclick="RenderJs.GadgetIndex.getGadgetById('jqm-details-gadget').updateCountry('Russia');"><a href="#">Moskvich</a></li>
<li onclick="RenderJs.GadgetIndex.getGadgetById('jqm-details-gadget').updateCountry('Bulgaria');"><a href="#">Moskvich Aleko</a></li>
</ul>
</body>
</html>
// JavaScript file that is used to load RenderJs depenencies
require.config({
baseUrl: "../..",
paths: {
jqm: "lib/jqm/jquery.mobile-1.3.0-rc.1.min"
},
});
require([ "require-renderjs", "renderjs", "jqm" ], function(domReady) {
// when all gadgets are loaded make sure JQM renders them
RenderJs.bindReady(
function () {
$("[data-gadget]").trigger('create');
}
);
});
<html>
<head>
<script data-main="require-renderjs.js"
type="text/javascript"
src="../../lib/require/require.js"></script>
<head>
<body>
<p>Contained below gadget will get some JSON data from server and update it on page.</p>
<p>It will also load an additional library (using requirejs) that will be used by this gadget.</p>
<div id="json-gadget"
data-gadget=""
data-gadget-source = "json_file.json"
data-gadget-handler="parseJSONAndUpdateDOM"
data-gadget-cacheable="0"
data-gadget-cache-id="json-gadget">
First name: <div id="first_name"></div>
Last name: <div id="last_name"></div>
</div>
</body>
</html>
\ No newline at end of file
{"first_name": "John",
"last_name": "Doh"}
\ No newline at end of file
function parseJSONAndUpdateDOM(result) {
$("#first_name").text(result['first_name']);
$("#last_name").text(result['last_name']);
}
\ No newline at end of file
// JavaScript file that is used to load RenderJs depenencies
require(["../../lib/jquery/jquery.js",
"../../renderjs.js",
"json_gadget.js"
],
function (domReady) {
// Place code to be executed when libraries are loaded
// impliticly call RenderJs bootstrap
RenderJs.init();
});
<html>
<head>
<script data-main="../../require-renderjs.js"
type="text/javascript"
src="../../lib/require/require.js"></script>
<head>
<body>
<div id="recursive"
data-gadget="recursive.html"
data-gadget-cacheable="0"
data-gadget-cache-id="recursive"></div>
</body>
</html>
\ No newline at end of file
<html>
<head></head>
<body>
<p>
Hello from the recursive contained gadget!
</p>
</body>
</html>
<html>
<head></head>
<body>
<p>
Hello from the recursive gadget!
Below a new gadget will be loaded
</p>
<div data-gadget="recursive-contained.html"
data-gadget-cacheable="0"
data-gadget-cache-id="recursive-contained"></div>
</body>
</html>
\ No newline at end of file
<html>
<head>
<script data-main="../../require-renderjs.js"
type="text/javascript"
src="../../lib/require/require.js"></script>
<head>
<body>
<div id="requirejs-gadget"
data-gadget="requirejs-gadget.html"
data-gadget-cacheable="0"
data-gadget-cache-id="requirejs-gadget"></div>
</body>
</html>
\ No newline at end of file
// This module contains gagdets JavaScript implementation code
function setLoaded() {
$("#loading").text('Loaded now using requirejs.');
}
\ No newline at end of file
// This file defines all required javascript files for this gadget
require(["require-gadget-implementation.js"],
function (domReady) {
// required by this gadget code is loaded so we can use it
setLoaded();
});
<html>
<head>
<script type="text/javascript"
src="require-gadget.js"></script>
</head>
<body>
<p>This gadget uses requirejs to load its dependency.</p>
<div id="loading">Loading ....</div>
</body>
</html>
\ No newline at end of file
.list-item {
text-decoration: none;
display: block;
width: 2em;
}
\ No newline at end of file
<html>
<head>
<link type="text/css" href="gadget-color-picker.css" rel="stylesheet">
</head>
<body>
<h2> Color picker</h2>
<div class="body">
<ul style="list-style: none;">
<li><a href="#/color/0/0/0/" class="list-item" style="background-color:rgb(0,0,0);">&nbsp;</a></li>
<li><a href="#/color/0/0/150/" class="list-item" style="background-color:rgb(0,0,150);">&nbsp;</a></li>
<li><a href="#/color/0/150/0/" class="list-item" style="background-color:rgb(0,150,0);">&nbsp;</a></li>
<li><a href="#/color/0/150/150/" class="list-item" style="background-color:rgb(0,150,150);">&nbsp;</a></li>
<li><a href="#/color/150/0/0/" class="list-item" style="background-color:rgb(150,0,0);">&nbsp;</a></li>
<li><a href="#/color/150/0/150/" class="list-item" style="background-color:rgb(150,0,150);">&nbsp;</a></li>
<li><a href="#/color/150/150/0/" class="list-item" style="background-color:rgb(150,150,0);">&nbsp;</a></li>
<li><a href="#/color/150/150/150/" class="list-item" style="background-color:rgb(150,150,150);">&nbsp;</a></li>
<li style="text-align: center;"><a href="#/color/X/X/X" style="text-decoration: none; display: block; width: 2em;">XXX</a></li>
</ul>
<div class="select-color" style="display: block;"></div>
</div>
<script type="text/javascript" language="javascript">
//<![CDATA[
$(document).ready(function() {
gadget = RenderJs.getSelfGadget();
gadget.redirect = function () {
// Default route. Redirect to color subapp
$.url.redirect('/color/');
};
gadget.selectColorMessage = function () {
$('.select-color').text("Please select a color");
};
gadget.updateColor = function (red, green, blue) {
$('.select-color').html(
"<div style='background-color:rgb(" + red + "," + green + "," + blue + ");'>&nbsp;<\/div>" +
"<p>Color (" + red + "," + green + "," + blue + ") selected at " + new Date() + "<\/p>");
};
gadget.initRoutes = function () {
// XXX: improve this part so we can declare in RouteGadget
RenderJs.RouteGadget.go($.url.getPath(),
function () {
$('.select-color').html("Unknown color (" + $.url.getPath() + ")");},
2);
};
});
//]]>
</script>
</body>
</html>
<html>
<head></head>
<body>
<h1> This is footer loaded asynchronously at startup</h1>
<div class="route-select">
<ul>
<li><a href="#/footer_gadget_1/">Footer Gadget 1</a></li>
<li><a href="#/footer_gadget_2/">Footer Gadget 2</a></li>
<li><a href="#/footer_gadget_3/">Footer Gadget 3</a></li>
<li><a href="#/footer_gadget_4/">Footer Gadget 4</a></li>
<li><a href="#/footer_gadget_5/">Footer Gadget 5</a></li>
</ul>
</div>
<div id="footer-container">
<div data-gadget-source="" data-gadget-handler="" data-gadget="gadget-three.html" id="footer_gadget_1"></div>
<div data-gadget-source="" data-gadget-handler="" data-gadget="gadget-three.html" id="footer_gadget_2"></div>
<div data-gadget-source="" data-gadget-handler="" data-gadget="gadget-three.html" id="footer_gadget_3"></div>
<div data-gadget-source="" data-gadget-handler="" data-gadget="gadget-three.html" id="footer_gadget_4"></div>
<div data-gadget-source="" data-gadget-handler="" data-gadget="gadget-three.html" id="footer_gadget_5"></div>
<div data-gadget=""
id="footer-router"
data-gadget-route="[
{&quot;source&quot;: &quot;/footer_gadget_1/&quot;, &quot;destination&quot;: &quot;footer_gadget_1.render&quot;},
{&quot;source&quot;: &quot;/footer_gadget_2/&quot;, &quot;destination&quot;: &quot;footer_gadget_2.render&quot;},
{&quot;source&quot;: &quot;/footer_gadget_3/&quot;, &quot;destination&quot;: &quot;footer_gadget_3.render&quot;},
{&quot;source&quot;: &quot;/footer_gadget_4/&quot;, &quot;destination&quot;: &quot;footer_gadget_4.render&quot;},
{&quot;source&quot;: &quot;/footer_gadget_5/&quot;, &quot;destination&quot;: &quot;footer_gadget_5.render&quot;}]">
</div>
</div>
</body>
</html>
<html>
<head></head>
<body>
<script type="text/javascript" language="javascript">
//<![CDATA[
$(document).ready(function() {
gadget = RenderJs.GadgetIndex.getGadgetById("gadget-one");
gadget.render = function (){
$("#gadget-one").html("<h2>Gadget 1</h2><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>");
$("#gadget-two").empty(); // XXX: this will become redundant if we use Tabbular gadget
};
});
//]]>
</script>
</body>
</html>
<html>
<head></head>
<body>
<script type="text/javascript" language="javascript">
//<![CDATA[
$(document).ready(function() {
gadget = RenderJs.getSelfGadget();
gadget.showMessage = function (msg){
$("#portal-status-message").html(msg);
window.setTimeout(function () {
$("#portal-status-message").empty();
}, 2000);
};
});
//]]>
</script>
</body>
</html>
<html>
<head></head>
<body>
<div class="gadget-id-viewer" style="display:none;">
</div>
<script type="text/javascript" language="javascript">
//<![CDATA[
$(document).ready(function() {
gadget = RenderJs.getSelfGadget();
$(gadget.getDom()).find(".gadget-id-viewer").html("This is " + gadget.getId())
gadget.render = function (){
gadget = RenderJs.getSelfGadget();
$(gadget.getDom().find(".gadget-id-viewer")).toggle();
};
});
//]]>
</script>
</body>
</html>
<html>
<head></head>
<body>
<script type="text/javascript" language="javascript">
//<![CDATA[
$(document).ready(function() {
gadget = RenderJs.GadgetIndex.getGadgetById("gadget-two");
gadget.render = function (){
$("#gadget-one").empty(); // XXX: this will become redundant if we use Tabbular gadget
$("#gadget-two").html("<h2>Gadget 2</h2><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>");
};
});
//]]>
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script data-main="require-renderjs_route.js"
type="text/javascript"
src="../../lib/require/require.js"></script>
</head>
<body>
<div id="portal-status-message"
data-gadget="gadget-portal-status-message.html"></div>
<div data-gadget=""
id="main-router"
data-gadget-route="[
{&quot;source&quot;: &quot;&quot;, &quot;destination&quot;: &quot;gadget-color-picker.redirect&quot;},
{&quot;source&quot;: &quot;/color<path:params>&quot;, &quot;destination&quot;: &quot;gadget-color-picker.initRoutes&quot;},
{&quot;source&quot;: &quot;/color/&quot;,
&quot;destination&quot;: &quot;gadget-color-picker.selectColorMessage&quot;,
&quot;priority&quot;:2},
{&quot;source&quot;: &quot;/color/<int:red>/<int:green>/<int:blue>/&quot;,
&quot;destination&quot;: &quot;gadget-color-picker.updateColor&quot;,
&quot;priority&quot;:2},
{&quot;source&quot;: &quot;/gadget-one/&quot;, &quot;destination&quot;: &quot;main-router.gadget_one&quot;},
{&quot;source&quot;: &quot;/gadget-two/&quot;, &quot;destination&quot;: &quot;main-router.gadget_two&quot;}]">
</div>
<a href="#unknown">Wrong route</a>
<div id="gadget-color-picker"
data-gadget="gadget-color-picker.html"></div>
<a href="#/gadget-one/">Gadget 1</a>&nbsp;
<a href="#/gadget-two/">Gadget 2</a>
<div id="gadget-one"
data-gadget="gadget-one.html"></div>
<div id="gadget-two"
data-gadget="gadget-two.html"></div>
<div id="gadget-footer"
data-gadget="gadget-footer.html"></div>
<div data-gadget=""
id="main-interactor"
data-gadget-connection="[
{&quot;source&quot;: &quot;main-router.gadget_one&quot;, &quot;destination&quot;: &quot;gadget-one.render&quot;},
{&quot;source&quot;: &quot;main-router.gadget_two&quot;, &quot;destination&quot;: &quot;gadget-two.render&quot;}]">
</div>
<noscript>
Please enable Javascript
</noscript>
</body>
</html>
// JavaScript file that is used to load RenderJs depenencies
require.config({
baseUrl: "../..",
paths: {
route: "lib/route/route",
url: "lib/route/url",
jquery: "lib/jquery/jquery",
renderjs: "renderjs",
},
shim: {
url: [ "renderjs" ]
}
});
require([ "renderjs", "require-renderjs", "jquery", "route", "url" ], function(domReady) {
RenderJs.bindReady(function (){
// XXX: try to encapsulate this in router gadget
gadget = RenderJs.GadgetIndex.getGadgetById("main-router");
gadget.gadget_one = function (){
// we use interactionGadget which will call proper gadgets' function
};
gadget.gadget_two = function (){
// we use interactionGadget which will call proper gadgets' function
};
// we have to re-bind (force) interaction gadget as main-route API implemantation changed
RenderJs.InteractionGadget.init(force=1);
$.url.onhashchange(function () {
RenderJs.RouteGadget.go($.url.getPath(),
function () {
// Method to display error to the user
gadget = RenderJs.GadgetIndex.getGadgetById("portal-status-message");
gadget.showMessage(
"<p>Oups, seems the route '<b>" + $.url.getPath() + "<\/b>' doesn't exist!<\/p>" +
"<a href='" + $.url.generateUrl("") + "'>Go back to home<\/a>");
// All routes have been deleted by fail.
// So recreate the default routes using RouteGadget
$("div[data-gadget-route]").each(function (index, element) {
RenderJs.RouteGadget.route($(element));
});
});
});
});
});
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global jIO: true, $: true, btoa: true */
// test here: http://enable-cors.org/
//http://metajack.im/2010/01/19/crossdomain-ajax-for-xmpp-http-binding-made-easy
jIO.addStorageType('dav', function (spec, my) {
spec = spec || {};
var that, priv, super_serialized;
that = my.basicStorage(spec, my);
priv = {};
super_serialized = that.serialized;
priv.secureDocId = function (string) {
var split = string.split('/'),
i;
if (split[0] === '') {
split = split.slice(1);
}
for (i = 0; i < split.length; i += 1) {
if (split[i] === '') {
return '';
}
}
return split.join('%2F');
};
priv.convertSlashes = function (string) {
return string.split('/').join('%2F');
};
priv.restoreSlashes = function (string) {
return string.split('%2F').join('/');
};
/**
* Checks if an object has no enumerable keys
* @method objectIsEmpty
* @param {object} obj The object
* @return {boolean} true if empty, else false
*/
priv.objectIsEmpty = function (obj) {
var k;
for (k in obj) {
if (obj.hasOwnProperty(k)) {
return false;
}
}
return true;
};
// ==================== Attributes ====================
priv.username = spec.username || '';
priv.secured_username = priv.convertSlashes(priv.username);
priv.password = spec.password || '';
priv.url = spec.url || '';
that.serialized = function () {
var o = super_serialized();
o.username = priv.username;
o.url = priv.url;
o.password = priv.password;
return o;
};
priv.newAsyncModule = function () {
var async = {};
async.call = function (obj, function_name, arglist) {
obj._wait = obj._wait || {};
if (obj._wait[function_name]) {
obj._wait[function_name] -= 1;
return function () {};
}
// ok if undef or 0
arglist = arglist || [];
return obj[function_name].apply(obj[function_name], arglist);
};
async.neverCall = function (obj, function_name) {
obj._wait = obj._wait || {};
obj._wait[function_name] = -1;
};
async.wait = function (obj, function_name, times) {
obj._wait = obj._wait || {};
obj._wait[function_name] = times;
};
async.end = function () {
async.call = function () {};
};
return async;
};
/**
* Checks if a browser supports cors (cross domain ajax requests)
* @method checkCors
* @return {boolean} true if supported, else false
*/
priv.checkCors = function () {
return $.support.cors;
};
/**
* Replaces last "." with "_." in document filenames
* @method underscoreFileExtenisons
* @param {string} url url to clean up
* @return {string} clean_url cleaned up URL
*/
priv.underscoreFileExtenisons = function (url) {
var clean_url = url.replace(/,\s(\w+)$/, "_.$1");
return clean_url;
};
priv.restoreDots = function (url) {
var clean_url = url.replace(/_\./g, '.');
return clean_url;
};
// wedDav methods rfc4918 (short summary)
// COPY Reproduces single resources (files) and collections (directory
// trees). Will overwrite files (if specified by request) but will
// respond 209 (Conflict) if it would overwrite a tree
// DELETE deletes files and directory trees
// GET just the vanilla HTTP/1.1 behaviour
// HEAD ditto
// LOCK locks a resources
// MKCOL creates a directory
// MOVE Moves (rename or copy) a file or a directory tree. Will
// 'overwrite' files (if specified by the request) but will respond
// 209 (Conflict) if it would overwrite a tree.
// OPTIONS If WebDAV is enabled and available for the path this reports the
// WebDAV extension methods
// PROPFIND Retrieves the requested file characteristics, DAV lock status
// and 'dead' properties for individual files, a directory and its
// child files, or a directory tree
// PROPPATCHset and remove 'dead' meta-data properties
// PUT Update or create resource or collections
// UNLOCK unlocks a resource
// Notes: all Ajax requests should be CORS (cross-domain)
// adding custom headers triggers preflight OPTIONS request!
// http://remysharp.com/2011/04/21/getting-cors-working/
priv.putOrPost = function (command, type) {
var docid = command.getDocId(),
secured_docid,
url;
// no docId
if (!(typeof docid === "string" && docid !== "")) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Cannot create document which id is undefined",
"reason": "Document id is undefined"
});
return;
}
// no cross domain ajax
if (priv.checkCors === false) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Browser does not support cross domain ajax requests",
"reason": "cors is undefined"
});
return;
}
secured_docid = priv.secureDocId(command.getDocId());
url = priv.url + '/' + priv.underscoreFileExtenisons(secured_docid);
// see if the document exists
$.ajax({
url: url + '?_=' + Date.now(),
type: "GET",
async: true,
dataType: 'text',
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
// xhrFields: {withCredentials: 'true'},
success: function () {
if (type === 'POST') {
// POST the document already exists
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot create a new document",
"reason": "Document already exists"
});
return;
}
// PUT update document
$.ajax({
url: url,
type: type,
data: JSON.stringify(command.getDoc()),
async: true,
crossdomain: true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
// xhrFields: {withCredentials: 'true'},
success: function () {
that.success({
ok: true,
id: command.getDocId()
});
},
error: function () {
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot modify document",
"reason": "Error trying to write to remote storage"
});
}
});
},
error: function (err) {
// Firefox returns 0 instead of 404 on CORS?
if (err.status === 404 || err.status === 0) {
$.ajax({
url: url,
type: 'PUT',
data: JSON.stringify(command.getDoc()),
async: true,
crossdomain: true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
// xhrFields: {withCredentials: 'true'},
success: function () {
that.success({
ok: true,
id: command.getDocId()
});
},
error: function () {
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot modify document",
"reason": "Error trying to write to remote storage"
});
}
});
} else {
// error accessing remote storage
that.error({
"status": err.status,
"statusText": err.statusText,
"error": "error",
"message": err.message,
"reason": "Failed to access remote storage"
});
}
}
});
};
/**
* Creates a new document
* @method post
* @param {object} command The JIO command
*/
that.post = function (command) {
priv.putOrPost(command, 'POST');
};
/**
* Creates or updates a document
* @method put
* @param {object} command The JIO command
*/
that.put = function (command) {
priv.putOrPost(command, 'PUT');
};
/**
* Add an attachment to a document
* @method putAttachment
* @param {object} command The JIO command
*/
that.putAttachment = function (command) {
var docid = command.getDocId(),
doc,
url,
secured_docid,
secured_attachmentid,
attachment_url;
// no docId
if (!(typeof docid === "string" && docid !== "")) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Cannot create document which id is undefined",
"reason": "Document id is undefined"
});
return;
}
// no cross domain ajax
if (priv.checkCors === false) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Browser does not support cross domain ajax requests",
"reason": "cors is undefined"
});
return;
}
secured_docid = priv.secureDocId(docid);
url = priv.url + '/' + priv.underscoreFileExtenisons(secured_docid);
// see if the underlying document exists
$.ajax({
url: url + '?_=' + Date.now(),
type: 'GET',
async: true,
dataType: 'text',
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function (response) {
doc = JSON.parse(response);
// the document exists - update document
doc._attachments = doc._attachments || {};
doc._attachments[command.getAttachmentId()] = {
"content_type": command.getAttachmentMimeType(),
"digest": "md5-" + command.md5SumAttachmentData(),
"length": command.getAttachmentLength()
};
// put updated document data
$.ajax({
url: url + '?_=' + Date.now(),
type: 'PUT',
data: JSON.stringify(doc),
async: true,
crossdomain: true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
// xhrFields: {withCredentials: 'true'},
success: function () {
secured_attachmentid = priv.secureDocId(command.getAttachmentId());
attachment_url = url + '.' +
priv.underscoreFileExtenisons(secured_attachmentid);
$.ajax({
url: attachment_url + '?_=' + Date.now(),
type: 'PUT',
data: JSON.stringify(command.getDoc()),
async: true,
crossdomain: true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
// xhrFields: {withCredentials: 'true'},
success: function () {
that.success({
ok: true,
id: command.getDocId() + '/' + command.getAttachmentId()
});
},
error: function () {
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot modify document",
"reason": "Error trying to save attachment to remote storage"
});
return;
}
});
},
error: function () {
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot modify document",
"reason": "Error trying to write to remote storage"
});
return;
}
});
},
error: function () {
// the document does not exist
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Impossible to add attachment",
"reason": "Document not found"
});
return;
}
});
};
/**
* Get a document or attachment from distant storage
* Options:
* - {boolean} revs Add simple revision history (false by default).
* - {boolean} revs_info Add revs info (false by default).
* - {boolean} conflicts Add conflict object (false by default).
* @method get
* @param {object} command The JIO command
*/
that.get = function (command) {
var docid = command.getDocId(),
doc,
url,
secured_docid,
secured_attachmentid,
attachment_url;
// no docId
if (!(typeof docid === "string" && docid !== "")) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Cannot create document which id is undefined",
"reason": "Document id is undefined"
});
return;
}
// no cors support
if (priv.checkCors === false) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Browser does not support cross domain ajax requests",
"reason": "cors is undefined"
});
return;
}
secured_docid = priv.secureDocId(command.getDocId());
url = priv.url + '/' + priv.underscoreFileExtenisons(secured_docid);
if (typeof command.getAttachmentId() === "string") {
secured_attachmentid = priv.secureDocId(command.getAttachmentId());
attachment_url = url + '.' + priv.underscoreFileExtenisons(
secured_attachmentid
);
// get attachment
$.ajax({
url: attachment_url + '?_=' + Date.now(),
type: 'GET',
async: true,
dataType: 'text',
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function (response) {
doc = JSON.parse(response);
that.success(doc);
},
error: function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the attachment",
"reason": "Attachment does not exist"
});
}
});
} else {
// get document
$.ajax({
url: url + '?_=' + Date.now(),
type: 'GET',
async: true,
dataType: 'text',
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function (response) {
// metadata_only should not be handled by jIO, as it is a
// webDav only option, shouldn't it?
// ditto for content_only
doc = JSON.parse(response);
that.success(doc);
},
error: function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Document does not exist"
});
}
});
}
};
/**
* Remove a document or attachment
* @method remove
* @param {object} command The JIO command
*/
that.remove = function (command) {
var docid = command.getDocId(), doc, url,
secured_docid, secured_attachmentid, attachment_url,
attachment_list = [], i, j, k = 1, deleteAttachment;
// no docId
if (!(typeof docid === "string" && docid !== "")) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Cannot create document which id is undefined",
"reason": "Document id is undefined"
});
}
// no cors support
if (priv.checkCors === false) {
that.error({
"status": 405,
"statusText": "Method Not Allowed",
"error": "method_not_allowed",
"message": "Browser does not support cross domain ajax requests",
"reason": "cors is undefined"
});
}
secured_docid = priv.secureDocId(command.getDocId());
url = priv.url + '/' + priv.underscoreFileExtenisons(secured_docid);
// remove attachment
if (typeof command.getAttachmentId() === "string") {
secured_attachmentid = priv.secureDocId(command.getAttachmentId());
attachment_url = url + '.' + priv.underscoreFileExtenisons(
secured_attachmentid
);
$.ajax({
url: attachment_url + '?_=' + Date.now(),
type: 'DELETE',
async: true,
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function () {
// retrieve underlying document
$.ajax({
url: url + '?_=' + Date.now(),
type: 'GET',
async: true,
dataType: 'text',
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function (response) {
// underlying document
doc = JSON.parse(response);
// update doc._attachments
if (typeof doc._attachments === "object") {
if (typeof doc._attachments[command.getAttachmentId()] ===
"object") {
delete doc._attachments[command.getAttachmentId()];
if (priv.objectIsEmpty(doc._attachments)) {
delete doc._attachments;
}
// PUT back to server
$.ajax({
url: url + '?_=' + Date.now(),
type: 'PUT',
data: JSON.stringify(doc),
async: true,
crossdomain: true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
// xhrFields: {withCredentials: 'true'},
success: function () {
that.success({
"ok": true,
"id": command.getDocId() + '/' +
command.getAttachmentId()
});
},
error: function () {
that.error({
"status": 409,
"statusText": "Conflicts",
"error": "conflicts",
"message": "Cannot modify document",
"reason": "Error trying to update document attachments"
});
}
});
} else {
// sure this if-else is needed?
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Error trying to update document attachments"
});
}
} else {
// no attachments, we are done
that.success({
"ok": true,
"id": command.getDocId() + '/' + command.getAttachmentId()
});
}
},
error: function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Document does not exist"
});
}
});
},
error: function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the attachment",
"reason": "Error trying to remove attachment"
});
}
});
// remove document
} else {
// get document to also remove all attachments
$.ajax({
url: url + '?_=' + Date.now(),
type: 'GET',
async: true,
dataType: 'text',
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function (response) {
var x;
doc = JSON.parse(response);
// prepare attachment loop
if (typeof doc._attachments === "object") {
// prepare list of attachments
for (x in doc._attachments) {
if (doc._attachments.hasOwnProperty(x)) {
attachment_list.push(x);
}
}
}
// delete document
$.ajax({
url: url + '?_=' + Date.now(),
type: 'DELETE',
async: true,
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function () {
j = attachment_list.length;
// no attachments, done
if (j === 0) {
that.success({
"ok": true,
"id": command.getDocId()
});
} else {
deleteAttachment = function (attachment_url, j, k) {
$.ajax({
url: attachment_url + '?_=' + Date.now(),
type: 'DELETE',
async: true,
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function () {
// all deleted, return response, need k as async couter
if (j === k) {
that.success({
"ok": true,
"id": command.getDocId()
});
} else {
k += 1;
}
},
error: function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the attachment",
"reason": "Error trying to remove attachment"
});
}
});
};
for (i = 0; i < j; i += 1) {
secured_attachmentid = priv.secureDocId(attachment_list[i]);
attachment_url = url + '.' + priv.underscoreFileExtenisons(
secured_attachmentid
);
deleteAttachment(attachment_url, j, k);
}
}
},
error: function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Error trying to remove document"
});
}
});
},
error: function () {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Document does not exist"
});
}
});
}
};
/**
* Gets a document list from a distant dav storage
* Options:
* - {boolean} include_docs Also retrieve the actual document content.
* @method allDocs
* @param {object} command The JIO command
*/
//{
// "total_rows": 4,
// "rows": [
// {
// "id": "otherdoc",
// "key": "otherdoc",
// "value": {
// "rev": "1-3753476B70A49EA4D8C9039E7B04254C"
// }
// },{...}
// ]
//}
that.allDocs = function (command) {
var rows = [], url,
am = priv.newAsyncModule(),
o = {};
o.getContent = function (file) {
var docid = priv.secureDocId(file.id),
url = priv.url + '/' + docid;
$.ajax({
url: url + '?_=' + Date.now(),
type: 'GET',
async: true,
dataType: 'text',
headers: {
'Authorization': 'Basic ' + btoa(
priv.username + ':' + priv.password
)
},
success: function (content) {
file.doc = JSON.parse(content);
rows.push(file);
am.call(o, 'success');
},
error: function (type) {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Cannot get a document from DAVStorage"
});
am.call(o, 'error', [type]);
}
});
};
o.getDocumentList = function () {
url = priv.url + '/';
$.ajax({
url: url + '?_=' + Date.now(),
type: 'PROPFIND',
async: true,
dataType: "xml",
crossdomain : true,
headers : {
Authorization: 'Basic ' + btoa(
priv.username + ':' + priv.password
),
Depth: '1'
},
success: function (xml) {
var response = $(xml).find('D\\:response, response'),
len = response.length;
if (len === 1) {
return am.call(o, 'success');
}
am.wait(o, 'success', len - 2);
response.each(function (i, data) {
if (i > 0) { // exclude parent folder
var file = {
value: {}
};
$(data).find('D\\:href, href').each(function () {
var split = $(this).text().split('/');
file.id = split[split.length - 1];
file.id = priv.restoreSlashes(file.id);
file.key = file.id;
});
if (command.getOption('include_docs')) {
am.call(o, 'getContent', [file]);
} else {
rows.push(file);
am.call(o, 'success');
}
}
});
},
error: function (type) {
that.error({
"status": 404,
"statusText": "Not Found",
"error": "not_found",
"message": "Cannot find the document",
"reason": "Cannot get a document list from DAVStorage"
});
am.call(o, 'retry', [type]);
}
});
};
o.retry = function (error) {
am.neverCall(o, 'retry');
am.neverCall(o, 'success');
am.neverCall(o, 'error');
that.retry(error);
};
o.error = function (error) {
am.neverCall(o, 'retry');
am.neverCall(o, 'success');
am.neverCall(o, 'error');
that.error(error);
};
o.success = function () {
am.neverCall(o, 'retry');
am.neverCall(o, 'success');
am.neverCall(o, 'error');
that.success({
total_rows: rows.length,
rows: rows
});
};
// first get the XML list
am.call(o, 'getDocumentList');
};
return that;
});
\ No newline at end of file
(function (scope, hex_md5) {
"use strict";
var localstorage;
if (typeof localStorage !== "undefined") {
localstorage = {
getItem: function (item) {
var value = localStorage.getItem(item);
return value === null ? null : JSON.parse(value);
},
setItem: function (item, value) {
return localStorage.setItem(item, JSON.stringify(value));
},
removeItem: function (item) {
delete localStorage[item];
},
clone: function () {
return JSON.parse(JSON.stringify(localStorage));
}
};
} else {
(function () {
var pseudo_localStorage = {};
localstorage = {
getItem: function (item) {
var value = pseudo_localStorage[item];
return value === undefined ?
null : JSON.parse(pseudo_localStorage[item]);
},
setItem: function (item, value) {
pseudo_localStorage[item] = JSON.stringify(value);
},
removeItem: function (item) {
delete pseudo_localStorage[item];
},
clone: function () {
return JSON.parse(JSON.stringify(pseudo_localStorage));
}
};
}());
}
/*jslint indent:2, maxlen: 80, sloppy: true */
var jioException = function (spec, my) {
var that = {};
spec = spec || {};
my = my || {};
that.name = 'jioException';
that.message = spec.message || 'Unknown Reason.';
that.toString = function () {
return that.name + ': ' + that.message;
};
return that;
};
var invalidCommandState = function (spec, my) {
var that = jioException(spec, my), command = spec.command;
spec = spec || {};
that.name = 'invalidCommandState';
that.toString = function () {
return that.name + ': ' +
command.getLabel() + ', ' + that.message;
};
return that;
};
var invalidStorage = function (spec, my) {
var that = jioException(spec, my), type = spec.storage.getType();
spec = spec || {};
that.name = 'invalidStorage';
that.toString = function () {
return that.name + ': ' +
'Type "' + type + '", ' + that.message;
};
return that;
};
var invalidStorageType = function (spec, my) {
var that = jioException(spec, my), type = spec.type;
that.name = 'invalidStorageType';
that.toString = function () {
return that.name + ': ' +
type + ', ' + that.message;
};
return that;
};
var jobNotReadyException = function (spec, my) {
var that = jioException(spec, my);
that.name = 'jobNotReadyException';
return that;
};
var tooMuchTriesJobException = function (spec, my) {
var that = jioException(spec, my);
that.name = 'tooMuchTriesJobException';
return that;
};
var invalidJobException = function (spec, my) {
var that = jioException(spec, my);
that.name = 'invalidJobException';
return that;
};
var jio = function(spec) {
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true, jobManager: true, job: true */
var storage = function (spec, my) {
var that = {}, priv = {};
spec = spec || {};
my = my || {};
// Attributes //
priv.type = spec.type || '';
// Methods //
Object.defineProperty(that, "getType", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return priv.type;
}
});
/**
* Execute the command on this storage.
* @method execute
* @param {object} command The command
*/
that.execute = function (command) {
that.success = command.success;
that.error = command.error;
that.retry = command.retry;
that.end = command.end;
if (that.validate(command)) {
command.executeOn(that);
}
};
/**
* Override this function to validate specifications.
* @method isValid
* @return {boolean} true if ok, else false.
*/
that.isValid = function () {
return true;
};
that.validate = function () {
var mess = that.validateState();
if (mess) {
that.error({
"status": 0,
"statusText": "Invalid Storage",
"error": "invalid_storage",
"message": mess,
"reason": mess
});
return false;
}
return true;
};
/**
* Returns a serialized version of this storage.
* @method serialized
* @return {object} The serialized storage.
*/
that.serialized = function () {
var o = that.specToStore() || {};
o.type = that.getType();
return o;
};
/**
* Returns an object containing spec to store on localStorage, in order to
* be restored later if something wrong happen.
* Override this method!
* @method specToStore
* @return {object} The spec to store
*/
that.specToStore = function () {
return {};
};
/**
* Validate the storage state. It returns a empty string all is ok.
* @method validateState
* @return {string} empty: ok, else error message.
*/
that.validateState = function () {
return '';
};
that.post = function () {
setTimeout(function () {
that.error({
"status": 0,
"statusText": "Not Implemented",
"error": "not_implemented",
"message": "\"Post\" command is not implemented",
"reason": "Command not implemented"
});
});
};
that.put = function () {
setTimeout(function () {
that.error({
"status": 0,
"statusText": "Not Implemented",
"error": "not_implemented",
"message": "\"Put\" command is not implemented",
"reason": "Command not implemented"
});
});
};
that.putAttachment = function () {
setTimeout(function () {
that.error({
"status": 0,
"statusText": "Not Implemented",
"error": "not_implemented",
"message": "\"PutAttachment\" command is not implemented",
"reason": "Command not implemented"
});
});
};
that.get = function () {
setTimeout(function () {
that.error({
"status": 0,
"statusText": "Not Implemented",
"error": "not_implemented",
"message": "\"Get\" command is not implemented",
"reason": "Command not implemented"
});
});
};
that.allDocs = function () {
setTimeout(function () {
that.error({
"status": 0,
"statusText": "Not Implemented",
"error": "not_implemented",
"message": "\"AllDocs\" command is not implemented",
"reason": "Command not implemented"
});
});
};
that.remove = function () {
setTimeout(function () {
that.error({
"status": 0,
"statusText": "Not Implemented",
"error": "not_implemented",
"message": "\"Remove\" command is not implemented",
"reason": "Command not implemented"
});
});
};
that.success = function () {};
that.retry = function () {};
that.error = function () {};
that.end = function () {}; // terminate the current job.
priv.newCommand = function (method, spec) {
var o = spec || {};
o.label = method;
return command(o, my);
};
priv.storage = my.storage;
delete my.storage;
that.addJob = function (method, storage_spec, doc, option, success, error) {
var command_opt = {
options: option,
callbacks: {success: success, error: error}
};
if (doc) {
if (method === 'get') {
command_opt.docid = doc;
} else {
command_opt.doc = doc;
}
}
jobManager.addJob(job({
storage: priv.storage(storage_spec || {}),
command: priv.newCommand(method, command_opt)
}, my));
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var allDocsCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'allDocs';
};
that.executeOn = function (storage) {
storage.allDocs(that);
};
that.canBeRestored = function () {
return false;
};
that.validateState = function () {
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true, nomen: true */
/*global postCommand: true, putCommand: true, getCommand: true,
removeCommand: true, allDocsCommand: true,
putAttachmentCommand: true, failStatus: true, doneStatus: true,
hex_md5: true */
var command = function (spec, my) {
var that = {},
priv = {};
spec = spec || {};
my = my || {};
priv.commandlist = {
'post': postCommand,
'put': putCommand,
'get': getCommand,
'remove': removeCommand,
'allDocs': allDocsCommand,
'putAttachment': putAttachmentCommand
};
// creates the good command thanks to his label
if (spec.label && priv.commandlist[spec.label]) {
priv.label = spec.label;
delete spec.label;
return priv.commandlist[priv.label](spec, my);
}
priv.tried = 0;
priv.doc = spec.doc || {};
if (typeof priv.doc !== "object") {
priv.doc = {
"_id": priv.doc.toString()
};
}
priv.docid = spec.docid || priv.doc._id;
priv.option = spec.options || {};
priv.callbacks = spec.callbacks || {};
priv.success = priv.callbacks.success || function () {};
priv.error = priv.callbacks.error || function () {};
priv.retry = function () {
that.error({
status: 13,
statusText: 'Fail Retry',
error: 'fail_retry',
message: 'Impossible to retry.',
reason: 'Impossible to retry.'
});
};
priv.end = function () {};
priv.on_going = false;
// Methods //
/**
* Returns a serialized version of this command.
* @method serialized
* @return {object} The serialized command.
*/
that.serialized = function () {
var o = {};
o.label = that.getLabel();
o.tried = priv.tried;
o.doc = that.cloneDoc();
o.option = that.cloneOption();
return o;
};
/**
* Returns the label of the command.
* @method getLabel
* @return {string} The label.
*/
that.getLabel = function () {
return 'command';
};
/**
* Gets the document id
* @method getDocId
* @return {string} The document id
*/
that.getDocId = function () {
if (typeof priv.docid !== "string") {
return undefined;
}
return priv.docid.split('/')[0];
};
/**
* Gets the attachment id
* @method getAttachmentId
* @return {string} The attachment id
*/
that.getAttachmentId = function () {
if (typeof priv.docid !== "string") {
return undefined;
}
return priv.docid.split('/')[1];
};
/**
* Returns the label of the command.
* @method getDoc
* @return {object} The document.
*/
that.getDoc = function () {
return priv.doc;
};
/**
* Returns the data of the attachment
* @method getAttachmentData
* @return {string} The data
*/
that.getAttachmentData = function () {
return priv.doc._data || "";
};
/**
* Returns the data length of the attachment
* @method getAttachmentLength
* @return {number} The length
*/
that.getAttachmentLength = function () {
return (priv.doc._data || "").length;
};
/**
* Returns the mimetype of the attachment
* @method getAttachmentMimeType
* @return {string} The mimetype
*/
that.getAttachmentMimeType = function () {
return priv.doc._mimetype;
};
/**
* Generate the md5sum of the attachment data
* @method md5SumAttachmentData
* @return {string} The md5sum
*/
that.md5SumAttachmentData = function () {
return hex_md5(priv.doc._data || "");
};
/**
* Returns an information about the document.
* @method getDocInfo
* @param {string} infoname The info name.
* @return The info value.
*/
that.getDocInfo = function (infoname) {
return priv.doc[infoname];
};
/**
* Returns the value of an option.
* @method getOption
* @param {string} optionname The option name.
* @return The option value.
*/
that.getOption = function (optionname) {
return priv.option[optionname];
};
/**
* Validates the storage.
* @param {object} storage The storage.
*/
that.validate = function (storage) {
if (typeof priv.docid === "string" &&
!priv.docid.match("^[^\/]+([\/][^\/]+)?$")) {
that.error({
status: 21,
statusText: 'Invalid Document Id',
error: 'invalid_document_id',
message: 'The document id must be like "abc" or "abc/def".',
reason: 'The document id is no like "abc" or "abc/def"'
});
return false;
}
if (!that.validateState()) {
return false;
}
return storage.validate();
};
/*
* Extend this function
*/
that.validateState = function () {
return true;
};
/**
* Check if the command can be retried.
* @method canBeRetried
* @return {boolean} The result
*/
that.canBeRetried = function () {
return (priv.option.max_retry === undefined ||
priv.option.max_retry === 0 ||
priv.tried < priv.option.max_retry);
};
/**
* Gets the number time the command has been tried.
* @method getTried
* @return {number} The number of time the command has been tried
*/
that.getTried = function () {
return priv.tried;
};
/**
* Delegate actual excecution the storage.
* @param {object} storage The storage.
*/
that.execute = function (storage) {
if (!priv.on_going) {
if (that.validate(storage)) {
priv.tried += 1;
priv.on_going = true;
storage.execute(that);
}
}
};
/**
* Execute the good method from the storage.
* Override this function.
* @method executeOn
* @param {object} storage The storage.
*/
that.executeOn = function (storage) {};
that.success = function (return_value) {
priv.on_going = false;
priv.success(return_value);
priv.end(doneStatus());
};
that.retry = function (return_error) {
priv.on_going = false;
if (that.canBeRetried()) {
priv.retry();
} else {
that.error(return_error);
}
};
that.error = function (return_error) {
priv.on_going = false;
priv.error(return_error);
priv.end(failStatus());
};
that.end = function () {
priv.end(doneStatus());
};
that.onSuccessDo = function (fun) {
if (fun) {
priv.success = fun;
} else {
return priv.success;
}
};
that.onErrorDo = function (fun) {
if (fun) {
priv.error = fun;
} else {
return priv.error;
}
};
that.onEndDo = function (fun) {
priv.end = fun;
};
that.onRetryDo = function (fun) {
priv.retry = fun;
};
/**
* Is the command can be restored by another JIO : yes.
* @method canBeRestored
* @return {boolean} true
*/
that.canBeRestored = function () {
return true;
};
/**
* Clones the command and returns it.
* @method clone
* @return {object} The cloned command.
*/
that.clone = function () {
return command(that.serialized(), my);
};
/**
* Clones the command options and returns the clone version.
* @method cloneOption
* @return {object} The clone of the command options.
*/
that.cloneOption = function () {
return JSON.parse(JSON.stringify(priv.option));
};
/**
* Clones the document and returns the clone version.
* @method cloneDoc
* @return {object} The clone of the document.
*/
that.cloneDoc = function () {
return JSON.parse(JSON.stringify(priv.doc));
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var getCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'get';
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" &&
that.getDocId() !== "")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
if (typeof that.getAttachmentId() === "string") {
if (that.getAttachmentId() === "") {
that.error({
"status": 23,
"statusText": "Invalid Attachment Id",
"error": "invalid_attachment_id",
"message": "The attachment id must not be an empty string",
"reason": "Attachment id is empty"
});
return false;
}
}
return true;
};
that.executeOn = function (storage) {
storage.get(that);
};
that.canBeRestored = function () {
return false;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var postCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Methods //
that.getLabel = function () {
return 'post';
};
that.validateState = function () {
if (that.getAttachmentId() !== undefined) {
that.error({
"status": 21,
"statusText": "Invalid Document Id",
"error": "invalid_document_id",
"message": "The document id contains '/' characters " +
"which are forbidden",
"reason": "Document id contains '/' character(s)"
});
return false;
}
return true;
};
that.executeOn = function (storage) {
storage.post(that);
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var putAttachmentCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'putAttachment';
};
that.executeOn = function (storage) {
storage.putAttachment(that);
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" && that.getDocId() !==
"")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
if (typeof that.getAttachmentId() !== "string") {
that.error({
"status": 22,
"statusText": "Attachment Id Required",
"error": "attachment_id_required",
"message": "The attachment id must be set",
"reason": "Attachment id not set"
});
return false;
}
if (that.getAttachmentId() === "") {
that.error({
"status": 23,
"statusText": "Invalid Attachment Id",
"error": "invalid_attachment_id",
"message": "The attachment id must not be an empty string",
"reason": "Attachment id is empty"
});
}
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var putCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Methods //
that.getLabel = function () {
return 'put';
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" && that.getDocId() !==
"")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
if (that.getAttachmentId() !== undefined) {
that.error({
"status": 21,
"statusText": "Invalid Document Id",
"error": "invalid_document_id",
"message": "The document id contains '/' characters " +
"which are forbidden",
"reason": "Document id contains '/' character(s)"
});
return false;
}
return true;
};
that.executeOn = function (storage) {
storage.put(that);
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global command: true */
var removeCommand = function (spec, my) {
var that = command(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'remove';
};
that.validateState = function () {
if (!(typeof that.getDocId() === "string" && that.getDocId() !==
"")) {
that.error({
"status": 20,
"statusText": "Document Id Required",
"error": "document_id_required",
"message": "The document id is not provided",
"reason": "Document id is undefined"
});
return false;
}
if (typeof that.getAttachmentId() === "string") {
if (that.getAttachmentId() === "") {
that.error({
"status": 23,
"statusText": "Invalid Attachment Id",
"error": "invalid_attachment_id",
"message": "The attachment id must not be an empty string",
"reason": "Attachment id is empty"
});
return false;
}
}
return true;
};
that.executeOn = function (storage) {
storage.remove(that);
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jobStatus: true */
var doneStatus = function (spec, my) {
var that = jobStatus(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'done';
};
that.canStart = function () {
return false;
};
that.canRestart = function () {
return false;
};
that.isDone = function () {
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jobStatus: true */
var failStatus = function (spec, my) {
var that = jobStatus(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'fail';
};
that.canStart = function () {
return false;
};
that.canRestart = function () {
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jobStatus: true */
var initialStatus = function (spec, my) {
var that = jobStatus(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return "initial";
};
that.canStart = function () {
return true;
};
that.canRestart = function () {
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jobStatus: true */
var jobStatus = function (spec, my) {
var that = {};
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'job status';
};
that.canStart = function () {};
that.canRestart = function () {};
that.serialized = function () {
return {"label": that.getLabel()};
};
that.isWaitStatus = function () {
return false;
};
that.isDone = function () {
return false;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jobStatus: true */
var onGoingStatus = function (spec, my) {
var that = jobStatus(spec, my);
spec = spec || {};
my = my || {};
// Attributes //
// Methods //
that.getLabel = function () {
return 'on going';
};
that.canStart = function () {
return false;
};
that.canRestart = function () {
return false;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jobStatus: true, jobManager: true */
var waitStatus = function (spec, my) {
var that = jobStatus(spec, my), priv = {};
spec = spec || {};
my = my || {};
// Attributes //
priv.job_id_array = spec.job_id_array || [];
priv.threshold = 0;
// Methods //
/**
* Returns the label of this status.
* @method getLabel
* @return {string} The label: 'wait'.
*/
that.getLabel = function () {
return 'wait';
};
/**
* Refresh the job id array to wait.
* @method refreshJobIdArray
*/
priv.refreshJobIdArray = function () {
var tmp_job_id_array = [], i;
for (i = 0; i < priv.job_id_array.length; i += 1) {
if (jobManager.jobIdExists(priv.job_id_array[i])) {
tmp_job_id_array.push(priv.job_id_array[i]);
}
}
priv.job_id_array = tmp_job_id_array;
};
/**
* The status must wait for the job end before start again.
* @method waitForJob
* @param {object} job The job to wait for.
*/
that.waitForJob = function (job) {
var i;
for (i = 0; i < priv.job_id_array.length; i += 1) {
if (priv.job_id_array[i] === job.getId()) {
return;
}
}
priv.job_id_array.push(job.getId());
};
/**
* The status stops to wait for this job.
* @method dontWaitForJob
* @param {object} job The job to stop waiting for.
*/
that.dontWaitForJob = function (job) {
var i, tmp_job_id_array = [];
for (i = 0; i < priv.job_id_array.length; i += 1) {
if (priv.job_id_array[i] !== job.getId()) {
tmp_job_id_array.push(priv.job_id_array[i]);
}
}
priv.job_id_array = tmp_job_id_array;
};
/**
* The status must wait for some milliseconds.
* @method waitForTime
* @param {number} ms The number of milliseconds
*/
that.waitForTime = function (ms) {
priv.threshold = Date.now() + ms;
};
/**
* The status stops to wait for some time.
* @method stopWaitForTime
*/
that.stopWaitForTime = function () {
priv.threshold = 0;
};
that.canStart = function () {
priv.refreshJobIdArray();
return (priv.job_id_array.length === 0 && Date.now() >= priv.threshold);
};
that.canRestart = function () {
return that.canStart();
};
that.serialized = function () {
return {
"label": that.getLabel(),
"waitfortime": priv.threshold,
"waitforjob": priv.job_id_array
};
};
/**
* Checks if this status is waitStatus
* @method isWaitStatus
* @return {boolean} true
*/
that.isWaitStatus = function () {
return true;
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jobIdHandler: true, initialStatus: true, invalidJobException: true,
waitStatus: true, failStatus: true, tooMuchTriesJobException: true,
jobManager: true, jobNotReadyException: true, onGoingStatus: true */
var job = function (spec) {
var that = {},
priv = {};
spec = spec || {};
priv.id = jobIdHandler.nextId();
priv.command = spec.command;
priv.storage = spec.storage;
priv.status = initialStatus();
priv.date = new Date();
// Initialize //
if (!priv.storage) {
throw invalidJobException({
job: that,
message: 'No storage set'
});
}
if (!priv.command) {
throw invalidJobException({
job: that,
message: 'No command set'
});
}
// Methods //
/**
* Returns the job command.
* @method getCommand
* @return {object} The job command.
*/
that.getCommand = function () {
return priv.command;
};
that.getStatus = function () {
return priv.status;
};
that.getId = function () {
return priv.id;
};
that.getStorage = function () {
return priv.storage;
};
that.getDate = function () {
return priv.date;
};
/**
* Checks if the job is ready.
* @method isReady
* @return {boolean} true if ready, else false.
*/
that.isReady = function () {
if (priv.command.getTried() === 0) {
return priv.status.canStart();
}
return priv.status.canRestart();
};
/**
* Returns a serialized version of this job.
* @method serialized
* @return {object} The serialized job.
*/
that.serialized = function () {
return {
id: priv.id,
date: priv.date.getTime(),
status: priv.status.serialized(),
command: priv.command.serialized(),
storage: priv.storage.serialized()
};
};
/**
* Tells the job to wait for another one.
* @method waitForJob
* @param {object} job The job to wait for.
*/
that.waitForJob = function (job) {
if (priv.status.getLabel() !== 'wait') {
priv.status = waitStatus({});
}
priv.status.waitForJob(job);
};
/**
* Tells the job to do not wait for a job.
* @method dontWaitForJob
* @param {object} job The other job.
*/
that.dontWaitFor = function (job) {
if (priv.status.getLabel() === 'wait') {
priv.status.dontWaitForJob(job);
}
};
/**
* Tells the job to wait for a while.
* @method waitForTime
* @param {number} ms Time to wait in millisecond.
*/
that.waitForTime = function (ms) {
if (priv.status.getLabel() !== 'wait') {
priv.status = waitStatus({});
}
priv.status.waitForTime(ms);
};
/**
* Tells the job to do not wait for a while anymore.
* @method stopWaitForTime
*/
that.stopWaitForTime = function () {
if (priv.status.getLabel() === 'wait') {
priv.status.stopWaitForTime();
}
};
that.eliminated = function () {
priv.command.error({
status: 10,
statusText: 'Stopped',
error: 'stopped',
message: 'This job has been stopped by another one.',
reason: 'this job has been stopped by another one'
});
};
that.notAccepted = function () {
priv.command.onEndDo(function () {
priv.status = failStatus();
jobManager.terminateJob(that);
});
priv.command.error({
status: 11,
statusText: 'Not Accepted',
error: 'not_accepted',
message: 'This job is already running.',
reason: 'this job is already running'
});
};
/**
* Updates the date of the job with the another one.
* @method update
* @param {object} job The other job.
*/
that.update = function (job) {
priv.command.error({
status: 12,
statusText: 'Replaced',
error: 'replaced',
message: 'Job has been replaced by another one.',
reason: 'job has been replaced by another one'
});
priv.date = new Date(job.getDate().getTime());
priv.command = job.getCommand();
priv.status = job.getStatus();
};
/**
* Executes this job.
* @method execute
*/
that.execute = function () {
if (!that.getCommand().canBeRetried()) {
throw tooMuchTriesJobException({
job: that,
message: 'The job was invoked too much time.'
});
}
if (!that.isReady()) {
throw jobNotReadyException({
job: that,
message: 'Can not execute this job.'
});
}
priv.status = onGoingStatus();
priv.command.onRetryDo(function () {
var ms = priv.command.getTried();
ms = ms * ms * 200;
if (ms > 10000) {
ms = 10000;
}
that.waitForTime(ms);
});
priv.command.onEndDo(function (status) {
priv.status = status;
jobManager.terminateJob(that);
});
priv.command.execute(priv.storage);
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global announcement: true */
var announcement = function (spec, my) {
var that = {},
callback_a = [],
announcer = spec.announcer || {};
spec = spec || {};
my = my || {};
// Methods //
that.add = function (callback) {
callback_a.push(callback);
};
that.remove = function (callback) {
var i, tmp_callback_a = [];
for (i = 0; i < callback_a.length; i += 1) {
if (callback_a[i] !== callback) {
tmp_callback_a.push(callback_a[i]);
}
}
callback_a = tmp_callback_a;
};
that.register = function () {
announcer.register(that);
};
that.unregister = function () {
announcer.unregister(that);
};
that.trigger = function (args) {
var i;
for (i = 0; i < callback_a.length; i += 1) {
callback_a[i].apply(null, args);
}
};
return that;
};
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global localstorage: true, setInterval: true, clearInterval: true */
var activityUpdater = (function (spec, my) {
var that = {}, priv = {};
spec = spec || {};
my = my || {};
priv.id = spec.id || 0;
priv.interval = 400;
priv.interval_id = null;
// Methods //
/**
* Update the last activity date in the localStorage.
* @method touch
*/
priv.touch = function () {
localstorage.setItem('jio/id/' + priv.id, Date.now());
};
/**
* Sets the jio id into the activity.
* @method setId
* @param {number} id The jio id.
*/
that.setId = function (id) {
priv.id = id;
};
/**
* Sets the interval delay between two updates.
* @method setIntervalDelay
* @param {number} ms In milliseconds
*/
that.setIntervalDelay = function (ms) {
priv.interval = ms;
};
/**
* Gets the interval delay.
* @method getIntervalDelay
* @return {number} The interval delay.
*/
that.getIntervalDelay = function () {
return priv.interval;
};
/**
* Starts the activity updater. It will update regulary the last activity
* date in the localStorage to show to other jio instance that this instance
* is active.
* @method start
*/
that.start = function () {
if (!priv.interval_id) {
priv.touch();
priv.interval_id = setInterval(function () {
priv.touch();
}, priv.interval);
}
};
/**
* Stops the activity updater.
* @method stop
*/
that.stop = function () {
if (priv.interval_id !== null) {
clearInterval(priv.interval_id);
priv.interval_id = null;
}
};
return that;
}());
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global announcement: true */
var announcer = (function (spec, my) {
var that = {},
announcement_o = {};
spec = spec || {};
my = my || {};
// Methods //
that.register = function (name) {
if (!announcement_o[name]) {
announcement_o[name] = announcement();
}
};
that.unregister = function (name) {
if (announcement_o[name]) {
delete announcement_o[name];
}
};
that.at = function (name) {
return announcement_o[name];
};
that.on = function (name, callback) {
that.register(name);
that.at(name).add(callback);
};
that.trigger = function (name, args) {
that.at(name).trigger(args);
};
return that;
}());
/*jslint indent: 2, maxlen: 80, sloppy: true */
var jobIdHandler = (function (spec) {
var that = {},
id = 0;
spec = spec || {};
// Methods //
that.nextId = function () {
id = id + 1;
return id;
};
return that;
}());
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global localstorage: true, setInterval: true, clearInterval: true,
command: true, job: true, jobRules: true */
var jobManager = (function (spec) {
var that = {},
job_array_name = 'jio/job_array',
priv = {};
spec = spec || {};
// Attributes //
priv.id = spec.id;
priv.interval_id = null;
priv.interval = 200;
priv.job_array = [];
// Methods //
/**
* Get the job array name in the localStorage
* @method getJobArrayName
* @return {string} The job array name
*/
priv.getJobArrayName = function () {
return job_array_name + '/' + priv.id;
};
/**
* Returns the job array from the localStorage
* @method getJobArray
* @return {array} The job array.
*/
priv.getJobArray = function () {
return localstorage.getItem(priv.getJobArrayName()) || [];
};
/**
* Does a backup of the job array in the localStorage.
* @method copyJobArrayToLocal
*/
priv.copyJobArrayToLocal = function () {
var new_a = [],
i;
for (i = 0; i < priv.job_array.length; i += 1) {
new_a.push(priv.job_array[i].serialized());
}
localstorage.setItem(priv.getJobArrayName(), new_a);
};
/**
* Removes a job from the current job array.
* @method removeJob
* @param {object} job The job object.
*/
priv.removeJob = function (job) {
var i,
tmp_job_array = [];
for (i = 0; i < priv.job_array.length; i += 1) {
if (priv.job_array[i] !== job) {
tmp_job_array.push(priv.job_array[i]);
}
}
priv.job_array = tmp_job_array;
priv.copyJobArrayToLocal();
};
/**
* Sets the job manager id.
* @method setId
* @param {number} id The id.
*/
that.setId = function (id) {
priv.id = id;
};
/**
* Starts listening to the job array, executing them regulary.
* @method start
*/
that.start = function () {
var i;
if (priv.interval_id === null) {
priv.interval_id = setInterval(function () {
priv.restoreOldJio();
for (i = 0; i < priv.job_array.length; i += 1) {
that.execute(priv.job_array[i]);
}
}, priv.interval);
}
};
/**
* Stops listening to the job array.
* @method stop
*/
that.stop = function () {
if (priv.interval_id !== null) {
clearInterval(priv.interval_id);
priv.interval_id = null;
if (priv.job_array.length === 0) {
localstorage.removeItem(priv.getJobArrayName());
}
}
};
/**
* Try to restore an the inactive older jio instances.
* It will restore the on going or initial jobs from their job array
* and it will add them to this job array.
* @method restoreOldJio
*/
priv.restoreOldJio = function () {
var i,
jio_id_a;
priv.lastrestore = priv.lastrestore || 0;
if (priv.lastrestore > (Date.now()) - 2000) {
return;
}
jio_id_a = localstorage.getItem('jio/id_array') || [];
for (i = 0; i < jio_id_a.length; i += 1) {
priv.restoreOldJioId(jio_id_a[i]);
}
priv.lastrestore = Date.now();
};
/**
* Try to restore an old jio according to an id.
* @method restoreOldJioId
* @param {number} id The jio id.
*/
priv.restoreOldJioId = function (id) {
var jio_date;
jio_date = localstorage.getItem('jio/id/' + id) || 0;
if (new Date(jio_date).getTime() < (Date.now() - 10000)) { // 10 sec
priv.restoreOldJobFromJioId(id);
priv.removeOldJioId(id);
priv.removeJobArrayFromJioId(id);
}
};
/**
* Try to restore all jobs from another jio according to an id.
* @method restoreOldJobFromJioId
* @param {number} id The jio id.
*/
priv.restoreOldJobFromJioId = function (id) {
var i,
command_object,
jio_job_array;
jio_job_array = localstorage.getItem('jio/job_array/' + id) || [];
for (i = 0; i < jio_job_array.length; i += 1) {
command_object = command(jio_job_array[i].command);
if (command_object.canBeRestored()) {
that.addJob(job({
storage: that.storage(jio_job_array[i].storage),
command: command_object
}));
}
}
};
/**
* Removes a jio instance according to an id.
* @method removeOldJioId
* @param {number} id The jio id.
*/
priv.removeOldJioId = function (id) {
var i,
jio_id_array,
new_array = [];
jio_id_array = localstorage.getItem('jio/id_array') || [];
for (i = 0; i < jio_id_array.length; i += 1) {
if (jio_id_array[i] !== id) {
new_array.push(jio_id_array[i]);
}
}
localstorage.setItem('jio/id_array', new_array);
localstorage.removeItem('jio/id/' + id);
};
/**
* Removes a job array from a jio instance according to an id.
* @method removeJobArrayFromJioId
* @param {number} id The jio id.
*/
priv.removeJobArrayFromJioId = function (id) {
localstorage.removeItem('jio/job_array/' + id);
};
/**
* Executes a job.
* @method execute
* @param {object} job The job object.
*/
that.execute = function (job) {
try {
job.execute();
} catch (e) {
switch (e.name) {
case 'jobNotReadyException':
break; // do nothing
case 'tooMuchTriesJobException':
break; // do nothing
default:
throw e;
}
}
priv.copyJobArrayToLocal();
};
/**
* Checks if a job exists in the job array according to a job id.
* @method jobIdExists
* @param {number} id The job id.
* @return {boolean} true if exists, else false.
*/
that.jobIdExists = function (id) {
var i;
for (i = 0; i < priv.job_array.length; i += 1) {
if (priv.job_array[i].getId() === id) {
return true;
}
}
return false;
};
/**
* Terminate a job. It only remove it from the job array.
* @method terminateJob
* @param {object} job The job object
*/
that.terminateJob = function (job) {
priv.removeJob(job);
};
/**
* Adds a job to the current job array.
* @method addJob
* @param {object} job The new job.
*/
that.addJob = function (job) {
var result_array = that.validateJobAccordingToJobList(priv.job_array, job);
priv.appendJob(job, result_array);
};
/**
* Generate a result array containing action string to do with the good job.
* @method validateJobAccordingToJobList
* @param {array} job_array A job array.
* @param {object} job The new job to compare with.
* @return {array} A result array.
*/
that.validateJobAccordingToJobList = function (job_array, job) {
var i,
result_array = [];
for (i = 0; i < job_array.length; i += 1) {
result_array.push(jobRules.validateJobAccordingToJob(job_array[i], job));
}
return result_array;
};
/**
* It will manage the job in order to know what to do thanks to a result
* array. The new job can be added to the job array, but it can also be
* not accepted. It is this method which can tells jobs to wait for another
* one, to replace one or to eliminate some while browsing.
* @method appendJob
* @param {object} job The job to append.
* @param {array} result_array The result array.
*/
priv.appendJob = function (job, result_array) {
var i;
if (priv.job_array.length !== result_array.length) {
throw new RangeError("Array out of bound");
}
for (i = 0; i < result_array.length; i += 1) {
if (result_array[i].action === 'dont accept') {
return job.notAccepted();
}
}
for (i = 0; i < result_array.length; i += 1) {
switch (result_array[i].action) {
case 'eliminate':
result_array[i].job.eliminated();
priv.removeJob(result_array[i].job);
break;
case 'update':
result_array[i].job.update(job);
priv.copyJobArrayToLocal();
return;
case 'wait':
job.waitForJob(result_array[i].job);
break;
default:
break;
}
}
priv.job_array.push(job);
priv.copyJobArrayToLocal();
};
that.serialized = function () {
var a = [],
i,
job_array = priv.job_array || [];
for (i = 0; i < job_array.length; i += 1) {
a.push(job_array[i].serialized());
}
return a;
};
return that;
}());
/*jslint indent: 2, maxlen: 80, sloppy: true */
var jobRules = (function () {
var that = {}, priv = {};
priv.compare = {};
priv.action = {};
Object.defineProperty(that, "eliminate", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return 'eliminate';
}
});
Object.defineProperty(that, "update", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return 'update';
}
});
Object.defineProperty(that, "dontAccept", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return 'dont accept';
}
});
Object.defineProperty(that, "wait", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return 'wait';
}
});
Object.defineProperty(that, "none", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return 'none';
}
});
that.default_action = that.none;
that.default_compare = function (job1, job2) {
return (job1.getCommand().getDocId() === job2.getCommand().getDocId() &&
job1.getCommand().getDocInfo('_rev') ===
job2.getCommand().getDocInfo('_rev') &&
job1.getCommand().getOption('rev') ===
job2.getCommand().getOption('rev') &&
JSON.stringify(job1.getStorage().serialized()) ===
JSON.stringify(job2.getStorage().serialized()));
};
// Methods //
/**
* Returns an action according the jobs given in parameters.
* @method getAction
* @param {object} job1 The already existant job.
* @param {object} job2 The job to compare with.
* @return {string} An action string.
*/
priv.getAction = function (job1, job2) {
var j1label, j2label, j1status;
j1label = job1.getCommand().getLabel();
j2label = job2.getCommand().getLabel();
j1status = (job1.getStatus().getLabel() === 'on going' ?
'on going' : 'not on going');
if (priv.action[j1label] && priv.action[j1label][j1status] &&
priv.action[j1label][j1status][j2label]) {
return priv.action[j1label][j1status][j2label](job1, job2);
}
return that.default_action(job1, job2);
};
/**
* Checks if the two jobs are comparable.
* @method canCompare
* @param {object} job1 The already existant job.
* @param {object} job2 The job to compare with.
* @return {boolean} true if comparable, else false.
*/
priv.canCompare = function (job1, job2) {
var job1label = job1.getCommand().getLabel(),
job2label = job2.getCommand().getLabel();
if (priv.compare[job1label] && priv.compare[job2label]) {
return priv.compare[job1label][job2label](job1, job2);
}
return that.default_compare(job1, job2);
};
/**
* Returns an action string to show what to do if we want to add a job.
* @method validateJobAccordingToJob
* @param {object} job1 The current job.
* @param {object} job2 The new job.
* @return {string} The action string.
*/
Object.defineProperty(that, "validateJobAccordingToJob", {
configurable: false,
enumerable: false,
writable: false,
value: function (job1, job2) {
if (priv.canCompare(job1, job2)) {
return {
action: priv.getAction(job1, job2),
job: job1
};
}
return {
action: that.default_action(job1, job2),
job: job1
};
}
});
/**
* Adds a rule the action rules.
* @method addActionRule
* @param {string} method1 The action label from the current job.
* @param {boolean} ongoing Is this action is on going or not?
* @param {string} method2 The action label from the new job.
* @param {function} rule The rule that return an action string.
*/
Object.defineProperty(that, "addActionRule", {
configurable: false,
enumerable: false,
writable: false,
value: function (method1, ongoing, method2, rule) {
var ongoing_s = (ongoing ? 'on going' : 'not on going');
priv.action[method1] = priv.action[method1] || {};
priv.action[method1][ongoing_s] = priv.action[method1][ongoing_s] || {};
priv.action[method1][ongoing_s][method2] = rule;
}
});
/**
* Adds a rule the compare rules.
* @method addCompareRule
* @param {string} method1 The action label from the current job.
* @param {string} method2 The action label from the new job.
* @param {function} rule The rule that return a boolean
* - true if job1 and job2 can be compared, else false.
*/
Object.defineProperty(that, "addCompareRule", {
configurable: false,
enumerable: false,
writable: false,
value: function (method1, method2, rule) {
priv.compare[method1] = priv.compare[method1] || {};
priv.compare[method1][method2] = rule;
}
});
////////////////////////////////////////////////////////////////////////////
// Adding some rules
/*
LEGEND:
- s: storage
- m: method
- n: name
- c: content
- o: options
- =: are equal
- !: are not equal
select ALL s= n=
removefailordone fail|done
/ elim repl nacc wait
Remove !ongoing Save 1 x x x
Save !ongoing Remove 1 x x x
GetList !ongoing GetList 0 1 x x
Remove !ongoing Remove 0 1 x x
Load !ongoing Load 0 1 x x
Save c= !ongoing Save 0 1 x x
Save c! !ongoing Save 0 1 x x
GetList ongoing GetList 0 0 1 x
Remove ongoing Remove 0 0 1 x
Remove ongoing Load 0 0 1 x
Remove !ongoing Load 0 0 1 x
Load ongoing Load 0 0 1 x
Save c= ongoing Save 0 0 1 x
Remove ongoing Save 0 0 0 1
Load ongoing Remove 0 0 0 1
Load ongoing Save 0 0 0 1
Load !ongoing Remove 0 0 0 1
Load !ongoing Save 0 0 0 1
Save ongoing Remove 0 0 0 1
Save ongoing Load 0 0 0 1
Save c! ongoing Save 0 0 0 1
Save !ongoing Load 0 0 0 1
GetList ongoing Remove 0 0 0 0
GetList ongoing Load 0 0 0 0
GetList ongoing Save 0 0 0 0
GetList !ongoing Remove 0 0 0 0
GetList !ongoing Load 0 0 0 0
GetList !ongoing Save 0 0 0 0
Remove ongoing GetList 0 0 0 0
Remove !ongoing GetList 0 0 0 0
Load ongoing GetList 0 0 0 0
Load !ongoing GetList 0 0 0 0
Save ongoing GetList 0 0 0 0
Save !ongoing GetList 0 0 0 0
For more information, see documentation
*/
that.addActionRule('post', true, 'post', that.dontAccept);
that.addActionRule('post', true, 'put', that.wait);
that.addActionRule('post', true, 'get', that.wait);
that.addActionRule('post', true, 'remove', that.wait);
that.addActionRule('post', true, 'putAttachment', that.wait);
that.addActionRule('post', false, 'post', that.update);
that.addActionRule('post', false, 'put', that.wait);
that.addActionRule('post', false, 'get', that.wait);
that.addActionRule('post', false, 'remove', that.eliminate);
that.addActionRule('post', false, 'putAttachment', that.wait);
that.addActionRule('put', true, 'post', that.dontAccept);
that.addActionRule('put', true, 'put', that.wait);
that.addActionRule('put', true, 'get', that.wait);
that.addActionRule('put', true, 'remove', that.wait);
that.addActionRule('put', true, 'putAttachment', that.wait);
that.addActionRule('put', false, 'post', that.dontAccept);
that.addActionRule('put', false, 'put', that.update);
that.addActionRule('put', false, 'get', that.wait);
that.addActionRule('put', false, 'remove', that.eliminate);
that.addActionRule('put', false, 'putAttachment', that.wait);
that.addActionRule('get', true, 'post', that.wait);
that.addActionRule('get', true, 'put', that.wait);
that.addActionRule('get', true, 'get', that.dontAccept);
that.addActionRule('get', true, 'remove', that.wait);
that.addActionRule('get', true, 'putAttachment', that.wait);
that.addActionRule('get', false, 'post', that.wait);
that.addActionRule('get', false, 'put', that.wait);
that.addActionRule('get', false, 'get', that.update);
that.addActionRule('get', false, 'remove', that.wait);
that.addActionRule('get', false, 'putAttachment', that.wait);
that.addActionRule('remove', true, 'post', that.wait);
that.addActionRule('remove', true, 'get', that.dontAccept);
that.addActionRule('remove', true, 'remove', that.dontAccept);
that.addActionRule('remove', true, 'putAttachment', that.dontAccept);
that.addActionRule('remove', false, 'post', that.eliminate);
that.addActionRule('remove', false, 'put', that.dontAccept);
that.addActionRule('remove', false, 'get', that.dontAccept);
that.addActionRule('remove', false, 'remove', that.update);
that.addActionRule('remove', false, 'putAttachment', that.dontAccept);
that.addActionRule('allDocs', true, 'allDocs', that.dontAccept);
that.addActionRule('allDocs', false, 'allDocs', that.update);
that.addActionRule('putAttachment', true, 'post', that.dontAccept);
that.addActionRule('putAttachment', true, 'put', that.wait);
that.addActionRule('putAttachment', true, 'get', that.wait);
that.addActionRule('putAttachment', true, 'remove', that.wait);
that.addActionRule('putAttachment', true, 'putAttachment', that.wait);
that.addActionRule('putAttachment', false, 'post', that.dontAccept);
that.addActionRule('putAttachment', false, 'put', that.wait);
that.addActionRule('putAttachment', false, 'get', that.wait);
that.addActionRule('putAttachment', false, 'remove', that.eliminate);
that.addActionRule('putAttachment', false, 'putAttachment', that.update);
// end adding rules
////////////////////////////////////////////////////////////////////////////
return that;
}());
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global spec: true, localstorage: true,
activityUpdater: true, jobManager: true, storage: true,
storage_type_object: true, invalidStorageType: true, jobRules: true,
job: true, postCommand: true, putCommand: true, getCommand:true,
allDocsCommand: true, putAttachmentCommand: true,
removeCommand: true */
// Class jio
var that = {}, priv = {}, jio_id_array_name = 'jio/id_array';
spec = spec || {};
// Attributes //
priv.id = null;
priv.storage_spec = spec;
priv.environments = {};
// initialize //
priv.init = function () {
// Initialize the jio id and add the new id to the list
if (priv.id === null) {
var i, jio_id_a =
localstorage.getItem(jio_id_array_name) || [];
priv.id = 1;
for (i = 0; i < jio_id_a.length; i += 1) {
if (jio_id_a[i] >= priv.id) {
priv.id = jio_id_a[i] + 1;
}
}
jio_id_a.push(priv.id);
localstorage.setItem(jio_id_array_name, jio_id_a);
activityUpdater.setId(priv.id);
jobManager.setId(priv.id);
}
};
// Methods //
/**
* Returns a storage from a storage description.
* @method storage
* @param {object} spec The specifications.
* @param {object} my The protected object.
* @param {string} forcetype Force storage type
* @return {object} The storage object.
*/
Object.defineProperty(that, "storage", {
configurable: false,
enumerable: false,
writable: false,
value: function (spec, my, forcetype) {
var spec_str, type;
spec = spec || {};
my = my || {};
my.basicStorage = storage;
spec_str = JSON.stringify(spec);
// environment initialization
priv.environments[spec_str] = priv.environments[spec_str] || {};
my.env = priv.environments[spec_str];
my.storage = that.storage; // NOTE : or proxy storage
type = forcetype || spec.type || 'base';
if (type === 'base') {
return storage(spec, my);
}
if (!storage_type_object[type]) {
throw invalidStorageType({
"type": type,
"message": "Storage does not exists."
});
}
return storage_type_object[type](spec, my);
}
});
jobManager.storage = that.storage;
Object.defineProperty(that, "start", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
priv.init();
activityUpdater.start();
jobManager.start();
}
});
Object.defineProperty(that, "stop", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
jobManager.stop();
}
});
Object.defineProperty(that, "close", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
activityUpdater.stop();
jobManager.stop();
priv.id = null;
}
});
/**
* Returns the jio id.
* @method getId
* @return {number} The jio id.
*/
Object.defineProperty(that, "getId", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return priv.id;
}
});
/**
* Returns the jio job rules object used by the job manager.
* @method getJobRules
* @return {object} The job rules object
*/
Object.defineProperty(that, "getJobRules", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return jobRules;
}
});
/**
* Checks if the storage description is valid or not.
* @method validateStorageDescription
* @param {object} description The description object.
* @return {boolean} true if ok, else false.
*/
Object.defineProperty(that, "validateStorageDescription", {
configurable: false,
enumerable: false,
writable: false,
value: function (description) {
return that.storage(description).isValid();
}
});
Object.defineProperty(that, "getJobArray", {
configurable: false,
enumerable: false,
writable: false,
value: function () {
return jobManager.serialized();
}
});
priv.makeCallbacks = function (param, callback1, callback2) {
param.callback = function (err, val) {
if (err) {
param.error(err);
} else {
param.success(val);
}
};
param.success = function (val) {
param.callback(undefined, val);
};
param.error = function (err) {
param.callback(err, undefined);
};
if (typeof callback1 === 'function') {
if (typeof callback2 === 'function') {
param.success = callback1;
param.error = callback2;
} else {
param.callback = callback1;
}
} else {
param.callback = function () {};
}
};
priv.parametersToObject = function (list, default_options) {
var k, i = 0, callbacks = [], param = {"options": {}};
for (i = 0; i < list.length; i += 1) {
if (typeof list[i] === 'object') {
// this is the option
param.options = list[i];
for (k in default_options) {
if ((typeof default_options[k]) !== (typeof list[i][k])) {
param.options[k] = default_options[k];
}
}
}
if (typeof list[i] === 'function') {
// this is a callback
callbacks.push(list[i]);
}
}
priv.makeCallbacks(param, callbacks[0], callbacks[1]);
return param;
};
priv.addJob = function (commandCreator, spec) {
jobManager.addJob(job({
"storage": that.storage(priv.storage_spec),
"command": commandCreator(spec)
}));
};
/**
* Post a document.
* @method post
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id (optional), "/" are forbidden
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Retreive the revisions.
* - {boolean} conflicts Retreive the conflict list.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "post", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, error) {
var param = priv.parametersToObject(
[options, success, error],
{max_retry: 0}
);
priv.addJob(postCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Put a document.
* @method put
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id, "/" are forbidden
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Retreive the revisions.
* - {boolean} conflicts Retreive the conflict list.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "put", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, error) {
var param = priv.parametersToObject(
[options, success, error],
{max_retry: 0}
);
priv.addJob(putCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Get a document.
* @method get
* @param {string} docid The document id: "doc_id" or "doc_id/attachmt_id".
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {string} rev The revision we want to get.
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Include list of revisions, and their availability.
* - {boolean} conflicts Include a list of conflicts.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "get", {
configurable: false,
enumerable: false,
writable: false,
value: function (id, options, success, error) {
var param = priv.parametersToObject(
[options, success, error],
{max_retry: 3}
);
priv.addJob(getCommand, {
docid: id,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Remove a document.
* @method remove
* @param {object} doc The document object. Contains at least:
* - {string} _id The document id: "doc_id" or "doc_id/attachment_id"
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Include list of revisions, and their availability.
* - {boolean} conflicts Include a list of conflicts.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "remove", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, callback) {
var param = priv.parametersToObject(
[options, success, callback],
{max_retry: 0}
);
priv.addJob(removeCommand, {
doc: doc,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Get a list of documents.
* @method allDocs
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} include_docs Include document metadata
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Include revisions.
* - {boolean} conflicts Include conflicts.
* @param {function} callback (optional) The callback(err,response).
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "allDocs", {
configurable: false,
enumerable: false,
writable: false,
value: function (options, success, error) {
var param = priv.parametersToObject(
[options, success, error],
{max_retry: 3}
);
priv.addJob(allDocsCommand, {
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
/**
* Put an attachment to a document.
* @method putAttachment
* @param {object} doc The document object. Contains at least:
* - {string} id The document id: "doc_id/attchment_id"
* - {string} data Base64 attachment data
* - {string} mimetype The attachment mimetype
* - {string} rev The attachment revision
* @param {object} options (optional) Contains some options:
* - {number} max_retry The number max of retries, 0 = infinity.
* - {boolean} revs Include revision history of the document.
* - {boolean} revs_info Include revisions.
* - {boolean} conflicts Include conflicts.
* @param {function} callback (optional) The callback(err,respons)
* @param {function} error (optional) The callback on error, if this
* callback is given in parameter, "callback" is changed as "success",
* called on success.
*/
Object.defineProperty(that, "putAttachment", {
configurable: false,
enumerable: false,
writable: false,
value: function (doc, options, success, error) {
var param, k, doc_with_underscores = {};
param = priv.parametersToObject(
[options, success, error],
{max_retry: 0}
);
for (k in doc) {
if (doc.hasOwnProperty(k) && k.match('[^_].*')) {
doc_with_underscores["_" + k] = doc[k];
}
}
priv.addJob(putAttachmentCommand, {
doc: doc_with_underscores,
options: param.options,
callbacks: {success: param.success, error: param.error}
});
}
});
return that;
}; // End Class jio
/*jslint indent: 2, maxlen: 80, sloppy: true */
/*global jio: true, invalidStorageType: true */
var storage_type_object = { // -> 'key':constructorFunction
'base': function () {} // overriden by jio
};
var jioNamespace = (function (spec) {
var that = {};
spec = spec || {};
// Attributes //
// Methods //
/**
* Creates a new jio instance.
* @method newJio
* @param {object} spec The storage description
* @return {object} The new Jio instance.
*/
Object.defineProperty(that, "newJio", {
configurable: false,
enumerable: false,
writable: false,
value: function (spec) {
var storage = spec,
instance = null;
if (typeof storage === 'string') {
storage = JSON.parse(storage);
} else {
storage = JSON.stringify(storage);
if (storage !== undefined) {
storage = JSON.parse(storage);
}
}
storage = storage || {
type: 'base'
};
instance = jio(storage);
instance.start();
return instance;
}
});
/**
* Add a storage type to jio.
* @method addStorageType
* @param {string} type The storage type
* @param {function} constructor The associated constructor
*/
Object.defineProperty(that, "addStorageType", {
configurable: false,
enumerable: false,
writable: false,
value: function (type, constructor) {
constructor = constructor || function () {
return null;
};
if (storage_type_object[type]) {
throw invalidStorageType({
type: type,
message: 'Already known.'
});
}
storage_type_object[type] = constructor;
}
});
return that;
}());
Object.defineProperty(scope, "jIO", {
configurable: false,
enumerable: false,
writable: false,
value: jioNamespace
});
}(window, hex_md5));
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_md5(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
function b64_md5(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
function hex_hmac_md5(k, d)
{ return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function b64_hmac_md5(k, d)
{ return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function any_hmac_md5(k, d, e)
{ return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
/*
* Perform a simple self-test to see if the VM is working
*/
function md5_vm_test()
{
return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72";
}
/*
* Calculate the MD5 of a raw string
*/
function rstr_md5(s)
{
return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
}
/*
* Calculate the HMAC-MD5, of a key and some data (raw strings)
*/
function rstr_hmac_md5(key, data)
{
var bkey = rstr2binl(key);
if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
var ipad = Array(16), opad = Array(16);
for(var i = 0; i < 16; i++)
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
}
/*
* Convert a raw string to a hex string
*/
function rstr2hex(input)
{
try { hexcase } catch(e) { hexcase=0; }
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var output = "";
var x;
for(var i = 0; i < input.length; i++)
{
x = input.charCodeAt(i);
output += hex_tab.charAt((x >>> 4) & 0x0F)
+ hex_tab.charAt( x & 0x0F);
}
return output;
}
/*
* Convert a raw string to a base-64 string
*/
function rstr2b64(input)
{
try { b64pad } catch(e) { b64pad=''; }
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var output = "";
var len = input.length;
for(var i = 0; i < len; i += 3)
{
var triplet = (input.charCodeAt(i) << 16)
| (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
| (i + 2 < len ? input.charCodeAt(i+2) : 0);
for(var j = 0; j < 4; j++)
{
if(i * 8 + j * 6 > input.length * 8) output += b64pad;
else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
}
}
return output;
}
/*
* Convert a raw string to an arbitrary string encoding
*/
function rstr2any(input, encoding)
{
var divisor = encoding.length;
var i, j, q, x, quotient;
/* Convert to an array of 16-bit big-endian values, forming the dividend */
var dividend = Array(Math.ceil(input.length / 2));
for(i = 0; i < dividend.length; i++)
{
dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
}
/*
* Repeatedly perform a long division. The binary array forms the dividend,
* the length of the encoding is the divisor. Once computed, the quotient
* forms the dividend for the next step. All remainders are stored for later
* use.
*/
var full_length = Math.ceil(input.length * 8 /
(Math.log(encoding.length) / Math.log(2)));
var remainders = Array(full_length);
for(j = 0; j < full_length; j++)
{
quotient = Array();
x = 0;
for(i = 0; i < dividend.length; i++)
{
x = (x << 16) + dividend[i];
q = Math.floor(x / divisor);
x -= q * divisor;
if(quotient.length > 0 || q > 0)
quotient[quotient.length] = q;
}
remainders[j] = x;
dividend = quotient;
}
/* Convert the remainders to the output string */
var output = "";
for(i = remainders.length - 1; i >= 0; i--)
output += encoding.charAt(remainders[i]);
return output;
}
/*
* Encode a string as utf-8.
* For efficiency, this assumes the input is valid utf-16.
*/
function str2rstr_utf8(input)
{
var output = "";
var i = -1;
var x, y;
while(++i < input.length)
{
/* Decode utf-16 surrogate pairs */
x = input.charCodeAt(i);
y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
{
x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
i++;
}
/* Encode output as utf-8 */
if(x <= 0x7F)
output += String.fromCharCode(x);
else if(x <= 0x7FF)
output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
0x80 | ( x & 0x3F));
else if(x <= 0xFFFF)
output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
else if(x <= 0x1FFFFF)
output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
0x80 | ((x >>> 12) & 0x3F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
}
return output;
}
/*
* Encode a string as utf-16
*/
function str2rstr_utf16le(input)
{
var output = "";
for(var i = 0; i < input.length; i++)
output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
(input.charCodeAt(i) >>> 8) & 0xFF);
return output;
}
function str2rstr_utf16be(input)
{
var output = "";
for(var i = 0; i < input.length; i++)
output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
input.charCodeAt(i) & 0xFF);
return output;
}
/*
* Convert a raw string to an array of little-endian words
* Characters >255 have their high-byte silently ignored.
*/
function rstr2binl(input)
{
var output = Array(input.length >> 2);
for(var i = 0; i < output.length; i++)
output[i] = 0;
for(var i = 0; i < input.length * 8; i += 8)
output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
return output;
}
/*
* Convert an array of little-endian words to a string
*/
function binl2rstr(input)
{
var output = "";
for(var i = 0; i < input.length * 32; i += 8)
output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
return output;
}
/*
* Calculate the MD5 of an array of little-endian words, and a bit length.
*/
function binl_md5(x, len)
{
/* append padding */
x[len >> 5] |= 0x80 << ((len) % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
for(var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}
return Array(a, b, c, d);
}
/*
* These functions implement the four basic operations the algorithm uses.
*/
function md5_cmn(q, a, b, x, s, t)
{
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function bit_rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
This source diff could not be displayed because it is too large. You can view the blob instead.
/*! jQuery v1.9.0 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license */(function(e,t){"use strict";function n(e){var t=e.length,n=st.type(e);return st.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}function r(e){var t=Tt[e]={};return st.each(e.match(lt)||[],function(e,n){t[n]=!0}),t}function i(e,n,r,i){if(st.acceptData(e)){var o,a,s=st.expando,u="string"==typeof n,l=e.nodeType,c=l?st.cache:e,f=l?e[s]:e[s]&&s;if(f&&c[f]&&(i||c[f].data)||!u||r!==t)return f||(l?e[s]=f=K.pop()||st.guid++:f=s),c[f]||(c[f]={},l||(c[f].toJSON=st.noop)),("object"==typeof n||"function"==typeof n)&&(i?c[f]=st.extend(c[f],n):c[f].data=st.extend(c[f].data,n)),o=c[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[st.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[st.camelCase(n)])):a=o,a}}function o(e,t,n){if(st.acceptData(e)){var r,i,o,a=e.nodeType,u=a?st.cache:e,l=a?e[st.expando]:st.expando;if(u[l]){if(t&&(r=n?u[l]:u[l].data)){st.isArray(t)?t=t.concat(st.map(t,st.camelCase)):t in r?t=[t]:(t=st.camelCase(t),t=t in r?[t]:t.split(" "));for(i=0,o=t.length;o>i;i++)delete r[t[i]];if(!(n?s:st.isEmptyObject)(r))return}(n||(delete u[l].data,s(u[l])))&&(a?st.cleanData([e],!0):st.support.deleteExpando||u!=u.window?delete u[l]:u[l]=null)}}}function a(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(Nt,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:wt.test(r)?st.parseJSON(r):r}catch(o){}st.data(e,n,r)}else r=t}return r}function s(e){var t;for(t in e)if(("data"!==t||!st.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function u(){return!0}function l(){return!1}function c(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function f(e,t,n){if(t=t||0,st.isFunction(t))return st.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return st.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=st.grep(e,function(e){return 1===e.nodeType});if(Wt.test(t))return st.filter(t,r,!n);t=st.filter(t,r)}return st.grep(e,function(e){return st.inArray(e,t)>=0===n})}function p(e){var t=zt.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function d(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function h(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function g(e){var t=nn.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function m(e,t){for(var n,r=0;null!=(n=e[r]);r++)st._data(n,"globalEval",!t||st._data(t[r],"globalEval"))}function y(e,t){if(1===t.nodeType&&st.hasData(e)){var n,r,i,o=st._data(e),a=st._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)st.event.add(t,n,s[n][r])}a.data&&(a.data=st.extend({},a.data))}}function v(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!st.support.noCloneEvent&&t[st.expando]){r=st._data(t);for(i in r.events)st.removeEvent(t,i,r.handle);t.removeAttribute(st.expando)}"script"===n&&t.text!==e.text?(h(t).text=e.text,g(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),st.support.html5Clone&&e.innerHTML&&!st.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Zt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}function b(e,n){var r,i,o=0,a=e.getElementsByTagName!==t?e.getElementsByTagName(n||"*"):e.querySelectorAll!==t?e.querySelectorAll(n||"*"):t;if(!a)for(a=[],r=e.childNodes||e;null!=(i=r[o]);o++)!n||st.nodeName(i,n)?a.push(i):st.merge(a,b(i,n));return n===t||n&&st.nodeName(e,n)?st.merge([e],a):a}function x(e){Zt.test(e.type)&&(e.defaultChecked=e.checked)}function T(e,t){if(t in e)return t;for(var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Nn.length;i--;)if(t=Nn[i]+n,t in e)return t;return r}function w(e,t){return e=t||e,"none"===st.css(e,"display")||!st.contains(e.ownerDocument,e)}function N(e,t){for(var n,r=[],i=0,o=e.length;o>i;i++)n=e[i],n.style&&(r[i]=st._data(n,"olddisplay"),t?(r[i]||"none"!==n.style.display||(n.style.display=""),""===n.style.display&&w(n)&&(r[i]=st._data(n,"olddisplay",S(n.nodeName)))):r[i]||w(n)||st._data(n,"olddisplay",st.css(n,"display")));for(i=0;o>i;i++)n=e[i],n.style&&(t&&"none"!==n.style.display&&""!==n.style.display||(n.style.display=t?r[i]||"":"none"));return e}function C(e,t,n){var r=mn.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function k(e,t,n,r,i){for(var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;4>o;o+=2)"margin"===n&&(a+=st.css(e,n+wn[o],!0,i)),r?("content"===n&&(a-=st.css(e,"padding"+wn[o],!0,i)),"margin"!==n&&(a-=st.css(e,"border"+wn[o]+"Width",!0,i))):(a+=st.css(e,"padding"+wn[o],!0,i),"padding"!==n&&(a+=st.css(e,"border"+wn[o]+"Width",!0,i)));return a}function E(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=ln(e),a=st.support.boxSizing&&"border-box"===st.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=un(e,t,o),(0>i||null==i)&&(i=e.style[t]),yn.test(i))return i;r=a&&(st.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+k(e,t,n||(a?"border":"content"),r,o)+"px"}function S(e){var t=V,n=bn[e];return n||(n=A(e,t),"none"!==n&&n||(cn=(cn||st("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(cn[0].contentWindow||cn[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=A(e,t),cn.detach()),bn[e]=n),n}function A(e,t){var n=st(t.createElement(e)).appendTo(t.body),r=st.css(n[0],"display");return n.remove(),r}function j(e,t,n,r){var i;if(st.isArray(t))st.each(t,function(t,i){n||kn.test(e)?r(e,i):j(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==st.type(t))r(e,t);else for(i in t)j(e+"["+i+"]",t[i],n,r)}function D(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(lt)||[];if(st.isFunction(n))for(;r=o[i++];)"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function L(e,n,r,i){function o(u){var l;return a[u]=!0,st.each(e[u]||[],function(e,u){var c=u(n,r,i);return"string"!=typeof c||s||a[c]?s?!(l=c):t:(n.dataTypes.unshift(c),o(c),!1)}),l}var a={},s=e===$n;return o(n.dataTypes[0])||!a["*"]&&o("*")}function H(e,n){var r,i,o=st.ajaxSettings.flatOptions||{};for(r in n)n[r]!==t&&((o[r]?e:i||(i={}))[r]=n[r]);return i&&st.extend(!0,e,i),e}function M(e,n,r){var i,o,a,s,u=e.contents,l=e.dataTypes,c=e.responseFields;for(o in c)o in r&&(n[c[o]]=r[o]);for(;"*"===l[0];)l.shift(),i===t&&(i=e.mimeType||n.getResponseHeader("Content-Type"));if(i)for(o in u)if(u[o]&&u[o].test(i)){l.unshift(o);break}if(l[0]in r)a=l[0];else{for(o in r){if(!l[0]||e.converters[o+" "+l[0]]){a=o;break}s||(s=o)}a=a||s}return a?(a!==l[0]&&l.unshift(a),r[a]):t}function q(e,t){var n,r,i,o,a={},s=0,u=e.dataTypes.slice(),l=u[0];if(e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u[1])for(n in e.converters)a[n.toLowerCase()]=e.converters[n];for(;i=u[++s];)if("*"!==i){if("*"!==l&&l!==i){if(n=a[l+" "+i]||a["* "+i],!n)for(r in a)if(o=r.split(" "),o[1]===i&&(n=a[l+" "+o[0]]||a["* "+o[0]])){n===!0?n=a[r]:a[r]!==!0&&(i=o[0],u.splice(s--,0,i));break}if(n!==!0)if(n&&e["throws"])t=n(t);else try{t=n(t)}catch(c){return{state:"parsererror",error:n?c:"No conversion from "+l+" to "+i}}}l=i}return{state:"success",data:t}}function _(){try{return new e.XMLHttpRequest}catch(t){}}function F(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}function O(){return setTimeout(function(){Qn=t}),Qn=st.now()}function B(e,t){st.each(t,function(t,n){for(var r=(rr[t]||[]).concat(rr["*"]),i=0,o=r.length;o>i;i++)if(r[i].call(e,t,n))return})}function P(e,t,n){var r,i,o=0,a=nr.length,s=st.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;for(var t=Qn||O(),n=Math.max(0,l.startTime+l.duration-t),r=n/l.duration||0,o=1-r,a=0,u=l.tweens.length;u>a;a++)l.tweens[a].run(o);return s.notifyWith(e,[l,o,n]),1>o&&u?n:(s.resolveWith(e,[l]),!1)},l=s.promise({elem:e,props:st.extend({},t),opts:st.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Qn||O(),duration:n.duration,tweens:[],createTween:function(t,n){var r=st.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)l.tweens[n].run(1);return t?s.resolveWith(e,[l,t]):s.rejectWith(e,[l,t]),this}}),c=l.props;for(R(c,l.opts.specialEasing);a>o;o++)if(r=nr[o].call(l,e,c,l.opts))return r;return B(l,c),st.isFunction(l.opts.start)&&l.opts.start.call(e,l),st.fx.timer(st.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always)}function R(e,t){var n,r,i,o,a;for(n in e)if(r=st.camelCase(n),i=t[r],o=e[n],st.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=st.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}function W(e,t,n){var r,i,o,a,s,u,l,c,f,p=this,d=e.style,h={},g=[],m=e.nodeType&&w(e);n.queue||(c=st._queueHooks(e,"fx"),null==c.unqueued&&(c.unqueued=0,f=c.empty.fire,c.empty.fire=function(){c.unqueued||f()}),c.unqueued++,p.always(function(){p.always(function(){c.unqueued--,st.queue(e,"fx").length||c.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[d.overflow,d.overflowX,d.overflowY],"inline"===st.css(e,"display")&&"none"===st.css(e,"float")&&(st.support.inlineBlockNeedsLayout&&"inline"!==S(e.nodeName)?d.zoom=1:d.display="inline-block")),n.overflow&&(d.overflow="hidden",st.support.shrinkWrapBlocks||p.done(function(){d.overflow=n.overflow[0],d.overflowX=n.overflow[1],d.overflowY=n.overflow[2]}));for(r in t)if(o=t[r],Zn.exec(o)){if(delete t[r],u=u||"toggle"===o,o===(m?"hide":"show"))continue;g.push(r)}if(a=g.length){s=st._data(e,"fxshow")||st._data(e,"fxshow",{}),"hidden"in s&&(m=s.hidden),u&&(s.hidden=!m),m?st(e).show():p.done(function(){st(e).hide()}),p.done(function(){var t;st._removeData(e,"fxshow");for(t in h)st.style(e,t,h[t])});for(r=0;a>r;r++)i=g[r],l=p.createTween(i,m?s[i]:0),h[i]=s[i]||st.style(e,i),i in s||(s[i]=l.start,m&&(l.end=l.start,l.start="width"===i||"height"===i?1:0))}}function $(e,t,n,r,i){return new $.prototype.init(e,t,n,r,i)}function I(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=wn[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}function z(e){return st.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}var X,U,V=e.document,Y=e.location,J=e.jQuery,G=e.$,Q={},K=[],Z="1.9.0",et=K.concat,tt=K.push,nt=K.slice,rt=K.indexOf,it=Q.toString,ot=Q.hasOwnProperty,at=Z.trim,st=function(e,t){return new st.fn.init(e,t,X)},ut=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,lt=/\S+/g,ct=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,ft=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,pt=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,dt=/^[\],:{}\s]*$/,ht=/(?:^|:|,)(?:\s*\[)+/g,gt=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,mt=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,yt=/^-ms-/,vt=/-([\da-z])/gi,bt=function(e,t){return t.toUpperCase()},xt=function(){V.addEventListener?(V.removeEventListener("DOMContentLoaded",xt,!1),st.ready()):"complete"===V.readyState&&(V.detachEvent("onreadystatechange",xt),st.ready())};st.fn=st.prototype={jquery:Z,constructor:st,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:ft.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof st?n[0]:n,st.merge(this,st.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:V,!0)),pt.test(i[1])&&st.isPlainObject(n))for(i in n)st.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=V.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=V,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):st.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),st.makeArray(e,this))},selector:"",length:0,size:function(){return this.length},toArray:function(){return nt.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=st.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return st.each(this,e,t)},ready:function(e){return st.ready.promise().done(e),this},slice:function(){return this.pushStack(nt.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(st.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:tt,sort:[].sort,splice:[].splice},st.fn.init.prototype=st.fn,st.extend=st.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},u=1,l=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},u=2),"object"==typeof s||st.isFunction(s)||(s={}),l===u&&(s=this,--u);l>u;u++)if(null!=(e=arguments[u]))for(n in e)r=s[n],i=e[n],s!==i&&(c&&i&&(st.isPlainObject(i)||(o=st.isArray(i)))?(o?(o=!1,a=r&&st.isArray(r)?r:[]):a=r&&st.isPlainObject(r)?r:{},s[n]=st.extend(c,a,i)):i!==t&&(s[n]=i));return s},st.extend({noConflict:function(t){return e.$===st&&(e.$=G),t&&e.jQuery===st&&(e.jQuery=J),st},isReady:!1,readyWait:1,holdReady:function(e){e?st.readyWait++:st.ready(!0)},ready:function(e){if(e===!0?!--st.readyWait:!st.isReady){if(!V.body)return setTimeout(st.ready);st.isReady=!0,e!==!0&&--st.readyWait>0||(U.resolveWith(V,[st]),st.fn.trigger&&st(V).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===st.type(e)},isArray:Array.isArray||function(e){return"array"===st.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?Q[it.call(e)]||"object":typeof e},isPlainObject:function(e){if(!e||"object"!==st.type(e)||e.nodeType||st.isWindow(e))return!1;try{if(e.constructor&&!ot.call(e,"constructor")&&!ot.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||ot.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||V;var r=pt.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=st.buildFragment([e],t,i),i&&st(i).remove(),st.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=st.trim(n),n&&dt.test(n.replace(gt,"@").replace(mt,"]").replace(ht,"")))?Function("return "+n)():(st.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||st.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&st.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(yt,"ms-").replace(vt,bt)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,r){var i,o=0,a=e.length,s=n(e);if(r){if(s)for(;a>o&&(i=t.apply(e[o],r),i!==!1);o++);else for(o in e)if(i=t.apply(e[o],r),i===!1)break}else if(s)for(;a>o&&(i=t.call(e[o],o,e[o]),i!==!1);o++);else for(o in e)if(i=t.call(e[o],o,e[o]),i===!1)break;return e},trim:at&&!at.call("\ufeff\u00a0")?function(e){return null==e?"":at.call(e)}:function(e){return null==e?"":(e+"").replace(ct,"")},makeArray:function(e,t){var r=t||[];return null!=e&&(n(Object(e))?st.merge(r,"string"==typeof e?[e]:e):tt.call(r,e)),r},inArray:function(e,t,n){var r;if(t){if(rt)return rt.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else for(;n[o]!==t;)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,r){var i,o=0,a=e.length,s=n(e),u=[];if(s)for(;a>o;o++)i=t(e[o],o,r),null!=i&&(u[u.length]=i);else for(o in e)i=t(e[o],o,r),null!=i&&(u[u.length]=i);return et.apply([],u)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(r=e[n],n=e,e=r),st.isFunction(e)?(i=nt.call(arguments,2),o=function(){return e.apply(n||this,i.concat(nt.call(arguments)))},o.guid=e.guid=e.guid||st.guid++,o):t},access:function(e,n,r,i,o,a,s){var u=0,l=e.length,c=null==r;if("object"===st.type(r)){o=!0;for(u in r)st.access(e,n,u,r[u],!0,a,s)}else if(i!==t&&(o=!0,st.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(st(e),n)})),n))for(;l>u;u++)n(e[u],r,s?i:i.call(e[u],u,n(e[u],r)));return o?e:c?n.call(e):l?n(e[0],r):a},now:function(){return(new Date).getTime()}}),st.ready.promise=function(t){if(!U)if(U=st.Deferred(),"complete"===V.readyState)setTimeout(st.ready);else if(V.addEventListener)V.addEventListener("DOMContentLoaded",xt,!1),e.addEventListener("load",st.ready,!1);else{V.attachEvent("onreadystatechange",xt),e.attachEvent("onload",st.ready);var n=!1;try{n=null==e.frameElement&&V.documentElement}catch(r){}n&&n.doScroll&&function i(){if(!st.isReady){try{n.doScroll("left")}catch(e){return setTimeout(i,50)}st.ready()}}()}return U.promise(t)},st.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){Q["[object "+t+"]"]=t.toLowerCase()}),X=st(V);var Tt={};st.Callbacks=function(e){e="string"==typeof e?Tt[e]||r(e):st.extend({},e);var n,i,o,a,s,u,l=[],c=!e.once&&[],f=function(t){for(n=e.memory&&t,i=!0,u=a||0,a=0,s=l.length,o=!0;l&&s>u;u++)if(l[u].apply(t[0],t[1])===!1&&e.stopOnFalse){n=!1;break}o=!1,l&&(c?c.length&&f(c.shift()):n?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function r(t){st.each(t,function(t,n){var i=st.type(n);"function"===i?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==i&&r(n)})})(arguments),o?s=l.length:n&&(a=t,f(n))}return this},remove:function(){return l&&st.each(arguments,function(e,t){for(var n;(n=st.inArray(t,l,n))>-1;)l.splice(n,1),o&&(s>=n&&s--,u>=n&&u--)}),this},has:function(e){return st.inArray(e,l)>-1},empty:function(){return l=[],this},disable:function(){return l=c=n=t,this},disabled:function(){return!l},lock:function(){return c=t,n||p.disable(),this},locked:function(){return!c},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!l||i&&!c||(o?c.push(t):f(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},st.extend({Deferred:function(e){var t=[["resolve","done",st.Callbacks("once memory"),"resolved"],["reject","fail",st.Callbacks("once memory"),"rejected"],["notify","progress",st.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return st.Deferred(function(n){st.each(t,function(t,o){var a=o[0],s=st.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&st.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?st.extend(e,r):r}},i={};return r.pipe=r.then,st.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t,n,r,i=0,o=nt.call(arguments),a=o.length,s=1!==a||e&&st.isFunction(e.promise)?a:0,u=1===s?e:st.Deferred(),l=function(e,n,r){return function(i){n[e]=this,r[e]=arguments.length>1?nt.call(arguments):i,r===t?u.notifyWith(n,r):--s||u.resolveWith(n,r)}};if(a>1)for(t=Array(a),n=Array(a),r=Array(a);a>i;i++)o[i]&&st.isFunction(o[i].promise)?o[i].promise().done(l(i,r,o)).fail(u.reject).progress(l(i,n,t)):--s;return s||u.resolveWith(r,o),u.promise()}}),st.support=function(){var n,r,i,o,a,s,u,l,c,f,p=V.createElement("div");if(p.setAttribute("className","t"),p.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",r=p.getElementsByTagName("*"),i=p.getElementsByTagName("a")[0],!r||!i||!r.length)return{};o=V.createElement("select"),a=o.appendChild(V.createElement("option")),s=p.getElementsByTagName("input")[0],i.style.cssText="top:1px;float:left;opacity:.5",n={getSetAttribute:"t"!==p.className,leadingWhitespace:3===p.firstChild.nodeType,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(i.getAttribute("style")),hrefNormalized:"/a"===i.getAttribute("href"),opacity:/^0.5/.test(i.style.opacity),cssFloat:!!i.style.cssFloat,checkOn:!!s.value,optSelected:a.selected,enctype:!!V.createElement("form").enctype,html5Clone:"<:nav></:nav>"!==V.createElement("nav").cloneNode(!0).outerHTML,boxModel:"CSS1Compat"===V.compatMode,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},s.checked=!0,n.noCloneChecked=s.cloneNode(!0).checked,o.disabled=!0,n.optDisabled=!a.disabled;try{delete p.test}catch(d){n.deleteExpando=!1}s=V.createElement("input"),s.setAttribute("value",""),n.input=""===s.getAttribute("value"),s.value="t",s.setAttribute("type","radio"),n.radioValue="t"===s.value,s.setAttribute("checked","t"),s.setAttribute("name","t"),u=V.createDocumentFragment(),u.appendChild(s),n.appendChecked=s.checked,n.checkClone=u.cloneNode(!0).cloneNode(!0).lastChild.checked,p.attachEvent&&(p.attachEvent("onclick",function(){n.noCloneEvent=!1}),p.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})p.setAttribute(l="on"+f,"t"),n[f+"Bubbles"]=l in e||p.attributes[l].expando===!1;return p.style.backgroundClip="content-box",p.cloneNode(!0).style.backgroundClip="",n.clearCloneStyle="content-box"===p.style.backgroundClip,st(function(){var r,i,o,a="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",s=V.getElementsByTagName("body")[0];s&&(r=V.createElement("div"),r.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",s.appendChild(r).appendChild(p),p.innerHTML="<table><tr><td></td><td>t</td></tr></table>",o=p.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",c=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",n.reliableHiddenOffsets=c&&0===o[0].offsetHeight,p.innerHTML="",p.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",n.boxSizing=4===p.offsetWidth,n.doesNotIncludeMarginInBodyOffset=1!==s.offsetTop,e.getComputedStyle&&(n.pixelPosition="1%"!==(e.getComputedStyle(p,null)||{}).top,n.boxSizingReliable="4px"===(e.getComputedStyle(p,null)||{width:"4px"}).width,i=p.appendChild(V.createElement("div")),i.style.cssText=p.style.cssText=a,i.style.marginRight=i.style.width="0",p.style.width="1px",n.reliableMarginRight=!parseFloat((e.getComputedStyle(i,null)||{}).marginRight)),p.style.zoom!==t&&(p.innerHTML="",p.style.cssText=a+"width:1px;padding:1px;display:inline;zoom:1",n.inlineBlockNeedsLayout=3===p.offsetWidth,p.style.display="block",p.innerHTML="<div></div>",p.firstChild.style.width="5px",n.shrinkWrapBlocks=3!==p.offsetWidth,s.style.zoom=1),s.removeChild(r),r=p=o=i=null)}),r=o=u=a=i=s=null,n}();var wt=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,Nt=/([A-Z])/g;st.extend({cache:{},expando:"jQuery"+(Z+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?st.cache[e[st.expando]]:e[st.expando],!!e&&!s(e)},data:function(e,t,n){return i(e,t,n,!1)},removeData:function(e,t){return o(e,t,!1)},_data:function(e,t,n){return i(e,t,n,!0)},_removeData:function(e,t){return o(e,t,!0)},acceptData:function(e){var t=e.nodeName&&st.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),st.fn.extend({data:function(e,n){var r,i,o=this[0],s=0,u=null;if(e===t){if(this.length&&(u=st.data(o),1===o.nodeType&&!st._data(o,"parsedAttrs"))){for(r=o.attributes;r.length>s;s++)i=r[s].name,i.indexOf("data-")||(i=st.camelCase(i.substring(5)),a(o,i,u[i]));st._data(o,"parsedAttrs",!0)}return u}return"object"==typeof e?this.each(function(){st.data(this,e)}):st.access(this,function(n){return n===t?o?a(o,e,st.data(o,e)):null:(this.each(function(){st.data(this,e,n)}),t)},null,n,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){st.removeData(this,e)})}}),st.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=st._data(e,n),r&&(!i||st.isArray(r)?i=st._data(e,n,st.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=st.queue(e,t),r=n.length,i=n.shift(),o=st._queueHooks(e,t),a=function(){st.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return st._data(e,n)||st._data(e,n,{empty:st.Callbacks("once memory").add(function(){st._removeData(e,t+"queue"),st._removeData(e,n)})})}}),st.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?st.queue(this[0],e):n===t?this:this.each(function(){var t=st.queue(this,e,n);st._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&st.dequeue(this,e)})},dequeue:function(e){return this.each(function(){st.dequeue(this,e)})},delay:function(e,t){return e=st.fx?st.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=st.Deferred(),a=this,s=this.length,u=function(){--i||o.resolveWith(a,[a])};for("string"!=typeof e&&(n=e,e=t),e=e||"fx";s--;)r=st._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(u));return u(),o.promise(n)}});var Ct,kt,Et=/[\t\r\n]/g,St=/\r/g,At=/^(?:input|select|textarea|button|object)$/i,jt=/^(?:a|area)$/i,Dt=/^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,Lt=/^(?:checked|selected)$/i,Ht=st.support.getSetAttribute,Mt=st.support.input;st.fn.extend({attr:function(e,t){return st.access(this,st.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){st.removeAttr(this,e)})},prop:function(e,t){return st.access(this,st.prop,e,t,arguments.length>1)},removeProp:function(e){return e=st.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,u="string"==typeof e&&e;if(st.isFunction(e))return this.each(function(t){st(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(lt)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(Et," "):" ")){for(o=0;i=t[o++];)0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=st.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,u=0===arguments.length||"string"==typeof e&&e;if(st.isFunction(e))return this.each(function(t){st(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(lt)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(Et," "):"")){for(o=0;i=t[o++];)for(;r.indexOf(" "+i+" ")>=0;)r=r.replace(" "+i+" "," ");n.className=e?st.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return st.isFunction(e)?this.each(function(n){st(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n)for(var i,o=0,a=st(this),s=t,u=e.match(lt)||[];i=u[o++];)s=r?s:!a.hasClass(i),a[s?"addClass":"removeClass"](i);else("undefined"===n||"boolean"===n)&&(this.className&&st._data(this,"__className__",this.className),this.className=this.className||e===!1?"":st._data(this,"__className__")||"")})},hasClass:function(e){for(var t=" "+e+" ",n=0,r=this.length;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(Et," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=st.isFunction(e),this.each(function(r){var o,a=st(this);1===this.nodeType&&(o=i?e.call(this,r,a.val()):e,null==o?o="":"number"==typeof o?o+="":st.isArray(o)&&(o=st.map(o,function(e){return null==e?"":e+""})),n=st.valHooks[this.type]||st.valHooks[this.nodeName.toLowerCase()],n&&"set"in n&&n.set(this,o,"value")!==t||(this.value=o))});if(o)return n=st.valHooks[o.type]||st.valHooks[o.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(o,"value"))!==t?r:(r=o.value,"string"==typeof r?r.replace(St,""):null==r?"":r)}}}),st.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){for(var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,u=0>i?s:o?i:0;s>u;u++)if(n=r[u],!(!n.selected&&u!==i||(st.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&st.nodeName(n.parentNode,"optgroup"))){if(t=st(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n=st.makeArray(t);return st(e).find("option").each(function(){this.selected=st.inArray(st(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attr:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return e.getAttribute===t?st.prop(e,n,r):(a=1!==s||!st.isXMLDoc(e),a&&(n=n.toLowerCase(),o=st.attrHooks[n]||(Dt.test(n)?kt:Ct)),r===t?o&&a&&"get"in o&&null!==(i=o.get(e,n))?i:(e.getAttribute!==t&&(i=e.getAttribute(n)),null==i?t:i):null!==r?o&&a&&"set"in o&&(i=o.set(e,r,n))!==t?i:(e.setAttribute(n,r+""),r):(st.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(lt);if(o&&1===e.nodeType)for(;n=o[i++];)r=st.propFix[n]||n,Dt.test(n)?!Ht&&Lt.test(n)?e[st.camelCase("default-"+n)]=e[r]=!1:e[r]=!1:st.attr(e,n,""),e.removeAttribute(Ht?n:r)},attrHooks:{type:{set:function(e,t){if(!st.support.radioValue&&"radio"===t&&st.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!st.isXMLDoc(e),a&&(n=st.propFix[n]||n,o=st.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):At.test(e.nodeName)||jt.test(e.nodeName)&&e.href?0:t}}}}),kt={get:function(e,n){var r=st.prop(e,n),i="boolean"==typeof r&&e.getAttribute(n),o="boolean"==typeof r?Mt&&Ht?null!=i:Lt.test(n)?e[st.camelCase("default-"+n)]:!!i:e.getAttributeNode(n);return o&&o.value!==!1?n.toLowerCase():t},set:function(e,t,n){return t===!1?st.removeAttr(e,n):Mt&&Ht||!Lt.test(n)?e.setAttribute(!Ht&&st.propFix[n]||n,n):e[st.camelCase("default-"+n)]=e[n]=!0,n}},Mt&&Ht||(st.attrHooks.value={get:function(e,n){var r=e.getAttributeNode(n);return st.nodeName(e,"input")?e.defaultValue:r&&r.specified?r.value:t
},set:function(e,n,r){return st.nodeName(e,"input")?(e.defaultValue=n,t):Ct&&Ct.set(e,n,r)}}),Ht||(Ct=st.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&("id"===n||"name"===n||"coords"===n?""!==r.value:r.specified)?r.value:t},set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},st.attrHooks.contenteditable={get:Ct.get,set:function(e,t,n){Ct.set(e,""===t?!1:t,n)}},st.each(["width","height"],function(e,n){st.attrHooks[n]=st.extend(st.attrHooks[n],{set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}})})),st.support.hrefNormalized||(st.each(["href","src","width","height"],function(e,n){st.attrHooks[n]=st.extend(st.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return null==r?t:r}})}),st.each(["href","src"],function(e,t){st.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}})),st.support.style||(st.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),st.support.optSelected||(st.propHooks.selected=st.extend(st.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),st.support.enctype||(st.propFix.enctype="encoding"),st.support.checkOn||st.each(["radio","checkbox"],function(){st.valHooks[this]={get:function(e){return null===e.getAttribute("value")?"on":e.value}}}),st.each(["radio","checkbox"],function(){st.valHooks[this]=st.extend(st.valHooks[this],{set:function(e,n){return st.isArray(n)?e.checked=st.inArray(st(e).val(),n)>=0:t}})});var qt=/^(?:input|select|textarea)$/i,_t=/^key/,Ft=/^(?:mouse|contextmenu)|click/,Ot=/^(?:focusinfocus|focusoutblur)$/,Bt=/^([^.]*)(?:\.(.+)|)$/;st.event={global:{},add:function(e,n,r,i,o){var a,s,u,l,c,f,p,d,h,g,m,y=3!==e.nodeType&&8!==e.nodeType&&st._data(e);if(y){for(r.handler&&(a=r,r=a.handler,o=a.selector),r.guid||(r.guid=st.guid++),(l=y.events)||(l=y.events={}),(s=y.handle)||(s=y.handle=function(e){return st===t||e&&st.event.triggered===e.type?t:st.event.dispatch.apply(s.elem,arguments)},s.elem=e),n=(n||"").match(lt)||[""],c=n.length;c--;)u=Bt.exec(n[c])||[],h=m=u[1],g=(u[2]||"").split(".").sort(),p=st.event.special[h]||{},h=(o?p.delegateType:p.bindType)||h,p=st.event.special[h]||{},f=st.extend({type:h,origType:m,data:i,handler:r,guid:r.guid,selector:o,needsContext:o&&st.expr.match.needsContext.test(o),namespace:g.join(".")},a),(d=l[h])||(d=l[h]=[],d.delegateCount=0,p.setup&&p.setup.call(e,i,g,s)!==!1||(e.addEventListener?e.addEventListener(h,s,!1):e.attachEvent&&e.attachEvent("on"+h,s))),p.add&&(p.add.call(e,f),f.handler.guid||(f.handler.guid=r.guid)),o?d.splice(d.delegateCount++,0,f):d.push(f),st.event.global[h]=!0;e=null}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,m=st.hasData(e)&&st._data(e);if(m&&(u=m.events)){for(t=(t||"").match(lt)||[""],l=t.length;l--;)if(s=Bt.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){for(f=st.event.special[d]||{},d=(r?f.delegateType:f.bindType)||d,p=u[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;o--;)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&f.teardown.call(e,h,m.handle)!==!1||st.removeEvent(e,d,m.handle),delete u[d])}else for(d in u)st.event.remove(e,d+t[l],n,r,!0);st.isEmptyObject(u)&&(delete m.handle,st._removeData(e,"events"))}},trigger:function(n,r,i,o){var a,s,u,l,c,f,p,d=[i||V],h=n.type||n,g=n.namespace?n.namespace.split("."):[];if(s=u=i=i||V,3!==i.nodeType&&8!==i.nodeType&&!Ot.test(h+st.event.triggered)&&(h.indexOf(".")>=0&&(g=h.split("."),h=g.shift(),g.sort()),c=0>h.indexOf(":")&&"on"+h,n=n[st.expando]?n:new st.Event(h,"object"==typeof n&&n),n.isTrigger=!0,n.namespace=g.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:st.makeArray(r,[n]),p=st.event.special[h]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!st.isWindow(i)){for(l=p.delegateType||h,Ot.test(l+h)||(s=s.parentNode);s;s=s.parentNode)d.push(s),u=s;u===(i.ownerDocument||V)&&d.push(u.defaultView||u.parentWindow||e)}for(a=0;(s=d[a++])&&!n.isPropagationStopped();)n.type=a>1?l:p.bindType||h,f=(st._data(s,"events")||{})[n.type]&&st._data(s,"handle"),f&&f.apply(s,r),f=c&&s[c],f&&st.acceptData(s)&&f.apply&&f.apply(s,r)===!1&&n.preventDefault();if(n.type=h,!(o||n.isDefaultPrevented()||p._default&&p._default.apply(i.ownerDocument,r)!==!1||"click"===h&&st.nodeName(i,"a")||!st.acceptData(i)||!c||!i[h]||st.isWindow(i))){u=i[c],u&&(i[c]=null),st.event.triggered=h;try{i[h]()}catch(m){}st.event.triggered=t,u&&(i[c]=u)}return n.result}},dispatch:function(e){e=st.event.fix(e);var n,r,i,o,a,s=[],u=nt.call(arguments),l=(st._data(this,"events")||{})[e.type]||[],c=st.event.special[e.type]||{};if(u[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){for(s=st.event.handlers.call(this,e,l),n=0;(o=s[n++])&&!e.isPropagationStopped();)for(e.currentTarget=o.elem,r=0;(a=o.handlers[r++])&&!e.isImmediatePropagationStopped();)(!e.namespace_re||e.namespace_re.test(a.namespace))&&(e.handleObj=a,e.data=a.data,i=((st.event.special[a.origType]||{}).handle||a.handler).apply(o.elem,u),i!==t&&(e.result=i)===!1&&(e.preventDefault(),e.stopPropagation()));return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],u=n.delegateCount,l=e.target;if(u&&l.nodeType&&(!e.button||"click"!==e.type))for(;l!=this;l=l.parentNode||this)if(l.disabled!==!0||"click"!==e.type){for(i=[],r=0;u>r;r++)a=n[r],o=a.selector+" ",i[o]===t&&(i[o]=a.needsContext?st(o,this).index(l)>=0:st.find(o,this,null,[l]).length),i[o]&&i.push(a);i.length&&s.push({elem:l,handlers:i})}return n.length>u&&s.push({elem:this,handlers:n.slice(u)}),s},fix:function(e){if(e[st.expando])return e;var t,n,r=e,i=st.event.fixHooks[e.type]||{},o=i.props?this.props.concat(i.props):this.props;for(e=new st.Event(r),t=o.length;t--;)n=o[t],e[n]=r[n];return e.target||(e.target=r.srcElement||V),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,i.filter?i.filter(e,r):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,a=n.button,s=n.fromElement;return null==e.pageX&&null!=n.clientX&&(r=e.target.ownerDocument||V,i=r.documentElement,o=r.body,e.pageX=n.clientX+(i&&i.scrollLeft||o&&o.scrollLeft||0)-(i&&i.clientLeft||o&&o.clientLeft||0),e.pageY=n.clientY+(i&&i.scrollTop||o&&o.scrollTop||0)-(i&&i.clientTop||o&&o.clientTop||0)),!e.relatedTarget&&s&&(e.relatedTarget=s===e.target?n.toElement:s),e.which||a===t||(e.which=1&a?1:2&a?3:4&a?2:0),e}},special:{load:{noBubble:!0},click:{trigger:function(){return st.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t}},focus:{trigger:function(){if(this!==V.activeElement&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===V.activeElement&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=st.extend(new st.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?st.event.trigger(i,null,t):st.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},st.removeEvent=V.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,n,r){var i="on"+n;e.detachEvent&&(e[i]===t&&(e[i]=null),e.detachEvent(i,r))},st.Event=function(e,n){return this instanceof st.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?u:l):this.type=e,n&&st.extend(this,n),this.timeStamp=e&&e.timeStamp||st.now(),this[st.expando]=!0,t):new st.Event(e,n)},st.Event.prototype={isDefaultPrevented:l,isPropagationStopped:l,isImmediatePropagationStopped:l,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=u,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=u,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u,this.stopPropagation()}},st.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){st.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!st.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),st.support.submitBubbles||(st.event.special.submit={setup:function(){return st.nodeName(this,"form")?!1:(st.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=st.nodeName(n,"input")||st.nodeName(n,"button")?n.form:t;r&&!st._data(r,"submitBubbles")&&(st.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),st._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&st.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return st.nodeName(this,"form")?!1:(st.event.remove(this,"._submit"),t)}}),st.support.changeBubbles||(st.event.special.change={setup:function(){return qt.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(st.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),st.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),st.event.simulate("change",this,e,!0)})),!1):(st.event.add(this,"beforeactivate._change",function(e){var t=e.target;qt.test(t.nodeName)&&!st._data(t,"changeBubbles")&&(st.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||st.event.simulate("change",this.parentNode,e,!0)}),st._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return st.event.remove(this,"._change"),!qt.test(this.nodeName)}}),st.support.focusinBubbles||st.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){st.event.simulate(t,e.target,st.event.fix(e),!0)};st.event.special[t]={setup:function(){0===n++&&V.addEventListener(e,r,!0)},teardown:function(){0===--n&&V.removeEventListener(e,r,!0)}}}),st.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(s in e)this.on(s,n,r,e[s],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=l;else if(!i)return this;return 1===o&&(a=i,i=function(e){return st().off(e),a.apply(this,arguments)},i.guid=a.guid||(a.guid=st.guid++)),this.each(function(){st.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,st(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=l),this.each(function(){st.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){st.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?st.event.trigger(e,n,r,!0):t},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),st.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){st.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)},_t.test(t)&&(st.event.fixHooks[t]=st.event.keyHooks),Ft.test(t)&&(st.event.fixHooks[t]=st.event.mouseHooks)}),function(e,t){function n(e){return ht.test(e+"")}function r(){var e,t=[];return e=function(n,r){return t.push(n+=" ")>C.cacheLength&&delete e[t.shift()],e[n]=r}}function i(e){return e[P]=!0,e}function o(e){var t=L.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}}function a(e,t,n,r){var i,o,a,s,u,l,c,d,h,g;if((t?t.ownerDocument||t:R)!==L&&D(t),t=t||L,n=n||[],!e||"string"!=typeof e)return n;if(1!==(s=t.nodeType)&&9!==s)return[];if(!M&&!r){if(i=gt.exec(e))if(a=i[1]){if(9===s){if(o=t.getElementById(a),!o||!o.parentNode)return n;if(o.id===a)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(a))&&O(t,o)&&o.id===a)return n.push(o),n}else{if(i[2])return Q.apply(n,K.call(t.getElementsByTagName(e),0)),n;if((a=i[3])&&W.getByClassName&&t.getElementsByClassName)return Q.apply(n,K.call(t.getElementsByClassName(a),0)),n}if(W.qsa&&!q.test(e)){if(c=!0,d=P,h=t,g=9===s&&e,1===s&&"object"!==t.nodeName.toLowerCase()){for(l=f(e),(c=t.getAttribute("id"))?d=c.replace(vt,"\\$&"):t.setAttribute("id",d),d="[id='"+d+"'] ",u=l.length;u--;)l[u]=d+p(l[u]);h=dt.test(e)&&t.parentNode||t,g=l.join(",")}if(g)try{return Q.apply(n,K.call(h.querySelectorAll(g),0)),n}catch(m){}finally{c||t.removeAttribute("id")}}}return x(e.replace(at,"$1"),t,n,r)}function s(e,t){for(var n=e&&t&&e.nextSibling;n;n=n.nextSibling)if(n===t)return-1;return e?1:-1}function u(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function l(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function c(e){return i(function(t){return t=+t,i(function(n,r){for(var i,o=e([],n.length,t),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function f(e,t){var n,r,i,o,s,u,l,c=X[e+" "];if(c)return t?0:c.slice(0);for(s=e,u=[],l=C.preFilter;s;){(!n||(r=ut.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),u.push(i=[])),n=!1,(r=lt.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(at," ")}),s=s.slice(n.length));for(o in C.filter)!(r=pt[o].exec(s))||l[o]&&!(r=l[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?a.error(e):X(e,u).slice(0)}function p(e){for(var t=0,n=e.length,r="";n>t;t++)r+=e[t].value;return r}function d(e,t,n){var r=t.dir,i=n&&"parentNode"===t.dir,o=I++;return t.first?function(t,n,o){for(;t=t[r];)if(1===t.nodeType||i)return e(t,n,o)}:function(t,n,a){var s,u,l,c=$+" "+o;if(a){for(;t=t[r];)if((1===t.nodeType||i)&&e(t,n,a))return!0}else for(;t=t[r];)if(1===t.nodeType||i)if(l=t[P]||(t[P]={}),(u=l[r])&&u[0]===c){if((s=u[1])===!0||s===N)return s===!0}else if(u=l[r]=[c],u[1]=e(t,n,a)||N,u[1]===!0)return!0}}function h(e){return e.length>1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1;return!0}:e[0]}function g(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;u>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),l&&t.push(s));return a}function m(e,t,n,r,o,a){return r&&!r[P]&&(r=m(r)),o&&!o[P]&&(o=m(o,a)),i(function(i,a,s,u){var l,c,f,p=[],d=[],h=a.length,m=i||b(t||"*",s.nodeType?[s]:s,[]),y=!e||!i&&t?m:g(m,p,e,s,u),v=n?o||(i?e:h||r)?[]:a:y;if(n&&n(y,v,s,u),r)for(l=g(v,d),r(l,[],s,u),c=l.length;c--;)(f=l[c])&&(v[d[c]]=!(y[d[c]]=f));if(i){if(o||e){if(o){for(l=[],c=v.length;c--;)(f=v[c])&&l.push(y[c]=f);o(null,v=[],l,u)}for(c=v.length;c--;)(f=v[c])&&(l=o?Z.call(i,f):p[c])>-1&&(i[l]=!(a[l]=f))}}else v=g(v===a?v.splice(h,v.length):v),o?o(null,a,v,u):Q.apply(a,v)})}function y(e){for(var t,n,r,i=e.length,o=C.relative[e[0].type],a=o||C.relative[" "],s=o?1:0,u=d(function(e){return e===t},a,!0),l=d(function(e){return Z.call(t,e)>-1},a,!0),c=[function(e,n,r){return!o&&(r||n!==j)||((t=n).nodeType?u(e,n,r):l(e,n,r))}];i>s;s++)if(n=C.relative[e[s].type])c=[d(h(c),n)];else{if(n=C.filter[e[s].type].apply(null,e[s].matches),n[P]){for(r=++s;i>r&&!C.relative[e[r].type];r++);return m(s>1&&h(c),s>1&&p(e.slice(0,s-1)).replace(at,"$1"),n,r>s&&y(e.slice(s,r)),i>r&&y(e=e.slice(r)),i>r&&p(e))}c.push(n)}return h(c)}function v(e,t){var n=0,r=t.length>0,o=e.length>0,s=function(i,s,u,l,c){var f,p,d,h=[],m=0,y="0",v=i&&[],b=null!=c,x=j,T=i||o&&C.find.TAG("*",c&&s.parentNode||s),w=$+=null==x?1:Math.E;for(b&&(j=s!==L&&s,N=n);null!=(f=T[y]);y++){if(o&&f){for(p=0;d=e[p];p++)if(d(f,s,u)){l.push(f);break}b&&($=w,N=++n)}r&&((f=!d&&f)&&m--,i&&v.push(f))}if(m+=y,r&&y!==m){for(p=0;d=t[p];p++)d(v,h,s,u);if(i){if(m>0)for(;y--;)v[y]||h[y]||(h[y]=G.call(l));h=g(h)}Q.apply(l,h),b&&!i&&h.length>0&&m+t.length>1&&a.uniqueSort(l)}return b&&($=w,j=x),v};return r?i(s):s}function b(e,t,n){for(var r=0,i=t.length;i>r;r++)a(e,t[r],n);return n}function x(e,t,n,r){var i,o,a,s,u,l=f(e);if(!r&&1===l.length){if(o=l[0]=l[0].slice(0),o.length>2&&"ID"===(a=o[0]).type&&9===t.nodeType&&!M&&C.relative[o[1].type]){if(t=C.find.ID(a.matches[0].replace(xt,Tt),t)[0],!t)return n;e=e.slice(o.shift().value.length)}for(i=pt.needsContext.test(e)?-1:o.length-1;i>=0&&(a=o[i],!C.relative[s=a.type]);i--)if((u=C.find[s])&&(r=u(a.matches[0].replace(xt,Tt),dt.test(o[0].type)&&t.parentNode||t))){if(o.splice(i,1),e=r.length&&p(o),!e)return Q.apply(n,K.call(r,0)),n;break}}return S(e,l)(r,t,M,n,dt.test(e)),n}function T(){}var w,N,C,k,E,S,A,j,D,L,H,M,q,_,F,O,B,P="sizzle"+-new Date,R=e.document,W={},$=0,I=0,z=r(),X=r(),U=r(),V=typeof t,Y=1<<31,J=[],G=J.pop,Q=J.push,K=J.slice,Z=J.indexOf||function(e){for(var t=0,n=this.length;n>t;t++)if(this[t]===e)return t;return-1},et="[\\x20\\t\\r\\n\\f]",tt="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",nt=tt.replace("w","w#"),rt="([*^$|!~]?=)",it="\\["+et+"*("+tt+")"+et+"*(?:"+rt+et+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+nt+")|)|)"+et+"*\\]",ot=":("+tt+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+it.replace(3,8)+")*)|.*)\\)|)",at=RegExp("^"+et+"+|((?:^|[^\\\\])(?:\\\\.)*)"+et+"+$","g"),ut=RegExp("^"+et+"*,"+et+"*"),lt=RegExp("^"+et+"*([\\x20\\t\\r\\n\\f>+~])"+et+"*"),ct=RegExp(ot),ft=RegExp("^"+nt+"$"),pt={ID:RegExp("^#("+tt+")"),CLASS:RegExp("^\\.("+tt+")"),NAME:RegExp("^\\[name=['\"]?("+tt+")['\"]?\\]"),TAG:RegExp("^("+tt.replace("w","w*")+")"),ATTR:RegExp("^"+it),PSEUDO:RegExp("^"+ot),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+et+"*(even|odd|(([+-]|)(\\d*)n|)"+et+"*(?:([+-]|)"+et+"*(\\d+)|))"+et+"*\\)|)","i"),needsContext:RegExp("^"+et+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+et+"*((?:-\\d)?\\d*)"+et+"*\\)|)(?=[^-]|$)","i")},dt=/[\x20\t\r\n\f]*[+~]/,ht=/\{\s*\[native code\]\s*\}/,gt=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,mt=/^(?:input|select|textarea|button)$/i,yt=/^h\d$/i,vt=/'|\\/g,bt=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,xt=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,Tt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{K.call(H.childNodes,0)[0].nodeType}catch(wt){K=function(e){for(var t,n=[];t=this[e];e++)n.push(t);return n}}E=a.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},D=a.setDocument=function(e){var r=e?e.ownerDocument||e:R;return r!==L&&9===r.nodeType&&r.documentElement?(L=r,H=r.documentElement,M=E(r),W.tagNameNoComments=o(function(e){return e.appendChild(r.createComment("")),!e.getElementsByTagName("*").length}),W.attributes=o(function(e){e.innerHTML="<select></select>";var t=typeof e.lastChild.getAttribute("multiple");return"boolean"!==t&&"string"!==t}),W.getByClassName=o(function(e){return e.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",e.getElementsByClassName&&e.getElementsByClassName("e").length?(e.lastChild.className="e",2===e.getElementsByClassName("e").length):!1}),W.getByName=o(function(e){e.id=P+0,e.innerHTML="<a name='"+P+"'></a><div name='"+P+"'></div>",H.insertBefore(e,H.firstChild);var t=r.getElementsByName&&r.getElementsByName(P).length===2+r.getElementsByName(P+0).length;return W.getIdNotName=!r.getElementById(P),H.removeChild(e),t}),C.attrHandle=o(function(e){return e.innerHTML="<a href='#'></a>",e.firstChild&&typeof e.firstChild.getAttribute!==V&&"#"===e.firstChild.getAttribute("href")})?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},W.getIdNotName?(C.find.ID=function(e,t){if(typeof t.getElementById!==V&&!M){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},C.filter.ID=function(e){var t=e.replace(xt,Tt);return function(e){return e.getAttribute("id")===t}}):(C.find.ID=function(e,n){if(typeof n.getElementById!==V&&!M){var r=n.getElementById(e);return r?r.id===e||typeof r.getAttributeNode!==V&&r.getAttributeNode("id").value===e?[r]:t:[]}},C.filter.ID=function(e){var t=e.replace(xt,Tt);return function(e){var n=typeof e.getAttributeNode!==V&&e.getAttributeNode("id");return n&&n.value===t}}),C.find.TAG=W.tagNameNoComments?function(e,n){return typeof n.getElementsByTagName!==V?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;n=o[i];i++)1===n.nodeType&&r.push(n);return r}return o},C.find.NAME=W.getByName&&function(e,n){return typeof n.getElementsByName!==V?n.getElementsByName(name):t},C.find.CLASS=W.getByClassName&&function(e,n){return typeof n.getElementsByClassName===V||M?t:n.getElementsByClassName(e)},_=[],q=[":focus"],(W.qsa=n(r.querySelectorAll))&&(o(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||q.push("\\["+et+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||q.push(":checked")}),o(function(e){e.innerHTML="<input type='hidden' i=''/>",e.querySelectorAll("[i^='']").length&&q.push("[*^$]="+et+"*(?:\"\"|'')"),e.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),q.push(",.*:")})),(W.matchesSelector=n(F=H.matchesSelector||H.mozMatchesSelector||H.webkitMatchesSelector||H.oMatchesSelector||H.msMatchesSelector))&&o(function(e){W.disconnectedMatch=F.call(e,"div"),F.call(e,"[s!='']:x"),_.push("!=",ot)}),q=RegExp(q.join("|")),_=RegExp(_.join("|")),O=n(H.contains)||H.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},B=H.compareDocumentPosition?function(e,t){var n;return e===t?(A=!0,0):(n=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t))?1&n||e.parentNode&&11===e.parentNode.nodeType?e===r||O(R,e)?-1:t===r||O(R,t)?1:0:4&n?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var n,i=0,o=e.parentNode,a=t.parentNode,u=[e],l=[t];if(e===t)return A=!0,0;if(e.sourceIndex&&t.sourceIndex)return(~t.sourceIndex||Y)-(O(R,e)&&~e.sourceIndex||Y);if(!o||!a)return e===r?-1:t===r?1:o?-1:a?1:0;if(o===a)return s(e,t);for(n=e;n=n.parentNode;)u.unshift(n);for(n=t;n=n.parentNode;)l.unshift(n);for(;u[i]===l[i];)i++;return i?s(u[i],l[i]):u[i]===R?-1:l[i]===R?1:0},A=!1,[0,0].sort(B),W.detectDuplicates=A,L):L},a.matches=function(e,t){return a(e,null,null,t)},a.matchesSelector=function(e,t){if((e.ownerDocument||e)!==L&&D(e),t=t.replace(bt,"='$1']"),!(!W.matchesSelector||M||_&&_.test(t)||q.test(t)))try{var n=F.call(e,t);if(n||W.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return a(t,L,null,[e]).length>0},a.contains=function(e,t){return(e.ownerDocument||e)!==L&&D(e),O(e,t)},a.attr=function(e,t){var n;return(e.ownerDocument||e)!==L&&D(e),M||(t=t.toLowerCase()),(n=C.attrHandle[t])?n(e):M||W.attributes?e.getAttribute(t):((n=e.getAttributeNode(t))||e.getAttribute(t))&&e[t]===!0?t:n&&n.specified?n.value:null},a.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},a.uniqueSort=function(e){var t,n=[],r=1,i=0;if(A=!W.detectDuplicates,e.sort(B),A){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));for(;i--;)e.splice(n[i],1)}return e},k=a.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=k(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=k(t);return n},C=a.selectors={cacheLength:50,createPseudo:i,match:pt,find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(xt,Tt),e[3]=(e[4]||e[5]||"").replace(xt,Tt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||a.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&a.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return pt.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&ct.test(n)&&(t=f(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){return"*"===e?function(){return!0}:(e=e.replace(xt,Tt).toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=z[e+" "];return t||(t=RegExp("(^|"+et+")"+e+"("+et+"|$)"))&&z(e,function(e){return t.test(e.className||typeof e.getAttribute!==V&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=a.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.substr(i.length-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s;if(m){if(o){for(;g;){for(f=t;f=f[g];)if(s?f.nodeName.toLowerCase()===y:1===f.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){for(c=m[P]||(m[P]={}),l=c[e]||[],d=l[0]===$&&l[1],p=l[0]===$&&l[2],f=d&&m.childNodes[d];f=++d&&f&&f[g]||(p=d=0)||h.pop();)if(1===f.nodeType&&++p&&f===t){c[e]=[$,d,p];break}}else if(v&&(l=(t[P]||(t[P]={}))[e])&&l[0]===$)p=l[1];else for(;(f=++d&&f&&f[g]||(p=d=0)||h.pop())&&((s?f.nodeName.toLowerCase()!==y:1!==f.nodeType)||!++p||(v&&((f[P]||(f[P]={}))[e]=[$,p]),f!==t)););return p-=i,p===r||0===p%r&&p/r>=0}}},PSEUDO:function(e,t){var n,r=C.pseudos[e]||C.setFilters[e.toLowerCase()]||a.error("unsupported pseudo: "+e);return r[P]?r(t):r.length>1?(n=[e,e,"",t],C.setFilters.hasOwnProperty(e.toLowerCase())?i(function(e,n){for(var i,o=r(e,t),a=o.length;a--;)i=Z.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:i(function(e){var t=[],n=[],r=S(e.replace(at,"$1"));return r[P]?i(function(e,t,n,i){for(var o,a=r(e,null,i,[]),s=e.length;s--;)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:i(function(e){return function(t){return a(e,t).length>0}}),contains:i(function(e){return function(t){return(t.textContent||t.innerText||k(t)).indexOf(e)>-1}}),lang:i(function(e){return ft.test(e||"")||a.error("unsupported lang: "+e),e=e.replace(xt,Tt).toLowerCase(),function(t){var n;do if(n=M?t.getAttribute("xml:lang")||t.getAttribute("lang"):t.lang)return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===H},focus:function(e){return e===L.activeElement&&(!L.hasFocus||L.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!C.pseudos.empty(e)},header:function(e){return yt.test(e.nodeName)},input:function(e){return mt.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:c(function(){return[0]}),last:c(function(e,t){return[t-1]}),eq:c(function(e,t,n){return[0>n?n+t:n]}),even:c(function(e,t){for(var n=0;t>n;n+=2)e.push(n);return e}),odd:c(function(e,t){for(var n=1;t>n;n+=2)e.push(n);return e}),lt:c(function(e,t,n){for(var r=0>n?n+t:n;--r>=0;)e.push(r);return e}),gt:c(function(e,t,n){for(var r=0>n?n+t:n;t>++r;)e.push(r);return e})}};for(w in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})C.pseudos[w]=u(w);for(w in{submit:!0,reset:!0})C.pseudos[w]=l(w);S=a.compile=function(e,t){var n,r=[],i=[],o=U[e+" "];if(!o){for(t||(t=f(e)),n=t.length;n--;)o=y(t[n]),o[P]?r.push(o):i.push(o);o=U(e,v(i,r))}return o},C.pseudos.nth=C.pseudos.eq,C.filters=T.prototype=C.pseudos,C.setFilters=new T,D(),a.attr=st.attr,st.find=a,st.expr=a.selectors,st.expr[":"]=st.expr.pseudos,st.unique=a.uniqueSort,st.text=a.getText,st.isXMLDoc=a.isXML,st.contains=a.contains}(e);var Pt=/Until$/,Rt=/^(?:parents|prev(?:Until|All))/,Wt=/^.[^:#\[\.,]*$/,$t=st.expr.match.needsContext,It={children:!0,contents:!0,next:!0,prev:!0};st.fn.extend({find:function(e){var t,n,r;if("string"!=typeof e)return r=this,this.pushStack(st(e).filter(function(){for(t=0;r.length>t;t++)if(st.contains(r[t],this))return!0}));for(n=[],t=0;this.length>t;t++)st.find(e,this[t],n);return n=this.pushStack(st.unique(n)),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t,n=st(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(st.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(f(this,e,!1))},filter:function(e){return this.pushStack(f(this,e,!0))},is:function(e){return!!e&&("string"==typeof e?$t.test(e)?st(e,this.context).index(this[0])>=0:st.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){for(var n,r=0,i=this.length,o=[],a=$t.test(e)||"string"!=typeof e?st(e,t||this.context):0;i>r;r++)for(n=this[r];n&&n.ownerDocument&&n!==t&&11!==n.nodeType;){if(a?a.index(n)>-1:st.find.matchesSelector(n,e)){o.push(n);break}n=n.parentNode}return this.pushStack(o.length>1?st.unique(o):o)},index:function(e){return e?"string"==typeof e?st.inArray(this[0],st(e)):st.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?st(e,t):st.makeArray(e&&e.nodeType?[e]:e),r=st.merge(this.get(),n);return this.pushStack(st.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),st.fn.andSelf=st.fn.addBack,st.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return st.dir(e,"parentNode")},parentsUntil:function(e,t,n){return st.dir(e,"parentNode",n)},next:function(e){return c(e,"nextSibling")},prev:function(e){return c(e,"previousSibling")
},nextAll:function(e){return st.dir(e,"nextSibling")},prevAll:function(e){return st.dir(e,"previousSibling")},nextUntil:function(e,t,n){return st.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return st.dir(e,"previousSibling",n)},siblings:function(e){return st.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return st.sibling(e.firstChild)},contents:function(e){return st.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:st.merge([],e.childNodes)}},function(e,t){st.fn[e]=function(n,r){var i=st.map(this,t,n);return Pt.test(e)||(r=n),r&&"string"==typeof r&&(i=st.filter(r,i)),i=this.length>1&&!It[e]?st.unique(i):i,this.length>1&&Rt.test(e)&&(i=i.reverse()),this.pushStack(i)}}),st.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),1===t.length?st.find.matchesSelector(t[0],e)?[t[0]]:[]:st.find.matches(e,t)},dir:function(e,n,r){for(var i=[],o=e[n];o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!st(o).is(r));)1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});var zt="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",Xt=/ jQuery\d+="(?:null|\d+)"/g,Ut=RegExp("<(?:"+zt+")[\\s/>]","i"),Vt=/^\s+/,Yt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,Jt=/<([\w:]+)/,Gt=/<tbody/i,Qt=/<|&#?\w+;/,Kt=/<(?:script|style|link)/i,Zt=/^(?:checkbox|radio)$/i,en=/checked\s*(?:[^=]|=\s*.checked.)/i,tn=/^$|\/(?:java|ecma)script/i,nn=/^true\/(.*)/,rn=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,on={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:st.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},an=p(V),sn=an.appendChild(V.createElement("div"));on.optgroup=on.option,on.tbody=on.tfoot=on.colgroup=on.caption=on.thead,on.th=on.td,st.fn.extend({text:function(e){return st.access(this,function(e){return e===t?st.text(this):this.empty().append((this[0]&&this[0].ownerDocument||V).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(st.isFunction(e))return this.each(function(t){st(this).wrapAll(e.call(this,t))});if(this[0]){var t=st(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstChild&&1===e.firstChild.nodeType;)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return st.isFunction(e)?this.each(function(t){st(this).wrapInner(e.call(this,t))}):this.each(function(){var t=st(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=st.isFunction(e);return this.each(function(n){st(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){st.nodeName(this,"body")||st(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.insertBefore(e,this.firstChild)})},before:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){for(var n,r=0;null!=(n=this[r]);r++)(!e||st.filter(e,[n]).length>0)&&(t||1!==n.nodeType||st.cleanData(b(n)),n.parentNode&&(t&&st.contains(n.ownerDocument,n)&&m(b(n,"script")),n.parentNode.removeChild(n)));return this},empty:function(){for(var e,t=0;null!=(e=this[t]);t++){for(1===e.nodeType&&st.cleanData(b(e,!1));e.firstChild;)e.removeChild(e.firstChild);e.options&&st.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return st.clone(this,e,t)})},html:function(e){return st.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(Xt,""):t;if(!("string"!=typeof e||Kt.test(e)||!st.support.htmlSerialize&&Ut.test(e)||!st.support.leadingWhitespace&&Vt.test(e)||on[(Jt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(Yt,"<$1></$2>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(st.cleanData(b(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){var t=st.isFunction(e);return t||"string"==typeof e||(e=st(e).not(this).detach()),this.domManip([e],!0,function(e){var t=this.nextSibling,n=this.parentNode;(n&&1===this.nodeType||11===this.nodeType)&&(st(this).remove(),t?t.parentNode.insertBefore(e,t):n.appendChild(e))})},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=et.apply([],e);var i,o,a,s,u,l,c=0,f=this.length,p=this,m=f-1,y=e[0],v=st.isFunction(y);if(v||!(1>=f||"string"!=typeof y||st.support.checkClone)&&en.test(y))return this.each(function(i){var o=p.eq(i);v&&(e[0]=y.call(this,i,n?o.html():t)),o.domManip(e,n,r)});if(f&&(i=st.buildFragment(e,this[0].ownerDocument,!1,this),o=i.firstChild,1===i.childNodes.length&&(i=o),o)){for(n=n&&st.nodeName(o,"tr"),a=st.map(b(i,"script"),h),s=a.length;f>c;c++)u=i,c!==m&&(u=st.clone(u,!0,!0),s&&st.merge(a,b(u,"script"))),r.call(n&&st.nodeName(this[c],"table")?d(this[c],"tbody"):this[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,st.map(a,g),c=0;s>c;c++)u=a[c],tn.test(u.type||"")&&!st._data(u,"globalEval")&&st.contains(l,u)&&(u.src?st.ajax({url:u.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):st.globalEval((u.text||u.textContent||u.innerHTML||"").replace(rn,"")));i=o=null}return this}}),st.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){st.fn[e]=function(e){for(var n,r=0,i=[],o=st(e),a=o.length-1;a>=r;r++)n=r===a?this:this.clone(!0),st(o[r])[t](n),tt.apply(i,n.get());return this.pushStack(i)}}),st.extend({clone:function(e,t,n){var r,i,o,a,s,u=st.contains(e.ownerDocument,e);if(st.support.html5Clone||st.isXMLDoc(e)||!Ut.test("<"+e.nodeName+">")?s=e.cloneNode(!0):(sn.innerHTML=e.outerHTML,sn.removeChild(s=sn.firstChild)),!(st.support.noCloneEvent&&st.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||st.isXMLDoc(e)))for(r=b(s),i=b(e),a=0;null!=(o=i[a]);++a)r[a]&&v(o,r[a]);if(t)if(n)for(i=i||b(e),r=r||b(s),a=0;null!=(o=i[a]);a++)y(o,r[a]);else y(e,s);return r=b(s,"script"),r.length>0&&m(r,!u&&b(e,"script")),r=i=o=null,s},buildFragment:function(e,t,n,r){for(var i,o,a,s,u,l,c,f=e.length,d=p(t),h=[],g=0;f>g;g++)if(o=e[g],o||0===o)if("object"===st.type(o))st.merge(h,o.nodeType?[o]:o);else if(Qt.test(o)){for(s=s||d.appendChild(t.createElement("div")),a=(Jt.exec(o)||["",""])[1].toLowerCase(),u=on[a]||on._default,s.innerHTML=u[1]+o.replace(Yt,"<$1></$2>")+u[2],c=u[0];c--;)s=s.lastChild;if(!st.support.leadingWhitespace&&Vt.test(o)&&h.push(t.createTextNode(Vt.exec(o)[0])),!st.support.tbody)for(o="table"!==a||Gt.test(o)?"<table>"!==u[1]||Gt.test(o)?0:s:s.firstChild,c=o&&o.childNodes.length;c--;)st.nodeName(l=o.childNodes[c],"tbody")&&!l.childNodes.length&&o.removeChild(l);for(st.merge(h,s.childNodes),s.textContent="";s.firstChild;)s.removeChild(s.firstChild);s=d.lastChild}else h.push(t.createTextNode(o));for(s&&d.removeChild(s),st.support.appendChecked||st.grep(b(h,"input"),x),g=0;o=h[g++];)if((!r||-1===st.inArray(o,r))&&(i=st.contains(o.ownerDocument,o),s=b(d.appendChild(o),"script"),i&&m(s),n))for(c=0;o=s[c++];)tn.test(o.type||"")&&n.push(o);return s=null,d},cleanData:function(e,n){for(var r,i,o,a,s=0,u=st.expando,l=st.cache,c=st.support.deleteExpando,f=st.event.special;null!=(o=e[s]);s++)if((n||st.acceptData(o))&&(i=o[u],r=i&&l[i])){if(r.events)for(a in r.events)f[a]?st.event.remove(o,a):st.removeEvent(o,a,r.handle);l[i]&&(delete l[i],c?delete o[u]:o.removeAttribute!==t?o.removeAttribute(u):o[u]=null,K.push(i))}}});var un,ln,cn,fn=/alpha\([^)]*\)/i,pn=/opacity\s*=\s*([^)]*)/,dn=/^(top|right|bottom|left)$/,hn=/^(none|table(?!-c[ea]).+)/,gn=/^margin/,mn=RegExp("^("+ut+")(.*)$","i"),yn=RegExp("^("+ut+")(?!px)[a-z%]+$","i"),vn=RegExp("^([+-])=("+ut+")","i"),bn={BODY:"block"},xn={position:"absolute",visibility:"hidden",display:"block"},Tn={letterSpacing:0,fontWeight:400},wn=["Top","Right","Bottom","Left"],Nn=["Webkit","O","Moz","ms"];st.fn.extend({css:function(e,n){return st.access(this,function(e,n,r){var i,o,a={},s=0;if(st.isArray(n)){for(i=ln(e),o=n.length;o>s;s++)a[n[s]]=st.css(e,n[s],!1,i);return a}return r!==t?st.style(e,n,r):st.css(e,n)},e,n,arguments.length>1)},show:function(){return N(this,!0)},hide:function(){return N(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:w(this))?st(this).show():st(this).hide()})}}),st.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=un(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":st.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,u=st.camelCase(n),l=e.style;if(n=st.cssProps[u]||(st.cssProps[u]=T(l,u)),s=st.cssHooks[n]||st.cssHooks[u],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:l[n];if(a=typeof r,"string"===a&&(o=vn.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(st.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||st.cssNumber[u]||(r+="px"),st.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(l[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{l[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,u=st.camelCase(n);return n=st.cssProps[u]||(st.cssProps[u]=T(e.style,u)),s=st.cssHooks[n]||st.cssHooks[u],s&&"get"in s&&(o=s.get(e,!0,r)),o===t&&(o=un(e,n,i)),"normal"===o&&n in Tn&&(o=Tn[n]),r?(a=parseFloat(o),r===!0||st.isNumeric(a)?a||0:o):o},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),e.getComputedStyle?(ln=function(t){return e.getComputedStyle(t,null)},un=function(e,n,r){var i,o,a,s=r||ln(e),u=s?s.getPropertyValue(n)||s[n]:t,l=e.style;return s&&(""!==u||st.contains(e.ownerDocument,e)||(u=st.style(e,n)),yn.test(u)&&gn.test(n)&&(i=l.width,o=l.minWidth,a=l.maxWidth,l.minWidth=l.maxWidth=l.width=u,u=s.width,l.width=i,l.minWidth=o,l.maxWidth=a)),u}):V.documentElement.currentStyle&&(ln=function(e){return e.currentStyle},un=function(e,n,r){var i,o,a,s=r||ln(e),u=s?s[n]:t,l=e.style;return null==u&&l&&l[n]&&(u=l[n]),yn.test(u)&&!dn.test(n)&&(i=l.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),l.left="fontSize"===n?"1em":u,u=l.pixelLeft+"px",l.left=i,a&&(o.left=a)),""===u?"auto":u}),st.each(["height","width"],function(e,n){st.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&hn.test(st.css(e,"display"))?st.swap(e,xn,function(){return E(e,n,i)}):E(e,n,i):t},set:function(e,t,r){var i=r&&ln(e);return C(e,t,r?k(e,n,r,st.support.boxSizing&&"border-box"===st.css(e,"boxSizing",!1,i),i):0)}}}),st.support.opacity||(st.cssHooks.opacity={get:function(e,t){return pn.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=st.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===st.trim(o.replace(fn,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=fn.test(o)?o.replace(fn,i):o+" "+i)}}),st(function(){st.support.reliableMarginRight||(st.cssHooks.marginRight={get:function(e,n){return n?st.swap(e,{display:"inline-block"},un,[e,"marginRight"]):t}}),!st.support.pixelPosition&&st.fn.position&&st.each(["top","left"],function(e,n){st.cssHooks[n]={get:function(e,r){return r?(r=un(e,n),yn.test(r)?st(e).position()[n]+"px":r):t}}})}),st.expr&&st.expr.filters&&(st.expr.filters.hidden=function(e){return 0===e.offsetWidth&&0===e.offsetHeight||!st.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||st.css(e,"display"))},st.expr.filters.visible=function(e){return!st.expr.filters.hidden(e)}),st.each({margin:"",padding:"",border:"Width"},function(e,t){st.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];4>r;r++)i[e+wn[r]+t]=o[r]||o[r-2]||o[0];return i}},gn.test(e)||(st.cssHooks[e+t].set=C)});var Cn=/%20/g,kn=/\[\]$/,En=/\r?\n/g,Sn=/^(?:submit|button|image|reset)$/i,An=/^(?:input|select|textarea|keygen)/i;st.fn.extend({serialize:function(){return st.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=st.prop(this,"elements");return e?st.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!st(this).is(":disabled")&&An.test(this.nodeName)&&!Sn.test(e)&&(this.checked||!Zt.test(e))}).map(function(e,t){var n=st(this).val();return null==n?null:st.isArray(n)?st.map(n,function(e){return{name:t.name,value:e.replace(En,"\r\n")}}):{name:t.name,value:n.replace(En,"\r\n")}}).get()}}),st.param=function(e,n){var r,i=[],o=function(e,t){t=st.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=st.ajaxSettings&&st.ajaxSettings.traditional),st.isArray(e)||e.jquery&&!st.isPlainObject(e))st.each(e,function(){o(this.name,this.value)});else for(r in e)j(r,e[r],n,o);return i.join("&").replace(Cn,"+")};var jn,Dn,Ln=st.now(),Hn=/\?/,Mn=/#.*$/,qn=/([?&])_=[^&]*/,_n=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Fn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,On=/^(?:GET|HEAD)$/,Bn=/^\/\//,Pn=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Rn=st.fn.load,Wn={},$n={},In="*/".concat("*");try{Dn=Y.href}catch(zn){Dn=V.createElement("a"),Dn.href="",Dn=Dn.href}jn=Pn.exec(Dn.toLowerCase())||[],st.fn.load=function(e,n,r){if("string"!=typeof e&&Rn)return Rn.apply(this,arguments);var i,o,a,s=this,u=e.indexOf(" ");return u>=0&&(i=e.slice(u,e.length),e=e.slice(0,u)),st.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(o="POST"),s.length>0&&st.ajax({url:e,type:o,dataType:"html",data:n}).done(function(e){a=arguments,s.html(i?st("<div>").append(st.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,a||[e.responseText,t,e])}),this},st.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){st.fn[t]=function(e){return this.on(t,e)}}),st.each(["get","post"],function(e,n){st[n]=function(e,r,i,o){return st.isFunction(r)&&(o=o||i,i=r,r=t),st.ajax({url:e,type:n,dataType:o,data:r,success:i})}}),st.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Dn,type:"GET",isLocal:Fn.test(jn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":In,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":st.parseJSON,"text xml":st.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?H(H(e,st.ajaxSettings),t):H(st.ajaxSettings,e)},ajaxPrefilter:D(Wn),ajaxTransport:D($n),ajax:function(e,n){function r(e,n,r,s){var l,f,v,b,T,N=n;2!==x&&(x=2,u&&clearTimeout(u),i=t,a=s||"",w.readyState=e>0?4:0,r&&(b=M(p,w,r)),e>=200&&300>e||304===e?(p.ifModified&&(T=w.getResponseHeader("Last-Modified"),T&&(st.lastModified[o]=T),T=w.getResponseHeader("etag"),T&&(st.etag[o]=T)),304===e?(l=!0,N="notmodified"):(l=q(p,b),N=l.state,f=l.data,v=l.error,l=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),w.status=e,w.statusText=(n||N)+"",l?g.resolveWith(d,[f,N,w]):g.rejectWith(d,[w,N,v]),w.statusCode(y),y=t,c&&h.trigger(l?"ajaxSuccess":"ajaxError",[w,p,l?f:v]),m.fireWith(d,[w,N]),c&&(h.trigger("ajaxComplete",[w,p]),--st.active||st.event.trigger("ajaxStop")))}"object"==typeof e&&(n=e,e=t),n=n||{};var i,o,a,s,u,l,c,f,p=st.ajaxSetup({},n),d=p.context||p,h=p.context&&(d.nodeType||d.jquery)?st(d):st.event,g=st.Deferred(),m=st.Callbacks("once memory"),y=p.statusCode||{},v={},b={},x=0,T="canceled",w={readyState:0,getResponseHeader:function(e){var t;if(2===x){if(!s)for(s={};t=_n.exec(a);)s[t[1].toLowerCase()]=t[2];t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===x?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return x||(e=b[n]=b[n]||e,v[e]=t),this},overrideMimeType:function(e){return x||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>x)for(t in e)y[t]=[y[t],e[t]];else w.always(e[w.status]);return this},abort:function(e){var t=e||T;return i&&i.abort(t),r(0,t),this}};if(g.promise(w).complete=m.add,w.success=w.done,w.error=w.fail,p.url=((e||p.url||Dn)+"").replace(Mn,"").replace(Bn,jn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=st.trim(p.dataType||"*").toLowerCase().match(lt)||[""],null==p.crossDomain&&(l=Pn.exec(p.url.toLowerCase()),p.crossDomain=!(!l||l[1]===jn[1]&&l[2]===jn[2]&&(l[3]||("http:"===l[1]?80:443))==(jn[3]||("http:"===jn[1]?80:443)))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=st.param(p.data,p.traditional)),L(Wn,p,n,w),2===x)return w;c=p.global,c&&0===st.active++&&st.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!On.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(Hn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=qn.test(o)?o.replace(qn,"$1_="+Ln++):o+(Hn.test(o)?"&":"?")+"_="+Ln++)),p.ifModified&&(st.lastModified[o]&&w.setRequestHeader("If-Modified-Since",st.lastModified[o]),st.etag[o]&&w.setRequestHeader("If-None-Match",st.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&w.setRequestHeader("Content-Type",p.contentType),w.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+In+"; q=0.01":""):p.accepts["*"]);for(f in p.headers)w.setRequestHeader(f,p.headers[f]);if(p.beforeSend&&(p.beforeSend.call(d,w,p)===!1||2===x))return w.abort();T="abort";for(f in{success:1,error:1,complete:1})w[f](p[f]);if(i=L($n,p,n,w)){w.readyState=1,c&&h.trigger("ajaxSend",[w,p]),p.async&&p.timeout>0&&(u=setTimeout(function(){w.abort("timeout")},p.timeout));try{x=1,i.send(v,r)}catch(N){if(!(2>x))throw N;r(-1,N)}}else r(-1,"No Transport");return w},getScript:function(e,n){return st.get(e,t,n,"script")},getJSON:function(e,t,n){return st.get(e,t,n,"json")}}),st.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return st.globalEval(e),e}}}),st.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),st.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=V.head||st("head")[0]||V.documentElement;return{send:function(t,i){n=V.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Xn=[],Un=/(=)\?(?=&|$)|\?\?/;st.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xn.pop()||st.expando+"_"+Ln++;return this[e]=!0,e}}),st.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,u=n.jsonp!==!1&&(Un.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Un.test(n.data)&&"data");return u||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=st.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,u?n[u]=n[u].replace(Un,"$1"+o):n.jsonp!==!1&&(n.url+=(Hn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||st.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Xn.push(o)),s&&st.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Vn,Yn,Jn=0,Gn=e.ActiveXObject&&function(){var e;for(e in Vn)Vn[e](t,!0)};st.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&_()||F()}:_,Yn=st.ajaxSettings.xhr(),st.support.cors=!!Yn&&"withCredentials"in Yn,Yn=st.support.ajax=!!Yn,Yn&&st.ajaxTransport(function(n){if(!n.crossDomain||st.support.cors){var r;return{send:function(i,o){var a,s,u=n.xhr();if(n.username?u.open(n.type,n.url,n.async,n.username,n.password):u.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)u[s]=n.xhrFields[s];n.mimeType&&u.overrideMimeType&&u.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)u.setRequestHeader(s,i[s])}catch(l){}u.send(n.hasContent&&n.data||null),r=function(e,i){var s,l,c,f,p;try{if(r&&(i||4===u.readyState))if(r=t,a&&(u.onreadystatechange=st.noop,Gn&&delete Vn[a]),i)4!==u.readyState&&u.abort();else{f={},s=u.status,p=u.responseXML,c=u.getAllResponseHeaders(),p&&p.documentElement&&(f.xml=p),"string"==typeof u.responseText&&(f.text=u.responseText);try{l=u.statusText}catch(d){l=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=f.text?200:404}}catch(h){i||o(-1,h)}f&&o(s,l,f,c)},n.async?4===u.readyState?setTimeout(r):(a=++Jn,Gn&&(Vn||(Vn={},st(e).unload(Gn)),Vn[a]=r),u.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Qn,Kn,Zn=/^(?:toggle|show|hide)$/,er=RegExp("^(?:([+-])=|)("+ut+")([a-z%]*)$","i"),tr=/queueHooks$/,nr=[W],rr={"*":[function(e,t){var n,r,i=this.createTween(e,t),o=er.exec(t),a=i.cur(),s=+a||0,u=1,l=20;if(o){if(n=+o[2],r=o[3]||(st.cssNumber[e]?"":"px"),"px"!==r&&s){s=st.css(i.elem,e,!0)||n||1;do u=u||".5",s/=u,st.style(i.elem,e,s+r);while(u!==(u=i.cur()/a)&&1!==u&&--l)}i.unit=r,i.start=s,i.end=o[1]?s+(o[1]+1)*n:n}return i}]};st.Animation=st.extend(P,{tweener:function(e,t){st.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");for(var n,r=0,i=e.length;i>r;r++)n=e[r],rr[n]=rr[n]||[],rr[n].unshift(t)},prefilter:function(e,t){t?nr.unshift(e):nr.push(e)}}),st.Tween=$,$.prototype={constructor:$,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(st.cssNumber[n]?"":"px")},cur:function(){var e=$.propHooks[this.prop];return e&&e.get?e.get(this):$.propHooks._default.get(this)},run:function(e){var t,n=$.propHooks[this.prop];return this.pos=t=this.options.duration?st.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):$.propHooks._default.set(this),this}},$.prototype.init.prototype=$.prototype,$.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=st.css(e.elem,e.prop,"auto"),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){st.fx.step[e.prop]?st.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[st.cssProps[e.prop]]||st.cssHooks[e.prop])?st.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},$.propHooks.scrollTop=$.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},st.each(["toggle","show","hide"],function(e,t){var n=st.fn[t];st.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(I(t,!0),e,r,i)}}),st.fn.extend({fadeTo:function(e,t,n,r){return this.filter(w).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=st.isEmptyObject(e),o=st.speed(t,n,r),a=function(){var t=P(this,st.extend({},e),o);a.finish=function(){t.stop(!0)},(i||st._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=st.timers,a=st._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&tr.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&st.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=st._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=st.timers,a=r?r.length:0;for(n.finish=!0,st.queue(this,e,[]),i&&i.cur&&i.cur.finish&&i.cur.finish.call(this),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}}),st.each({slideDown:I("show"),slideUp:I("hide"),slideToggle:I("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){st.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),st.speed=function(e,t,n){var r=e&&"object"==typeof e?st.extend({},e):{complete:n||!n&&t||st.isFunction(e)&&e,duration:e,easing:n&&t||t&&!st.isFunction(t)&&t};return r.duration=st.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in st.fx.speeds?st.fx.speeds[r.duration]:st.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){st.isFunction(r.old)&&r.old.call(this),r.queue&&st.dequeue(this,r.queue)},r},st.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},st.timers=[],st.fx=$.prototype.init,st.fx.tick=function(){var e,n=st.timers,r=0;for(Qn=st.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||st.fx.stop(),Qn=t},st.fx.timer=function(e){e()&&st.timers.push(e)&&st.fx.start()},st.fx.interval=13,st.fx.start=function(){Kn||(Kn=setInterval(st.fx.tick,st.fx.interval))},st.fx.stop=function(){clearInterval(Kn),Kn=null},st.fx.speeds={slow:600,fast:200,_default:400},st.fx.step={},st.expr&&st.expr.filters&&(st.expr.filters.animated=function(e){return st.grep(st.timers,function(t){return e===t.elem}).length}),st.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){st.offset.setOffset(this,e,t)});var n,r,i={top:0,left:0},o=this[0],a=o&&o.ownerDocument;if(a)return n=a.documentElement,st.contains(n,o)?(o.getBoundingClientRect!==t&&(i=o.getBoundingClientRect()),r=z(a),{top:i.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:i.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):i},st.offset={setOffset:function(e,t,n){var r=st.css(e,"position");"static"===r&&(e.style.position="relative");var i,o,a=st(e),s=a.offset(),u=st.css(e,"top"),l=st.css(e,"left"),c=("absolute"===r||"fixed"===r)&&st.inArray("auto",[u,l])>-1,f={},p={};c?(p=a.position(),i=p.top,o=p.left):(i=parseFloat(u)||0,o=parseFloat(l)||0),st.isFunction(t)&&(t=t.call(e,n,s)),null!=t.top&&(f.top=t.top-s.top+i),null!=t.left&&(f.left=t.left-s.left+o),"using"in t?t.using.call(e,f):a.css(f)}},st.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===st.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),st.nodeName(e[0],"html")||(n=e.offset()),n.top+=st.css(e[0],"borderTopWidth",!0),n.left+=st.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-st.css(r,"marginTop",!0),left:t.left-n.left-st.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var e=this.offsetParent||V.documentElement;e&&!st.nodeName(e,"html")&&"static"===st.css(e,"position");)e=e.offsetParent;return e||V.documentElement})}}),st.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);st.fn[e]=function(i){return st.access(this,function(e,i,o){var a=z(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?st(a).scrollLeft():o,r?o:st(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}}),st.each({Height:"height",Width:"width"},function(e,n){st.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){st.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return st.access(this,function(n,r,i){var o;return st.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?st.css(n,r,s):st.style(n,r,i,s)},n,a?i:t,a,null)}})}),e.jQuery=e.$=st,"function"==typeof define&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return st})})(window);
//@ sourceMappingURL=jquery.min.map
\ No newline at end of file
/*! jQuery JSON plugin 2.4.0 | code.google.com/p/jquery-json */
(function($){'use strict';var escape=/["\\\x00-\x1f\x7f-\x9f]/g,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},hasOwn=Object.prototype.hasOwnProperty;$.toJSON=typeof JSON==='object'&&JSON.stringify?JSON.stringify:function(o){if(o===null){return'null';}
var pairs,k,name,val,type=$.type(o);if(type==='undefined'){return undefined;}
if(type==='number'||type==='boolean'){return String(o);}
if(type==='string'){return $.quoteString(o);}
if(typeof o.toJSON==='function'){return $.toJSON(o.toJSON());}
if(type==='date'){var month=o.getUTCMonth()+1,day=o.getUTCDate(),year=o.getUTCFullYear(),hours=o.getUTCHours(),minutes=o.getUTCMinutes(),seconds=o.getUTCSeconds(),milli=o.getUTCMilliseconds();if(month<10){month='0'+month;}
if(day<10){day='0'+day;}
if(hours<10){hours='0'+hours;}
if(minutes<10){minutes='0'+minutes;}
if(seconds<10){seconds='0'+seconds;}
if(milli<100){milli='0'+milli;}
if(milli<10){milli='0'+milli;}
return'"'+year+'-'+month+'-'+day+'T'+
hours+':'+minutes+':'+seconds+'.'+milli+'Z"';}
pairs=[];if($.isArray(o)){for(k=0;k<o.length;k++){pairs.push($.toJSON(o[k])||'null');}
return'['+pairs.join(',')+']';}
if(typeof o==='object'){for(k in o){if(hasOwn.call(o,k)){type=typeof k;if(type==='number'){name='"'+k+'"';}else if(type==='string'){name=$.quoteString(k);}else{continue;}
type=typeof o[k];if(type!=='function'&&type!=='undefined'){val=$.toJSON(o[k]);pairs.push(name+':'+val);}}}
return'{'+pairs.join(',')+'}';}};$.evalJSON=typeof JSON==='object'&&JSON.parse?JSON.parse:function(str){return eval('('+str+')');};$.secureEvalJSON=typeof JSON==='object'&&JSON.parse?JSON.parse:function(str){var filtered=str.replace(/\\["\\\/bfnrtu]/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,'');if(/^[\],:{}\s]*$/.test(filtered)){return eval('('+str+')');}
throw new SyntaxError('Error parsing JSON, source is not valid.');};$.quoteString=function(str){if(str.match(escape)){return'"'+str.replace(escape,function(a){var c=meta[a];if(typeof c==='string'){return c;}
c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+(c%16).toString(16);})+'"';}
return'"'+str+'"';};}(jQuery));
\ No newline at end of file
/**
* QUnit v1.11.0 - A JavaScript Unit Testing Framework
*
* http://qunitjs.com
*
* Copyright 2012 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*/
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
margin: 0;
padding: 0;
}
/** Header */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699a4;
background-color: #0d3349;
font-size: 1.5em;
line-height: 1em;
font-weight: normal;
border-radius: 5px 5px 0 0;
-moz-border-radius: 5px 5px 0 0;
-webkit-border-top-right-radius: 5px;
-webkit-border-top-left-radius: 5px;
}
#qunit-header a {
text-decoration: none;
color: #c2ccd1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #fff;
}
#qunit-testrunner-toolbar label {
display: inline-block;
padding: 0 .5em 0 .1em;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
overflow: hidden;
}
#qunit-userAgent {
padding: 0.5em 0 0.5em 2.5em;
background-color: #2b81af;
color: #fff;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
#qunit-modulefilter-container {
float: right;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 0.5em 0.4em 2.5em;
border-bottom: 1px solid #fff;
list-style-position: inside;
}
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
display: none;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li a {
padding: 0.5em;
color: #c2ccd1;
text-decoration: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests li .runtime {
float: right;
font-size: smaller;
}
.qunit-assert-list {
margin-top: 0.5em;
padding: 0.5em;
background-color: #fff;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
.qunit-collapsed {
display: none;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: .2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 .5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
background-color: #e0f2be;
color: #374e0c;
text-decoration: none;
}
#qunit-tests ins {
background-color: #ffcaca;
color: #500;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: black; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
padding: 5px;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #3c510c;
background-color: #fff;
border-left: 10px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 10px solid #EE5757;
white-space: pre;
}
#qunit-tests > li:last-child {
border-radius: 0 0 5px 5px;
-moz-border-radius: 0 0 5px 5px;
-webkit-border-bottom-right-radius: 5px;
-webkit-border-bottom-left-radius: 5px;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: green; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/** Result */
#qunit-testresult {
padding: 0.5em 0.5em 0.5em 2.5em;
color: #2b81af;
background-color: #D2E0E6;
border-bottom: 1px solid white;
}
#qunit-testresult .module-name {
font-weight: bold;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
width: 1000px;
height: 1000px;
}
/**
* QUnit v1.11.0 - A JavaScript Unit Testing Framework
*
* http://qunitjs.com
*
* Copyright 2012 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*/
(function( window ) {
var QUnit,
assert,
config,
onErrorFnPrev,
testId = 0,
fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty,
// Keep a local reference to Date (GH-283)
Date = window.Date,
defined = {
setTimeout: typeof window.setTimeout !== "undefined",
sessionStorage: (function() {
var x = "qunit-test-string";
try {
sessionStorage.setItem( x, x );
sessionStorage.removeItem( x );
return true;
} catch( e ) {
return false;
}
}())
},
/**
* Provides a normalized error string, correcting an issue
* with IE 7 (and prior) where Error.prototype.toString is
* not properly implemented
*
* Based on http://es5.github.com/#x15.11.4.4
*
* @param {String|Error} error
* @return {String} error message
*/
errorString = function( error ) {
var name, message,
errorString = error.toString();
if ( errorString.substring( 0, 7 ) === "[object" ) {
name = error.name ? error.name.toString() : "Error";
message = error.message ? error.message.toString() : "";
if ( name && message ) {
return name + ": " + message;
} else if ( name ) {
return name;
} else if ( message ) {
return message;
} else {
return "Error";
}
} else {
return errorString;
}
},
/**
* Makes a clone of an object using only Array or Object as base,
* and copies over the own enumerable properties.
*
* @param {Object} obj
* @return {Object} New object with only the own properties (recursively).
*/
objectValues = function( obj ) {
// Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392.
/*jshint newcap: false */
var key, val,
vals = QUnit.is( "array", obj ) ? [] : {};
for ( key in obj ) {
if ( hasOwn.call( obj, key ) ) {
val = obj[key];
vals[key] = val === Object(val) ? objectValues(val) : val;
}
}
return vals;
};
function Test( settings ) {
extend( this, settings );
this.assertions = [];
this.testNumber = ++Test.count;
}
Test.count = 0;
Test.prototype = {
init: function() {
var a, b, li,
tests = id( "qunit-tests" );
if ( tests ) {
b = document.createElement( "strong" );
b.innerHTML = this.nameHtml;
// `a` initialized at top of scope
a = document.createElement( "a" );
a.innerHTML = "Rerun";
a.href = QUnit.url({ testNumber: this.testNumber });
li = document.createElement( "li" );
li.appendChild( b );
li.appendChild( a );
li.className = "running";
li.id = this.id = "qunit-test-output" + testId++;
tests.appendChild( li );
}
},
setup: function() {
if ( this.module !== config.previousModule ) {
if ( config.previousModule ) {
runLoggingCallbacks( "moduleDone", QUnit, {
name: config.previousModule,
failed: config.moduleStats.bad,
passed: config.moduleStats.all - config.moduleStats.bad,
total: config.moduleStats.all
});
}
config.previousModule = this.module;
config.moduleStats = { all: 0, bad: 0 };
runLoggingCallbacks( "moduleStart", QUnit, {
name: this.module
});
} else if ( config.autorun ) {
runLoggingCallbacks( "moduleStart", QUnit, {
name: this.module
});
}
config.current = this;
this.testEnvironment = extend({
setup: function() {},
teardown: function() {}
}, this.moduleTestEnvironment );
this.started = +new Date();
runLoggingCallbacks( "testStart", QUnit, {
name: this.testName,
module: this.module
});
// allow utility functions to access the current test environment
// TODO why??
QUnit.current_testEnvironment = this.testEnvironment;
if ( !config.pollution ) {
saveGlobal();
}
if ( config.notrycatch ) {
this.testEnvironment.setup.call( this.testEnvironment );
return;
}
try {
this.testEnvironment.setup.call( this.testEnvironment );
} catch( e ) {
QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
}
},
run: function() {
config.current = this;
var running = id( "qunit-testresult" );
if ( running ) {
running.innerHTML = "Running: <br/>" + this.nameHtml;
}
if ( this.async ) {
QUnit.stop();
}
this.callbackStarted = +new Date();
if ( config.notrycatch ) {
this.callback.call( this.testEnvironment, QUnit.assert );
this.callbackRuntime = +new Date() - this.callbackStarted;
return;
}
try {
this.callback.call( this.testEnvironment, QUnit.assert );
this.callbackRuntime = +new Date() - this.callbackStarted;
} catch( e ) {
this.callbackRuntime = +new Date() - this.callbackStarted;
QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
// else next test will carry the responsibility
saveGlobal();
// Restart the tests if they're blocking
if ( config.blocking ) {
QUnit.start();
}
}
},
teardown: function() {
config.current = this;
if ( config.notrycatch ) {
if ( typeof this.callbackRuntime === "undefined" ) {
this.callbackRuntime = +new Date() - this.callbackStarted;
}
this.testEnvironment.teardown.call( this.testEnvironment );
return;
} else {
try {
this.testEnvironment.teardown.call( this.testEnvironment );
} catch( e ) {
QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
}
}
checkPollution();
},
finish: function() {
config.current = this;
if ( config.requireExpects && this.expected === null ) {
QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
} else if ( this.expected !== null && this.expected !== this.assertions.length ) {
QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
} else if ( this.expected === null && !this.assertions.length ) {
QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
}
var i, assertion, a, b, time, li, ol,
test = this,
good = 0,
bad = 0,
tests = id( "qunit-tests" );
this.runtime = +new Date() - this.started;
config.stats.all += this.assertions.length;
config.moduleStats.all += this.assertions.length;
if ( tests ) {
ol = document.createElement( "ol" );
ol.className = "qunit-assert-list";
for ( i = 0; i < this.assertions.length; i++ ) {
assertion = this.assertions[i];
li = document.createElement( "li" );
li.className = assertion.result ? "pass" : "fail";
li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
ol.appendChild( li );
if ( assertion.result ) {
good++;
} else {
bad++;
config.stats.bad++;
config.moduleStats.bad++;
}
}
// store result when possible
if ( QUnit.config.reorder && defined.sessionStorage ) {
if ( bad ) {
sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
} else {
sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
}
}
if ( bad === 0 ) {
addClass( ol, "qunit-collapsed" );
}
// `b` initialized at top of scope
b = document.createElement( "strong" );
b.innerHTML = this.nameHtml + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
addEvent(b, "click", function() {
var next = b.parentNode.lastChild,
collapsed = hasClass( next, "qunit-collapsed" );
( collapsed ? removeClass : addClass )( next, "qunit-collapsed" );
});
addEvent(b, "dblclick", function( e ) {
var target = e && e.target ? e.target : window.event.srcElement;
if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) {
target = target.parentNode;
}
if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
window.location = QUnit.url({ testNumber: test.testNumber });
}
});
// `time` initialized at top of scope
time = document.createElement( "span" );
time.className = "runtime";
time.innerHTML = this.runtime + " ms";
// `li` initialized at top of scope
li = id( this.id );
li.className = bad ? "fail" : "pass";
li.removeChild( li.firstChild );
a = li.firstChild;
li.appendChild( b );
li.appendChild( a );
li.appendChild( time );
li.appendChild( ol );
} else {
for ( i = 0; i < this.assertions.length; i++ ) {
if ( !this.assertions[i].result ) {
bad++;
config.stats.bad++;
config.moduleStats.bad++;
}
}
}
runLoggingCallbacks( "testDone", QUnit, {
name: this.testName,
module: this.module,
failed: bad,
passed: this.assertions.length - bad,
total: this.assertions.length,
duration: this.runtime
});
QUnit.reset();
config.current = undefined;
},
queue: function() {
var bad,
test = this;
synchronize(function() {
test.init();
});
function run() {
// each of these can by async
synchronize(function() {
test.setup();
});
synchronize(function() {
test.run();
});
synchronize(function() {
test.teardown();
});
synchronize(function() {
test.finish();
});
}
// `bad` initialized at top of scope
// defer when previous test run passed, if storage is available
bad = QUnit.config.reorder && defined.sessionStorage &&
+sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
if ( bad ) {
run();
} else {
synchronize( run, true );
}
}
};
// Root QUnit object.
// `QUnit` initialized at top of scope
QUnit = {
// call on start of module test to prepend name to all tests
module: function( name, testEnvironment ) {
config.currentModule = name;
config.currentModuleTestEnvironment = testEnvironment;
config.modules[name] = true;
},
asyncTest: function( testName, expected, callback ) {
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
QUnit.test( testName, expected, callback, true );
},
test: function( testName, expected, callback, async ) {
var test,
nameHtml = "<span class='test-name'>" + escapeText( testName ) + "</span>";
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
if ( config.currentModule ) {
nameHtml = "<span class='module-name'>" + escapeText( config.currentModule ) + "</span>: " + nameHtml;
}
test = new Test({
nameHtml: nameHtml,
testName: testName,
expected: expected,
async: async,
callback: callback,
module: config.currentModule,
moduleTestEnvironment: config.currentModuleTestEnvironment,
stack: sourceFromStacktrace( 2 )
});
if ( !validTest( test ) ) {
return;
}
test.queue();
},
// Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
expect: function( asserts ) {
if (arguments.length === 1) {
config.current.expected = asserts;
} else {
return config.current.expected;
}
},
start: function( count ) {
// QUnit hasn't been initialized yet.
// Note: RequireJS (et al) may delay onLoad
if ( config.semaphore === undefined ) {
QUnit.begin(function() {
// This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
setTimeout(function() {
QUnit.start( count );
});
});
return;
}
config.semaphore -= count || 1;
// don't start until equal number of stop-calls
if ( config.semaphore > 0 ) {
return;
}
// ignore if start is called more often then stop
if ( config.semaphore < 0 ) {
config.semaphore = 0;
QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) );
return;
}
// A slight delay, to avoid any current callbacks
if ( defined.setTimeout ) {
window.setTimeout(function() {
if ( config.semaphore > 0 ) {
return;
}
if ( config.timeout ) {
clearTimeout( config.timeout );
}
config.blocking = false;
process( true );
}, 13);
} else {
config.blocking = false;
process( true );
}
},
stop: function( count ) {
config.semaphore += count || 1;
config.blocking = true;
if ( config.testTimeout && defined.setTimeout ) {
clearTimeout( config.timeout );
config.timeout = window.setTimeout(function() {
QUnit.ok( false, "Test timed out" );
config.semaphore = 1;
QUnit.start();
}, config.testTimeout );
}
}
};
// `assert` initialized at top of scope
// Asssert helpers
// All of these must either call QUnit.push() or manually do:
// - runLoggingCallbacks( "log", .. );
// - config.current.assertions.push({ .. });
// We attach it to the QUnit object *after* we expose the public API,
// otherwise `assert` will become a global variable in browsers (#341).
assert = {
/**
* Asserts rough true-ish result.
* @name ok
* @function
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
*/
ok: function( result, msg ) {
if ( !config.current ) {
throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
}
result = !!result;
var source,
details = {
module: config.current.module,
name: config.current.testName,
result: result,
message: msg
};
msg = escapeText( msg || (result ? "okay" : "failed" ) );
msg = "<span class='test-message'>" + msg + "</span>";
if ( !result ) {
source = sourceFromStacktrace( 2 );
if ( source ) {
details.source = source;
msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr></table>";
}
}
runLoggingCallbacks( "log", QUnit, details );
config.current.assertions.push({
result: result,
message: msg
});
},
/**
* Assert that the first two arguments are equal, with an optional message.
* Prints out both actual and expected values.
* @name equal
* @function
* @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
*/
equal: function( actual, expected, message ) {
/*jshint eqeqeq:false */
QUnit.push( expected == actual, actual, expected, message );
},
/**
* @name notEqual
* @function
*/
notEqual: function( actual, expected, message ) {
/*jshint eqeqeq:false */
QUnit.push( expected != actual, actual, expected, message );
},
/**
* @name propEqual
* @function
*/
propEqual: function( actual, expected, message ) {
actual = objectValues(actual);
expected = objectValues(expected);
QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
},
/**
* @name notPropEqual
* @function
*/
notPropEqual: function( actual, expected, message ) {
actual = objectValues(actual);
expected = objectValues(expected);
QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
},
/**
* @name deepEqual
* @function
*/
deepEqual: function( actual, expected, message ) {
QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
},
/**
* @name notDeepEqual
* @function
*/
notDeepEqual: function( actual, expected, message ) {
QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
},
/**
* @name strictEqual
* @function
*/
strictEqual: function( actual, expected, message ) {
QUnit.push( expected === actual, actual, expected, message );
},
/**
* @name notStrictEqual
* @function
*/
notStrictEqual: function( actual, expected, message ) {
QUnit.push( expected !== actual, actual, expected, message );
},
"throws": function( block, expected, message ) {
var actual,
expectedOutput = expected,
ok = false;
// 'expected' is optional
if ( typeof expected === "string" ) {
message = expected;
expected = null;
}
config.current.ignoreGlobalErrors = true;
try {
block.call( config.current.testEnvironment );
} catch (e) {
actual = e;
}
config.current.ignoreGlobalErrors = false;
if ( actual ) {
// we don't want to validate thrown error
if ( !expected ) {
ok = true;
expectedOutput = null;
// expected is a regexp
} else if ( QUnit.objectType( expected ) === "regexp" ) {
ok = expected.test( errorString( actual ) );
// expected is a constructor
} else if ( actual instanceof expected ) {
ok = true;
// expected is a validation function which returns true is validation passed
} else if ( expected.call( {}, actual ) === true ) {
expectedOutput = null;
ok = true;
}
QUnit.push( ok, actual, expectedOutput, message );
} else {
QUnit.pushFailure( message, null, 'No exception was thrown.' );
}
}
};
/**
* @deprecate since 1.8.0
* Kept assertion helpers in root for backwards compatibility.
*/
extend( QUnit, assert );
/**
* @deprecated since 1.9.0
* Kept root "raises()" for backwards compatibility.
* (Note that we don't introduce assert.raises).
*/
QUnit.raises = assert[ "throws" ];
/**
* @deprecated since 1.0.0, replaced with error pushes since 1.3.0
* Kept to avoid TypeErrors for undefined methods.
*/
QUnit.equals = function() {
QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
};
QUnit.same = function() {
QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
};
// We want access to the constructor's prototype
(function() {
function F() {}
F.prototype = QUnit;
QUnit = new F();
// Make F QUnit's constructor so that we can add to the prototype later
QUnit.constructor = F;
}());
/**
* Config object: Maintain internal state
* Later exposed as QUnit.config
* `config` initialized at top of scope
*/
config = {
// The queue of tests to run
queue: [],
// block until document ready
blocking: true,
// when enabled, show only failing tests
// gets persisted through sessionStorage and can be changed in UI via checkbox
hidepassed: false,
// by default, run previously failed tests first
// very useful in combination with "Hide passed tests" checked
reorder: true,
// by default, modify document.title when suite is done
altertitle: true,
// when enabled, all tests must call expect()
requireExpects: false,
// add checkboxes that are persisted in the query-string
// when enabled, the id is set to `true` as a `QUnit.config` property
urlConfig: [
{
id: "noglobals",
label: "Check for Globals",
tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
},
{
id: "notrycatch",
label: "No try-catch",
tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
}
],
// Set of all modules.
modules: {},
// logging callback queues
begin: [],
done: [],
log: [],
testStart: [],
testDone: [],
moduleStart: [],
moduleDone: []
};
// Export global variables, unless an 'exports' object exists,
// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
if ( typeof exports === "undefined" ) {
extend( window, QUnit );
// Expose QUnit object
window.QUnit = QUnit;
}
// Initialize more QUnit.config and QUnit.urlParams
(function() {
var i,
location = window.location || { search: "", protocol: "file:" },
params = location.search.slice( 1 ).split( "&" ),
length = params.length,
urlParams = {},
current;
if ( params[ 0 ] ) {
for ( i = 0; i < length; i++ ) {
current = params[ i ].split( "=" );
current[ 0 ] = decodeURIComponent( current[ 0 ] );
// allow just a key to turn on a flag, e.g., test.html?noglobals
current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
urlParams[ current[ 0 ] ] = current[ 1 ];
}
}
QUnit.urlParams = urlParams;
// String search anywhere in moduleName+testName
config.filter = urlParams.filter;
// Exact match of the module name
config.module = urlParams.module;
config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
// Figure out if we're running the tests from a server or not
QUnit.isLocal = location.protocol === "file:";
}());
// Extend QUnit object,
// these after set here because they should not be exposed as global functions
extend( QUnit, {
assert: assert,
config: config,
// Initialize the configuration options
init: function() {
extend( config, {
stats: { all: 0, bad: 0 },
moduleStats: { all: 0, bad: 0 },
started: +new Date(),
updateRate: 1000,
blocking: false,
autostart: true,
autorun: false,
filter: "",
queue: [],
semaphore: 1
});
var tests, banner, result,
qunit = id( "qunit" );
if ( qunit ) {
qunit.innerHTML =
"<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
"<h2 id='qunit-banner'></h2>" +
"<div id='qunit-testrunner-toolbar'></div>" +
"<h2 id='qunit-userAgent'></h2>" +
"<ol id='qunit-tests'></ol>";
}
tests = id( "qunit-tests" );
banner = id( "qunit-banner" );
result = id( "qunit-testresult" );
if ( tests ) {
tests.innerHTML = "";
}
if ( banner ) {
banner.className = "";
}
if ( result ) {
result.parentNode.removeChild( result );
}
if ( tests ) {
result = document.createElement( "p" );
result.id = "qunit-testresult";
result.className = "result";
tests.parentNode.insertBefore( result, tests );
result.innerHTML = "Running...<br/>&nbsp;";
}
},
// Resets the test setup. Useful for tests that modify the DOM.
reset: function() {
var fixture = id( "qunit-fixture" );
if ( fixture ) {
fixture.innerHTML = config.fixture;
}
},
// Trigger an event on an element.
// @example triggerEvent( document.body, "click" );
triggerEvent: function( elem, type, event ) {
if ( document.createEvent ) {
event = document.createEvent( "MouseEvents" );
event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
elem.dispatchEvent( event );
} else if ( elem.fireEvent ) {
elem.fireEvent( "on" + type );
}
},
// Safe object type checking
is: function( type, obj ) {
return QUnit.objectType( obj ) === type;
},
objectType: function( obj ) {
if ( typeof obj === "undefined" ) {
return "undefined";
// consider: typeof null === object
}
if ( obj === null ) {
return "null";
}
var match = toString.call( obj ).match(/^\[object\s(.*)\]$/),
type = match && match[1] || "";
switch ( type ) {
case "Number":
if ( isNaN(obj) ) {
return "nan";
}
return "number";
case "String":
case "Boolean":
case "Array":
case "Date":
case "RegExp":
case "Function":
return type.toLowerCase();
}
if ( typeof obj === "object" ) {
return "object";
}
return undefined;
},
push: function( result, actual, expected, message ) {
if ( !config.current ) {
throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
}
var output, source,
details = {
module: config.current.module,
name: config.current.testName,
result: result,
message: message,
actual: actual,
expected: expected
};
message = escapeText( message ) || ( result ? "okay" : "failed" );
message = "<span class='test-message'>" + message + "</span>";
output = message;
if ( !result ) {
expected = escapeText( QUnit.jsDump.parse(expected) );
actual = escapeText( QUnit.jsDump.parse(actual) );
output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>";
if ( actual !== expected ) {
output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>";
output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
}
source = sourceFromStacktrace();
if ( source ) {
details.source = source;
output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
}
output += "</table>";
}
runLoggingCallbacks( "log", QUnit, details );
config.current.assertions.push({
result: !!result,
message: output
});
},
pushFailure: function( message, source, actual ) {
if ( !config.current ) {
throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
}
var output,
details = {
module: config.current.module,
name: config.current.testName,
result: false,
message: message
};
message = escapeText( message ) || "error";
message = "<span class='test-message'>" + message + "</span>";
output = message;
output += "<table>";
if ( actual ) {
output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText( actual ) + "</pre></td></tr>";
}
if ( source ) {
details.source = source;
output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
}
output += "</table>";
runLoggingCallbacks( "log", QUnit, details );
config.current.assertions.push({
result: false,
message: output
});
},
url: function( params ) {
params = extend( extend( {}, QUnit.urlParams ), params );
var key,
querystring = "?";
for ( key in params ) {
if ( !hasOwn.call( params, key ) ) {
continue;
}
querystring += encodeURIComponent( key ) + "=" +
encodeURIComponent( params[ key ] ) + "&";
}
return window.location.protocol + "//" + window.location.host +
window.location.pathname + querystring.slice( 0, -1 );
},
extend: extend,
id: id,
addEvent: addEvent
// load, equiv, jsDump, diff: Attached later
});
/**
* @deprecated: Created for backwards compatibility with test runner that set the hook function
* into QUnit.{hook}, instead of invoking it and passing the hook function.
* QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
* Doing this allows us to tell if the following methods have been overwritten on the actual
* QUnit object.
*/
extend( QUnit.constructor.prototype, {
// Logging callbacks; all receive a single argument with the listed properties
// run test/logs.html for any related changes
begin: registerLoggingCallback( "begin" ),
// done: { failed, passed, total, runtime }
done: registerLoggingCallback( "done" ),
// log: { result, actual, expected, message }
log: registerLoggingCallback( "log" ),
// testStart: { name }
testStart: registerLoggingCallback( "testStart" ),
// testDone: { name, failed, passed, total, duration }
testDone: registerLoggingCallback( "testDone" ),
// moduleStart: { name }
moduleStart: registerLoggingCallback( "moduleStart" ),
// moduleDone: { name, failed, passed, total }
moduleDone: registerLoggingCallback( "moduleDone" )
});
if ( typeof document === "undefined" || document.readyState === "complete" ) {
config.autorun = true;
}
QUnit.load = function() {
runLoggingCallbacks( "begin", QUnit, {} );
// Initialize the config, saving the execution queue
var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter,
numModules = 0,
moduleFilterHtml = "",
urlConfigHtml = "",
oldconfig = extend( {}, config );
QUnit.init();
extend(config, oldconfig);
config.blocking = false;
len = config.urlConfig.length;
for ( i = 0; i < len; i++ ) {
val = config.urlConfig[i];
if ( typeof val === "string" ) {
val = {
id: val,
label: val,
tooltip: "[no tooltip available]"
};
}
config[ val.id ] = QUnit.urlParams[ val.id ];
urlConfigHtml += "<input id='qunit-urlconfig-" + escapeText( val.id ) +
"' name='" + escapeText( val.id ) +
"' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) +
" title='" + escapeText( val.tooltip ) +
"'><label for='qunit-urlconfig-" + escapeText( val.id ) +
"' title='" + escapeText( val.tooltip ) + "'>" + val.label + "</label>";
}
moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " +
( config.module === undefined ? "selected='selected'" : "" ) +
">< All Modules ></option>";
for ( i in config.modules ) {
if ( config.modules.hasOwnProperty( i ) ) {
numModules += 1;
moduleFilterHtml += "<option value='" + escapeText( encodeURIComponent(i) ) + "' " +
( config.module === i ? "selected='selected'" : "" ) +
">" + escapeText(i) + "</option>";
}
}
moduleFilterHtml += "</select>";
// `userAgent` initialized at top of scope
userAgent = id( "qunit-userAgent" );
if ( userAgent ) {
userAgent.innerHTML = navigator.userAgent;
}
// `banner` initialized at top of scope
banner = id( "qunit-header" );
if ( banner ) {
banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
}
// `toolbar` initialized at top of scope
toolbar = id( "qunit-testrunner-toolbar" );
if ( toolbar ) {
// `filter` initialized at top of scope
filter = document.createElement( "input" );
filter.type = "checkbox";
filter.id = "qunit-filter-pass";
addEvent( filter, "click", function() {
var tmp,
ol = document.getElementById( "qunit-tests" );
if ( filter.checked ) {
ol.className = ol.className + " hidepass";
} else {
tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
ol.className = tmp.replace( / hidepass /, " " );
}
if ( defined.sessionStorage ) {
if (filter.checked) {
sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
} else {
sessionStorage.removeItem( "qunit-filter-passed-tests" );
}
}
});
if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
filter.checked = true;
// `ol` initialized at top of scope
ol = document.getElementById( "qunit-tests" );
ol.className = ol.className + " hidepass";
}
toolbar.appendChild( filter );
// `label` initialized at top of scope
label = document.createElement( "label" );
label.setAttribute( "for", "qunit-filter-pass" );
label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." );
label.innerHTML = "Hide passed tests";
toolbar.appendChild( label );
urlConfigCheckboxesContainer = document.createElement("span");
urlConfigCheckboxesContainer.innerHTML = urlConfigHtml;
urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input");
// For oldIE support:
// * Add handlers to the individual elements instead of the container
// * Use "click" instead of "change"
// * Fallback from event.target to event.srcElement
addEvents( urlConfigCheckboxes, "click", function( event ) {
var params = {},
target = event.target || event.srcElement;
params[ target.name ] = target.checked ? true : undefined;
window.location = QUnit.url( params );
});
toolbar.appendChild( urlConfigCheckboxesContainer );
if (numModules > 1) {
moduleFilter = document.createElement( 'span' );
moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' );
moduleFilter.innerHTML = moduleFilterHtml;
addEvent( moduleFilter.lastChild, "change", function() {
var selectBox = moduleFilter.getElementsByTagName("select")[0],
selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } );
});
toolbar.appendChild(moduleFilter);
}
}
// `main` initialized at top of scope
main = id( "qunit-fixture" );
if ( main ) {
config.fixture = main.innerHTML;
}
if ( config.autostart ) {
QUnit.start();
}
};
addEvent( window, "load", QUnit.load );
// `onErrorFnPrev` initialized at top of scope
// Preserve other handlers
onErrorFnPrev = window.onerror;
// Cover uncaught exceptions
// Returning true will surpress the default browser handler,
// returning false will let it run.
window.onerror = function ( error, filePath, linerNr ) {
var ret = false;
if ( onErrorFnPrev ) {
ret = onErrorFnPrev( error, filePath, linerNr );
}
// Treat return value as window.onerror itself does,
// Only do our handling if not surpressed.
if ( ret !== true ) {
if ( QUnit.config.current ) {
if ( QUnit.config.current.ignoreGlobalErrors ) {
return true;
}
QUnit.pushFailure( error, filePath + ":" + linerNr );
} else {
QUnit.test( "global failure", extend( function() {
QUnit.pushFailure( error, filePath + ":" + linerNr );
}, { validTest: validTest } ) );
}
return false;
}
return ret;
};
function done() {
config.autorun = true;
// Log the last module results
if ( config.currentModule ) {
runLoggingCallbacks( "moduleDone", QUnit, {
name: config.currentModule,
failed: config.moduleStats.bad,
passed: config.moduleStats.all - config.moduleStats.bad,
total: config.moduleStats.all
});
}
var i, key,
banner = id( "qunit-banner" ),
tests = id( "qunit-tests" ),
runtime = +new Date() - config.started,
passed = config.stats.all - config.stats.bad,
html = [
"Tests completed in ",
runtime,
" milliseconds.<br/>",
"<span class='passed'>",
passed,
"</span> assertions of <span class='total'>",
config.stats.all,
"</span> passed, <span class='failed'>",
config.stats.bad,
"</span> failed."
].join( "" );
if ( banner ) {
banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
}
if ( tests ) {
id( "qunit-testresult" ).innerHTML = html;
}
if ( config.altertitle && typeof document !== "undefined" && document.title ) {
// show ✖ for good, ✔ for bad suite result in title
// use escape sequences in case file gets loaded with non-utf-8-charset
document.title = [
( config.stats.bad ? "\u2716" : "\u2714" ),
document.title.replace( /^[\u2714\u2716] /i, "" )
].join( " " );
}
// clear own sessionStorage items if all tests passed
if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
// `key` & `i` initialized at top of scope
for ( i = 0; i < sessionStorage.length; i++ ) {
key = sessionStorage.key( i++ );
if ( key.indexOf( "qunit-test-" ) === 0 ) {
sessionStorage.removeItem( key );
}
}
}
// scroll back to top to show results
if ( window.scrollTo ) {
window.scrollTo(0, 0);
}
runLoggingCallbacks( "done", QUnit, {
failed: config.stats.bad,
passed: passed,
total: config.stats.all,
runtime: runtime
});
}
/** @return Boolean: true if this test should be ran */
function validTest( test ) {
var include,
filter = config.filter && config.filter.toLowerCase(),
module = config.module && config.module.toLowerCase(),
fullName = (test.module + ": " + test.testName).toLowerCase();
// Internally-generated tests are always valid
if ( test.callback && test.callback.validTest === validTest ) {
delete test.callback.validTest;
return true;
}
if ( config.testNumber ) {
return test.testNumber === config.testNumber;
}
if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
return false;
}
if ( !filter ) {
return true;
}
include = filter.charAt( 0 ) !== "!";
if ( !include ) {
filter = filter.slice( 1 );
}
// If the filter matches, we need to honour include
if ( fullName.indexOf( filter ) !== -1 ) {
return include;
}
// Otherwise, do the opposite
return !include;
}
// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
// Later Safari and IE10 are supposed to support error.stack as well
// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
function extractStacktrace( e, offset ) {
offset = offset === undefined ? 3 : offset;
var stack, include, i;
if ( e.stacktrace ) {
// Opera
return e.stacktrace.split( "\n" )[ offset + 3 ];
} else if ( e.stack ) {
// Firefox, Chrome
stack = e.stack.split( "\n" );
if (/^error$/i.test( stack[0] ) ) {
stack.shift();
}
if ( fileName ) {
include = [];
for ( i = offset; i < stack.length; i++ ) {
if ( stack[ i ].indexOf( fileName ) !== -1 ) {
break;
}
include.push( stack[ i ] );
}
if ( include.length ) {
return include.join( "\n" );
}
}
return stack[ offset ];
} else if ( e.sourceURL ) {
// Safari, PhantomJS
// hopefully one day Safari provides actual stacktraces
// exclude useless self-reference for generated Error objects
if ( /qunit.js$/.test( e.sourceURL ) ) {
return;
}
// for actual exceptions, this is useful
return e.sourceURL + ":" + e.line;
}
}
function sourceFromStacktrace( offset ) {
try {
throw new Error();
} catch ( e ) {
return extractStacktrace( e, offset );
}
}
/**
* Escape text for attribute or text content.
*/
function escapeText( s ) {
if ( !s ) {
return "";
}
s = s + "";
// Both single quotes and double quotes (for attributes)
return s.replace( /['"<>&]/g, function( s ) {
switch( s ) {
case '\'':
return '&#039;';
case '"':
return '&quot;';
case '<':
return '&lt;';
case '>':
return '&gt;';
case '&':
return '&amp;';
}
});
}
function synchronize( callback, last ) {
config.queue.push( callback );
if ( config.autorun && !config.blocking ) {
process( last );
}
}
function process( last ) {
function next() {
process( last );
}
var start = new Date().getTime();
config.depth = config.depth ? config.depth + 1 : 1;
while ( config.queue.length && !config.blocking ) {
if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
config.queue.shift()();
} else {
window.setTimeout( next, 13 );
break;
}
}
config.depth--;
if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
done();
}
}
function saveGlobal() {
config.pollution = [];
if ( config.noglobals ) {
for ( var key in window ) {
// in Opera sometimes DOM element ids show up here, ignore them
if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) {
continue;
}
config.pollution.push( key );
}
}
}
function checkPollution() {
var newGlobals,
deletedGlobals,
old = config.pollution;
saveGlobal();
newGlobals = diff( config.pollution, old );
if ( newGlobals.length > 0 ) {
QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
}
deletedGlobals = diff( old, config.pollution );
if ( deletedGlobals.length > 0 ) {
QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
}
}
// returns a new Array with the elements that are in a but not in b
function diff( a, b ) {
var i, j,
result = a.slice();
for ( i = 0; i < result.length; i++ ) {
for ( j = 0; j < b.length; j++ ) {
if ( result[i] === b[j] ) {
result.splice( i, 1 );
i--;
break;
}
}
}
return result;
}
function extend( a, b ) {
for ( var prop in b ) {
if ( b[ prop ] === undefined ) {
delete a[ prop ];
// Avoid "Member not found" error in IE8 caused by setting window.constructor
} else if ( prop !== "constructor" || a !== window ) {
a[ prop ] = b[ prop ];
}
}
return a;
}
/**
* @param {HTMLElement} elem
* @param {string} type
* @param {Function} fn
*/
function addEvent( elem, type, fn ) {
// Standards-based browsers
if ( elem.addEventListener ) {
elem.addEventListener( type, fn, false );
// IE
} else {
elem.attachEvent( "on" + type, fn );
}
}
/**
* @param {Array|NodeList} elems
* @param {string} type
* @param {Function} fn
*/
function addEvents( elems, type, fn ) {
var i = elems.length;
while ( i-- ) {
addEvent( elems[i], type, fn );
}
}
function hasClass( elem, name ) {
return (" " + elem.className + " ").indexOf(" " + name + " ") > -1;
}
function addClass( elem, name ) {
if ( !hasClass( elem, name ) ) {
elem.className += (elem.className ? " " : "") + name;
}
}
function removeClass( elem, name ) {
var set = " " + elem.className + " ";
// Class name may appear multiple times
while ( set.indexOf(" " + name + " ") > -1 ) {
set = set.replace(" " + name + " " , " ");
}
// If possible, trim it for prettiness, but not neccecarily
elem.className = window.jQuery ? jQuery.trim( set ) : ( set.trim ? set.trim() : set );
}
function id( name ) {
return !!( typeof document !== "undefined" && document && document.getElementById ) &&
document.getElementById( name );
}
function registerLoggingCallback( key ) {
return function( callback ) {
config[key].push( callback );
};
}
// Supports deprecated method of completely overwriting logging callbacks
function runLoggingCallbacks( key, scope, args ) {
var i, callbacks;
if ( QUnit.hasOwnProperty( key ) ) {
QUnit[ key ].call(scope, args );
} else {
callbacks = config[ key ];
for ( i = 0; i < callbacks.length; i++ ) {
callbacks[ i ].call( scope, args );
}
}
}
// Test for equality any JavaScript type.
// Author: Philippe Rathé <prathe@gmail.com>
QUnit.equiv = (function() {
// Call the o related callback with the given arguments.
function bindCallbacks( o, callbacks, args ) {
var prop = QUnit.objectType( o );
if ( prop ) {
if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
return callbacks[ prop ].apply( callbacks, args );
} else {
return callbacks[ prop ]; // or undefined
}
}
}
// the real equiv function
var innerEquiv,
// stack to decide between skip/abort functions
callers = [],
// stack to avoiding loops from circular referencing
parents = [],
getProto = Object.getPrototypeOf || function ( obj ) {
return obj.__proto__;
},
callbacks = (function () {
// for string, boolean, number and null
function useStrictEquality( b, a ) {
/*jshint eqeqeq:false */
if ( b instanceof a.constructor || a instanceof b.constructor ) {
// to catch short annotaion VS 'new' annotation of a
// declaration
// e.g. var i = 1;
// var j = new Number(1);
return a == b;
} else {
return a === b;
}
}
return {
"string": useStrictEquality,
"boolean": useStrictEquality,
"number": useStrictEquality,
"null": useStrictEquality,
"undefined": useStrictEquality,
"nan": function( b ) {
return isNaN( b );
},
"date": function( b, a ) {
return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
},
"regexp": function( b, a ) {
return QUnit.objectType( b ) === "regexp" &&
// the regex itself
a.source === b.source &&
// and its modifers
a.global === b.global &&
// (gmi) ...
a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline &&
a.sticky === b.sticky;
},
// - skip when the property is a method of an instance (OOP)
// - abort otherwise,
// initial === would have catch identical references anyway
"function": function() {
var caller = callers[callers.length - 1];
return caller !== Object && typeof caller !== "undefined";
},
"array": function( b, a ) {
var i, j, len, loop;
// b could be an object literal here
if ( QUnit.objectType( b ) !== "array" ) {
return false;
}
len = a.length;
if ( len !== b.length ) {
// safe and faster
return false;
}
// track reference to avoid circular references
parents.push( a );
for ( i = 0; i < len; i++ ) {
loop = false;
for ( j = 0; j < parents.length; j++ ) {
if ( parents[j] === a[i] ) {
loop = true;// dont rewalk array
}
}
if ( !loop && !innerEquiv(a[i], b[i]) ) {
parents.pop();
return false;
}
}
parents.pop();
return true;
},
"object": function( b, a ) {
var i, j, loop,
// Default to true
eq = true,
aProperties = [],
bProperties = [];
// comparing constructors is more strict than using
// instanceof
if ( a.constructor !== b.constructor ) {
// Allow objects with no prototype to be equivalent to
// objects with Object as their constructor.
if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) ||
( getProto(b) === null && getProto(a) === Object.prototype ) ) ) {
return false;
}
}
// stack constructor before traversing properties
callers.push( a.constructor );
// track reference to avoid circular references
parents.push( a );
for ( i in a ) { // be strict: don't ensures hasOwnProperty
// and go deep
loop = false;
for ( j = 0; j < parents.length; j++ ) {
if ( parents[j] === a[i] ) {
// don't go down the same path twice
loop = true;
}
}
aProperties.push(i); // collect a's properties
if (!loop && !innerEquiv( a[i], b[i] ) ) {
eq = false;
break;
}
}
callers.pop(); // unstack, we are done
parents.pop();
for ( i in b ) {
bProperties.push( i ); // collect b's properties
}
// Ensures identical properties name
return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
}
};
}());
innerEquiv = function() { // can take multiple arguments
var args = [].slice.apply( arguments );
if ( args.length < 2 ) {
return true; // end transition
}
return (function( a, b ) {
if ( a === b ) {
return true; // catch the most you can
} else if ( a === null || b === null || typeof a === "undefined" ||
typeof b === "undefined" ||
QUnit.objectType(a) !== QUnit.objectType(b) ) {
return false; // don't lose time with error prone cases
} else {
return bindCallbacks(a, callbacks, [ b, a ]);
}
// apply transition with (1..n) arguments
}( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) );
};
return innerEquiv;
}());
/**
* jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
* http://flesler.blogspot.com Licensed under BSD
* (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
*
* @projectDescription Advanced and extensible data dumping for Javascript.
* @version 1.0.0
* @author Ariel Flesler
* @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
*/
QUnit.jsDump = (function() {
function quote( str ) {
return '"' + str.toString().replace( /"/g, '\\"' ) + '"';
}
function literal( o ) {
return o + "";
}
function join( pre, arr, post ) {
var s = jsDump.separator(),
base = jsDump.indent(),
inner = jsDump.indent(1);
if ( arr.join ) {
arr = arr.join( "," + s + inner );
}
if ( !arr ) {
return pre + post;
}
return [ pre, inner + arr, base + post ].join(s);
}
function array( arr, stack ) {
var i = arr.length, ret = new Array(i);
this.up();
while ( i-- ) {
ret[i] = this.parse( arr[i] , undefined , stack);
}
this.down();
return join( "[", ret, "]" );
}
var reName = /^function (\w+)/,
jsDump = {
// type is used mostly internally, you can fix a (custom)type in advance
parse: function( obj, type, stack ) {
stack = stack || [ ];
var inStack, res,
parser = this.parsers[ type || this.typeOf(obj) ];
type = typeof parser;
inStack = inArray( obj, stack );
if ( inStack !== -1 ) {
return "recursion(" + (inStack - stack.length) + ")";
}
if ( type === "function" ) {
stack.push( obj );
res = parser.call( this, obj, stack );
stack.pop();
return res;
}
return ( type === "string" ) ? parser : this.parsers.error;
},
typeOf: function( obj ) {
var type;
if ( obj === null ) {
type = "null";
} else if ( typeof obj === "undefined" ) {
type = "undefined";
} else if ( QUnit.is( "regexp", obj) ) {
type = "regexp";
} else if ( QUnit.is( "date", obj) ) {
type = "date";
} else if ( QUnit.is( "function", obj) ) {
type = "function";
} else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
type = "window";
} else if ( obj.nodeType === 9 ) {
type = "document";
} else if ( obj.nodeType ) {
type = "node";
} else if (
// native arrays
toString.call( obj ) === "[object Array]" ||
// NodeList objects
( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
) {
type = "array";
} else if ( obj.constructor === Error.prototype.constructor ) {
type = "error";
} else {
type = typeof obj;
}
return type;
},
separator: function() {
return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
},
// extra can be a number, shortcut for increasing-calling-decreasing
indent: function( extra ) {
if ( !this.multiline ) {
return "";
}
var chr = this.indentChar;
if ( this.HTML ) {
chr = chr.replace( /\t/g, " " ).replace( / /g, "&nbsp;" );
}
return new Array( this._depth_ + (extra||0) ).join(chr);
},
up: function( a ) {
this._depth_ += a || 1;
},
down: function( a ) {
this._depth_ -= a || 1;
},
setParser: function( name, parser ) {
this.parsers[name] = parser;
},
// The next 3 are exposed so you can use them
quote: quote,
literal: literal,
join: join,
//
_depth_: 1,
// This is the list of parsers, to modify them, use jsDump.setParser
parsers: {
window: "[Window]",
document: "[Document]",
error: function(error) {
return "Error(\"" + error.message + "\")";
},
unknown: "[Unknown]",
"null": "null",
"undefined": "undefined",
"function": function( fn ) {
var ret = "function",
// functions never have name in IE
name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];
if ( name ) {
ret += " " + name;
}
ret += "( ";
ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" );
return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" );
},
array: array,
nodelist: array,
"arguments": array,
object: function( map, stack ) {
var ret = [ ], keys, key, val, i;
QUnit.jsDump.up();
keys = [];
for ( key in map ) {
keys.push( key );
}
keys.sort();
for ( i = 0; i < keys.length; i++ ) {
key = keys[ i ];
val = map[ key ];
ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) );
}
QUnit.jsDump.down();
return join( "{", ret, "}" );
},
node: function( node ) {
var len, i, val,
open = QUnit.jsDump.HTML ? "&lt;" : "<",
close = QUnit.jsDump.HTML ? "&gt;" : ">",
tag = node.nodeName.toLowerCase(),
ret = open + tag,
attrs = node.attributes;
if ( attrs ) {
for ( i = 0, len = attrs.length; i < len; i++ ) {
val = attrs[i].nodeValue;
// IE6 includes all attributes in .attributes, even ones not explicitly set.
// Those have values like undefined, null, 0, false, "" or "inherit".
if ( val && val !== "inherit" ) {
ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" );
}
}
}
ret += close;
// Show content of TextNode or CDATASection
if ( node.nodeType === 3 || node.nodeType === 4 ) {
ret += node.nodeValue;
}
return ret + open + "/" + tag + close;
},
// function calls it internally, it's the arguments part of the function
functionArgs: function( fn ) {
var args,
l = fn.length;
if ( !l ) {
return "";
}
args = new Array(l);
while ( l-- ) {
// 97 is 'a'
args[l] = String.fromCharCode(97+l);
}
return " " + args.join( ", " ) + " ";
},
// object calls it internally, the key part of an item in a map
key: quote,
// function calls it internally, it's the content of the function
functionCode: "[code]",
// node calls it internally, it's an html attribute value
attribute: quote,
string: quote,
date: quote,
regexp: literal,
number: literal,
"boolean": literal
},
// if true, entities are escaped ( <, >, \t, space and \n )
HTML: false,
// indentation unit
indentChar: " ",
// if true, items in a collection, are separated by a \n, else just a space.
multiline: true
};
return jsDump;
}());
// from jquery.js
function inArray( elem, array ) {
if ( array.indexOf ) {
return array.indexOf( elem );
}
for ( var i = 0, length = array.length; i < length; i++ ) {
if ( array[ i ] === elem ) {
return i;
}
}
return -1;
}
/*
* Javascript Diff Algorithm
* By John Resig (http://ejohn.org/)
* Modified by Chu Alan "sprite"
*
* Released under the MIT license.
*
* More Info:
* http://ejohn.org/projects/javascript-diff-algorithm/
*
* Usage: QUnit.diff(expected, actual)
*
* QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
*/
QUnit.diff = (function() {
/*jshint eqeqeq:false, eqnull:true */
function diff( o, n ) {
var i,
ns = {},
os = {};
for ( i = 0; i < n.length; i++ ) {
if ( !hasOwn.call( ns, n[i] ) ) {
ns[ n[i] ] = {
rows: [],
o: null
};
}
ns[ n[i] ].rows.push( i );
}
for ( i = 0; i < o.length; i++ ) {
if ( !hasOwn.call( os, o[i] ) ) {
os[ o[i] ] = {
rows: [],
n: null
};
}
os[ o[i] ].rows.push( i );
}
for ( i in ns ) {
if ( !hasOwn.call( ns, i ) ) {
continue;
}
if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) {
n[ ns[i].rows[0] ] = {
text: n[ ns[i].rows[0] ],
row: os[i].rows[0]
};
o[ os[i].rows[0] ] = {
text: o[ os[i].rows[0] ],
row: ns[i].rows[0]
};
}
}
for ( i = 0; i < n.length - 1; i++ ) {
if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null &&
n[ i + 1 ] == o[ n[i].row + 1 ] ) {
n[ i + 1 ] = {
text: n[ i + 1 ],
row: n[i].row + 1
};
o[ n[i].row + 1 ] = {
text: o[ n[i].row + 1 ],
row: i + 1
};
}
}
for ( i = n.length - 1; i > 0; i-- ) {
if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
n[ i - 1 ] == o[ n[i].row - 1 ]) {
n[ i - 1 ] = {
text: n[ i - 1 ],
row: n[i].row - 1
};
o[ n[i].row - 1 ] = {
text: o[ n[i].row - 1 ],
row: i - 1
};
}
}
return {
o: o,
n: n
};
}
return function( o, n ) {
o = o.replace( /\s+$/, "" );
n = n.replace( /\s+$/, "" );
var i, pre,
str = "",
out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ),
oSpace = o.match(/\s+/g),
nSpace = n.match(/\s+/g);
if ( oSpace == null ) {
oSpace = [ " " ];
}
else {
oSpace.push( " " );
}
if ( nSpace == null ) {
nSpace = [ " " ];
}
else {
nSpace.push( " " );
}
if ( out.n.length === 0 ) {
for ( i = 0; i < out.o.length; i++ ) {
str += "<del>" + out.o[i] + oSpace[i] + "</del>";
}
}
else {
if ( out.n[0].text == null ) {
for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) {
str += "<del>" + out.o[n] + oSpace[n] + "</del>";
}
}
for ( i = 0; i < out.n.length; i++ ) {
if (out.n[i].text == null) {
str += "<ins>" + out.n[i] + nSpace[i] + "</ins>";
}
else {
// `pre` initialized at top of scope
pre = "";
for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
pre += "<del>" + out.o[n] + oSpace[n] + "</del>";
}
str += " " + out.n[i].text + nSpace[i] + pre;
}
}
}
return str;
};
}());
// for CommonJS enviroments, export everything
if ( typeof exports !== "undefined" ) {
extend( exports, QUnit );
}
// get at whatever the global object is, like window in browsers
}( (function() {return this;}.call()) ));
/*
RequireJS 2.1.4 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
Available via the MIT or new BSD license.
see: http://github.com/jrburke/requirejs for details
*/
var requirejs,require,define;
(function(Y){function I(b){return"[object Function]"===L.call(b)}function J(b){return"[object Array]"===L.call(b)}function x(b,c){if(b){var d;for(d=0;d<b.length&&(!b[d]||!c(b[d],d,b));d+=1);}}function M(b,c){if(b){var d;for(d=b.length-1;-1<d&&(!b[d]||!c(b[d],d,b));d-=1);}}function r(b,c){return da.call(b,c)}function i(b,c){return r(b,c)&&b[c]}function E(b,c){for(var d in b)if(r(b,d)&&c(b[d],d))break}function Q(b,c,d,i){c&&E(c,function(c,h){if(d||!r(b,h))i&&"string"!==typeof c?(b[h]||(b[h]={}),Q(b[h],
c,d,i)):b[h]=c});return b}function t(b,c){return function(){return c.apply(b,arguments)}}function Z(b){if(!b)return b;var c=Y;x(b.split("."),function(b){c=c[b]});return c}function F(b,c,d,i){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=i;d&&(c.originalError=d);return c}function ea(b){function c(a,f,v){var e,n,b,c,d,k,g,h=f&&f.split("/");e=h;var l=m.map,j=l&&l["*"];if(a&&"."===a.charAt(0))if(f){e=i(m.pkgs,f)?h=[f]:h.slice(0,h.length-1);f=a=e.concat(a.split("/"));
for(e=0;f[e];e+=1)if(n=f[e],"."===n)f.splice(e,1),e-=1;else if(".."===n)if(1===e&&(".."===f[2]||".."===f[0]))break;else 0<e&&(f.splice(e-1,2),e-=2);e=i(m.pkgs,f=a[0]);a=a.join("/");e&&a===f+"/"+e.main&&(a=f)}else 0===a.indexOf("./")&&(a=a.substring(2));if(v&&(h||j)&&l){f=a.split("/");for(e=f.length;0<e;e-=1){b=f.slice(0,e).join("/");if(h)for(n=h.length;0<n;n-=1)if(v=i(l,h.slice(0,n).join("/")))if(v=i(v,b)){c=v;d=e;break}if(c)break;!k&&(j&&i(j,b))&&(k=i(j,b),g=e)}!c&&k&&(c=k,d=g);c&&(f.splice(0,d,
c),a=f.join("/"))}return a}function d(a){z&&x(document.getElementsByTagName("script"),function(f){if(f.getAttribute("data-requiremodule")===a&&f.getAttribute("data-requirecontext")===k.contextName)return f.parentNode.removeChild(f),!0})}function y(a){var f=i(m.paths,a);if(f&&J(f)&&1<f.length)return d(a),f.shift(),k.require.undef(a),k.require([a]),!0}function g(a){var f,b=a?a.indexOf("!"):-1;-1<b&&(f=a.substring(0,b),a=a.substring(b+1,a.length));return[f,a]}function h(a,f,b,e){var n,u,d=null,h=f?f.name:
null,l=a,m=!0,j="";a||(m=!1,a="_@r"+(L+=1));a=g(a);d=a[0];a=a[1];d&&(d=c(d,h,e),u=i(p,d));a&&(d?j=u&&u.normalize?u.normalize(a,function(a){return c(a,h,e)}):c(a,h,e):(j=c(a,h,e),a=g(j),d=a[0],j=a[1],b=!0,n=k.nameToUrl(j)));b=d&&!u&&!b?"_unnormalized"+(M+=1):"";return{prefix:d,name:j,parentMap:f,unnormalized:!!b,url:n,originalName:l,isDefine:m,id:(d?d+"!"+j:j)+b}}function q(a){var f=a.id,b=i(j,f);b||(b=j[f]=new k.Module(a));return b}function s(a,f,b){var e=a.id,n=i(j,e);if(r(p,e)&&(!n||n.defineEmitComplete))"defined"===
f&&b(p[e]);else q(a).on(f,b)}function A(a,f){var b=a.requireModules,e=!1;if(f)f(a);else if(x(b,function(f){if(f=i(j,f))f.error=a,f.events.error&&(e=!0,f.emit("error",a))}),!e)l.onError(a)}function w(){R.length&&(fa.apply(G,[G.length-1,0].concat(R)),R=[])}function B(a,f,b){var e=a.map.id;a.error?a.emit("error",a.error):(f[e]=!0,x(a.depMaps,function(e,c){var d=e.id,h=i(j,d);h&&(!a.depMatched[c]&&!b[d])&&(i(f,d)?(a.defineDep(c,p[d]),a.check()):B(h,f,b))}),b[e]=!0)}function C(){var a,f,b,e,n=(b=1E3*m.waitSeconds)&&
k.startTime+b<(new Date).getTime(),c=[],h=[],g=!1,l=!0;if(!T){T=!0;E(j,function(b){a=b.map;f=a.id;if(b.enabled&&(a.isDefine||h.push(b),!b.error))if(!b.inited&&n)y(f)?g=e=!0:(c.push(f),d(f));else if(!b.inited&&(b.fetched&&a.isDefine)&&(g=!0,!a.prefix))return l=!1});if(n&&c.length)return b=F("timeout","Load timeout for modules: "+c,null,c),b.contextName=k.contextName,A(b);l&&x(h,function(a){B(a,{},{})});if((!n||e)&&g)if((z||$)&&!U)U=setTimeout(function(){U=0;C()},50);T=!1}}function D(a){r(p,a[0])||
q(h(a[0],null,!0)).init(a[1],a[2])}function H(a){var a=a.currentTarget||a.srcElement,b=k.onScriptLoad;a.detachEvent&&!V?a.detachEvent("onreadystatechange",b):a.removeEventListener("load",b,!1);b=k.onScriptError;(!a.detachEvent||V)&&a.removeEventListener("error",b,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}function K(){var a;for(w();G.length;){a=G.shift();if(null===a[0])return A(F("mismatch","Mismatched anonymous define() module: "+a[a.length-1]));D(a)}}var T,W,k,N,U,m={waitSeconds:7,
baseUrl:"./",paths:{},pkgs:{},shim:{},map:{},config:{}},j={},X={},G=[],p={},S={},L=1,M=1;N={require:function(a){return a.require?a.require:a.require=k.makeRequire(a.map)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports?a.exports:a.exports=p[a.map.id]={}},module:function(a){return a.module?a.module:a.module={id:a.map.id,uri:a.map.url,config:function(){return m.config&&i(m.config,a.map.id)||{}},exports:p[a.map.id]}}};W=function(a){this.events=i(X,a.id)||{};this.map=a;this.shim=
i(m.shim,a.id);this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};W.prototype={init:function(a,b,c,e){e=e||{};if(!this.inited){this.factory=b;if(c)this.on("error",c);else this.events.error&&(c=t(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.errback=c;this.inited=!0;this.ignore=e.ignore;e.enabled||this.enabled?this.enable():this.check()}},defineDep:function(a,b){this.depMatched[a]||(this.depMatched[a]=!0,this.depCount-=1,this.depExports[a]=
b)},fetch:function(){if(!this.fetched){this.fetched=!0;k.startTime=(new Date).getTime();var a=this.map;if(this.shim)k.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],t(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=this.map.url;S[a]||(S[a]=!0,k.load(this.map.id,a))},check:function(){if(this.enabled&&!this.enabling){var a,b,c=this.map.id;b=this.depExports;var e=this.exports,n=this.factory;
if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(1>this.depCount&&!this.defined){if(I(n)){if(this.events.error)try{e=k.execCb(c,n,b,e)}catch(d){a=d}else e=k.execCb(c,n,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=[this.map.id],a.requireType="define",A(this.error=a)}else e=n;this.exports=e;if(this.map.isDefine&&
!this.ignore&&(p[c]=e,l.onResourceLoad))l.onResourceLoad(k,this.map,this.depMaps);delete j[c];this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=h(a.prefix);this.depMaps.push(d);s(d,"defined",t(this,function(e){var n,d;d=this.map.name;var v=this.map.parentMap?this.map.parentMap.name:null,g=k.makeRequire(a.parentMap,{enableBuildCallback:!0});
if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,v,!0)})||""),e=h(a.prefix+"!"+d,this.map.parentMap),s(e,"defined",t(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=i(j,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",t(this,function(a){this.emit("error",a)}));d.enable()}}else n=t(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),n.error=t(this,function(a){this.inited=!0;this.error=a;a.requireModules=
[b];E(j,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&delete j[a.map.id]});A(a)}),n.fromText=t(this,function(e,c){var d=a.name,u=h(d),v=O;c&&(e=c);v&&(O=!1);q(u);r(m.config,b)&&(m.config[d]=m.config[b]);try{l.exec(e)}catch(j){return A(F("fromtexteval","fromText eval for "+b+" failed: "+j,j,[b]))}v&&(O=!0);this.depMaps.push(u);k.completeLoad(d);g([d],n)}),e.load(a.name,g,n,m)}));k.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){this.enabling=this.enabled=!0;x(this.depMaps,t(this,function(a,
b){var c,e;if("string"===typeof a){a=h(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=i(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;s(a,"defined",t(this,function(a){this.defineDep(b,a);this.check()}));this.errback&&s(a,"error",this.errback)}c=a.id;e=j[c];!r(N,c)&&(e&&!e.enabled)&&k.enable(a,this)}));E(this.pluginMaps,t(this,function(a){var b=i(j,a.id);b&&!b.enabled&&k.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=
this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){x(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};k={config:m,contextName:b,registry:j,defined:p,urlFetched:S,defQueue:G,Module:W,makeModuleMap:h,nextTick:l.nextTick,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=m.pkgs,c=m.shim,e={paths:!0,config:!0,map:!0};E(a,function(a,b){e[b]?"map"===b?Q(m[b],a,!0,!0):Q(m[b],a,!0):m[b]=a});a.shim&&(E(a.shim,function(a,
b){J(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=k.makeShimExports(a);c[b]=a}),m.shim=c);a.packages&&(x(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ga,"").replace(aa,"")}}),m.pkgs=b);E(j,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=h(b))});if(a.deps||a.callback)k.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(Y,arguments));
return b||a.exports&&Z(a.exports)}},makeRequire:function(a,d){function g(e,c,u){var i,m;d.enableBuildCallback&&(c&&I(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(I(c))return A(F("requireargs","Invalid require call"),u);if(a&&r(N,e))return N[e](j[a.id]);if(l.get)return l.get(k,e,a);i=h(e,a,!1,!0);i=i.id;return!r(p,i)?A(F("notloaded",'Module name "'+i+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):p[i]}K();k.nextTick(function(){K();m=q(h(null,a));m.skipMap=d.skipMap;
m.init(e,c,u,{enabled:!0});C()});return g}d=d||{};Q(g,{isBrowser:z,toUrl:function(b){var d,f=b.lastIndexOf("."),h=b.split("/")[0];if(-1!==f&&(!("."===h||".."===h)||1<f))d=b.substring(f,b.length),b=b.substring(0,f);b=k.nameToUrl(c(b,a&&a.id,!0),d||".fake");return d?b:b.substring(0,b.length-5)},defined:function(b){return r(p,h(b,a,!1,!0).id)},specified:function(b){b=h(b,a,!1,!0).id;return r(p,b)||r(j,b)}});a||(g.undef=function(b){w();var c=h(b,a,!0),d=i(j,b);delete p[b];delete S[c.url];delete X[b];
d&&(d.events.defined&&(X[b]=d.events),delete j[b])});return g},enable:function(a){i(j,a.id)&&q(a).enable()},completeLoad:function(a){var b,c,d=i(m.shim,a)||{},h=d.exports;for(w();G.length;){c=G.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===a&&(b=!0);D(c)}c=i(j,a);if(!b&&!r(p,a)&&c&&!c.inited){if(m.enforceDefine&&(!h||!Z(h)))return y(a)?void 0:A(F("nodefine","No define call for "+a,null,[a]));D([a,d.deps||[],d.exportsFn])}C()},nameToUrl:function(a,b){var c,d,h,g,k,j;if(l.jsExtRegExp.test(a))g=
a+(b||"");else{c=m.paths;d=m.pkgs;g=a.split("/");for(k=g.length;0<k;k-=1)if(j=g.slice(0,k).join("/"),h=i(d,j),j=i(c,j)){J(j)&&(j=j[0]);g.splice(0,k,j);break}else if(h){c=a===h.name?h.location+"/"+h.main:h.location;g.splice(0,k,c);break}g=g.join("/");g+=b||(/\?/.test(g)?"":".js");g=("/"===g.charAt(0)||g.match(/^[\w\+\.\-]+:/)?"":m.baseUrl)+g}return m.urlArgs?g+((-1===g.indexOf("?")?"?":"&")+m.urlArgs):g},load:function(a,b){l.load(k,a,b)},execCb:function(a,b,c,d){return b.apply(d,c)},onScriptLoad:function(a){if("load"===
a.type||ha.test((a.currentTarget||a.srcElement).readyState))P=null,a=H(a),k.completeLoad(a.id)},onScriptError:function(a){var b=H(a);if(!y(b.id))return A(F("scripterror","Script error",a,[b.id]))}};k.require=k.makeRequire();return k}var l,w,B,D,s,H,P,K,ba,ca,ia=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ja=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,aa=/\.js$/,ga=/^\.\//;w=Object.prototype;var L=w.toString,da=w.hasOwnProperty,fa=Array.prototype.splice,z=!!("undefined"!==typeof window&&navigator&&
document),$=!z&&"undefined"!==typeof importScripts,ha=z&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,V="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),C={},q={},R=[],O=!1;if("undefined"===typeof define){if("undefined"!==typeof requirejs){if(I(requirejs))return;q=requirejs;requirejs=void 0}"undefined"!==typeof require&&!I(require)&&(q=require,require=void 0);l=requirejs=function(b,c,d,y){var g,h="_";!J(b)&&"string"!==typeof b&&(g=b,J(c)?(b=c,c=d,d=y):b=[]);
g&&g.context&&(h=g.context);(y=i(C,h))||(y=C[h]=l.s.newContext(h));g&&y.configure(g);return y.require(b,c,d)};l.config=function(b){return l(b)};l.nextTick="undefined"!==typeof setTimeout?function(b){setTimeout(b,4)}:function(b){b()};require||(require=l);l.version="2.1.4";l.jsExtRegExp=/^\/|:|\?|\.js$/;l.isBrowser=z;w=l.s={contexts:C,newContext:ea};l({});x(["toUrl","undef","defined","specified"],function(b){l[b]=function(){var c=C._;return c.require[b].apply(c,arguments)}});if(z&&(B=w.head=document.getElementsByTagName("head")[0],
D=document.getElementsByTagName("base")[0]))B=w.head=D.parentNode;l.onError=function(b){throw b;};l.load=function(b,c,d){var i=b&&b.config||{},g;if(z)return g=i.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script"),g.type=i.scriptType||"text/javascript",g.charset="utf-8",g.async=!0,g.setAttribute("data-requirecontext",b.contextName),g.setAttribute("data-requiremodule",c),g.attachEvent&&!(g.attachEvent.toString&&0>g.attachEvent.toString().indexOf("[native code"))&&
!V?(O=!0,g.attachEvent("onreadystatechange",b.onScriptLoad)):(g.addEventListener("load",b.onScriptLoad,!1),g.addEventListener("error",b.onScriptError,!1)),g.src=d,K=g,D?B.insertBefore(g,D):B.appendChild(g),K=null,g;$&&(importScripts(d),b.completeLoad(c))};z&&M(document.getElementsByTagName("script"),function(b){B||(B=b.parentNode);if(s=b.getAttribute("data-main"))return q.baseUrl||(H=s.split("/"),ba=H.pop(),ca=H.length?H.join("/")+"/":"./",q.baseUrl=ca,s=ba),s=s.replace(aa,""),q.deps=q.deps?q.deps.concat(s):
[s],!0});define=function(b,c,d){var i,g;"string"!==typeof b&&(d=c,c=b,b=null);J(c)||(d=c,c=[]);!c.length&&I(d)&&d.length&&(d.toString().replace(ia,"").replace(ja,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c));if(O){if(!(i=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),i=P;i&&(b||(b=i.getAttribute("data-requiremodule")),g=C[i.getAttribute("data-requirecontext")])}(g?
g.defQueue:R).push([b,c,d])};define.amd={jQuery:!0};l.exec=function(b){return eval(b)};l(q)}})(this);
/*global window, jQuery */
/*!
* route.js v1.0.0
*
* Copyright 2012, Romain Courteaud
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Date: Mon Jul 16 2012
*/
"use strict";
(function (window, $) {
$.extend({
StatelessDeferred: function () {
var doneList = $.Callbacks("memory"),
promise = {
done: doneList.add,
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function (obj) {
var i,
keys = ['done', 'promise'];
if (obj === undefined) {
obj = promise;
} else {
for (i = 0; i < keys.length; i += 1) {
obj[keys[i]] = promise[keys[i]];
}
}
return obj;
}
},
deferred = promise.promise({});
deferred.resolveWith = doneList.fireWith;
// All done!
return deferred;
}
});
var routes = [],
current_priority = 0,
methods = {
add: function (pattern, priority) {
var i = 0,
inserted = false,
length = routes.length,
dfr = $.StatelessDeferred(),
context = $(this),
escapepattern,
matchingpattern;
if (priority === undefined) {
priority = 0;
}
if (pattern !== undefined) {
// http://simonwillison.net/2006/Jan/20/escape/
escapepattern = pattern.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
matchingpattern = escapepattern
.replace(/<int:\w+>/g, "(\\d+)")
.replace(/<path:\w+>/g, "(.+)")
.replace(/<\w+>/g, "([^/]+)");
while (!inserted) {
if ((i === length) || (priority >= routes[i][2])) {
routes.splice(i, 0, [new RegExp('^' + matchingpattern + '$'), dfr, priority, context]);
inserted = true;
} else {
i += 1;
}
}
}
return dfr.promise();
},
go: function (path, min_priority) {
var dfr = $.Deferred(),
context = $(this),
result;
if (min_priority === undefined) {
min_priority = 0;
}
setTimeout(function () {
var i = 0,
found = false,
slice_index = -1,
slice_priority = -1;
for (i = 0; i < routes.length; i += 1) {
if (slice_priority !== routes[i][2]) {
slice_priority = routes[i][2];
slice_index = i;
}
if (routes[i][2] < min_priority) {
break;
} else if (routes[i][0].test(path)) {
result = routes[i][0].exec(path);
dfr = routes[i][1];
context = routes[i][3];
current_priority = routes[i][2];
found = true;
break;
}
}
if (i === routes.length) {
slice_index = i;
}
if (slice_index > -1) {
routes = routes.slice(slice_index);
}
if (found) {
dfr.resolveWith(
context,
result.slice(1)
);
} else {
dfr.rejectWith(context);
}
});
return dfr.promise();
},
};
$.routereset = function () {
routes = [];
current_priority = 0;
};
$.routepriority = function () {
return current_priority;
};
$.fn.route = function (method) {
var result;
if (methods.hasOwnProperty(method)) {
result = methods[method].apply(
this,
Array.prototype.slice.call(arguments, 1)
);
} else {
$.error('Method ' + method +
' does not exist on jQuery.route');
}
return result;
};
}(window, jQuery));
/*!
* url.js v1.0.0
*
* Copyright 2012, Romain Courteaud
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Date: Mon Jul 16 2012
*/
"use strict";
(function (window, $) {
var hashchangeinitialized = false,
previousurl,
currentcallback,
getRawHash = function () {
return window.location.toString().split('#')[1];
},
callbackwrapper = function () {
if (previousurl !== window.location.hash) {
previousurl = window.location.hash;
if (currentcallback !== undefined) {
currentcallback();
}
}
},
timeoutwrapper = function () {
callbackwrapper();
window.setTimeout(timeoutwrapper, 500);
};
function UrlHandler() {}
UrlHandler.prototype = {
'generateUrl': function (path, options) {
var pathhash,
hash = '#',
key;
if (path !== undefined) {
hash += encodeURIComponent(path);
}
hash = hash.replace(/%2F/g, '/');
pathhash = hash;
for (key in options) {
if (options.hasOwnProperty(key)) {
if (hash === pathhash) {
hash = hash + '?';
} else {
hash = hash + '&';
}
hash += encodeURIComponent(key) +
'=' + encodeURIComponent(options[key]);
}
}
return hash;
},
'go': function (path, options) {
window.location.hash = this.generateUrl(path, options);
},
'redirect': function (path, options) {
var host = window.location.protocol + '//' +
window.location.host +
window.location.pathname +
window.location.search;
window.location.replace(host + this.generateUrl(path, options));
// window.location.replace(window.location.href.replace(/#.*/, ""));
},
'getPath': function () {
var hash = getRawHash(),
result = '';
if (hash !== undefined) {
result = decodeURIComponent(hash.split('?')[0]);
}
return result;
},
'getOptions': function () {
var options = {},
hash = getRawHash(),
subhashes,
subhash,
index,
keyvalue;
if (hash !== undefined) {
hash = hash.split('?')[1];
if (hash !== undefined) {
subhashes = hash.split('&');
for (index in subhashes) {
if (subhashes.hasOwnProperty(index)) {
subhash = subhashes[index];
if (subhash !== '') {
keyvalue = subhash.split('=');
if (keyvalue.length === 2) {
options[decodeURIComponent(keyvalue[0])] =
decodeURIComponent(keyvalue[1]);
}
}
}
}
}
}
return options;
},
'onhashchange': function (callback) {
previousurl = undefined;
currentcallback = callback;
if (!hashchangeinitialized) {
if (window.onhashchange !== undefined) {
$(window).bind('hashchange', callbackwrapper);
window.setTimeout(callbackwrapper);
} else {
timeoutwrapper();
}
hashchangeinitialized = true;
}
},
};
// Expose to the global object
$.url = new UrlHandler();
}(window, jQuery));
/*! RenderJs v0.2 */
/*global console, require, $, localStorage, document, jIO */
/*jslint evil: true, white: true */
"use strict";
/*
* RenderJs - Generic Gadget library renderer.
* http://www.renderjs.org/documentation
*/
/**
* By default RenderJs will render all gadgets when page is loaded
* still it's possible to override this and use explicit gadget rendering.
*
* @property RENDERJS_ENABLE_IMPLICIT_GADGET_RENDERING
* @type {Boolean}
* @default "true"
*/
var RENDERJS_ENABLE_IMPLICIT_GADGET_RENDERING = true;
/**
* By default RenderJs will examine and bind all interaction gadgets
* available.
*
* @property RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND
* @type {Boolean}
* @default "true"
*/
var RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND = true;
/**
* By default RenderJs will examine and create all routes
*
* @property RENDERJS_ENABLE_IMPLICIT_ROUTE_CREATE
* @type {Boolean}
* @default "true"
*/
var RENDERJS_ENABLE_IMPLICIT_ROUTE_CREATE = true;
// fallback for IE
if (console === undefined || console.log === undefined) {
var console = {};
console.log = function () {};
}
/**
Provides the base RenderJs class
@module RenderJs
**/
var RenderJs = (function () {
// a variable indicating if current gadget loading is over or not
var is_ready = false, current_gadget;
function setSelfGadget (gadget) {
/*
* Only used internally to set current gadget being executed.
*/
current_gadget = gadget;
// TODOS:
// -> remove wrapping elements on hardcoded gadgets
// -> JQM needs trigger("create") on parent, easy to call on wrapper
// -> enhance before replace?
// -> does a sandbox need a wrapper?
// -> addGadget
// -> add callbacks
// -> allow to load remote src files, which should only have <link> elements
// to publish their services
// -> find a way to prevent a gadget from reloading a plugin that's already
// active on the page
// DISCUSSION POINTS:
// (1)
// (2) If not used for interactions or routing, what's the purpose of
// gadgetIndex?
// (3) Do we dumb-store in the index or perform some sort of validation
// before adding a gadget to an index?
// (4)
// (5) Should findGadget() = find recursive gadgets, also work with a single
// gadget id, like findGadget({"id":"1ewnel73"}), I guess not as we
// are using random uuids internally only
// (6)
// (7)
// (8)
// (9) When hard-coding services (see content.html), the "url" parameter can
// be omitted which would default to current iFrame (window) or root?
// (10)
// (11)is passing the root URL through the URL a security vulnerablity?
// (12)should findGadget and findService be API methods? They will scan the
// DOM for services/gadgets, but this should actually be done (and is done)
// automatically
// (13)we need to use an id to identify <frames> on a page, currently set as
// uuid by renderJs. We could also use data-id or remove the id and try
// to match by src (url), but this will be worse in terms of performance
// (14)what do to about service parameters. A service that requires parameters
// a and b passed to return c should somewhere also specify this. Do we
// need a service JSON/HAL API? or where should this information be made
// availabel
// Info:
// iframe communication:
// http://stackoverflow.com/questions/15884994/javascript-can-data-be-passed-bi-directionally-through-an-iframe/
// http://stackoverflow.com/questions/16689280/how-to-set-jquery-data-on-an-iframe-body-tag-and-retrieve-it-from-inside-the-i/16689994?noredirect=1#16689994
// custom URI schemes:
// http://stackoverflow.com/questions/4403992/possible-to-handle-your-own-http-url-schemes-in-ios/5149668#5149668
// validate URL:
// http://stackoverflow.com/questions/161738/what-is-the-best-regular-expression-to-check-if-a-string-is-a-valid-url
(function ($, window, undefined) {
var priv = {};
var that = {};
// ================== utility methods ==================
// => cross-browser reduce (no support in ie8-, opera 12-)
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FArray%2FReduce
if ('function' !== typeof Array.prototype.reduce) {
Array.prototype.reduce = function(callback, opt_initialValue){
'use strict';
if (null === this || 'undefined' === typeof this) {
// At the moment all modern browsers, that support strict mode, have
// native implementation of Array.prototype.reduce. For instance, IE8
// does not support strict mode, so this check is actually useless.
throw new TypeError(
'Array.prototype.reduce called on null or undefined');
}
if ('function' !== typeof callback) {
throw new TypeError(callback + ' is not a function');
}
var index = 0, length = this.length >>> 0, value, isValueSet = false;
if (1 < arguments.length) {
value = opt_initialValue;
isValueSet = true;
}
for ( ; length > index; ++index) {
if (!this.hasOwnProperty(index)) continue;
if (isValueSet) {
value = callback(value, this[index], index, this);
} else {
value = this[index];
isValueSet = true;
}
}
if (!isValueSet) {
throw new TypeError('Reduce of empty array with no initial value');
}
return value;
};
}
// => regexes used to convert Ajax response string into HTML element list
// thx require: http://requirejs.org/docs/release/2.1.6/comments/require.js
priv.removeJSComments = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg;
priv.removeHTMLComments = /<!--[\s\S]*?-->/mg;
priv.removeLineBreaks = /(\r\n|\n|\r)/mg;
priv.removeWhiteSpace = /\s+/mg;
priv.removeWhiteSpaceBetweenElements = />\s+</mg;
// => convert all URLs to absolute URLs
// thx JQM - http://code.jquery.com/mobile/latest/jquery.mobile.js
// URL regexp
// [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content
// [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread
// [2]: http://jblas:password@mycompany.com:8080/mail/inbox
// [3]: http://jblas:password@mycompany.com:8080
// [4]: http:
// [5]: //
// [6]: jblas:password@mycompany.com:8080
// [7]: jblas:password
// [8]: jblas
// [9]: password
// [10]: mycompany.com:8080
// [11]: mycompany.com
// [12]: 8080
// [13]: /mail/inbox
// [14]: /mail/
// [15]: inbox
// [16]: ?msg=1234&type=unread
// [17]: #msg-content
priv.urlParser = /^\s*(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/
// parse a URL
priv.parseUrl = function( url ) {
if ( $.type( url ) === "object" ) {
return url;
}
var matches = priv.urlParser.exec( url || "" ) || [];
return {
href: matches[ 0 ] || "",
hrefNoHash: matches[ 1 ] || "",
hrefNoSearch: matches[ 2 ] || "",
domain: matches[ 3 ] || "",
protocol: matches[ 4 ] || "",
doubleSlash: matches[ 5 ] || "",
authority: matches[ 6 ] || "",
username: matches[ 8 ] || "",
password: matches[ 9 ] || "",
host: matches[ 10 ] || "",
hostname: matches[ 11 ] || "",
port: matches[ 12 ] || "",
pathname: matches[ 13 ] || "",
directory: matches[ 14 ] || "",
filename: matches[ 15 ] || "",
search: matches[ 16 ] || "",
hash: matches[ 17 ] || ""
};
};
init: function () {
/*
* Do all initialization
*/
if (RENDERJS_ENABLE_IMPLICIT_GADGET_RENDERING) {
RenderJs.bootstrap($('body'));
}
var root_gadget = RenderJs.GadgetIndex.getRootGadget();
if (RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND||RENDERJS_ENABLE_IMPLICIT_ROUTE_CREATE) {
// We might have a page without gadgets.
// Be careful, right now we can be in this case because
// asynchronous gadget loading is not finished
if (root_gadget !== undefined) {
RenderJs.bindReady(
function () {
if (RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND) {
// examine all Intaction Gadgets and bind accordingly
RenderJs.InteractionGadget.init();
}
if (RENDERJS_ENABLE_IMPLICIT_ROUTE_CREATE) {
// create all routes between gadgets
RenderJs.RouteGadget.init();
}
});
}
// is relateive URl (check protocol)
priv.isRelativeUrl = function (url ) {
return priv.parseUrl( url ).protocol === "";
};
// get location
priv.getLocation = function (url) {
var uri = url ? priv.parseUrl( url ) : location,
hash = priv.parseUrl( url || location.href ).hash;
// mimic the browser with an empty string when the hash is empty
hash = hash === "#" ? "" : hash;
// Make sure to parse the url or the location object for the hash because using location.hash
// is autodecoded in firefox, the rest of the url should be from the object (location unless
// we're testing) to avoid the inclusion of the authority
return uri.protocol + "//" + uri.host + uri.pathname + uri.search + hash;
};
// make PATH absolute
priv.makePathAbsolute = function (relPath, absPath) {
var absStack, relStack, i, d;
if ( relPath && relPath.charAt( 0 ) === "/" ) {
return relPath;
}
relPath = relPath || "";
absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : "";
absStack = absPath ? absPath.split( "/" ) : [];
relStack = relPath.split( "/" );
for ( i = 0; i < relStack.length; i++ ) {
d = relStack[ i ];
switch ( d ) {
case ".":
break;
case "..":
if ( absStack.length ) {
absStack.pop();
}
},
break;
default:
absStack.push( d );
break;
}
}
return "/" + absStack.join( "/" );
};
bootstrap: function (root) {
/*
* Load all gadgets for this DOM element
* (including recursively contained ones)
*/
var gadget_id, is_gadget;
gadget_id = root.attr("id");
is_gadget = root.attr("data-gadget") !== undefined;
// this will make RenderJs fire "ready" event when all gadgets are loaded.
RenderJs.setReady(false);
if (is_gadget && gadget_id !== undefined ) {
// bootstart root gadget only if it is indeed a gadget
RenderJs.loadGadget(root);
}
RenderJs.loadRecursiveGadget(root);
},
// make URL absolute
priv.makeUrlAbsolute = function (relUrl, absUrl) {
if ( !priv.isRelativeUrl( relUrl ) ) {
return relUrl;
}
if ( absUrl === undefined ) {
absUrl = priv.parseUrl(priv.getLocation());
}
loadRecursiveGadget: function (root) {
/*
* Load all contained gadgets inside passed DOM element.
*/
var gadget_list, gadget, gadget_id, gadget_js;
gadget_list = root.find("[data-gadget]");
// register all gadget in advance so checkAndTriggerReady
// can have accurate information for list of all gadgets
gadget_list.each(function () {
gadget = $(this);
gadget_id = gadget.attr("id");
gadget_js = new RenderJs.Gadget(gadget_id, gadget);
RenderJs.GadgetIndex.registerGadget(gadget_js);
});
// Load chilren
gadget_list.each(function () {
RenderJs.loadGadget($(this));
});
},
var relObj = priv.parseUrl( relUrl ),
absObj = priv.parseUrl( absUrl ),
protocol = relObj.protocol || absObj.protocol,
doubleSlash = relObj.protocol ? relObj.doubleSlash : ( relObj.doubleSlash || absObj.doubleSlash ),
authority = relObj.authority || absObj.authority,
hasPath = relObj.pathname !== "",
pathname = priv.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ),
search = relObj.search || ( !hasPath && absObj.search ) || "",
hash = relObj.hash;
return protocol + doubleSlash + authority + pathname + search + hash;
};
// => generate unique identifier
priv.generateUuid = function () {
var S4 = function () {
/* 65536 */
var i, string = Math.floor(
Math.random() * 0x10000
).toString(16);
for (i = string.length; i < 4; i += 1) {
string = "0" + string;
}
return string;
};
return S4() + S4();
};
// extract module name from path
priv.extractModuleName = function (src) {
var re = /([\w\d_-]*)\.?[^\\\/]*$/i;
return src.match(re)[1]
}
// => safe getAttribute for data-*
// thx JQM - http://code.jquery.com/mobile/latest/jquery.mobile.js
priv.getAttribute = function (element, attribute, json) {
var value;
value = element.getAttribute( "data-" + attribute );
return value === "true" ? true :
value === "false" ? false :
value === null ? (json ? "" : undefined ) : value;
};
// => URI methods
// decode URI
priv.decodeURI = function (string) {
return decodeURIComponent(string);
};
// encode URI
priv.encodeURI = function (string) {
return encodeURIComponent(string);
};
// decode URI array
priv.decodeURIArray = function (array) {
var i, newArray;
for (i = 0; i < array.length; i += 1) {
newArray.push(priv.decodeURI(array[i]));
}
return newArray;
};
setGadgetAndRecurse: function (gadget, data) {
/*
* Set gadget data and recursively load it in case it holds another
* gadgets.
*/
// set current gadget as being loaded so gadget instance itself knows which gadget it is
setSelfGadget(RenderJs.GadgetIndex.getGadgetById(gadget.attr("id")));
gadget.append(data);
// reset as no longer current gadget
setSelfGadget(undefined);
// a gadget may contain sub gadgets
RenderJs.loadRecursiveGadget(gadget);
},
getSelfGadget: function () {
/*
* Get current gadget being loaded
* This function must be used with care as it relies on Javascript nature of being a single
* threaded application. Currently current gadget is set in a global RenderJs variable
* before its HTML is inserted into DOM and if multiple threads were running (which is not the case currently)
* this could lead to reace conditions and unreliable getSelfGadget results.
* Additionally this function is available only at gadget's script load time - i.e.
* it can't be used in after that calls. In this case gagdget can save this value internally.
*/
return current_gadget;
},
// ================== internal methods ==================
loadGadget: function (gadget) {
/*
* Load gadget's SPECs from URL
*/
var url, gadget_id, gadget_property, cacheable, cache_id,
i, gadget_index, gadget_index_id,
app_cache, data, gadget_js, is_update_gadget_data_running;
url = gadget.attr("data-gadget");
gadget_id = gadget.attr("id");
gadget_js = RenderJs.GadgetIndex.getGadgetById(gadget_id);
gadget_index = RenderJs.GadgetIndex.getGadgetList();
if (gadget_js === undefined) {
// register gadget in javascript namespace if not already registered
gadget_js = new RenderJs.Gadget(gadget_id, gadget);
RenderJs.GadgetIndex.registerGadget(gadget_js);
}
if (gadget_js.isReady()) {
// avoid loading again gadget which was loaded before in same page
return ;
}
// update Gadget's instance with contents of "data-gadget-property"
gadget_property = gadget.attr("data-gadget-property");
if (gadget_property !== undefined) {
gadget_property = $.parseJSON(gadget_property);
$.each(gadget_property, function (key, value) {
gadget_js[key] = value;
});
}
if (url !== undefined && url !== "") {
cacheable = gadget.attr("data-gadget-cacheable");
cache_id = gadget.attr("data-gadget-cache-id");
if (cacheable !== undefined && cache_id !== undefined) {
cacheable = Boolean(parseInt(cacheable, 10));
}
//cacheable = false ; // to develop faster
if (cacheable) {
// get from cache if possible, use last part from URL as
// cache_key
app_cache = RenderJs.Cache.get(cache_id, undefined);
if (app_cache === undefined || app_cache === null) {
// not in cache so we pull from network and cache
$.ajax({
url: url,
yourCustomData: {
"gadget_id": gadget_id,
"cache_id": cache_id
},
success: function (data) {
cache_id = this.yourCustomData.cache_id;
gadget_id = this.yourCustomData.gadget_id;
RenderJs.Cache.set(cache_id, data);
RenderJs.GadgetIndex.getGadgetById(gadget_id).
setReady();
RenderJs.setGadgetAndRecurse(gadget, data);
RenderJs.checkAndTriggerReady();
RenderJs.updateGadgetData(gadget);
}
});
} else {
// get from cache
data = app_cache;
gadget_js.setReady();
this.setGadgetAndRecurse(gadget, data);
this.checkAndTriggerReady();
RenderJs.updateGadgetData(gadget);
}
} else {
// not to be cached
$.ajax({
url: url,
yourCustomData: {"gadget_id": gadget_id},
success: function (data) {
gadget_id = this.yourCustomData.gadget_id;
RenderJs.GadgetIndex.getGadgetById(gadget_id).
setReady();
RenderJs.setGadgetAndRecurse(gadget, data);
RenderJs.checkAndTriggerReady();
RenderJs.updateGadgetData(gadget);
}
});
}
}
else {
// gadget is an inline (InteractorGadget or one using
// data-gadget-source / data-gadget-handler) so no need
// to load it from network
is_update_gadget_data_running = RenderJs.updateGadgetData(gadget);
if (!is_update_gadget_data_running) {
// no update is running so gadget is basically ready
// if update is running then it should take care and set status
gadget_js.setReady();
}
RenderJs.checkAndTriggerReady();
}
},
// => keep track of service requesters to reply to
priv.trackRequest = function (id, respondTo) {
if (priv.serviceTracker === undefined) {
priv.serviceTracker = [];
}
priv.serviceTracker.push({"id": id, "respondTo": respondTo});
};
// => retrieve window which called a specific service
priv.retrieveCallingWindow = function (id) {
var i, service, callee;
for (i = 0; i < priv.serviceTracker.length; i += 1) {
service = priv.serviceTracker[i];
if (service.id === id) {
callee = service.respondTo;
priv.serviceTracker.splice(i, 1);
return callee;
}
}
};
// => mapping URL query-string (configuration
priv.mapUrlString = function (spec) {
var key, obj, parsedJSON, config = {};
if (spec !== undefined && spec !== "") {
obj = spec.slice(1).split("=");
key = obj[0];
switch (key) {
case "string":
case "url":
config.root = priv.decodeURI(obj[1]);
break;
case "json":
parsedJSON = JSON.parse(priv.decodeURI(obj[1]));
config.root = parsedJSON.root || window.location.pathname;
config.src = priv.decodeURIArray(parsedJSON.src) || [];
break;
case "hal":
parsedJSON = JSON.parse(priv.decodeURI(obj[1]));
config.root = parsedJSON._links.self || window.location.pathname;
config.src = priv.decodeURIArray(parsedJSON.src) || [];
break;
case "data":
break;
default:
// no allowable-type - ignore config-parameter!
config.root = window.location.href
config.src = [];
break;
}
} else {
config = {"root": window.location.href}
}
return config;
};
// => create Index of gadgets on page (excluding gadgets inside iFrame/Sandbox)
priv.createGadgetIndex = function () {
that.gadgetIndex = [];
};
// => create gadget reference tree (includes gadgets inside iFrame/Sandbox)
priv.createGadgetTree = function () {
that.gadgetTree = {
"id": "root",
"src": window.location.href,
"children":[]
}
};
// => add gadget to index
priv.addGadgetToIndex = function (data, options) {
that.gadgetIndex.unshift({
"id": options.id,
"options": options,
"data": data
});
};
// => add gadget to tree
priv.addGadgetToTree = function (options, treeNode) {
var i, newNode;
// recursive add
if (options.parentFrame === undefined) {
treeNode.children.unshift({
"id": options.id,
"src": options.src,
"children": []
});
} else {
for (i = 0; i < treeNode.children.length; i += 1) {
newNode = treeNode.children[i];
if (options.parentFrame === newNode.id) {
delete options.parentFrame;
priv.addGadgetToTree(options, newNode);
break;
} else {
if (newNode.children.length > 0) {
priv.addGadgetToTree(options, newNode);
}
}
}
}
isReady: function () {
/*
* Get rendering status
*/
return is_ready;
},
// if we are in a renderJs instance other than the root-instance
// (e.g. inside an iFrame) we also need to tell the root how this
// gadget can be accessed in case we want to call it's services
if (window.top !== window) {
window.top.postMessage({
// this will trigger addGagdetToTree() on root-in
"type":"tree/update",
"options": {
// passing "options":options will procude a DataCloneError, so
// this is the only way (it seems) to pass the options object.
"parentFrame": window.frameElement.getAttribute("id"),
"id": options.id,
"src": options.src,
"children": []
}
}, window.location.href.split("?")[0])
}
};
// => loop the gadgetTree to construct a selector to call a service
priv.constructSelectorForService = function (src, node, selector) {
var i, result;
selector = selector || [];
// we must not push "root" into the array to make reduce work
if (node.id !== "root") {
selector.push(node.id);
}
if (node.src === src) {
return selector;
}
for (i = 0; i < node.children.length; i += 1) {
result = priv.constructSelectorForService(src, node.children[i], selector);
if (result !== undefined) {
return result;
}
selector.pop();
}
};
// => interaction gadget and listener
// if initializing config is provided in the URL, we may have an src=[]
// of links to additional functional libraries (?), which should be
// available here. So we should load them.
priv.createServiceMap = function (spec) {
that.gadgetService = {
"root": spec.root || window.location.href,
"directories": spec.src || [],
"map": []
}
// listen for service postings to THIS renderJs instance
window.addEventListener("message", priv.serviceHandler, false);
};
// => manages all interactions (listens to incoming postMessages)
// need a switch, because only one "message" listener can be set
priv.serviceHandler = function (event) {
var route = event.data.type.split("/"),
trackingId;
// authenticate all message senders
// route
switch (route[0]) {
case "service":
priv.registerNewService(event);
break;
case "request":
trackingId = priv.generateUuid();
// track this request, so we know where to send the response
priv.trackRequest(trackingId, event.originalTarget);
// request the service
priv.requestServiceFromGadget(event, trackingId);
break;
case "tree":
if (route[1] === "update") {
priv.addGadgetToTree(event.data.options, that.gadgetTree);
}
break;
case "run":
priv.runService(event);
break;
case "result":
priv.sendServiceReply(event);
break;
case "reply":
priv.returnResult(event.data.result);
break;
}
};
// => return the result to the function call
priv.returnResult = function (result) {
console.log("hello inside reply");
return result;
};
// => sends a response message after a service has been run
priv.sendServiceReply = function (event) {
var targetWindow = priv.retrieveCallingWindow(event.data.trackingId);
targetWindow.postMessage({
"type": "reply",
"result": event.data.result
}, window.location.href.split("?")[0]);
};
// => run a service and post the result
priv.runService = function (event) {
var result = window[event.data.service].apply(this, event.data.parameters);
window.top.postMessage({
"type":"result",
"result": result,
"trackingId" : event.data.trackingId,
}, window.location.href.split("?")[0]);
};
// => request a service provided by a gadget
priv.requestServiceFromGadget = function (event, trackingId) {
var callService = priv.findServiceInMap(
event.data.service, event.data.type.split("/")[1]
),
selector, i, targetWindow;
if (callService) {
// services are stored by URL (not id), so we need to find the service
// in our gadget tree by using the URL provided by the service...
// and return an id path, so we can create a selector
selector = priv.constructSelectorForService(
callService.src, that.gadgetTree, []
);
// for plain nested gadgets (no iFrame/sandbox) this will return
// only an empty array
// for iFrames/sandbox, selector will be an array of ids from
// which we have to construct our window element to postMessage to
// see http://stackoverflow.com/questions/15076293/window-postmessage-to-a-nested-iframe-in-cross-domain
// https://developer.mozilla.org/en-US/docs/Web/API/window.frames
if (selector.length === 0) {
targetWindow = window;
} else {
targetWindow = selector.reduce(function(tgt, o) {
return tgt && tgt.getElementById(o).contentWindow;
}, document);
}
// and request the service
targetWindow.postMessage({
"type":"run",
"trackingId": trackingId,
"service": event.data.service,
"parameters":event.data.parameters
}, window.location.href)
}
};
// => check whether a service is available
priv.findServiceInMap = function (requestedService, scope) {
// scope... use for ???
var i, service;
for (i = 0; i < that.gadgetService.map.length; i += 1) {
service = that.gadgetService.map[i];
if (service.service === requestedService.service) {
return service;
}
}
return null;
};
// => register a new Service to the root
priv.registerNewService = function (event) {
var i, check, addInteraction = true;
// prevent duplicate entrys of same service
for (i = 0; i < that.gadgetService.map.length; i += 1) {
check = that.gadgetService.map[i];
if (event.data.rel === check.rel) {
if (event.data.src === check.src) {
addInteraction = false;
}
}
}
if (addInteraction) {
that.gadgetService.map.push(event.data);
}
};
setReady: function (value) {
/*
* Update rendering status
*/
is_ready = value;
},
// => register gadget in index and tree
priv.registerGadget = function (data, options) {
// create index
if (that.gadgetIndex === undefined) {
priv.createGadgetIndex();
}
bindReady: function (ready_function) {
/*
* Bind a function on ready gadget loading.
*/
$("body").one("ready", ready_function);
},
// create tree
if (that.gadgetTree === undefined) {
priv.createGadgetTree();
}
checkAndTriggerReady: function () {
/*
* Trigger "ready" event only if all gadgets were marked as "ready"
*/
var is_gadget_list_loaded;
is_gadget_list_loaded = RenderJs.GadgetIndex.isGadgetListLoaded();
if (is_gadget_list_loaded) {
if (!RenderJs.isReady()) {
// backwards compatability with already written code
RenderJs.GadgetIndex.getRootGadget().getDom().
trigger("ready");
// trigger ready on root body element
$("body").trigger("ready");
// this set will make sure we fire this event only once
RenderJs.setReady(true);
}
}
return is_gadget_list_loaded;
},
// index ~ cache
priv.addGadgetToIndex(data, options);
// tree ~ lookup reference
priv.addGadgetToTree(options, that.gadgetTree);
};
// => find hardcoded services in source HTML
priv.findServiceInHTML = function (spec, root) {
var root = root ? root : document,
services, service, options, i, j;
updateGadgetData: function (gadget) {
/*
* Gadget can be updated from "data-gadget-source" (i.e. a json)
* and "data-gadget-handler" attributes (i.e. a namespace Javascript)
*/
var data_source, data_handler;
data_source = gadget.attr("data-gadget-source");
data_handler = gadget.attr("data-gadget-handler");
// acquire data and pass it to method handler
if (data_source !== undefined && data_source !== "") {
$.ajax({
url: data_source,
dataType: "json",
yourCustomData: {"data_handler": data_handler,
"gadget_id": gadget.attr("id")},
success: function (result) {
var data_handler, gadget_id;
data_handler = this.yourCustomData.data_handler;
gadget_id = this.yourCustomData.gadget_id;
if (data_handler !== undefined) {
// eval is not nice to use
eval(data_handler + "(result)");
gadget = RenderJs.GadgetIndex.getGadgetById(gadget_id);
// mark gadget as loaded and fire a check
// to see if all gadgets are loaded
gadget.setReady();
RenderJs.checkAndTriggerReady();
}
}
try {
services = root.querySelectorAll('[data-service], link[type^=service]');
for (i = 0; i < services.length; i += 1) {
service = JSON.parse(priv.getAttribute(services[i], 'service'));
for (j = 0; j < service.length; j += 1) {
options = {
"rel": service[j].rel,
"type": service[j].type,
"src": service[j].src || window.location.href.split("?")[0]
}
$(root).addService(options);
}
}
}
catch (error) {
// permission denied accessing document of foreign domains
}
};
// => find hardcoded gadgets in source HTML
priv.findGadgetinHTML = function (spec, root) {
var root = root ? root : document,
gadgets, gadget, options, i;
// need to try/catch because cross domain will not permit qsa
// > so any cross domain gadgets have to be self-sufficient
// > have renderJs and load their own gadgets!
try {
gadgets = root.querySelectorAll('[data-gadget]');
// gadget options
for (i = 0; i < gadgets.length; i += 1) {
gadget = gadgets[i];
options = {
"src" : priv.makeUrlAbsolute(priv.getAttribute(gadget, 'gadget')) || null,
"id": priv.generateUuid(),
"param" : JSON.parse(priv.getAttribute(gadget, 'param', true) || null),
"sandbox" : priv.getAttribute(gadget, 'sandbox') || false,
"iframe" : priv.getAttribute(gadget, 'iframe') || false,
"wrapper": gadget,
"directory": spec
};
// add gadget
$(root).addGadget(options);
}
}
catch(error) {
// permission denied accessing document of foreign domains
}
};
// => insert a gadget into the DOM
priv.appendGadget = function (gadgetData, options) {
var newHTML = [],
newParentElement,
newRootElement,
callback,
cleanedString,
content,
i,
element;
// update gadgetIndex
priv.registerGadget(gadgetData, options);
// MODULE, DEFAULT handler
if (gadgetData !== undefined) {
if (typeof gadgetData === "object") {
newHTML = gadgetData.data;
callback = gadgetData.callback;
} else {
// extract relevant page elements here!
cleanedString = gadgetData
.replace(priv.removeJSComments, "")
.replace(priv.removeHTMLComments,"")
.replace(priv.removeLineBreaks, "")
.replace(priv.removeWhiteSpace, " ")
.replace(priv.removeWhiteSpaceBetweenElements, "><");
// this will return a nodeList with head and body elements
// e.g. [meta, title, link, p, div]
content = $.parseHTML(cleanedString, true);
for (i = 0; i < content.length; i += 1) {
element = content[i];
switch(element.tagName) {
case "LINK":
if (element.getAttribute("type").split("/")[0] === "service") {
$(element).addService({
"src": element.getAttribute("src") || window.location.href.split("?")[0],
"type": element.getAttribute("type"),
"rel": element.getAttribute("rel")
});
// asynchronous update happens and respective thread will update status
return true;
}
return false;
},
}
break;
case "META":
case "TITLE":
break;
case "SCRIPT":
// TODOS: this is bad, problem is gadgets being injected into
// the DOM without iFrame, will also have all script tags
// inserted, so if they share any plugins (like renderJs), they
// will be re-requested and end up as additional instances
// in the same scope, so renderJs.addGadget() will trigger x-times
if (!content[i].getAttribute("src")) {
newHTML.push(content[i]);
}
break;
default:
// create a collection to append
newHTML.push(content[i]);
break;
}
}
}
// append or replace (as below, remove duplicate code later)
if (options.wrapper) {
newParentElement = options.parent[0] || options.parent;
$(options.wrapper).replaceWith( newHTML );
} else if (options.replaceParent) {
newParentElement = options.parent.parent()[0];
options.parent.replaceWith( newHTML );
} else {
newParentElement = options.parent;
$( newHTML ).prependTo(options.parent);
}
if (callback) {
callback();
}
// find recursive gadgets
$(newParentElement).findGadget();
// find recursive services
$(newParentElement).findService();
} else {
// IFRAME handler
newHTML = document.createElement("iframe");
newHTML.setAttribute("src", options.src + "?base=" + priv.encodeURI(options.directory.root));
newHTML.setAttribute("frameborder", 0);
newHTML.setAttribute("seamless", "seamless");
newHTML.setAttribute("id", options.id);
// append or replace
if (options.wrapper) {
newParentElement = options.parent[0] || options.parent;
$(options.wrapper).replaceWith( newHTML );
} else if (options.replaceParent) {
newParentElement = options.parent.parent()[0];
options.parent.replaceWith( newHTML );
} else {
newParentElement = options.parent;
$( newHTML ).prependTo(options.parent);
}
// select iframe
newRootElement = newParentElement.querySelectorAll(
'[id="'+options.id+'"]'
);
// add configuration and find recursive gadgets
$( newRootElement[0] ).load(function () {
var newElement = $(this);
newWindow = newElement[0].contentWindow;
//newHref = newWindow.location.href;
// pass parameters to nested iFrame by setting on <iframe> body
// if (options.param) {
// newElement.contents().find("body")[0].config = options.param;
// }
// find recursive gadgets
newElement.findGadget();
// find services to publish
newElement.findService();
});
}
};
// => initialize
priv.initialize = function () {
addGadget: function (dom_id, gadget_id, gadget, gadget_data_handler,
gadget_data_source, bootstrap) {
/*
* add new gadget and render it
*/
var html_string, tab_container, tab_gadget;
tab_container = $('#' + dom_id);
tab_container.empty();
html_string = [
'<div id="' + gadget_id + '"',
'data-gadget="' + gadget + '"',
'data-gadget-handler="' + gadget_data_handler + '" ',
'data-gadget-source="' + gadget_data_source + '"></div>'
].join('\n');
tab_container.append(html_string);
tab_gadget = tab_container.find('#' + gadget_id);
// render new gadget
if (bootstrap !== false) {
RenderJs.bootstrap(tab_container);
}
return tab_gadget;
},
// both root and iFrame try to map location.search, either for initial
// configuration or to retrieve the root when inside an iFrame
var spec = priv.mapUrlString(window.location.search);
Cache: (function () {
/*
* Generic cache implementation that can fall back to local
* namespace storage if no "modern" storage like localStorage
* is available
*/
return {
ROOT_CACHE_ID: 'APP_CACHE',
getCacheId: function (cache_id) {
/*
* We should have a way to 'purge' localStorage by setting a
* ROOT_CACHE_ID in all browser instances
*/
return this.ROOT_CACHE_ID + cache_id;
},
hasLocalStorage: function () {
/*
* Feature test if localStorage is supported
*/
var mod;
mod = 'localstorage_test_12345678';
try {
localStorage.setItem(mod, mod);
localStorage.removeItem(mod);
return true;
} catch (e) {
return false;
}
},
get: function (cache_id, default_value) {
/* Get cache key value */
cache_id = this.getCacheId(cache_id);
if (this.hasLocalStorage()) {
return this.LocalStorageCachePlugin.
get(cache_id, default_value);
}
//fallback to javscript namespace cache
return this.NameSpaceStorageCachePlugin.
get(cache_id, default_value);
},
set: function (cache_id, data) {
/* Set cache key value */
cache_id = this.getCacheId(cache_id);
if (this.hasLocalStorage()) {
this.LocalStorageCachePlugin.set(cache_id, data);
} else {
this.NameSpaceStorageCachePlugin.set(cache_id, data);
}
},
LocalStorageCachePlugin: (function () {
/*
* This plugin saves using HTML5 localStorage.
*/
return {
get: function (cache_id, default_value) {
/* Get cache key value */
if (localStorage.getItem(cache_id) !== null) {
return JSON.parse(localStorage.getItem(cache_id));
}
return default_value;
},
set: function (cache_id, data) {
/* Set cache key value */
localStorage.setItem(cache_id, JSON.stringify(data));
}
};
}()),
NameSpaceStorageCachePlugin: (function () {
/*
* This plugin saves within current page namespace.
*/
var namespace = {};
return {
get: function (cache_id, default_value) {
/* Get cache key value */
return namespace[cache_id];
},
set: function (cache_id, data) {
/* Set cache key value */
namespace[cache_id] = data;
}
};
}())
};
}()),
Gadget: function (gadget_id, dom) {
/*
* Javascript Gadget representation
*/
this.id = gadget_id;
this.dom = dom;
this.is_ready = false;
},
// all instances of renderJs should have an serviceMap
priv.createServiceMap(spec);
TabbularGadget: (function () {
/*
* Generic tabular gadget
*/
var gadget_list = [];
return {
toggleVisibility: function (visible_dom) {
/*
* Set tab as active visually and mark as not active rest.
*/
$(".selected").addClass("not_selected");
$(".selected").removeClass("selected");
visible_dom.addClass("selected");
visible_dom.removeClass("not_selected");
},
addNewTabGadget: function (dom_id, gadget_id, gadget, gadget_data_handler,
gadget_data_source, bootstrap) {
/*
* add new gadget and render it
*/
var tab_gadget;
tab_gadget = RenderJs.addGadget(
dom_id, gadget_id, gadget, gadget_data_handler, gadget_data_source, bootstrap
);
// we should unregister all gadgets part of this TabbularGadget
$.each(gadget_list,
function (index, gadget_id) {
var gadget = RenderJs.GadgetIndex.getGadgetById(gadget_id);
gadget.remove();
// update list of root gadgets inside TabbularGadget
gadget_list.splice($.inArray(gadget_id, gadget_list), 1);
}
);
// add it as root gadget
gadget_list.push(tab_gadget.attr("id"));
}
};
}()),
GadgetIndex: (function () {
/*
* Generic gadget index placeholder
*/
var gadget_list = [];
return {
getGadgetIdListFromDom: function (dom) {
/*
* Get list of all gadget's ID from DOM
*/
var gadget_id_list = [];
$.each(dom.find('[data-gadget]'),
function (index, value) {
gadget_id_list.push($(value).attr("id"));}
);
return gadget_id_list;
},
setGadgetList: function (gadget_list_value) {
/*
* Set list of registered gadgets
*/
gadget_list = gadget_list_value;
},
getGadgetList: function () {
/*
* Return list of registered gadgets
*/
return gadget_list;
},
registerGadget: function (gadget) {
/*
* Register gadget
*/
if (RenderJs.GadgetIndex.getGadgetById(gadget.id) === undefined) {
// register only if not already added
gadget_list.push(gadget);
}
},
unregisterGadget: function (gadget) {
/*
* Unregister gadget
*/
var index = $.inArray(gadget, gadget_list);
if (index !== -1) {
gadget_list.splice(index, 1);
}
},
getGadgetById: function (gadget_id) {
/*
* Get gadget javascript representation by its Id
*/
var gadget;
gadget = undefined;
$(RenderJs.GadgetIndex.getGadgetList()).each(
function (index, value) {
if (value.getId() === gadget_id) {
gadget = value;
}
}
);
return gadget;
},
getRootGadget: function () {
/*
* Return root gadget (always first one in list)
*/
return this.getGadgetList()[0];
},
isGadgetListLoaded: function () {
/*
* Return True if all gadgets were loaded from network or
* cache
*/
var result;
result = true;
$(this.getGadgetList()).each(
function (index, value) {
if (value.isReady() === false) {
result = false;
}
}
);
return result;
}
};
}()),
GadgetCatalog : (function () {
/*
* Gadget catalog provides API to get list of gadgets from a repository
*/
var cache_id = "setGadgetIndexUrlList";
function updateGadgetIndexFromURL(url) {
// split to base and document url
var url_list = url.split('/'),
document_url = url_list[url_list.length-1],
d = url_list.splice($.inArray(document_url, url_list), 1),
base_url = url_list.join('/'),
web_dav = jIO.newJio({
"type": "dav",
"username": "",
"password": "",
"url": base_url});
web_dav.get(document_url,
function (err, response) {
RenderJs.Cache.set(url, response);
});
}
return {
updateGadgetIndex: function () {
/*
* Update gadget index from all configured remote repositories.
*/
$.each(RenderJs.GadgetCatalog.getGadgetIndexUrlList(),
function(index, value) {
updateGadgetIndexFromURL(value);
});
},
setGadgetIndexUrlList: function (url_list) {
/*
* Set list of Gadget Index repositories.
*/
// store in Cache (html5 storage)
RenderJs.Cache.set(cache_id, url_list);
},
getGadgetIndexUrlList: function () {
/*
* Get list of Gadget Index repositories.
*/
// get from Cache (html5 storage)
return RenderJs.Cache.get(cache_id, undefined);
},
getGadgetListThatProvide: function (service) {
/*
* Return list of all gadgets that providen a given service.
* Read this list from data structure created in HTML5 local
* storage by updateGadgetIndexFromURL
*/
// get from Cache stored index and itterate over it
// to find matching ones
var gadget_list = [];
$.each(RenderJs.GadgetCatalog.getGadgetIndexUrlList(),
function(index, url) {
// get repos from cache
var cached_repo = RenderJs.Cache.get(url);
$.each(cached_repo.gadget_list,
function(index, gadget) {
if ($.inArray(service, gadget.service_list) > -1) {
// gadget provides a service, add to list
gadget_list.push(gadget);
}
}
);
});
return gadget_list;
},
registerServiceList: function (gadget, service_list) {
/*
* Register a service provided by a gadget.
*/
}
};
}()),
InteractionGadget : (function () {
/*
* Basic gadget interaction gadget implementation.
*/
return {
init: function (force) {
/*
* Inspect DOM and initialize this gadget
*/
var dom_list, gadget_id;
if (force===1) {
// we explicitly want to re-init elements even if already this is done before
dom_list = $("div[data-gadget-connection]");
}
else {
// XXX: improve and save 'bound' on javascript representation of a gadget not DOM
dom_list = $("div[data-gadget-connection]")
.filter(function() { return $(this).data("bound") !== true; })
.data('bound', true );
}
dom_list.each(function (index, element) {
RenderJs.InteractionGadget.bind($(element));});
},
bind: function (gadget_dom) {
/*
* Bind event between gadgets.
*/
var gadget_id, gadget_connection_list,
createMethodInteraction = function (
original_source_method_id, source_gadget_id,
source_method_id, destination_gadget_id,
destination_method_id) {
var interaction = function () {
// execute source method
RenderJs.GadgetIndex.getGadgetById(
source_gadget_id)[original_source_method_id].
apply(null, arguments);
// call trigger so bind can be asynchronously called
RenderJs.GadgetIndex.getGadgetById(
destination_gadget_id).dom.trigger(source_method_id);
};
return interaction;
},
createTriggerInteraction = function (
destination_gadget_id, destination_method_id) {
var interaction = function () {
RenderJs.GadgetIndex.getGadgetById(
destination_gadget_id)[destination_method_id].
apply(null, arguments);
};
return interaction;
};
gadget_id = gadget_dom.attr("id");
gadget_connection_list = gadget_dom.attr("data-gadget-connection");
gadget_connection_list = $.parseJSON(gadget_connection_list);
$.each(gadget_connection_list, function (key, value) {
var source, source_gadget_id, source_method_id,
source_gadget, destination, destination_gadget_id,
destination_method_id, destination_gadget,
original_source_method_id;
source = value.source.split(".");
source_gadget_id = source[0];
source_method_id = source[1];
source_gadget = RenderJs.GadgetIndex.
getGadgetById(source_gadget_id);
destination = value.destination.split(".");
destination_gadget_id = destination[0];
destination_method_id = destination[1];
destination_gadget = RenderJs.GadgetIndex.
getGadgetById(destination_gadget_id);
if (source_gadget.hasOwnProperty(source_method_id)) {
// direct javascript use case
original_source_method_id = "original_" +
source_method_id;
source_gadget[original_source_method_id] =
source_gadget[source_method_id];
source_gadget[source_method_id] =
createMethodInteraction(
original_source_method_id,
source_gadget_id,
source_method_id,
destination_gadget_id,
destination_method_id
);
// we use html custom events for asyncronous method call so
// bind destination_gadget to respective event
destination_gadget.dom.bind(
source_method_id,
createTriggerInteraction(
destination_gadget_id, destination_method_id
)
);
}
else {
// this is a custom event attached to HTML gadget
// representation
source_gadget.dom.bind(
source_method_id,
createTriggerInteraction(
destination_gadget_id, destination_method_id
)
);
}
});
}
};
}()),
RouteGadget : (function () {
/*
* A gadget that defines possible routes (i.e. URL changes) between gadgets.
*/
var route_list = [];
return {
init: function () {
/*
* Inspect DOM and initialize this gadget
*/
$("div[data-gadget-route]").each(function (index, element) {
RenderJs.RouteGadget.route($(element));
});
},
route: function (gadget_dom) {
/*
* Create routes between gadgets.
*/
var body = $("body"),
handler_func, priority,
gadget_route_list = gadget_dom.attr("data-gadget-route");
gadget_route_list = $.parseJSON(gadget_route_list);
$.each(gadget_route_list, function (key, gadget_route) {
handler_func = function () {
var gadget_id = gadget_route.destination.split('.')[0],
method_id = gadget_route.destination.split('.')[1],
gadget = RenderJs.GadgetIndex.getGadgetById(gadget_id);
// set gadget value so getSelfGadget can work
setSelfGadget(gadget);
gadget[method_id].apply(null, arguments);
// reset as no longer needed
setSelfGadget(undefined);
};
// add route itself
priority = gadget_route.priority;
if (priority === undefined) {
// default is 1 -i.e.first level
priority = 1;
}
RenderJs.RouteGadget.add(gadget_route.source, handler_func, priority);
});
},
add: function (path, handler_func, priority) {
/*
* Add a route between path (hashable) and a handler function (part of Gadget's API).
*/
var body = $("body");
body
.route("add", path, 1)
.done(handler_func);
// save locally
route_list.push({"path": path,
"handler_func": handler_func,
"priority": priority});
},
go: function (path, handler_func, priority) {
/*
* Go a route.
*/
var body = $("body");
body
.route("go", path, priority)
.fail(handler_func);
},
remove: function (path) {
/*
* Remove a route.
*/
// XXX: implement remove a route when route.js supports it
},
getRouteList: function () {
/*
* Get list of all router
*/
return route_list;
}
};
}())
};
}());
// Define Gadget prototype
RenderJs.Gadget.prototype.getId = function () {
return this.id;
};
RenderJs.Gadget.prototype.getDom = function () {
return this.dom;
};
RenderJs.Gadget.prototype.isReady = function () {
/*
* Return True if remote gadget is loaded into DOM.
*/
return this.is_ready;
};
RenderJs.Gadget.prototype.setReady = function () {
/*
* Return True if remote gadget is loaded into DOM.
*/
this.is_ready = true;
};
RenderJs.Gadget.prototype.remove = function () {
/*
* Remove gadget (including its DOM element).
*/
var gadget;
// unregister root from GadgetIndex
RenderJs.GadgetIndex.unregisterGadget(this);
// gadget might contain sub gadgets so before remove entire
// DOM we must unregister them from GadgetIndex
this.getDom().find("[data-gadget]").each( function () {
gadget = RenderJs.GadgetIndex.getGadgetById($(this).attr("id"));
RenderJs.GadgetIndex.unregisterGadget(gadget);
// trigger => find HTML gadgets in root document
priv.findGadgetinHTML(spec);
// trigger => find HTML coded interactions in root document
priv.findServiceInHTML(spec);
// expose API
window.renderJs = that;
};
// ================ public API (call on renderJs and $(elem) ===========
// => publish a service to this instance (and root instance)
that.addService = $.fn.addService = function (options) {
var adressArray = window.location.href.split("?"), targetUrl;
options.src = options.src || adressArray[0];
// posts to URL passed (need for CORS?)
// otherwise window.top.location.href) would also work
if (adressArray.length === 1) {
targetUrl = priv.decodeURI(adressArray[0]);
} else {
targetUrl = priv.decodeURI(adressArray[1].split("=")[1]);
}
window.top.postMessage(options, targetUrl);
};
// => request a service to be run
that.requestService = $.fn.requestService = function (options) {
// set type
if (options.type === undefined) {
options.type = "request/any";
}
window.top.postMessage(options, window.location.href);
};
// => load gadget
that.addGadget = $.fn.addGadget = function (options, callback) {
var adressArray = window.location.href.split("?");
// set parent
if (this[0] === document || this[0] === window) {
options.parent = document.body;
options.replaceParent = false;
} else {
options.parent = this;
options.replaceParent = true;
}
// set uuid
if (options.id === undefined) {
options.id = priv.generateUuid();
}
// set directory (root)
// if no ?-param is available, we can only set to href
if (options.directory === undefined) {
if (adressArray.length > 1) {
options.directory = {
"root": priv.decodeURI(adressArray[1].split("=")[1])
};
} else {
options.directory = {
"root": that.gadgetService ? that.gadgetService.root : window.location.href
}
}
}
// set offline
// set cors
// LOADING
// module
if (options.module && require !== undefined) {
require([priv.extractModuleName(options.src)], function (response) {
priv.appendGadget(response, options);
});
// iFrame
} else if (options.iframe) {
priv.appendGadget(undefined, options);
// via Ajax (default)
} else {
$.ajax({
url: options.src,
// not sure this is helpful or not
cache: true,
method: options.method || "GET",
success: function (data) {
priv.appendGadget(data, options);
},
error: function (error, status, message) {
console.log(error)
console.log(status)
console.log(message)
}
});
}
};
// => find gadgets inside a newly added gadget
that.findGadget = $.fn.findGadget = function () {
var root = root || this;
var spec = {};
if (root[0].tagName === "IFRAME") {
// will not be possible in external iframe, because of cors!
root = root[0].contentDocument || root[0].contentWindow.document;
} else {
root = root[0];
}
priv.findGadgetinHTML(spec, root);
};
// => recursive call - find services inside newly added gadget
that.findService = $.fn.findService = function () {
var root = root|| this;
var spec = {};
if (root[0].tagName === "IFRAME") {
// will not be possible in external iframe, because of cors!
root = root[0].contentDocument || root[0].contentWindow.document;
} else {
root = root[0];
}
priv.findServiceInHTML(spec, root);
};
// ================== ENTRY =============
// => start here
// should not use doc.ready, but otherwise cannot load reference body from <HEAD>
$(document).ready(function() {
// prevent renderJs reloads from different URLs!
// this does not solve the problem of re-requesting all dependencies with timestamp
// when injecting elements into a page without iFrame
if (window.renderJs === undefined) {
priv.initialize();
}
});
// remove root's entire DOM element
$(this.getDom()).remove();
};
}(jQuery, window));
// JavaScript file that is used to load RenderJs depenencies
require.config({
paths: {
jquery: "lib/jquery/jquery",
"jquery.json": "lib/json/jquery.json.min",
davstorage: "lib/jio/davstorage",
md5: "lib/jio/md5",
jio: "lib/jio/jio",
renderjs: "renderjs"
},
shim: {
"jquery.json": [ "jquery" ],
jio: ["md5"],
davstorage: ["jio"],
renderjs: [ "jquery", "jquery.json", "jio", "md5", "davstorage" ]
}
});
require([ "renderjs" ], function(domReady) {
RenderJs.init();
});
\ No newline at end of file
{
"gadget_list":[ {"title": "HTML WYSIWYG",
"description": "A simple HTML editor",
"url": "http://example.com/html-editor.html",
"service_list": ["edit_html", "view_html"]},
{"title": "SVG WYSIWYG",
"description": "A simple SVG editor",
"url": "http://example.com/svg-editor.html",
"service_list": ["edit_svg", "view_svg"]}
]
}
\ No newline at end of file
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<link rel="stylesheet" href="../lib/qunit/qunit.css" type="text/css"/>
<script src="../lib/qunit/qunit.js" type="text/javascript"></script>
<script data-main="require-renderjs_test.js"
type="text/javascript"
src="../lib/require/require.js"></script>
</head>
<body>
<h1 id="qunit-header">QUnit RenderJS test suite</h1>
<h2 id="qunit-banner"></h2>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture"> </div>
</body>
</html>
<div>A (gadget)</div>
<script type="text/javascript" language="javascript">
//<![CDATA[
$(document).ready(function() {
gadget = RenderJs.GadgetIndex.getGadgetById("A");
gadget.inc = function (){counter = counter +1;};
gadget.inc2 = function (){counter = counter +2;};
});
//]]>
</script>
\ No newline at end of file
<div> B (gadget) </div>
<script type="text/javascript" language="javascript">
//<![CDATA[
$(document).ready(function() {
gadget = RenderJs.GadgetIndex.getGadgetById("B");
gadget.inc = function (){counter = counter + 1; };
gadget.htmlEvent1 = function (){counter = counter + 1;};
gadget.inc2 = function (){counter = counter + 2; };
});
//]]>
</script>
\ No newline at end of file
<div id="A"
data-gadget="interactions/A.html"
data-gadget-cacheable="0"
data-gadget-cache-id="A"></div>
<div id="B"
data-gadget="interactions/B.html"
data-gadget-cacheable="0"
data-gadget-cache-id="B"></div>
<div data-gadget=""
id="main-interactor"
data-gadget-connection="[
{&quot;source&quot;: &quot;A.inc&quot;, &quot;destination&quot;: &quot;B.inc&quot;},
{&quot;source&quot;: &quot;A.htmlEvent1&quot;, &quot;destination&quot;: &quot;B.htmlEvent1&quot;},
{&quot;source&quot;: &quot;main-interactor.multiEvent&quot;, &quot;destination&quot;: &quot;A.inc&quot;},
{&quot;source&quot;: &quot;main-interactor.multiEvent&quot;, &quot;destination&quot;: &quot;B.inc&quot;}]"></div>
<div data-gadget=""
id="supplimentary-interactor"
data-gadget-connection="[
{&quot;source&quot;: &quot;A.inc2&quot;, &quot;destination&quot;: &quot;B.inc2&quot;}]"></div>
{"first_name": "John",
"last_name": "Doh"}
\ No newline at end of file
test!!!
\ No newline at end of file
<div id="A"
data-gadget="loading/A.html"></div>
<div id="B"
data-gadget="loading/A.html"></div>
\ No newline at end of file
test!!!
\ No newline at end of file
/*
* RenderJs tests
*/
// in tests we need to call function manually rather than rely
// on implicit calling
RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND=false;
function cleanUp () {
/*
* Clean up namespace between tests
*/
// re-init GadgetIndex
RenderJs.GadgetIndex.setGadgetList([]);
equal(0, RenderJs.GadgetIndex.getGadgetList().length);
}
// used by tests namespace variables
counter = 0;
first_name=''
last_name=''
route_changed=0
function parseJSONAndUpdateNameSpace(result) {
first_name=result['first_name'];
last_name=result['last_name'];
}
function makeid() {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 5; i++ )
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
function setupRenderJSTest(){
/*
* Main RenderJS test entry point
*/
module("Cache");
test('Cache', function () {
cache_id = 'my_test';
data = {'gg':1};
RenderJs.Cache.set(cache_id, data);
deepEqual(data, RenderJs.Cache.get(cache_id));
// test return default value if key is missing works
equal("no such key", RenderJs.Cache.get("no_such_key", "no such key"));
});
module("GadgetIndex");
test('GadgetIndex', function () {
cleanUp();
$("#qunit-fixture").append('<div data-gadget="loading/test-gadget.html" id="new">X</div>');
RenderJs.bootstrap($("#qunit-fixture"));
stop();
RenderJs.bindReady(function (){
start();
equal(RenderJs.GadgetIndex.getGadgetList().length, 1);
equal(true, RenderJs.GadgetIndex.isGadgetListLoaded());
equal("new", RenderJs.GadgetIndex.getRootGadget().getDom().attr("id"));
equal(RenderJs.GadgetIndex.getGadgetById("new"), RenderJs.GadgetIndex.getRootGadget());
dom = $("#qunit-fixture");
deepEqual(["new"], RenderJs.GadgetIndex.getGadgetIdListFromDom(dom));
// try register gadget twice and check it's registered once only
RenderJs.GadgetIndex.registerGadget(RenderJs.GadgetIndex.getGadgetById("new"));
// test unregister gadget
equal(RenderJs.GadgetIndex.getGadgetList().length, 1);
equal(RenderJs.GadgetIndex.getGadgetById("new"), RenderJs.GadgetIndex.getRootGadget());
RenderJs.GadgetIndex.unregisterGadget(RenderJs.GadgetIndex.getGadgetById("new"));
equal(RenderJs.GadgetIndex.getGadgetList().length, 0);
});
});
module("GadgetObject");
test('GadgetObject', function () {
cleanUp();
$("#qunit-fixture").append('<div data-gadget="loading/test-gadget.html" id="new-gadget">X</div>');
RenderJs.bootstrap($("#qunit-fixture"));
stop();
RenderJs.bindReady(function (){
start();
equal(RenderJs.GadgetIndex.getGadgetList().length, 1);
root_gadget = RenderJs.GadgetIndex.getRootGadget();
equal("new-gadget", root_gadget.getDom().attr("id"));
// test remove gadget
root_gadget.remove();
equal(RenderJs.GadgetIndex.getGadgetList().length, 0);
equal(0, $("#new-gadget").length);
// XXX: test removing a root gadget also removes its sibling gadgets and their DOM
});
});
module("addGadget");
test('addGadget', function () {
cleanUp();
equal(RenderJs.GadgetIndex.getGadgetList().length, 0);
RenderJs.addGadget("qunit-fixture", "new_added", "loading/test-gadget.html", "", "");
stop();
RenderJs.bindReady(function (){
start();
equal($("#qunit-fixture").children("#new_added").length, 1);
equal(RenderJs.GadgetIndex.getGadgetList().length, 1);
equal(RenderJs.GadgetIndex.getRootGadget().getDom().attr("id"), "new_added");
});
});
module("TabularGadget");
test('addTabularGadget', function () {
cleanUp();
equal(RenderJs.GadgetIndex.getGadgetList().length, 0);
RenderJs.TabbularGadget.addNewTabGadget("qunit-fixture", "new_added", "loading/test-gadget.html", "", "");
stop();
RenderJs.bindReady(function (){
start();
equal($("#qunit-fixture").children("#new_added").length, 1);
equal(RenderJs.GadgetIndex.getGadgetList().length, 1);
equal(RenderJs.GadgetIndex.getRootGadget().getDom().attr("id"), "new_added");
// XXX: test adding a new tab gadget and that old one was removed from
// both DOM and GadgetIndex
});
});
module("GadgetInitialization");
test('GadgetInitialization', function () {
cleanUp();
$("#qunit-fixture").append('<div data-gadget="" id="new-init" data-gadget-property="{&quot;name&quot;: &quot;Ivan&quot;, &quot;age&quot;: 33}">X</div>');
RenderJs.bootstrap($("#qunit-fixture"));
// test that gadget get a proper initialization from data-gadget-property
equal('Ivan', RenderJs.GadgetIndex.getGadgetById("new-init").name);
equal(33, RenderJs.GadgetIndex.getGadgetById("new-init").age);
});
module("GadgetReadyEvent");
test('GadgetReadyEvent', function () {
cleanUp();
RenderJs.addGadget("qunit-fixture", "new_added", "interactions/index.html", "", "");
stop();
// we need to wait for all gadgets loading ...
RenderJs.bindReady(function () {
start();
equal(true, RenderJs.GadgetIndex.isGadgetListLoaded());
});
});
module("InteractionGadget");
test('InteractionGadget', function () {
cleanUp();
RenderJs.addGadget("qunit-fixture", "new_add", "interactions/index.html", "", "");
stop();
// we need to wait for all gadgets loading ...
RenderJs.bindReady(function () {
RenderJs.InteractionGadget.init();
start();
equal(0, counter);
// A.inc will call B.inc, both will increase counter by 1
RenderJs.GadgetIndex.getGadgetById("A").inc();
equal(2, counter);
// fire pure HTML event on A and test it calls respective B method
$('#A').trigger('htmlEvent1');
equal(3, counter);
// fire pure HTML event that calls multiple destinations methods
// On its side these methods themself can call each other like now
// when A.inc calls B.inc thus result is 6 NOT 5!
$('#main-interactor').trigger('multiEvent');
equal(6, counter);
// check multiple interactors can coexist (a.inc2 +2 -> B.inc2 +2)
RenderJs.GadgetIndex.getGadgetById("A").inc2();
equal(10, counter);
// test force rebind
RenderJs.GadgetIndex.getGadgetById("A").inc2 = function () {return 0;};
equal(0, RenderJs.GadgetIndex.getGadgetById("A").inc2());
// rebind should not override inc2 as already changed
RenderJs.InteractionGadget.init();
equal(0, RenderJs.GadgetIndex.getGadgetById("A").inc2());
// if we force rebind it should be back to previous state
RenderJs.GadgetIndex.getGadgetById("A").inc2 = function (){counter = counter +2;};
RenderJs.InteractionGadget.init(force=1);
RenderJs.GadgetIndex.getGadgetById("A").inc2()
equal(16, counter);
// XXX: test dynamically adding an InteractionGadget
});
});
module("GadgetDataHandler");
test('GadgetDataHandler', function () {
cleanUp();
$("#qunit-fixture").append('<div data-gadget="" id="json-gadget" data-gadget-source = "json/json_file.json" data-gadget-handler="parseJSONAndUpdateNameSpace"><p>some content</p></div>');
RenderJs.bootstrap($("#qunit-fixture"));
equal('', first_name);
equal('', last_name);
stop();
// we need to wait for all gadgets loading ...
RenderJs.bindReady(function () {
start();
equal('John', first_name);
equal('Doh', last_name);
});
});
module("GadgetCatalog");
test('GadgetCatalog', function () {
cleanUp();
// allow test to be run alone (i.e. url contains arguments)
var base_url = window.location.protocol + "//" + window.location.hostname + window.location.pathname;
// generate random argument to test always with new cache id
var url_list = new Array(base_url + '/gadget_index/gadget_index.json?t='+makeid());
RenderJs.GadgetCatalog.setGadgetIndexUrlList(url_list)
deepEqual(url_list, RenderJs.GadgetCatalog.getGadgetIndexUrlList());
RenderJs.GadgetCatalog.updateGadgetIndex();
stop();
// XXX: until we have a way to know that update which runs asynchronously is over
// we use hard coded timeouts.
setTimeout(function(){
start();
cached = RenderJs.Cache.get(url_list[0]);
equal("HTML WYSIWYG", cached["gadget_list"][0]["title"]);
deepEqual(["edit_html", "view_html"], cached["gadget_list"][0]["service_list"]);
// check that we can find gadgets that provide some service_list
gadget_list = RenderJs.GadgetCatalog.getGadgetListThatProvide("edit_html");
equal("HTML WYSIWYG", gadget_list[0]["title"]);
deepEqual(["edit_html", "view_html"], gadget_list[0]["service_list"]);
gadget_list = RenderJs.GadgetCatalog.getGadgetListThatProvide("view_html");
equal("HTML WYSIWYG", gadget_list[0]["title"]);
deepEqual(["edit_html", "view_html"], gadget_list[0]["service_list"]);
gadget_list = RenderJs.GadgetCatalog.getGadgetListThatProvide("edit_svg");
equal("SVG WYSIWYG", gadget_list[0]["title"]);
deepEqual(["edit_svg", "view_svg"], gadget_list[0]["service_list"]);
// no such service is provided by gadget repos
equal(0, RenderJs.GadgetCatalog.getGadgetListThatProvide("edit_html1"));
}, 3000)
});
module("RouteGadget");
test('RouteGadget', function () {
cleanUp();
RenderJs.addGadget("qunit-fixture", "new_route_add", "route/index.html", "", "");
equal(0, route_changed);
stop();
// we need to wait for all gadgets loading ...
RenderJs.bindReady(function () {
start();
// initialize route gadget as it's loaded asynchronously
RenderJs.RouteGadget.init();
var path_list = [];
$.each(RenderJs.RouteGadget.getRouteList(),
function (index, value) {
path_list.push(value.path);
});
equal(3, RenderJs.RouteGadget.getRouteList().length);
deepEqual(["/gadget-one/", "/gadget-two/","/gadget-three/"], path_list);
// listen to event and do actual routing
$.url.onhashchange(function () {
RenderJs.RouteGadget.go($.url.getPath(),
function () {
console.log("no route");});
});
$.url.go('/gadget-one/');
// give some time so .render finishes, most likely we need some event like .bindReady
// to indicate that respective .render method finishes in generic RenderJs ?
stop();
setTimeout( function () {
// respective gadget .render method must be called and global var changed
equal(1, route_changed);
equal('/gadget-one/', window.location.toString().split('#')[1]);
start();
$.url.go('/gadget-two/');
stop();
setTimeout( function () {
// respective gadget .render method must be called and global var changed
equal(2, route_changed);
equal('/gadget-two/', window.location.toString().split('#')[1]);
start();
$.url.go('/gadget-three/');
stop();
setTimeout( function () {
// respective gadget .render method must be called and global var changed
equal(3, route_changed);
equal('/gadget-three/', window.location.toString().split('#')[1]);
start();
}, 1000);
}, 1000);
}, 1000);
// XXX: test dynamically adding a RouteGadget
});
});
module("GadgetMultipleInstances");
test('GadgetMultipleInstances', function () {
cleanUp();
$("#qunit-fixture").append('<div data-gadget="self_gadget/self.html" id="self1"</div>');
$("#qunit-fixture").append('<div data-gadget="self_gadget/self.html" id="self2"</div>');
$("#qunit-fixture").append('<div data-gadget="self_gadget/self.html" id="self3"</div>');
RenderJs.bootstrap($("#qunit-fixture"));
stop();
// we need to wait for all gadgets loading ...
RenderJs.bindReady(function () {
start();
// check it's instanciated properly and getSelfGadget returns proper gadget instance
equal("self1", RenderJs.GadgetIndex.getGadgetById("self1").instance_id);
equal("self2", RenderJs.GadgetIndex.getGadgetById("self2").instance_id);
equal("self3", RenderJs.GadgetIndex.getGadgetById("self3").instance_id);
// getSelfGadget works only when gadget is loaded so it must be reset in all other cases
equal(undefined, RenderJs.getSelfGadget());
// only 3 instance should exist
equal(3, RenderJs.GadgetIndex.getGadgetList().length);
});
});
};
// JavaScript file that is used to load RenderJs depenencies
require.config({
baseUrl: "..",
paths: {
route: "lib/route/route",
url: "lib/route/url",
jquery: "lib/jquery/jquery",
renderjs: "renderjs",
},
shim: {
"test/renderjs_test": [ "renderjs" ],
"url": ["renderjs"],
"route": ["url"]
}
});
require([ "require-renderjs", "test/renderjs_test", "url", "route" ], function(domReady) {
setupRenderJSTest();
});
<html>
<head></head>
<body>
<script type="text/javascript" language="javascript">
//<![CDATA[
$(document).ready(function() {
gadget = RenderJs.GadgetIndex.getGadgetById("gadget-one");
gadget.gadget_one = function () {
route_changed=1; // so we can test this method was actually called from qunit
};
gadget.gadget_two = function () {
route_changed=2; // so we can test this method was actually called from qunit
};
gadget.gadget_three = function () {
route_changed=3; // so we can test this method was actually called from qunit
};
});
//]]>
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div data-gadget=""
id="main-router"
data-gadget-route="[
{&quot;source&quot;: &quot;/gadget-one/&quot;, &quot;destination&quot;: &quot;gadget-one.gadget_one&quot;},
{&quot;source&quot;: &quot;/gadget-two/&quot;, &quot;destination&quot;: &quot;gadget-one.gadget_two&quot;}]">
</div>
<div data-gadget=""
id="suplimentary-router"
data-gadget-route="[
{&quot;source&quot;: &quot;/gadget-three/&quot;, &quot;destination&quot;: &quot;gadget-one.gadget_three&quot;}]">
</div>
<div id="gadget-one"
data-gadget="route/gadget-one.html"></div>
</body>
</html>
\ No newline at end of file
<script type="text/javascript" language="javascript">
//<![CDATA[
$(document).ready(function() {
gadget = RenderJs.getSelfGadget();
gadget.instance_id = gadget.getId();
});
//]]>
</script>
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment