123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
- define(["util", "jquery"], function (util, $) {
- var elementFinder = util.Module("elementFinder");
- var assert = util.assert;
- elementFinder.ignoreElement = function ignoreElement(el) {
- if (el instanceof $) {
- el = el[0];
- }
- while (el) {
- if ($(el).hasClass("togetherjs")) {
- return true;
- }
- el = el.parentNode;
- }
- return false;
- };
- elementFinder.elementLocation = function elementLocation(el) {
- assert(el !== null, "Got null element");
- if (el instanceof $) {
- // a jQuery element
- el = el[0];
- }
- if (el[0] && el.attr && el[0].nodeType == 1) {
- // Or a jQuery element not made by us
- el = el[0];
- }
- if (el.id) {
- return "#" + el.id;
- }
- if (el.tagName == "BODY") {
- return "body";
- }
- if (el.tagName == "HEAD") {
- return "head";
- }
- if (el === document) {
- return "document";
- }
- var parent = el.parentNode;
- if ((! parent) || parent == el) {
- console.warn("elementLocation(", el, ") has null parent");
- throw new Error("No locatable parent found");
- }
- var parentLocation = elementLocation(parent);
- var children = parent.childNodes;
- var _len = children.length;
- var index = 0;
- for (var i=0; i<_len; i++) {
- if (children[i] == el) {
- break;
- }
- if (children[i].nodeType == document.ELEMENT_NODE) {
- if (children[i].className.indexOf("togetherjs") != -1) {
- // Don't count our UI
- continue;
- }
- // Don't count text or comments
- index++;
- }
- }
- return parentLocation + ":nth-child(" + (index+1) + ")";
- };
- elementFinder.CannotFind = util.Class({
- constructor: function CannotFind(location, reason, context) {
- this.prefix = "";
- this.location = location;
- this.reason = reason;
- this.context = context;
- },
- toString: function () {
- var loc;
- try {
- loc = elementFinder.elementLocation(this.context);
- } catch (e) {
- loc = this.context;
- }
- return (
- "[CannotFind " + this.prefix +
- "(" + this.location + "): " +
- this.reason + " in " +
- loc + "]");
- }
- });
- elementFinder.findElement = function findElement(loc, container) {
- // FIXME: should this all just be done with document.querySelector()?
- // But no! We can't ignore togetherjs elements with querySelector.
- // But maybe! We *could* make togetherjs elements less obtrusive?
- container = container || document;
- var el, rest;
- if (loc === "body") {
- return document.body;
- } else if (loc === "head") {
- return document.head;
- } else if (loc === "document") {
- return document;
- } else if (loc.indexOf("body") === 0) {
- el = document.body;
- try {
- return findElement(loc.substr(("body").length), el);
- } catch (e) {
- if (e instanceof elementFinder.CannotFind) {
- e.prefix = "body" + e.prefix;
- }
- throw e;
- }
- } else if (loc.indexOf("head") === 0) {
- el = document.head;
- try {
- return findElement(loc.substr(("head").length), el);
- } catch (e) {
- if (e instanceof elementFinder.CannotFind) {
- e.prefix = "head" + e.prefix;
- }
- throw e;
- }
- } else if (loc.indexOf("#") === 0) {
- var id;
- loc = loc.substr(1);
- if (loc.indexOf(":") === -1) {
- id = loc;
- rest = "";
- } else {
- id = loc.substr(0, loc.indexOf(":"));
- rest = loc.substr(loc.indexOf(":"));
- }
- el = document.getElementById(id);
- if (! el) {
- throw elementFinder.CannotFind("#" + id, "No element by that id", container);
- }
- if (rest) {
- try {
- return findElement(rest, el);
- } catch (e) {
- if (e instanceof elementFinder.CannotFind) {
- e.prefix = "#" + id + e.prefix;
- }
- throw e;
- }
- } else {
- return el;
- }
- } else if (loc.indexOf(":nth-child(") === 0) {
- loc = loc.substr((":nth-child(").length);
- if (loc.indexOf(")") == -1) {
- throw "Invalid location, missing ): " + loc;
- }
- var num = loc.substr(0, loc.indexOf(")"));
- num = parseInt(num, 10);
- var count = num;
- loc = loc.substr(loc.indexOf(")") + 1);
- var children = container.childNodes;
- el = null;
- for (var i=0; i<children.length; i++) {
- var child = children[i];
- if (child.nodeType == document.ELEMENT_NODE) {
- if (child.className.indexOf("togetherjs") != -1) {
- continue;
- }
- count--;
- if (count === 0) {
- // this is the element
- el = child;
- break;
- }
- }
- }
- if (! el) {
- throw elementFinder.CannotFind(":nth-child(" + num + ")", "container only has " + (num - count) + " elements", container);
- }
- if (loc) {
- try {
- return elementFinder.findElement(loc, el);
- } catch (e) {
- if (e instanceof elementFinder.CannotFind) {
- e.prefix = ":nth-child(" + num + ")" + e.prefix;
- }
- throw e;
- }
- } else {
- return el;
- }
- } else {
- throw elementFinder.CannotFind(loc, "Malformed location", container);
- }
- };
- elementFinder.elementByPixel = function (height) {
- /* Returns {location: "...", offset: pixels}
- To get the pixel position back, you'd do:
- $(location).offset().top + offset
- */
- function search(start, height) {
- var last = null;
- var children = start.children();
- children.each(function () {
- var el = $(this);
- if (el.hasClass("togetherjs") || el.css("position") == "fixed" || ! el.is(":visible")) {
- return;
- }
- if (el.offset().top > height) {
- return false;
- }
- last = el;
- });
- if ((! children.length) || (! last)) {
- // There are no children, or only inapplicable children
- return {
- location: elementFinder.elementLocation(start[0]),
- offset: height - start.offset().top,
- absoluteTop: height,
- documentHeight: $(document).height()
- };
- }
- return search(last, height);
- }
- return search($(document.body), height);
- };
- elementFinder.pixelForPosition = function (position) {
- /* Inverse of elementFinder.elementByPixel */
- if (position.location == "body") {
- return position.offset;
- }
- var el;
- try {
- el = elementFinder.findElement(position.location);
- } catch (e) {
- if (e instanceof elementFinder.CannotFind && position.absoluteTop) {
- // We don't trust absoluteTop to be quite right locally, so we adjust
- // for the total document height differences:
- var percent = position.absoluteTop / position.documentHeight;
- return $(document).height() * percent;
- }
- throw e;
- }
- var top = $(el).offset().top;
- // FIXME: maybe here we should test for sanity, like if an element is
- // hidden. We can use position.absoluteTop to get a sense of where the
- // element roughly should be. If the sanity check failed we'd use
- // absoluteTop
- return top + position.offset;
- };
- return elementFinder;
- });
|