1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-20 10:17:59 +01:00

Add experimental jQuery-ish DOM object.

This commit is contained in:
Kris Maglione
2011-08-14 16:58:24 -04:00
parent 93bdb3dd42
commit 1509ee96e5
5 changed files with 599 additions and 134 deletions

View File

@@ -491,7 +491,7 @@ var Buffer = Module("buffer", {
try {
let [x, y] = elem.getAttribute("coords").split(",").map(parseFloat);
events.dispatch(elem, events.create(elem.ownerDocument, "mouseover", { screenX: x, screenY: y }));
DOM(elem).mouseover({ screenX: x, screenY: y });
}
catch (e) {}
}
@@ -606,13 +606,15 @@ var Buffer = Module("buffer", {
prefs.withContext(function () {
prefs.set("browser.tabs.loadInBackground", true);
["mousedown", "mouseup", "click"].slice(0, util.haveGecko("2b") ? 2 : 3)
.forEach(function (event) {
events.dispatch(elem, events.create(doc, event, {
let params = {
screenX: offsetX, screenY: offsetY,
ctrlKey: ctrlKey, shiftKey: shiftKey, metaKey: ctrlKey
}));
});
};
DOM(elem).mousedown(params).mouseup(params);
if (!util.haveGecko("2b"))
DOM(elem).click(params);
let sel = util.selectionController(win);
sel.getSelection(sel.SELECTION_FOCUS_REGION).collapseToStart();
});
@@ -1422,8 +1424,7 @@ var Buffer = Module("buffer", {
let file = io.File(path);
dactyl.assert(file.exists());
elem.value = file.path;
events.dispatch(elem, events.create(elem.ownerDocument, "change", {}));
DOM(elem).val(file.path).change();
}
}).open(elem.value);
}

View File

@@ -55,7 +55,7 @@ var Editor = Module("editor", {
elem.scrollTop = top;
elem.scrollLeft = left;
events.dispatch(elem, events.create(elem.ownerDocument, "input"));
DOM(elem).input();
}
},
@@ -248,10 +248,10 @@ var Editor = Module("editor", {
textBox.value = val;
if (false) {
textBox.setAttributeNS(NS, "modifiable", true);
util.computedStyle(textBox).MozUserInput;
events.dispatch(textBox, events.create(textBox.ownerDocument, "input", {}));
textBox.removeAttributeNS(NS, "modifiable");
let elem = DOM(textBox);
elem.attrNS(NS, "modifiable", true)
.style.MozUserInput;
elem.input().attrNS(NS, "modifiable", null);
}
}
else {

View File

@@ -641,8 +641,8 @@ var Events = Module("events", {
let elem = target || event.originalTarget;
if (elem) {
let doc = elem.ownerDocument || elem.document || elem;
let evt = events.create(doc, event.type, event);
events.dispatch(elem, evt, extra);
let evt = DOM.Event(doc, event.type, event);
DOM.Event.dispatch(elem, evt, extra);
}
else if (i > 0 && event.type === "keypress")
events.events.keypress.call(events, event);
@@ -691,10 +691,10 @@ var Events = Module("events", {
evt.isMacro = true;
evt.dactylMode = mode;
evt.dactylSavedEvents = savedEvents;
this.feedingEvent = evt;
DOM.Event.feedingEvent = evt;
let doc = document.commandDispatcher.focusedWindow.document;
let event = events.create(doc, type, evt);
let target = dactyl.focusedElement
|| ["complete", "interactive"].indexOf(doc.readyState) >= 0 && doc.documentElement
|| doc.defaultView;
@@ -703,6 +703,7 @@ var Events = Module("events", {
["<Return>", "<Space>"].indexOf(key) == -1)
target = target.ownerDocument.documentElement;
let event = DOM.Event(doc, type, evt);
if (!evt_obj.dactylString && !mode)
events.dispatch(target, event, evt);
else if (type === "keypress")
@@ -717,7 +718,7 @@ var Events = Module("events", {
util.reportError(e);
}
finally {
this.feedingEvent = null;
DOM.Event.feedingEvent = null;
this.feedingKeys = wasFeeding;
if (quiet)
commandline.quiet = wasQuiet;
@@ -726,64 +727,8 @@ var Events = Module("events", {
return true;
},
/**
* Creates an actual event from a pseudo-event object.
*
* The pseudo-event object (such as may be retrieved from events.fromString)
* should have any properties you want the event to have.
*
* @param {Document} doc The DOM document to associate this event with
* @param {Type} type The type of event (keypress, click, etc.)
* @param {Object} opts The pseudo-event. @optional
*/
create: function (doc, type, opts) {
const DEFAULTS = {
HTML: {
type: type, bubbles: true, cancelable: false
},
Key: {
type: type,
bubbles: true, cancelable: true,
view: doc.defaultView,
ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
keyCode: 0, charCode: 0
},
Mouse: {
type: type,
bubbles: true, cancelable: true,
view: doc.defaultView,
detail: 1,
screenX: 0, screenY: 0,
clientX: 0, clientY: 0,
ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
button: 0,
relatedTarget: null
}
};
opts = opts || {};
var t = this._create_types[type];
var evt = doc.createEvent((t || "HTML") + "Events");
let defaults = DEFAULTS[t || "HTML"];
let args = Object.keys(defaults)
.map(function (k) k in opts ? opts[k] : defaults[k]);
evt["init" + t + "Event"].apply(evt, args);
return evt;
},
_create_types: Class.memoize(function () iter(
{
Mouse: "click mousedown mouseout mouseover mouseup",
Key: "keydown keypress keyup",
"": "change dactyl-input input submit"
}
).map(function ([k, v]) v.split(" ").map(function (v) [v, k]))
.flatten()
.toObject()),
create: deprecated("DOM.Event", function create() DOM.Event.apply(null, arguments)),
dispatch: deprecated("DOM.Event.dispatch", function dispatch() DOM.Event.dispatch.apply(DOM.Event, arguments)),
/**
* Converts a user-input string of keys into a canonical
@@ -817,44 +762,6 @@ var Events = Module("events", {
yield match[0];
}()),
/**
* Dispatches an event to an element as if it were a native event.
*
* @param {Node} target The DOM node to which to dispatch the event.
* @param {Event} event The event to dispatch.
*/
dispatch: Class.memoize(function ()
util.haveGecko("2b")
? function dispatch(target, event, extra) {
try {
this.feedingEvent = extra;
if (target instanceof Element)
// This causes a crash on Gecko<2.0, it seems.
return (target.ownerDocument || target.document || target).defaultView
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
.dispatchDOMEventViaPresShell(target, event, true);
else {
target.dispatchEvent(event);
return !event.getPreventDefault();
}
}
catch (e) {
util.reportError(e);
}
finally {
this.feedingEvent = null;
}
}
: function dispatch(target, event, extra) {
try {
this.feedingEvent = extra;
target.dispatchEvent(update(event, extra));
}
finally {
this.feedingEvent = null;
}
}),
get defaultTarget() dactyl.focusedElement || content.document.body || document.documentElement,
/**
@@ -1306,11 +1213,11 @@ var Events = Module("events", {
let duringFeed = this.duringFeed || [];
this.duringFeed = [];
try {
if (this.feedingEvent)
for (let [k, v] in Iterator(this.feedingEvent))
if (DOM.Event.feedingEvent)
for (let [k, v] in Iterator(DOM.Event.feedingEvent))
if (!(k in event))
event[k] = v;
this.feedingEvent = null;
DOM.Event.feedingEvent = null;
let key = events.toString(event);
@@ -1321,7 +1228,7 @@ var Events = Module("events", {
elem.dactylKeyPress = elem.value;
util.timeout(function () {
if (elem.dactylKeyPress !== undefined && elem.value !== elem.dactylKeyPress)
events.dispatch(elem, events.create(elem.ownerDocument, "dactyl-input"));
DOM(elem).dactylInput();
elem.dactylKeyPress = undefined;
});
}
@@ -1425,7 +1332,7 @@ var Events = Module("events", {
this.keyEvents = [];
let pass = this.passing && !event.isMacro ||
this.feedingEvent && this.feedingEvent.isReplay ||
DOM.Event.feedingEvent && DOM.Event.feedingEvent.isReplay ||
event.isReplay ||
modes.main == modes.PASS_THROUGH ||
modes.main == modes.QUOTE

View File

@@ -731,8 +731,10 @@ var Styles = Module("Styles", {
template.highlightCSS = function highlightCSS(css) {
XML.prettyPrinting = XML.ignoreWhitespace = false;
return this.highlightRegexp(css, patterns.property, function (match) <>{
match.preSpace}{template.filter(match.name)}: {
return this.highlightRegexp(css, patterns.property, function (match) {
if (!match.length)
return <></>;
return <>{match.preSpace}{template.filter(match.name)}: {
template.highlightRegexp(match.value, patterns.token, function (match) {
if (match.function)
@@ -749,7 +751,7 @@ var Styles = Module("Styles", {
})
}{ match.postSpace }</>
)
})
}
},
});

View File

@@ -11,7 +11,7 @@ try {
Components.utils.import("resource://dactyl/bootstrap.jsm");
let frag=1;
defineModule("util", {
exports: ["frag", "FailedAssertion", "Math", "NS", "Point", "Util", "XBL", "XHTML", "XUL", "util"],
exports: ["$", "DOM", "FailedAssertion", "Math", "NS", "Point", "Util", "XBL", "XHTML", "XUL", "util"],
require: ["services"],
use: ["commands", "config", "highlight", "messages", "storage", "template"]
}, this);
@@ -37,6 +37,7 @@ var FailedAssertion = Class("FailedAssertion", ErrorBase, {
var Point = Struct("x", "y");
var wrapCallback = function wrapCallback(fn) {
if (!fn.wrapper)
fn.wrapper = function wrappedCallback() {
try {
return fn.apply(this, arguments);
@@ -161,6 +162,14 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
return condition;
},
/**
* CamelCases a -non-camel-cased identifier name.
*
* @param {string} name The name to mangle.
* @returns {string} The mangled name.
*/
camelCase: function camelCase(name) String.replace(name, /-(.)/g, function (m, m1) m1.toUpperCase()),
/**
* Capitalizes the first character of the given string.
* @param {string} str The string to capitalize
@@ -2127,6 +2136,552 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
Array: array
});
var DOM = Class("DOM", {
init: function init(val, context) {
let self;
let length = 0;
if (context instanceof Ci.nsIDOMDocument)
this.document = context;
if (typeof val == "string")
val = context.querySelectorAll(val);
if (val == null)
;
else if (typeof val == "xml")
this[length++] = util.xmlToDom(val, context);
else if (val instanceof Ci.nsIDOMNode)
this[length++] = val;
else if ("length" in val)
for (let i = 0; i < val.length; i++)
this[length++] = val[i];
this.length = length;
return self || this;
},
__iterator__: function __iterator__() {
for (let i = 0; i < this.length; i++)
yield this[i];
},
Empty: function Empty() this.constructor(null, this.document),
get items() {
for (let i = 0; i < this.length; i++)
yield this.eq(i);
},
get document() this._document || this[0].ownerDocument,
set document(val) this._document = val,
attrHooks: array.toObject([
["", {
href: { get: function (elem) elem.href },
src: { get: function (elem) elem.src }
}]
]),
matcher: function matcher(sel) {
let res;
if (/^([a-z0-9_-]+)$/i.exec(sel))
res = function (elem) elem.localName == val;
else if (/^#([a-z0-9:_-]+)$/i.exec(sel))
res = function (elem) elem.id == val;
else if (/^\.([a-z0-9:_-]+)$/i.exec(sel))
res = function (elem) elem.classList.contains(val);
else if (/^\[([a-z0-9:_-]+)\]$/i.exec(sel))
res = function (elem) elem.hasAttribute(val);
else
res = function (elem) ~Array.indexOf(elem.parentNode.querySelectorAll(sel),
elem);
let val = RegExp.$1;
return res;
},
each: function each(fn, self) {
let obj = self || this.Empty();
for (let i = 0; i < this.length; i++)
fn.call(self || update(obj, [this[i]]), this[i], i);
return this;
},
eachDOM: function eachDOM(val, fn, self) {
if (typeof val == "xml")
return this.each(function (elem, i) {
fn.call(this, util.xmlToDom(val, elem.ownerDocument), elem, i);
}, self || this);
let dom = this;
function munge(val) {
if (typeof val == "xml")
val = dom.constructor(val, dom.document);
if (isObject(val) && "length" in val) {
let frag = dom.document.createDocumentFragment();
for (let i = 0; i < val.length; i++)
frag.appendChild(val[i]);
return frag;
}
return val;
}
if (callable(val))
return this.each(function (elem, i) {
fn.call(this, munge(val.call(this, elem, i)), elem, i);
}, self || this);
fn.call(self || this, munge(val), this[0], 0);
return this;
},
eq: function eq(idx) {
return this.constructor(this[idx >= 0 ? idx : this.length + idx]);
},
find: function find(val) {
return this.map(function (elem) elem.querySelectorAll(val));
},
filter: function filter(val, self) {
let res = this.Empty();
if (!callable(val))
val = this.matcher(val);
this.constructor(Array.filter(this, val, self || this));
for (let i = 0; i < this.length; i++)
if (val.call(self, this[i], i))
res[res.length++] = this[i];
return res;
},
is: function is(val) {
return this.some(this.matcher(val));
},
reverse: function reverse() {
Array.reverse(this);
return this;
},
all: function all(fn, self) {
let res = this.Empty();
this.each(function (elem) {
while((elem = fn.call(this, elem)) instanceof Ci.nsIDOMElement)
res[res.length++] = elem;
}, self || this);
return res;
},
map: function map(fn, self) {
let res = this.Empty();
let obj = self || this.Empty();
for (let i = 0; i < this.length; i++) {
let tmp = fn.call(self || update(obj, [this[i]]), this[i], i);
if ("length" in tmp)
for (let j = 0; j < tmp.length; j++)
res[res.length++] = tmp[j];
else
res[res.length++] = tmp;
}
return res;
},
slice: function eq(start, end) {
return this.constructor(Array.slice(this, start, end));
},
some: function some(fn, self) {
for (let i = 0; i < this.length; i++)
if (fn.call(self || this, this[i], i))
return true;
return false;
},
get parent() this.map(function (elem) elem.parentNode, this),
get ancestors() this.all(function (elem) elem.parentNode),
get children() this.map(function (elem) Array.filter(elem.childNodes,
function (e) e instanceof Ci.nsIDOMElement),
this),
get contents() this.map(function (elem) elem.childNodes, this),
get siblings() this.map(function (elem) Array.filter(elem.parentNode.childNodes,
function (e) e != elem && e instanceof Ci.nsIDOMElement),
this),
get siblingsBefore() this.all(function (elem) elem.previousElementSibling),
get siblingsAfter() this.all(function (elem) elem.nextElementSibling),
get class() let (self = this) ({
toString: function () self[0].className,
get list() Array.slice(self[0].classList),
set list(val) self.attr("class", val.join(" ")),
each: function each(meth, arg) {
return self.each(function (elem) {
elem.classList[meth](arg);
})
},
add: function add(cls) this.each("add", cls),
remove: function remove(cls) this.each("remove", cls),
toggle: function toggle(cls) this.each("toggle", cls),
has: function has(cls) this[0].classList.has(cls)
}),
get highlight() let (self = this) ({
toString: function () self.attrNS(NS, "highlight") || "",
get list() this.toString().trim().split(/\s+/),
set list(val) self.attrNS(NS, "highlight", val.join(" ")),
has: function has(hl) ~this.list.indexOf(hl),
add: function add(hl) self.each(function () {
this.attrNS(NS, "highlight",
array.uniq(this.highlight.list.concat(hl)).join(" "));
}),
remove: function remove(hl) self.each(function () {
this.attrNS(NS, "highlight",
this.highlight.list.filter(function (h) h != hl));
}),
toggle: function toggle(hl) self.each(function () {
let { highlight } = this;
highlight[highlight.has(hl) ? "remove" : "add"](hl)
}),
}),
get rect() this[0].getBoundingClientRect(),
get style() util.computedStyle(this[0]),
attr: function attr(key, val) {
return this.attrNS("", key, val);
},
attrNS: function attrNS(ns, key, val) {
if (val !== undefined)
key = array.toObject([[key, val]]);
let hooks = this.attrHooks[ns] || {};
if (isObject(key))
return this.each(function (elem) {
for (let [k, v] in Iterator(key))
if (Set.has(hooks, k) && hooks[k].set)
hooks[k].set.call(this, elem, v);
else if (v == null)
elem.removeAttributeNS(ns, k);
else
elem.setAttributeNS(ns, k, v);
});
if (Set.has(hooks, k) && hooks[k].get)
return hooks[k].get.call(this, elem);
if (!this[0].hasAttributeNS(ns, key))
return null;
return this[0].getAttributeNS(ns, key);
},
css: update(function css(key, val) {
if (val !== undefined)
key = array.toObject([[key, val]]);
if (isObject(key))
return this.each(function (elem) {
for (let [k, v] in Iterator(key))
elem.style[css.property(k)] = v;
});
return this[0].style[css.property(key)];
}, {
name: function (property) property.replace(/[A-Z]/g, function (m0) "-" + m0.toLowerCase()),
property: function (name) name.replace(/-(.)/g, function (m0, m1) m1.toUpperCase())
}),
append: function append(val) {
return this.eachDOM(val, function (elem, target) {
target.appendChild(elem);
});
},
prepend: function prepend(val) {
return this.eachDOM(val, function (elem, target) {
target.insertBefore(elem, target.firstChild);
});
},
before: function before(val) {
return this.eachDOM(val, function (elem, target) {
target.parentNode.insertBefore(elem, target);
});
},
after: function after(val) {
return this.eachDOM(val, function (elem, target) {
target.parentNode.insertBefore(elem, target.nextSibling);
});
},
appendTo: function appendTo(elem) {
if (!(elem instanceof this.constructor))
elem = this.constructor(elem, this.document);
elem.append(this);
return this;
},
prependTo: function appendTo(elem) {
if (!(elem instanceof this.constructor))
elem = this.constructor(elem, this.document);
elem.prepend(this);
return this;
},
insertBefore: function insertBefore(elem) {
if (!(elem instanceof this.constructor))
elem = this.constructor(elem, this.document);
elem.before(this);
return this;
},
insertAfter: function insertAfter(elem) {
if (!(elem instanceof this.constructor))
elem = this.constructor(elem, this.document);
elem.after(this);
return this;
},
remove: function remove() {
return this.each(function (elem) {
if (elem.parentNode)
elem.parentNode.removeChild(elem);
}, this);
},
empty: function empty() {
return this.each(function (elem) {
while (elem.firstChild)
elem.removeChild(elem.firstChild);
}, this);
},
toggle: function toggle(val) {
if (arguments.length)
return this[val ? "show" : "hide"]();
return this.each(function (elem) {
elem.style.display = this.style.display == "none" ? "block" : "none";
});
},
hide: function hide() {
return this.each(function (elem) { elem.style.display = "none"; }, this);
},
show: function show() {
return this.each(function (elem) { elem.style.display = "block"; }, this);
},
getSet: function getSet(args, get, set) {
if (!args.length)
return get.call(this, this[0]);
let [fn, self] = args;
if (!callable(fn))
fn = function () args[0];
return this.each(function (elem, i) {
set.call(this, elem, fn.call(self || this, elem, i));
}, this);
},
html: function html(txt, self) {
return this.getSet(arguments,
function (elem) elem.innerHTML,
function (elem, val) { elem.innerHTML = val });
},
text: function text(txt, self) {
return this.getSet(arguments,
function (elem) elem.textContent,
function (elem, val) { elem.textContent = val });
},
val: function val(txt) {
return this.getSet(arguments,
function (elem) elem.value,
function (elem, val) { elem.value = val });
},
listen: function listen(event, listener, capture) {
if (isObject(event))
capture = listener;
else
event = array.toObject([[event, listener]]);
for (let [k, v] in Iterator(event))
event[k] = util.wrapCallback(v);
return this.each(function (elem) {
for (let [k, v] in Iterator(event))
elem.addEventListener(k, v, capture);
});
},
unlisten: function unlisten(event, listener, capture) {
if (isObject(event))
capture = listener;
else
event = array.toObject([[key, val]]);
return this.each(function (elem) {
for (let [k, v] in Iterator(event))
elem.removeEventListener(k, v.wrapper || v, capture);
});
},
dispatch: function dispatch(event, params, extraProps) {
return this.each(function (elem) {
let evt = DOM.Event(this.document, event, params);
DOM.Event.dispatch(elem, evt, extraProps);
}, this);
},
focus: function focus(arg, extra) {
if (callable(arg))
return this.listen("focus", arg, extra);
services.focus.setFocus(this[0], extra || services.focus.FLAG_BYMOUSE);
return this;
},
blur: function blur(arg, extra) {
if (callable(arg))
return this.listen("blur", arg, extra);
return this.each(function (elem) { elem.blur(); }, this);
}
}, {
/**
* Creates an actual event from a pseudo-event object.
*
* The pseudo-event object (such as may be retrieved from events.fromString)
* should have any properties you want the event to have.
*
* @param {Document} doc The DOM document to associate this event with
* @param {Type} type The type of event (keypress, click, etc.)
* @param {Object} opts The pseudo-event. @optional
*/
Event: Class("Event", {
init: function Event(doc, type, opts) {
const DEFAULTS = {
HTML: {
type: type, bubbles: true, cancelable: false
},
Key: {
type: type,
bubbles: true, cancelable: true,
view: doc.defaultView,
ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
keyCode: 0, charCode: 0
},
Mouse: {
type: type,
bubbles: true, cancelable: true,
view: doc.defaultView,
detail: 1,
screenX: 0, screenY: 0,
clientX: 0, clientY: 0,
ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
button: 0,
relatedTarget: null
}
};
opts = opts || {};
var t = this.constructor.types[type];
var evt = doc.createEvent((t || "HTML") + "Events");
let defaults = DEFAULTS[t || "HTML"];
let args = Object.keys(defaults)
.map(function (k) k in opts ? opts[k] : defaults[k]);
evt["init" + t + "Event"].apply(evt, args);
return evt;
}
}, {
types: Class.memoize(function () iter(
{
Mouse: "click mousedown mouseout mouseover mouseup",
Key: "keydown keypress keyup",
"": "change dactyl-input input submit"
}
).map(function ([k, v]) v.split(" ").map(function (v) [v, k]))
.flatten()
.toObject()),
/**
* Dispatches an event to an element as if it were a native event.
*
* @param {Node} target The DOM node to which to dispatch the event.
* @param {Event} event The event to dispatch.
*/
dispatch: Class.memoize(function ()
util.haveGecko("2b")
? function dispatch(target, event, extra) {
try {
this.feedingEvent = extra;
if (target instanceof Ci.nsIDOMElement)
// This causes a crash on Gecko<2.0, it seems.
return (target.ownerDocument || target.document || target).defaultView
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
.dispatchDOMEventViaPresShell(target, event, true);
else {
target.dispatchEvent(event);
return !event.getPreventDefault();
}
}
catch (e) {
util.reportError(e);
}
finally {
this.feedingEvent = null;
}
}
: function dispatch(target, event, extra) {
try {
this.feedingEvent = extra;
target.dispatchEvent(update(event, extra));
}
finally {
this.feedingEvent = null;
}
})
})
});
Object.keys(DOM.Event.types).forEach(function (event) {
DOM.prototype[util.camelCase(event)] = function _event(arg, extra) {
return this[callable(arg) ? "listen" : "dispatch"](event, arg, extra);
};
});
var $ = DOM;
/**
* Math utility methods.
* @singleton