diff --git a/pom.xml b/pom.xml
index aeeea7b..b2c3247 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,6 +43,17 @@
false
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+
+ jar
+
+
+
+
diff --git a/src/main/java/pl/labno/bernard/htmlunified/DomChangeLogger.java b/src/main/java/pl/labno/bernard/htmlunified/DomChangeLogger.java
index b809ca9..ff18684 100644
--- a/src/main/java/pl/labno/bernard/htmlunified/DomChangeLogger.java
+++ b/src/main/java/pl/labno/bernard/htmlunified/DomChangeLogger.java
@@ -7,9 +7,13 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Node;
+import java.util.HashSet;
+import java.util.Set;
+
public class DomChangeLogger implements DomChangeListener {
private static final Log LOG = LogFactory.getLog(DomChangeLogger.class);
+ private Set detailIds = new HashSet();
public void nodeAdded(DomChangeEvent event) {
logEvent(event, "added");
@@ -19,6 +23,14 @@ public class DomChangeLogger implements DomChangeListener {
logEvent(event, "removed");
}
+ public void showDetails(String id) {
+ detailIds.add(id);
+ }
+
+ public void hideDetails(String id) {
+ detailIds.remove(id);
+ }
+
private void logEvent(DomChangeEvent event, String action) {
String changed;
final DomNode changedNode = event.getChangedNode();
@@ -36,7 +48,7 @@ public class DomChangeLogger implements DomChangeListener {
}
LOG.info("DomChangeEvent[action=" + action + ";changedElement=" + changed + ";source=" + event.getSource() + ";parent=" + parent + "]");
final Node id = event.getChangedNode().getAttributes().getNamedItem("id");
- if (id != null && "searchClass:searchStudent".equals(id.getTextContent())) {
+ if (id != null && detailIds.contains(id.getTextContent())) {
LOG.info(event.getChangedNode().asXml());
}
}
diff --git a/src/main/java/pl/labno/bernard/htmlunified/WebClientUtils.java b/src/main/java/pl/labno/bernard/htmlunified/WebClientUtils.java
index 8c23130..b65af72 100644
--- a/src/main/java/pl/labno/bernard/htmlunified/WebClientUtils.java
+++ b/src/main/java/pl/labno/bernard/htmlunified/WebClientUtils.java
@@ -1,11 +1,15 @@
package pl.labno.bernard.htmlunified;
import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.DomNodeList;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlTableCell;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
public class WebClientUtils {
@@ -29,19 +33,27 @@ public class WebClientUtils {
WebClientUtils.defaultTimeout = defaultTimeout;
}
- public static int waitForJSJob(WebClient webClient) {
- return waitForJSJob(webClient, webClient.waitForBackgroundJavaScript(10) - 1);
+ public static int waitForJSJob(String message, WebClient webClient) {
+ return waitForJSJob(message, webClient, webClient.waitForBackgroundJavaScript(10) - 1);
}
- public static int waitForJSJob(WebClient webClient, int initialJobCount) {
- return waitForJSJob(webClient, initialJobCount, defaultTimeout);
+ public static int waitForJSJob(String message, WebClient webClient, int initialJobCount) {
+ return waitForJSJob(message, webClient, initialJobCount, defaultTimeout);
}
- public static int waitForJSJob(WebClient webClient, int initialJobCount, long timeout) {
- return waitForJSJob(webClient, initialJobCount, timeout, defaultCheckInterval);
+ public static int waitForJSJob(WebClient webClient, int initialJobCount, int timeout) {
+ return waitForJSJob(null, webClient, initialJobCount, timeout);
}
- public static int waitForJSJob(WebClient webClient, int initialJobCount, long timeout, long checkInterval) {
+ public static int waitForJSJob(String message, WebClient webClient, int initialJobCount, long timeout) {
+ return waitForJSJob(message, webClient, initialJobCount, timeout, defaultCheckInterval);
+ }
+
+ public static int waitForJSJob(String message, WebClient webClient, int initialJobCount, int timeout) {
+ return waitForJSJob(message, webClient, initialJobCount, timeout, defaultCheckInterval);
+ }
+
+ public static int waitForJSJob(String message, WebClient webClient, int initialJobCount, long timeout, long checkInterval) {
int jobs;
long startTime = System.currentTimeMillis();
do {
@@ -50,7 +62,7 @@ public class WebClientUtils {
throw new RuntimeException("Number of JavaScript jobs doesn't drop to initial level for " + timeout + " seconds. It's memory leak in your JavaScript rather then request taking so long!");
}
} while (jobs > initialJobCount);
- System.out.println("Waiting took: " + (System.currentTimeMillis() - startTime) + "ms");
+ System.out.println("Waiting" + (message == null ? "" : " for " + message) + " took: " + (System.currentTimeMillis() - startTime) + "ms");
return jobs;
}
@@ -88,4 +100,32 @@ public class WebClientUtils {
}
} while (startTime + timeout > System.currentTimeMillis());
}
+
+ public static void executeAjaxReRenderedScripts(HtmlPage page) {
+ final DomNodeList scripts = page.getElementsByTagName("script");
+ /**
+ * We cannot iterate over html DomNodeList cause it depends on sibling relationship which we will modify.
+ */
+ final List scriptsList = new ArrayList();
+ for (HtmlElement element : scripts) {
+ scriptsList.add(element);
+ }
+ for (HtmlElement element : scriptsList) {
+ if (element.getChildNodes().size() > 1) {
+ element.removeChild(element.getFirstChild());
+ final DomNode sibling = element.getNextSibling();
+ final DomNode parentNode = element.getParentNode();
+ /**
+ * Script will be executed upon inserting into DOM tree, so we removed and add it again.
+ */
+ if (sibling != null) {
+ parentNode.removeChild(element);
+ sibling.insertBefore(element);
+ } else {
+ parentNode.removeChild(element);
+ parentNode.appendChild(element);
+ }
+ }
+ }
+ }
}
diff --git a/src/test/java/pl/labno/bernard/htmlunified/ClassDetailsTest.java b/src/test/java/pl/labno/bernard/htmlunified/ClassDetailsTest.java
new file mode 100644
index 0000000..18cc828
--- /dev/null
+++ b/src/test/java/pl/labno/bernard/htmlunified/ClassDetailsTest.java
@@ -0,0 +1,97 @@
+package pl.labno.bernard.htmlunified;
+
+import com.gargoylesoftware.htmlunit.BrowserVersion;
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.HtmlElement;
+import com.gargoylesoftware.htmlunit.html.HtmlInput;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.html.HtmlSpan;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+
+public class ClassDetailsTest {
+
+ @Test
+ public void openDetailsModalPanel() throws IOException {
+ WebClient client = new WebClient(BrowserVersion.FIREFOX_3_6);
+ HtmlPage page = login(client);
+ @SuppressWarnings("unchecked")
+ final List titleElements = (List) page.getElementById("results:schedule").getByXPath(".//span[@class='fc-event-title']");
+ Assert.assertNotSame(0, titleElements.size());
+ new RequestResponseLogger(client);
+ page.addDomChangeListener(new DomChangeLogger());
+ WebClientUtils.waitForJSJob("initial javascript to finish", client, 0, 30000);
+ System.out.println("item:" + titleElements.get(0).asText());
+ ((HtmlElement) titleElements.get(0).getParentNode()).mouseOver();
+ ((HtmlElement) titleElements.get(0).getParentNode()).click();
+ WebClientUtils.waitForJSJob("classDetailsModalPanel shown after schedule event clicked", client, 0, 30000);
+ Assert.assertNotNull(page.getElementById("classDetailsModalPanelContainer"));
+ }
+
+ @Test
+ public void closeDetailsModalPanel() throws IOException {
+ WebClient client = new WebClient(BrowserVersion.FIREFOX_3_6);
+ HtmlPage page = login(client);
+ @SuppressWarnings("unchecked")
+ final List titleElements = (List) page.getElementById("results:schedule").getByXPath(".//span[@class='fc-event-title']");
+ Assert.assertNotSame(0, titleElements.size());
+// new RequestResponseLogger(client);
+// page.addDomChangeListener(new DomChangeLogger());
+ WebClientUtils.waitForJSJob("initial javascript to finish", client, 0, 30000);
+ System.out.println("item:" + titleElements.get(0).asText());
+ ((HtmlElement) titleElements.get(0).getParentNode()).mouseOver();
+ ((HtmlElement) titleElements.get(0).getParentNode()).click();
+ WebClientUtils.waitForJSJob("classDetailsModalPanel shown after schedule event clicked", client, 0, 30000);
+ Assert.assertNotNull(page.getElementById("classDetailsModalPanelContainer"));
+ ((HtmlElement) page.getElementById("classDetailsModalPanelContainer").getByXPath("//*[contains(@src,'close.png')]").get(0)).click();
+ WebClientUtils.waitForJSJob("classDetailsModalPanel hidden after cancel button", client, 0, 30000);
+ Assert.assertNull(page.getElementById("classDetailsModalPanelContainer"));
+ }
+
+ @Test
+ public void removeClass() throws IOException {
+ WebClient client = new WebClient(BrowserVersion.FIREFOX_2);
+ final RequestResponseLogger requestResponseLogger = new RequestResponseLogger(client);
+ requestResponseLogger.off();
+ HtmlPage page = login(client);
+ @SuppressWarnings("unchecked")
+ final List titleElements = (List) page.getElementById("results:schedule").getByXPath(".//span[@class='fc-event-title']");
+ Assert.assertEquals(6, titleElements.size());
+ Assert.assertNotSame(0, titleElements.size());
+ WebClientUtils.waitForJSJob("initial javascript to finish", client, 0, 30000);
+
+ client.setAlertHandler(new AlertLogger());
+ final DomChangeLogger domChangeLogger = new DomChangeLogger();
+ domChangeLogger.showDetails("results:j_id2158");
+// page.addDomChangeListener(domChangeLogger);
+// page.addHtmlAttsributeChangeListener(new HtmlAttributeChangeLogger());
+// requestResponseLogger.on();
+
+ ((HtmlElement) titleElements.get(0).getParentNode()).mouseOver();
+ ((HtmlElement) titleElements.get(0).getParentNode()).click();
+ WebClientUtils.waitForJSJob("classDetailsModalPanel shown after schedule event clicked", client, 0, 30000);
+ WebClientUtils.executeAjaxReRenderedScripts(page);
+ Assert.assertNotNull(page.getElementById("classDetailsModalPanelContainer"));
+ ((HtmlElement) page.getElementById("classDetailsModalPanelContainer").getByXPath("//*[contains(@src,'remove.png')]").get(0)).click();
+ WebClientUtils.waitForJSJob("classDetailsModalPanel hidden after remove button", client, 0, 30000);
+ WebClientUtils.executeAjaxReRenderedScripts(page);
+ WebClientUtils.waitForJSJob("schedule refresh", client, 0, 30000);
+ Assert.assertNull(page.getElementById("classDetailsModalPanelContainer"));
+ Assert.assertEquals(5, page.getElementById("results:schedule").getByXPath(".//span[@class='fc-event-title']").size());
+ }
+
+ private HtmlPage login(WebClient client) throws IOException {
+ HtmlPage page = (HtmlPage) client.getPage("http://localhost:8080/schoolmanager/view/class/current.seam?networkId=salsafactory");
+ ((HtmlInput) page.getElementById("loginForm:email")).setValueAttribute("it.crowd.test@gmail.com");
+ ((HtmlInput) page.getElementById("loginForm:password")).setValueAttribute("aaaaa");
+ HtmlPage oldPage = page;
+ page = page.getElementById("loginForm:submit").click();
+// This assert would fail (when page redidrects than you need to obtain new page)
+// Assert.assertEquals(page,oldPage);
+ Assert.assertEquals(client.getCurrentWindow().getEnclosedPage(), page);
+ return page;
+ }
+}
diff --git a/src/test/java/pl/labno/bernard/htmlunified/SandboxTest.java b/src/test/java/pl/labno/bernard/htmlunified/SandboxTest.java
index 22a4039..8623fca 100644
--- a/src/test/java/pl/labno/bernard/htmlunified/SandboxTest.java
+++ b/src/test/java/pl/labno/bernard/htmlunified/SandboxTest.java
@@ -1,6 +1,7 @@
package pl.labno.bernard.htmlunified;
import com.gargoylesoftware.htmlunit.BrowserVersion;
+import com.gargoylesoftware.htmlunit.CollectingAlertHandler;
import com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
@@ -8,11 +9,12 @@ import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
+import java.util.ArrayList;
public class SandboxTest {
@Test
- public void test() throws IOException {
+ public void testClickImage() throws IOException {
final WebClient client = new WebClient(BrowserVersion.FIREFOX_3_6);
client.setAjaxController(new NicelyResynchronizingAjaxController());
HtmlPage page = (HtmlPage) client.getPage("file:./target/test-classes/sandbox.html");
@@ -20,4 +22,23 @@ public class SandboxTest {
page.getElementById("close").click();
Assert.assertEquals("", page.getElementById("elementToHide").asText());
}
+
+ @Test
+ public void testClickJQueryBoundElement() throws IOException {
+ final WebClient client = new WebClient(BrowserVersion.FIREFOX_3_6);
+ client.setAjaxController(new NicelyResynchronizingAjaxController());
+ HtmlPage page = (HtmlPage) client.getPage("file:./target/test-classes/sandbox.html");
+ final ArrayList alerts = new ArrayList();
+ client.setAlertHandler(new CollectingAlertHandler(alerts));
+ page.getElementById("clickable").click();
+ System.out.println(alerts);
+ Assert.assertEquals(1, alerts.size());
+ Assert.assertEquals("Clicked clickable", alerts.get(0));
+
+ alerts.clear();
+ page.getElementById("clickable2child").click();
+ System.out.println(alerts);
+ Assert.assertEquals(1, alerts.size());
+ Assert.assertEquals("Clicked clickable2", alerts.get(0));
+ }
}
diff --git a/src/test/java/pl/labno/bernard/htmlunified/ScheduleTest.java b/src/test/java/pl/labno/bernard/htmlunified/ScheduleTest.java
index f906a31..a7206c7 100644
--- a/src/test/java/pl/labno/bernard/htmlunified/ScheduleTest.java
+++ b/src/test/java/pl/labno/bernard/htmlunified/ScheduleTest.java
@@ -30,7 +30,7 @@ public class ScheduleTest {
for (HtmlSpan o : titleElements) {
titles.add(o.asText());
}
- Assert.assertEquals(4, titles.size());
+ Assert.assertEquals(6, titles.size());
}
@Test
@@ -58,7 +58,6 @@ public class ScheduleTest {
System.out.println("Success!");
-// page = (HtmlPage) client.getPage("http://localhost:8080/schoolmanager/view/class/current.seam?networkId=salsafactory");
WebClientUtils.waitForJSJob(client, 0, 30000);
Assert.assertEquals(client.getCurrentWindow().getEnclosedPage(), page);
studentInput = (HtmlInput) page.getElementById("searchClass:student_i");
@@ -66,7 +65,6 @@ public class ScheduleTest {
studentInput.type('l');
studentInput.type('i');
Assert.assertEquals(client.getCurrentWindow().getEnclosedPage(), page);
-// WebClientUtils.forceWait(2000);
// Here the suggestionBox is still hidden
WebClientUtils.waitForJSJob(client, 0, 30000);
// Here suggestionBox is visible
@@ -138,7 +136,7 @@ public class ScheduleTest {
private HtmlPage login(WebClient client) throws IOException {
HtmlPage page = (HtmlPage) client.getPage("http://localhost:8080/schoolmanager/view/class/current.seam?networkId=salsafactory");
- ((HtmlInput) page.getElementById("loginForm:email")).setValueAttribute("s4237@pjwstk.edu.pl");
+ ((HtmlInput) page.getElementById("loginForm:email")).setValueAttribute("it.crowd.test@gmail.com");
((HtmlInput) page.getElementById("loginForm:password")).setValueAttribute("aaaaa");
HtmlPage oldPage = page;
page = page.getElementById("loginForm:submit").click();
diff --git a/src/test/resources/jquery-1.3.2.js b/src/test/resources/jquery-1.3.2.js
new file mode 100644
index 0000000..e5fb9a5
--- /dev/null
+++ b/src/test/resources/jquery-1.3.2.js
@@ -0,0 +1,4420 @@
+/*!
+ * jQuery JavaScript Library v1.3.2
+ * http://jquery.com/
+ *
+ * Copyright (c) 2009 John Resig
+ * Dual licensed under the MIT and GPL licenses.
+ * http://docs.jquery.com/License
+ *
+ * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
+ * Revision: 6246
+ */
+(function() {
+
+ var
+ // Will speed up references to window, and allows munging its name.
+ window = this,
+ // Will speed up references to undefined, and allows munging its name.
+ undefined,
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ jQuery = window.jQuery = window.$ = function(selector, context) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init(selector, context);
+ },
+
+ // A simple way to check for HTML strings or ID strings
+ // (both of which we optimize for)
+ quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
+ // Is it a simple selector
+ isSimple = /^.[^:#\[\.,]*$/;
+
+ jQuery.fn = jQuery.prototype = {
+ init: function(selector, context) {
+ // Make sure that a selection was provided
+ selector = selector || document;
+
+ // Handle $(DOMElement)
+ if (selector.nodeType) {
+ this[0] = selector;
+ this.length = 1;
+ this.context = selector;
+ return this;
+ }
+ // Handle HTML strings
+ if (typeof selector === "string") {
+ // Are we dealing with HTML string or an ID?
+ var match = quickExpr.exec(selector);
+
+ // Verify a match, and that no context was specified for #id
+ if (match && (match[1] || !context)) {
+
+ // HANDLE: $(html) -> $(array)
+ if (match[1])
+ selector = jQuery.clean([ match[1] ], context);
+
+ // HANDLE: $("#id")
+ else {
+ var elem = document.getElementById(match[3]);
+
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if (elem && elem.id != match[3])
+ return jQuery().find(selector);
+
+ // Otherwise, we inject the element directly into the jQuery object
+ var ret = jQuery(elem || []);
+ ret.context = document;
+ ret.selector = selector;
+ return ret;
+ }
+
+ // HANDLE: $(expr, [context])
+ // (which is just equivalent to: $(content).find(expr)
+ } else
+ return jQuery(context).find(selector);
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if (jQuery.isFunction(selector))
+ return jQuery(document).ready(selector);
+
+ // Make sure that old selector state is passed along
+ if (selector.selector && selector.context) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return this.setArray(jQuery.isArray(selector) ?
+ selector :
+ jQuery.makeArray(selector));
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.3.2",
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function(num) {
+ return num === undefined ?
+
+ // Return a 'clean' array
+ Array.prototype.slice.call(this) :
+
+ // Return just the object
+ this[ num ];
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function(elems, name, selector) {
+ // Build a new jQuery matched element set
+ var ret = jQuery(elems);
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if (name === "find")
+ ret.selector = this.selector + (this.selector ? " " : "") + selector;
+ else if (name)
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Force the current matched set of elements to become
+ // the specified array of elements (destroying the stack in the process)
+ // You should use pushStack() in order to do this, but maintain the stack
+ setArray: function(elems) {
+ // Resetting the length to 0, then using the native Array push
+ // is a super-fast way to populate an object with array-like properties
+ this.length = 0;
+ Array.prototype.push.apply(this, elems);
+
+ return this;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function(callback, args) {
+ return jQuery.each(this, callback, args);
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function(elem) {
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem && elem.jquery ? elem[0] : elem
+ , this);
+ },
+
+ attr: function(name, value, type) {
+ var options = name;
+
+ // Look for the case where we're accessing a style value
+ if (typeof name === "string")
+ if (value === undefined)
+ return this[0] && jQuery[ type || "attr" ](this[0], name);
+
+ else {
+ options = {};
+ options[ name ] = value;
+ }
+
+ // Check to see if we're setting style values
+ return this.each(function(i) {
+ // Set all the styles
+ for (name in options)
+ jQuery.attr(
+ type ?
+ this.style :
+ this,
+ name, jQuery.prop(this, options[ name ], type, i, name)
+ );
+ });
+ },
+
+ css: function(key, value) {
+ // ignore negative width and height values
+ if ((key == 'width' || key == 'height') && parseFloat(value) < 0)
+ value = undefined;
+ return this.attr(key, value, "curCSS");
+ },
+
+ text: function(text) {
+ if (typeof text !== "object" && text != null)
+ return this.empty().append((this[0] && this[0].ownerDocument || document).createTextNode(text));
+
+ var ret = "";
+
+ jQuery.each(text || this, function() {
+ jQuery.each(this.childNodes, function() {
+ if (this.nodeType != 8)
+ ret += this.nodeType != 1 ?
+ this.nodeValue :
+ jQuery.fn.text([ this ]);
+ });
+ });
+
+ return ret;
+ },
+
+ wrapAll: function(html) {
+ if (this[0]) {
+ // The elements to wrap the target around
+ var wrap = jQuery(html, this[0].ownerDocument).clone();
+
+ if (this[0].parentNode)
+ wrap.insertBefore(this[0]);
+
+ wrap.map(function() {
+ var elem = this;
+
+ while (elem.firstChild)
+ elem = elem.firstChild;
+
+ return elem;
+ }).append(this);
+ }
+
+ return this;
+ },
+
+ wrapInner: function(html) {
+ return this.each(function() {
+ jQuery(this).contents().wrapAll(html);
+ });
+ },
+
+ wrap: function(html) {
+ return this.each(function() {
+ jQuery(this).wrapAll(html);
+ });
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, function(elem) {
+ if (this.nodeType == 1)
+ this.appendChild(elem);
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, function(elem) {
+ if (this.nodeType == 1)
+ this.insertBefore(elem, this.firstChild);
+ });
+ },
+
+ before: function() {
+ return this.domManip(arguments, false, function(elem) {
+ this.parentNode.insertBefore(elem, this);
+ });
+ },
+
+ after: function() {
+ return this.domManip(arguments, false, function(elem) {
+ this.parentNode.insertBefore(elem, this.nextSibling);
+ });
+ },
+
+ end: function() {
+ return this.prevObject || jQuery([]);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: [].push,
+ sort: [].sort,
+ splice: [].splice,
+
+ find: function(selector) {
+ if (this.length === 1) {
+ var ret = this.pushStack([], "find", selector);
+ ret.length = 0;
+ jQuery.find(selector, this[0], ret);
+ return ret;
+ } else {
+ return this.pushStack(jQuery.unique(jQuery.map(this, function(elem) {
+ return jQuery.find(selector, elem);
+ })), "find", selector);
+ }
+ },
+
+ clone: function(events) {
+ // Do the clone
+ var ret = this.map(function() {
+ if (!jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this)) {
+ // IE copies events bound via attachEvent when
+ // using cloneNode. Calling detachEvent on the
+ // clone will also remove the events from the orignal
+ // In order to get around this, we use innerHTML.
+ // Unfortunately, this means some modifications to
+ // attributes in IE that are actually only stored
+ // as properties will not be copied (such as the
+ // the name attribute on an input).
+ var html = this.outerHTML;
+ if (!html) {
+ var div = this.ownerDocument.createElement("div");
+ div.appendChild(this.cloneNode(true));
+ html = div.innerHTML;
+ }
+
+ return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0];
+ } else
+ return this.cloneNode(true);
+ });
+
+ // Copy the events from the original to the clone
+ if (events === true) {
+ var orig = this.find("*").andSelf(), i = 0;
+
+ ret.find("*").andSelf().each(function() {
+ if (this.nodeName !== orig[i].nodeName)
+ return;
+
+ var events = jQuery.data(orig[i], "events");
+
+ for (var type in events) {
+ for (var handler in events[ type ]) {
+ jQuery.event.add(this, type, events[ type ][ handler ], events[ type ][ handler ].data);
+ }
+ }
+
+ i++;
+ });
+ }
+
+ // Return the cloned set
+ return ret;
+ },
+
+ filter: function(selector) {
+ return this.pushStack(
+ jQuery.isFunction(selector) &&
+ jQuery.grep(this, function(elem, i) {
+ return selector.call(elem, i);
+ }) ||
+
+ jQuery.multiFilter(selector, jQuery.grep(this, function(elem) {
+ return elem.nodeType === 1;
+ })), "filter", selector);
+ },
+
+ closest: function(selector) {
+ var pos = jQuery.expr.match.POS.test(selector) ? jQuery(selector) : null,
+ closer = 0;
+
+ return this.map(function() {
+ var cur = this;
+ while (cur && cur.ownerDocument) {
+ if (pos ? pos.index(cur) > -1 : jQuery(cur).is(selector)) {
+ jQuery.data(cur, "closest", closer);
+ return cur;
+ }
+ cur = cur.parentNode;
+ closer++;
+ }
+ });
+ },
+
+ not: function(selector) {
+ if (typeof selector === "string")
+ // test special case where just one selector is passed in
+ if (isSimple.test(selector))
+ return this.pushStack(jQuery.multiFilter(selector, this, true), "not", selector);
+ else
+ selector = jQuery.multiFilter(selector, this);
+
+ var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
+ return this.filter(function() {
+ return isArrayLike ? jQuery.inArray(this, selector) < 0 : this != selector;
+ });
+ },
+
+ add: function(selector) {
+ return this.pushStack(jQuery.unique(jQuery.merge(
+ this.get(),
+ typeof selector === "string" ?
+ jQuery(selector) :
+ jQuery.makeArray(selector)
+ )));
+ },
+
+ is: function(selector) {
+ return !!selector && jQuery.multiFilter(selector, this).length > 0;
+ },
+
+ hasClass: function(selector) {
+ return !!selector && this.is("." + selector);
+ },
+
+ val: function(value) {
+ if (value === undefined) {
+ var elem = this[0];
+
+ if (elem) {
+ if (jQuery.nodeName(elem, 'option'))
+ return (elem.attributes.value || {}).specified ? elem.value : elem.text;
+
+ // We need to handle select boxes special
+ if (jQuery.nodeName(elem, "select")) {
+ var index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type == "select-one";
+
+ // Nothing was selected
+ if (index < 0)
+ return null;
+
+ // Loop through all the selected options
+ for (var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++) {
+ var option = options[ i ];
+
+ if (option.selected) {
+ // Get the specifc value for the option
+ value = jQuery(option).val();
+
+ // We don't need an array for one selects
+ if (one)
+ return value;
+
+ // Multi-Selects return an array
+ values.push(value);
+ }
+ }
+
+ return values;
+ }
+
+ // Everything else, we just grab the value
+ return (elem.value || "").replace(/\r/g, "");
+
+ }
+
+ return undefined;
+ }
+
+ if (typeof value === "number")
+ value += '';
+
+ return this.each(function() {
+ if (this.nodeType != 1)
+ return;
+
+ if (jQuery.isArray(value) && /radio|checkbox/.test(this.type))
+ this.checked = (jQuery.inArray(this.value, value) >= 0 ||
+ jQuery.inArray(this.name, value) >= 0);
+
+ else if (jQuery.nodeName(this, "select")) {
+ var values = jQuery.makeArray(value);
+
+ jQuery("option", this).each(function() {
+ this.selected = (jQuery.inArray(this.value, values) >= 0 ||
+ jQuery.inArray(this.text, values) >= 0);
+ });
+
+ if (!values.length)
+ this.selectedIndex = -1;
+
+ } else
+ this.value = value;
+ });
+ },
+
+ html: function(value) {
+ return value === undefined ?
+ (this[0] ?
+ this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") :
+ null) :
+ this.empty().append(value);
+ },
+
+ replaceWith: function(value) {
+ return this.after(value).remove();
+ },
+
+ eq: function(i) {
+ return this.slice(i, +i + 1);
+ },
+
+ slice: function() {
+ return this.pushStack(Array.prototype.slice.apply(this, arguments),
+ "slice", Array.prototype.slice.call(arguments).join(","));
+ },
+
+ map: function(callback) {
+ return this.pushStack(jQuery.map(this, function(elem, i) {
+ return callback.call(elem, i, elem);
+ }));
+ },
+
+ andSelf: function() {
+ return this.add(this.prevObject);
+ },
+
+ domManip: function(args, table, callback) {
+ if (this[0]) {
+ var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(),
+ scripts = jQuery.clean(args, (this[0].ownerDocument || this[0]), fragment),
+ first = fragment.firstChild;
+
+ if (first)
+ for (var i = 0, l = this.length; i < l; i++)
+ callback.call(root(this[i], first), this.length > 1 || i > 0 ?
+ fragment.cloneNode(true) : fragment);
+
+ if (scripts)
+ jQuery.each(scripts, evalScript);
+ }
+
+ return this;
+
+ function root(elem, cur) {
+ return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ?
+ (elem.getElementsByTagName("tbody")[0] ||
+ elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+ elem;
+ }
+ }
+ };
+
+ // Give the init function the jQuery prototype for later instantiation
+ jQuery.fn.init.prototype = jQuery.fn;
+
+ function evalScript(i, elem) {
+ if (elem.src)
+ jQuery.ajax({
+ url: elem.src,
+ async: false,
+ dataType: "script"
+ });
+
+ else
+ jQuery.globalEval(elem.text || elem.textContent || elem.innerHTML || "");
+
+ if (elem.parentNode)
+ elem.parentNode.removeChild(elem);
+ }
+
+ function now() {
+ return +new Date;
+ }
+
+ jQuery.extend = jQuery.fn.extend = function() {
+ // copy reference to target object
+ var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
+
+ // Handle a deep copy situation
+ if (typeof target === "boolean") {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if (typeof target !== "object" && !jQuery.isFunction(target))
+ target = {};
+
+ // extend jQuery itself if only one argument is passed
+ if (length == i) {
+ target = this;
+ --i;
+ }
+
+ for (; i < length; i++)
+ // Only deal with non-null/undefined values
+ if ((options = arguments[ i ]) != null)
+ // Extend the base object
+ for (var name in options) {
+ var src = target[ name ], copy = options[ name ];
+
+ // Prevent never-ending loop
+ if (target === copy)
+ continue;
+
+ // Recurse if we're merging object values
+ if (deep && copy && typeof copy === "object" && !copy.nodeType)
+ target[ name ] = jQuery.extend(deep,
+ // Never move original objects, clone them
+ src || ( copy.length != null ? [ ] : { } )
+ , copy);
+
+ // Don't bring in undefined values
+ else if (copy !== undefined)
+ target[ name ] = copy;
+
+ }
+
+ // Return the modified object
+ return target;
+ };
+
+ // exclude the following css properties to add px
+ var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
+ // cache defaultView
+ defaultView = document.defaultView || {},
+ toString = Object.prototype.toString;
+
+ jQuery.extend({
+ noConflict: function(deep) {
+ window.$ = _$;
+
+ if (deep)
+ window.jQuery = _jQuery;
+
+ return jQuery;
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function(obj) {
+ return toString.call(obj) === "[object Function]";
+ },
+
+ isArray: function(obj) {
+ return toString.call(obj) === "[object Array]";
+ },
+
+ // check if an element is in a (or is an) XML document
+ isXMLDoc: function(elem) {
+ return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
+ !!elem.ownerDocument && jQuery.isXMLDoc(elem.ownerDocument);
+ },
+
+ // Evalulates a script in a global context
+ globalEval: function(data) {
+ if (data && /\S/.test(data)) {
+ // Inspired by code by Andrea Giammarchi
+ // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+ var head = document.getElementsByTagName("head")[0] || document.documentElement,
+ script = document.createElement("script");
+
+ script.type = "text/javascript";
+ if (jQuery.support.scriptEval)
+ script.appendChild(document.createTextNode(data));
+ else
+ script.text = data;
+
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709).
+ head.insertBefore(script, head.firstChild);
+ head.removeChild(script);
+ }
+ },
+
+ nodeName: function(elem, name) {
+ return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
+ },
+
+ // args is for internal usage only
+ each: function(object, callback, args) {
+ var name, i = 0, length = object.length;
+
+ if (args) {
+ if (length === undefined) {
+ for (name in object)
+ if (callback.apply(object[ name ], args) === false)
+ break;
+ } else
+ for (; i < length;)
+ if (callback.apply(object[ i++ ], args) === false)
+ break;
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if (length === undefined) {
+ for (name in object)
+ if (callback.call(object[ name ], name, object[ name ]) === false)
+ break;
+ } else
+ for (var value = object[0];
+ i < length && callback.call(value, i, value) !== false; value = object[++i]) {
+ }
+ }
+
+ return object;
+ },
+
+ prop: function(elem, value, type, i, name) {
+ // Handle executable functions
+ if (jQuery.isFunction(value))
+ value = value.call(elem, i);
+
+ // Handle passing in a number to a CSS property
+ return typeof value === "number" && type == "curCSS" && !exclude.test(name) ?
+ value + "px" :
+ value;
+ },
+
+ className: {
+ // internal only, use addClass("class")
+ add: function(elem, classNames) {
+ jQuery.each((classNames || "").split(/\s+/), function(i, className) {
+ if (elem.nodeType == 1 && !jQuery.className.has(elem.className, className))
+ elem.className += (elem.className ? " " : "") + className;
+ });
+ },
+
+ // internal only, use removeClass("class")
+ remove: function(elem, classNames) {
+ if (elem.nodeType == 1)
+ elem.className = classNames !== undefined ?
+ jQuery.grep(elem.className.split(/\s+/), function(className) {
+ return !jQuery.className.has(classNames, className);
+ }).join(" ") :
+ "";
+ },
+
+ // internal only, use hasClass("class")
+ has: function(elem, className) {
+ return elem && jQuery.inArray(className, (elem.className || elem).toString().split(/\s+/)) > -1;
+ }
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function(elem, options, callback) {
+ var old = {};
+ // Remember the old values, and insert the new ones
+ for (var name in options) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ callback.call(elem);
+
+ // Revert the old values
+ for (var name in options)
+ elem.style[ name ] = old[ name ];
+ },
+
+ css: function(elem, name, force, extra) {
+ if (name == "width" || name == "height") {
+ var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
+
+ function getWH() {
+ val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
+
+ if (extra === "border")
+ return;
+
+ jQuery.each(which, function() {
+ if (!extra)
+ val -= parseFloat(jQuery.curCSS(elem, "padding" + this, true)) || 0;
+ if (extra === "margin")
+ val += parseFloat(jQuery.curCSS(elem, "margin" + this, true)) || 0;
+ else
+ val -= parseFloat(jQuery.curCSS(elem, "border" + this + "Width", true)) || 0;
+ });
+ }
+
+ if (elem.offsetWidth !== 0)
+ getWH();
+ else
+ jQuery.swap(elem, props, getWH);
+
+ return Math.max(0, Math.round(val));
+ }
+
+ return jQuery.curCSS(elem, name, force);
+ },
+
+ curCSS: function(elem, name, force) {
+ var ret, style = elem.style;
+
+ // We need to handle opacity special in IE
+ if (name == "opacity" && !jQuery.support.opacity) {
+ ret = jQuery.attr(style, "opacity");
+
+ return ret == "" ?
+ "1" :
+ ret;
+ }
+
+ // Make sure we're using the right name for getting the float value
+ if (name.match(/float/i))
+ name = styleFloat;
+
+ if (!force && style && style[ name ])
+ ret = style[ name ];
+
+ else if (defaultView.getComputedStyle) {
+
+ // Only "float" is needed here
+ if (name.match(/float/i))
+ name = "float";
+
+ name = name.replace(/([A-Z])/g, "-$1").toLowerCase();
+
+ var computedStyle = defaultView.getComputedStyle(elem, null);
+
+ if (computedStyle)
+ ret = computedStyle.getPropertyValue(name);
+
+ // We should always get a number back from opacity
+ if (name == "opacity" && ret == "")
+ ret = "1";
+
+ } else if (elem.currentStyle) {
+ var camelCase = name.replace(/\-(\w)/g, function(all, letter) {
+ return letter.toUpperCase();
+ });
+
+ ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ if (!/^\d+(px)?$/i.test(ret) && /^\d/.test(ret)) {
+ // Remember the original values
+ var left = style.left, rsLeft = elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ style.left = ret || 0;
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret;
+ },
+
+ clean: function(elems, context, fragment) {
+ context = context || document;
+
+ // !context.createElement fails in IE with an error but returns typeof 'object'
+ if (typeof context.createElement === "undefined")
+ context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+
+ // If a single string is passed in and it's a single tag
+ // just do a createElement and skip the rest
+ if (!fragment && elems.length === 1 && typeof elems[0] === "string") {
+ var match = /^<(\w+)\s*\/?>$/.exec(elems[0]);
+ if (match)
+ return [ context.createElement(match[1]) ];
+ }
+
+ var ret = [], scripts = [], div = context.createElement("div");
+
+ jQuery.each(elems, function(i, elem) {
+ if (typeof elem === "number")
+ elem += '';
+
+ if (!elem)
+ return;
+
+ // Convert html string into DOM nodes
+ if (typeof elem === "string") {
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag) {
+ return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
+ all :
+ front + ">" + tag + ">";
+ });
+
+ // Trim whitespace, otherwise indexOf won't work as expected
+ var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase();
+
+ var wrap =
+ // option or optgroup
+ !tags.indexOf("", "" ] ||
+
+ !tags.indexOf("", "" ] ||
+
+ tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
+ [ 1, "" ] ||
+
+ !tags.indexOf("
", "" ] ||
+
+ // matched above
+ (!tags.indexOf(" | ", "
" ] ||
+
+ !tags.indexOf("", "" ] ||
+
+ // IE can't serialize and
+
elementToHide
+
+Click me
+