Skip to content

Instantly share code, notes, and snippets.

@enjalot
Created January 6, 2012 06:26
Show Gist options
  • Save enjalot/1569370 to your computer and use it in GitHub Desktop.
Save enjalot/1569370 to your computer and use it in GitHub Desktop.
spermboids

Built with D3.js. An answer to the challenge posed here

// Boid flocking based on http://harry.me/2011/02/17/neat-algorithms---flocking
var boid = (function() {
function boid() {
var position = [0, 0],
velocity = [0, 0],
gravityCenter = null,
gravityMultiplier = 1,
neighborRadius = 50,
mouseRadius = 50,
maxForce = .1,
maxSpeed = 1,
separationWeight = 2,
alignmentWeight = 1,
cohesionWeight = 1,
desiredSeparation = 10;
function boid(neighbors) {
var accel = flock(neighbors);
d3_ai_boidWrap(position);
velocity[0] += accel[0];
velocity[1] += accel[1];
if (gravityCenter) {
//var g = d3_ai_boidGravity(gravityCenter, position, neighborRadius);
var g = d3_ai_boidGravity(gravityCenter, position, mouseRadius)
velocity[0] += g[0] * gravityMultiplier;
velocity[1] += g[1] * gravityMultiplier;;
}
d3_ai_boidLimit(velocity, maxSpeed);
position[0] += velocity[0];
position[1] += velocity[1];
return position;
}
function flock(neighbors) {
var separation = [0, 0],
alignment = [0, 0],
cohesion = [0, 0],
separationCount = 0,
alignmentCount = 0,
cohesionCount = 0,
i = -1,
l = neighbors.length;
while (++i < l) {
var n = neighbors[i];
if (n === this) continue;
var npos = n.position(),
d = d3_ai_boidDistance(position, npos);
if (d > 0) {
if (d < desiredSeparation) {
var tmp = d3_ai_boidNormalize(d3_ai_boidSubtract(position.slice(), npos));
separation[0] += tmp[0] / d;
separation[1] += tmp[1] / d;
separationCount++;
}
if (d < neighborRadius) {
var nvel = n.velocity();
alignment[0] += nvel[0];
alignment[1] += nvel[1];
alignmentCount++;
cohesion[0] += npos[0];
cohesion[1] += npos[1];
cohesionCount++;
}
}
}
if (separationCount > 0) {
separation[0] /= separationCount;
separation[1] /= separationCount;
}
if (alignmentCount > 0) {
alignment[0] /= alignmentCount;
alignment[1] /= alignmentCount;
}
d3_ai_boidLimit(alignment, maxForce);
if (cohesionCount > 0) {
cohesion[0] /= cohesionCount;
cohesion[1] /= cohesionCount;
} else {
cohesion = position.slice();
}
cohesion = steerTo(cohesion);
return [
separation[0] * separationWeight +
alignment[0] * alignmentWeight +
cohesion[0] * cohesionWeight,
separation[1] * separationWeight +
alignment[1] * alignmentWeight +
cohesion[1] * cohesionWeight
];
}
function steerTo(target) {
var desired = d3_ai_boidSubtract(target, position),
d = d3_ai_boidMagnitude(desired);
if (d > 0) {
d3_ai_boidNormalize(desired);
// Two options for desired vector magnitude (1 -- based on distance, 2 -- maxspeed)
var mul = maxSpeed * (d < 100 ? d / 100 : 1);
desired[0] *= mul;
desired[1] *= mul;
// Steering = Desired minus Velocity
var steer = d3_ai_boidSubtract(desired, velocity);
d3_ai_boidLimit(steer, maxForce) // Limit to maximum steering force
} else {
steer = [0, 0];
}
return steer;
}
boid.position = function(x) {
if (!arguments.length) return position;
position = x;
return boid;
}
boid.velocity = function(x) {
if (!arguments.length) return velocity;
velocity = x;
return boid;
}
boid.gravityCenter = function(x) {
if (!arguments.length) return gravityCenter;
gravityCenter = x;
return boid;
}
boid.gravityMultiplier = function(x) {
if (!arguments.length) return gravityMultiplier;
gravityMultiplier = x;
return boid;
}
boid.neighborRadius = function(x) {
if (!arguments.length) return neighborRadius;
neighborRadius = x;
return boid;
}
boid.mouseRadius = function(x) {
if (!arguments.length) return mouseRadius;
mouseRadius = x;
return boid;
}
boid.maxForce = function(x) {
if (!arguments.length) return maxForce;
maxForce = x;
return boid;
}
boid.maxSpeed = function(x) {
if (!arguments.length) return maxSpeed;
maxSpeed = x;
return boid;
}
boid.separationWeight = function(x) {
if (!arguments.length) return separationWeight;
separationWeight = x;
return boid;
}
boid.alignmentWeight = function(x) {
if (!arguments.length) return alignmentWeight;
alignmentWeight = x;
return boid;
}
boid.cohesionWeight = function(x) {
if (!arguments.length) return cohesionWeight;
cohesionWeight = x;
return boid;
}
boid.desiredSeparation = function(x) {
if (!arguments.length) return desiredSeparation;
desiredSeparation = x;
return boid;
}
return boid;
}
function d3_ai_boidNormalize(a) {
var m = d3_ai_boidMagnitude(a);
if (m > 0) {
a[0] /= m;
a[1] /= m;
}
return a;
}
function d3_ai_boidWrap(position) {
if (position[0] > w) position[0] = 0;
else if (position[0] < 0) position[0] = w;
if (position[1] > h) position[1] = 0;
else if (position[1] < 0) position[1] = h;
}
function d3_ai_boidGravity(center, position, neighborRadius) {
if (center[0] != null) {
var m = d3_ai_boidSubtract(center.slice(), position),
d = d3_ai_boidMagnitude(m) - 10;
if (d > 0 && d < neighborRadius * 5) {
d3_ai_boidNormalize(m);
m[0] /= d;
m[1] /= d;
return m;
}
}
return [0, 0];
}
function d3_ai_boidDistance(a, b) {
var dx = a[0] - b[0],
dy = a[1] - b[1];
if (dx > w / 2) dx = w - dx;
if (dy > h / 2) dy = h - dy;
return Math.sqrt(dx * dx + dy * dy);
}
function d3_ai_boidSubtract(a, b) {
a[0] -= b[0];
a[1] -= b[1];
return a;
}
function d3_ai_boidMagnitude(v) {
return Math.sqrt(v[0] * v[0] + v[1] * v[1]);
}
function d3_ai_boidLimit(a, max) {
if (d3_ai_boidMagnitude(a) > max) {
d3_ai_boidNormalize(a);
a[0] *= max;
a[1] *= max;
}
return a;
}
return boid;
})();
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js?2.5.0"></script>
<script type="text/javascript" src="boid.js"></script>
<script type="text/javascript" src="jwerty.js"></script>
<style type="text/css">
body {
background: #000;
}
ellipse {
fill: #fff;
}
path {
fill: none;
stroke: #fff;
stroke-opacity: .75;
stroke-linecap: round;
}
.mid {
stroke-width: 4px;
}
.tail {
stroke-width: 2px;
}
</style>
</head>
<body>
<script type="text/javascript">
var w = 960,
h = 500,
n = 100,
m = 10,
degrees = 180 / Math.PI,
mouse = [null, null];
function nullGravity() {
mouse[0] = mouse[1] = null;
}
d3.select(window).on("blur", nullGravity);
var pause = false;
jwerty.key('p', function () {
pause = !pause;
});
var spermatozoa = d3.range(n).map(function() {
var x = Math.random() * w, y = Math.random() * h;
var b = boid()
.position([Math.random() * w, Math.random() * h])
.velocity([Math.random() * 2 - 1, Math.random() * 2 - 1])
.gravityCenter(mouse)
.separationWeight(4)
.neighborRadius(50)
.desiredSeparation(40)
.mouseRadius(350)
.gravityMultiplier(30);
b.vx = Math.random() * 2 - 1,
b.vy = Math.random() * 2 - 1,
b.path = d3.range(m).map(function() { return [x, y]; }),
b.count = 0
return b;
});
// Compute initial positions.
var vertices = spermatozoa.map(function(boid) {
return boid(spermatozoa);
});
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.on("mousemove", function() {
var m = d3.svg.mouse(this);
mouse[0] = m[0];
mouse[1] = m[1];
})
.on("mouseout", nullGravity);
var g = svg.selectAll("g")
.data(spermatozoa)
.enter().append("svg:g");
var head = g.append("svg:ellipse")
.attr("rx", 6.5)
.attr("ry", 4);
g.append("svg:path")
.map(function(d) { return d.path.slice(0, 3); })
.attr("class", "mid");
var tail = g.append("svg:g")
.attr("class", "tail")
tail
.append("svg:path")
.map(function(d) { return d.path; })
//.attr("class", "tail");
var tail_paths = g.selectAll("path");
d3.timer(function() {
if(pause) return false;
for (var i = -1; ++i < n;) {
var spermatozoon = spermatozoa[i],
path = spermatozoon.path,
//dx = spermatozoon.vx,
//dy = spermatozoon.vy;
dx = spermatozoon.velocity()[0],
dy = spermatozoon.velocity()[1];
vertices[i] = spermatozoon(spermatozoa);
path[0][0] = vertices[i][0];
path[0][1] = vertices[i][1];
var x = path[0][0] += dx,
y = path[0][1] += dy,
//x = vertices[i] += dx,
//y = vertices[i] += dy,
speed = Math.sqrt(dx * dx + dy * dy),
count = speed * 10,
k1 = -5 - speed / 3;
// Bounce off the walls.
//if (x < 0 || x > w) spermatozoon.vx *= -1;
//if (y < 0 || y > h) spermatozoon.vy *= -1;
angle = Math.atan2(spermatozoon.velocity()[0], spermatozoon.velocity()[1]) * degrees
// Swim!
for (var j = 0; ++j < m;) {
var vx = x - path[j][0],
vy = y - path[j][1],
k2 = Math.sin(((spermatozoon.count += count) + j * 3) / 300) / speed;
path[j][0] = (x += dx / speed * k1) - dy * k2;
path[j][1] = (y += dy / speed * k1) + dx * k2;
speed = Math.sqrt((dx = vx) * dx + (dy = vy) * dy);
}
///spermatozoon.vx = spermatozoon.velocity[0]
//spermatozoon.vy = spermatozoon.velocity[1]
}
head.attr("transform", function(d,i) {
//return "translate(" + d.path[0] + ")rotate(" + Math.atan2(d.vy, d.vx) * degrees + ")";
//return "translate(" + vertices[i] + ")rotate(" + Math.atan2(d.vy, d.vx) * degrees + ")";
var angle = 90-Math.atan2(d.velocity()[0], d.velocity()[1]) * degrees;// + 180;
return "translate(" + vertices[i] + ")rotate(" + angle + ")";
});
tail
/*
.attr("transform", function(d,i) {
//return "translate(" + [-vertices[i][0], -vertices[i][1]] + ")";
//d3.select(this.parentNode)
var angle = Math.atan2(d.velocity()[0], d.velocity()[1]) * degrees + 90;
return "rotate(" + angle + ", " + vertices[i] + ")";
})
*/
tail_paths
.attr("d", function(d) {
return "M" + d.join("L");
});
});
</script>
</body>
</html>
/*
* jwerty - Awesome handling of keyboard events
*
* jwerty is a JS lib which allows you to bind, fire and assert key combination
* strings against elements and events. It normalises the poor std api into
* something easy to use and clear.
*
* This code is licensed under the MIT
* For the full license see: http://keithamus.mit-license.org/
* For more information see: http://keithamus.github.com/jwerty
*
* @author Keith Cirkel ('keithamus') <jwerty@keithcirkel.co.uk>
* @license http://keithamus.mit-license.org/
* @copyright Copyright © 2011, Keith Cirkel
*
*/
(function (global, exports) {
// Helper methods & vars:
var $d = global.document
, $ = (global.jQuery || global.Zepto || global.ender || $d)
, $$
, $b
, ke = 'keydown';
function realTypeOf(v, s) {
return (v === null) ? s === 'null'
: (v === undefined) ? s === 'undefined'
: (v.is && v instanceof $) ? s === 'element'
: Object.prototype.toString.call(v).toLowerCase().indexOf(s) > 7;
}
if ($ === $d) {
$$ = function (selector, context) {
return selector ? $.querySelector(selector, context || $) : $;
};
$b = function (e, fn) { e.addEventListener(ke, fn, false); };
$f = function (e, jwertyEv) {
var ret = document.createEvent('Event')
, i;
ret.initEvent(ke, true, true);
for (i in jwertyEv) ret[i] = jwertyEv[i];
return (e || $).dispatchEvent(ret);
}
} else {
$$ = function (selector, context, fn) { return $(selector || $d, context); };
$b = function (e, fn) { $(e).bind(ke + '.jwerty', fn); };
$f = function (e, ob) { $(e || $d).trigger($.Event(ke, ob)); };
}
// Private
var _modProps = { 16: 'shiftKey', 17: 'ctrlKey', 18: 'altKey', 91: 'metaKey' };
// Generate key mappings for common keys that are not printable.
var _keys = {
// MOD aka toggleable keys
mods: {
// Shift key, ⇧
'⇧': 16, shift: 16,
// CTRL key, on Mac: ⌃
'⌃': 17, ctrl: 17,
// ALT key, on Mac: ⌥ (Alt)
'⌥': 18, alt: 18, option: 18,
// META, on Mac: ⌘ (CMD), on Windows (Win), on Linux (Super)
'⌘': 91, meta: 91, cmd: 91, 'super': 91, win: 91
},
// Normal keys
keys: {
// Backspace key, on Mac: ⌫ (Backspace)
'⌫': 8, backspace: 8,
// Tab Key, on Mac: ⇥ (Tab), on Windows ⇥⇥
'⇥': 9, '⇆': 9, tab: 9,
// Return key, ↩
'↩': 13, 'return': 13, enter: 13, '⌅': 13,
// Pause/Break key
'pause': 19, 'pause-break': 19,
// Caps Lock key, ⇪
'⇪': 20, caps: 20, 'caps-lock': 20,
// Escape key, on Mac: ⎋, on Windows: Esc
'⎋': 27, escape: 27, esc: 27,
// Space key
space: 32,
// Page-Up key, or pgup, on Mac: ↖
'↖': 33, pgup: 33, 'page-up': 33,
// Page-Down key, or pgdown, on Mac: ↘
'↘': 34, pgdown: 34, 'page-down': 34,
// END key, on Mac: ⇟
'⇟': 35, end: 35,
// HOME key, on Mac: ⇞
'⇞': 36, home: 36,
// Insert key, or ins
ins: 45, insert: 45,
// Delete key, on Mac: ⌫ (Delete)
del: 45, 'delete': 45,
// Left Arrow Key, or ←
'←': 37, left: 37, 'arrow-left': 37,
// Up Arrow Key, or ↑
'↑': 38, up: 38, 'arrow-up': 38,
// Right Arrow Key, or →
'→': 39, right: 39, 'arrow-right': 39,
// Up Arrow Key, or ↓
'↓': 40, down: 40, 'arrow-down': 40,
// odities, printing characters that come out wrong:
// Num-Multiply, or *
'*': 106, star: 106, asterisk: 106, multiply: 106,
// Num-Plus or +
'+': 107, 'plus': 107,
// Num-Subtract, or -
'-': 109, subtract: 109,
//';': 186, //???
// = or equals
'=': 187, 'equals': 187,
// Comma, or ,
',': 188, comma: 188,
//'-': 189, //???
// Period, or ., or full-stop
'.': 190, period: 190, 'full-stop': 190,
// Slash, or /, or forward-slash
'/': 191, slash: 191, 'forward-slash': 191,
// Tick, or `, or back-quote
'`': 192, tick: 192, 'back-quote': 192,
// Open bracket, or [
'[': 219, 'open-bracket': 219,
// Back slash, or \
'\\': 220, 'back-slash': 220,
// Close backet, or ]
']': 221, 'close-bracket': 221,
// Apostraphe, or Quote, or '
'\'': 222, quote: 222, apostraphe: 222
}
};
// To minimise code bloat, add all of the NUMPAD 0-9 keys in a loop
i = 95, n = 0;
while(++i < 106) {
_keys.keys['num-' + n] = i;
++n;
}
// To minimise code bloat, add all of the top row 0-9 keys in a loop
i = 47, n = 0;
while(++i < 58) {
_keys.keys[n] = i;
++n;
}
// To minimise code bloat, add all of the F1-F25 keys in a loop
i = 111, n = 1;
while(++i < 136) {
_keys.keys['f' + n] = i;
++n;
}
// To minimise code bloat, add all of the letters of the alphabet in a loop
var i = 64;
while(++i < 91) {
_keys.keys[String.fromCharCode(i).toLowerCase()] = i;
}
function JwertyCode(jwertyCode) {
var i
, c
, n
, z
, keyCombo
, optionals
, jwertyCodeFragment
, rangeMatches
, rangeI;
// In-case we get called with an instance of ourselves, just return that.
if (jwertyCode instanceof JwertyCode) return jwertyCode;
// If jwertyCode isn't an array, cast it as a string and split into array.
if (!realTypeOf(jwertyCode, 'array')) {
jwertyCode = (String(jwertyCode)).replace(/\s/g, '').toLowerCase().
match(/(?:\+,|[^,])+/g);
}
// Loop through each key sequence in jwertyCode
for (i = 0, c = jwertyCode.length; i < c; ++i) {
// If the key combo at this part of the sequence isn't an array,
// cast as a string and split into an array.
if (!realTypeOf(jwertyCode[i], 'array')) {
jwertyCode[i] = String(jwertyCode[i])
.match(/(?:\+\/|[^\/])+/g);
}
// Parse the key optionals in this sequence
optionals = [], n = jwertyCode[i].length;
while (n--) {
// Begin creating the object for this key combo
var jwertyCodeFragment = jwertyCode[i][n];
keyCombo = {
jwertyCombo: String(jwertyCodeFragment),
shiftKey: false,
ctrlKey: false,
altKey: false,
metaKey: false
}
// If jwertyCodeFragment isn't an array then cast as a string
// and split it into one.
if (!realTypeOf(jwertyCodeFragment, 'array')) {
jwertyCodeFragment = String(jwertyCodeFragment).toLowerCase()
.match(/(?:(?:[^\+])+|\+\+|^\+$)/g);
}
z = jwertyCodeFragment.length;
while (z--) {
// Normalise matching errors
if (jwertyCodeFragment[z] === '++') jwertyCodeFragment[z] = '+';
// Inject either keyCode or ctrl/meta/shift/altKey into keyCombo
if (jwertyCodeFragment[z] in _keys.mods) {
keyCombo[_modProps[_keys.mods[jwertyCodeFragment[z]]]] = true;
} else if(jwertyCodeFragment[z] in _keys.keys) {
keyCombo.keyCode = _keys.keys[jwertyCodeFragment[z]];
} else {
rangeMatches = jwertyCodeFragment[z].match(/^\[([^-]+\-?[^-]*)-([^-]+\-?[^-]*)\]$/);
}
}
if (realTypeOf(keyCombo.keyCode, 'undefined')) {
// If we picked up a range match earlier...
if (rangeMatches && (rangeMatches[1] in _keys.keys) && (rangeMatches[2] in _keys.keys)) {
rangeMatches[2] = _keys.keys[rangeMatches[2]];
rangeMatches[1] = _keys.keys[rangeMatches[1]];
// Go from match 1 and capture all key-comobs up to match 2
for (rangeI = rangeMatches[1]; rangeI < rangeMatches[2]; ++rangeI) {
optionals.push({
altKey: keyCombo.altKey,
shiftKey: keyCombo.shiftKey,
metaKey: keyCombo.metaKey,
ctrlKey: keyCombo.ctrlKey,
keyCode: rangeI,
jwertyCombo: String(jwertyCodeFragment)
});
}
keyCombo.keyCode = rangeI;
// Inject either keyCode or ctrl/meta/shift/altKey into keyCombo
} else {
keyCombo.keyCode = 0;
}
}
optionals.push(keyCombo);
}
this[i] = optionals;
}
this.length = i;
return this;
}
var jwerty = exports.jwerty = {
/**
* jwerty.event
*
* `jwerty.event` will return a function, which expects the first
* argument to be a key event. When the key event matches `jwertyCode`,
* `callbackFunction` is fired. `jwerty.event` is used by `jwerty.key`
* to bind the function it returns. `jwerty.event` is useful for
* attaching to your own event listeners. It can be used as a decorator
* method to encapsulate functionality that you only want to fire after
* a specific key combo. If `callbackContext` is specified then it will
* be supplied as `callbackFunction`'s context - in other words, the
* keyword `this` will be set to `callbackContext` inside the
* `callbackFunction` function.
*
* @param {Mixed} jwertyCode can be an array, or string of key
* combinations, which includes optinals and or sequences
* @param {Function} callbackFucntion is a function (or boolean) which
* is fired when jwertyCode is matched. Return false to
* preventDefault()
* @param {Object} callbackContext (Optional) The context to call
* `callback` with (i.e this)
*
*/
event: function (jwertyCode, callbackFunction, callbackContext /*? this */) {
// Construct a function out of callbackFunction, if it is a boolean.
if (realTypeOf(callbackFunction, 'boolean')) {
var bool = callbackFunction;
callbackFunction = function () { return bool; }
}
jwertyCode = new JwertyCode(jwertyCode);
// Initialise in-scope vars.
var i = 0
, c = jwertyCode.length - 1
, returnValue
, jwertyCodeIs;
// This is the event listener function that gets returned...
return function (event) {
// if jwertyCodeIs returns truthy (string)...
if ((jwertyCodeIs = jwerty.is(jwertyCode, event, i))) {
// ... and this isn't the last key in the sequence,
// incriment the key in sequence to check.
if (i < c) {
++i;
return;
// ... and this is the last in the sequence (or the only
// one in sequence), then fire the callback
} else {
returnValue = callbackFunction.call(
callbackContext || this, event, jwertyCodeIs);
// If the callback returned false, then we should run
// preventDefault();
if (returnValue === false) event.preventDefault();
// Reset i for the next sequence to fire.
i = 0;
return;
}
}
// If the event didn't hit this time, we should reset i to 0,
// that is, unless this combo was the first in the sequence,
// in which case we should reset i to 1.
i = jwerty.is(jwertyCode, event) ? 1 : 0;
}
},
/**
* jwerty.is
*
* `jwerty.is` will return a boolean value, based on if `event` matches
* `jwertyCode`. `jwerty.is` is called by `jwerty.event` to check
* whether or not to fire the callback. `event` can be a DOM event, or
* a jQuery/Zepto/Ender manufactured event. The properties of
* `jwertyCode` (speficially ctrlKey, altKey, metaKey, shiftKey and
* keyCode) should match `jwertyCode`'s properties - if they do, then
* `jwerty.is` will return `true`. If they don't, `jwerty.is` will
* return `false`.
*
* @param {Mixed} jwertyCode can be an array, or string of key
* combinations, which includes optinals and or sequences
* @param {KeyboardEvent} event is the KeyboardEvent to assert against
* @param {Integer} i (Optional) checks the `i` key in jwertyCode
* sequence
*
*/
is: function (jwertyCode, event, i /*? 0*/) {
jwertyCode = new JwertyCode(jwertyCode);
// Default `i` to 0
i = i || 0;
// We are only interesting in `i` of jwertyCode;
jwertyCode = jwertyCode[i];
// jQuery stores the *real* event in `originalEvent`, which we use
// because it does annoything stuff to `metaKey`
event = event.originalEvent || event;
// We'll look at each optional in this jwertyCode sequence...
var key
, n = jwertyCode.length
, returnValue = false;
// Loop through each fragment of jwertyCode
while (n--) {
returnValue = jwertyCode[n].jwertyCombo;
// For each property in the jwertyCode object, compare to `event`
for (var p in jwertyCode[n]) {
// ...except for jwertyCode.jwertyCombo...
if (p !== 'jwertyCombo' && event[p] != jwertyCode[n][p]) returnValue = false;
}
// If this jwertyCode optional wasn't falsey, then we can return early.
if (returnValue !== false) return returnValue;
}
return returnValue;
},
/**
* jwerty.key
*
* `jwerty.key` will attach an event listener and fire
* `callbackFunction` when `jwertyCode` matches. The event listener is
* attached to `document`, meaning it will listen for any key events
* on the page (a global shortcut listener). If `callbackContext` is
* specified then it will be supplied as `callbackFunction`'s context
* - in other words, the keyword `this` will be set to
* `callbackContext` inside the `callbackFunction` function.
*
* @param {Mixed} jwertyCode can be an array, or string of key
* combinations, which includes optinals and or sequences
* @param {Function} callbackFunction is a function (or boolean) which
* is fired when jwertyCode is matched. Return false to
* preventDefault()
* @param {Object} callbackContext (Optional) The context to call
* `callback` with (i.e this)
* @param {Mixed} selector can be a string, jQuery/Zepto/Ender object,
* or an HTML*Element on which to bind the eventListener
* @param {Mixed} selectorContext can be a string, jQuery/Zepto/Ender
* object, or an HTML*Element on which to scope the selector
*
*/
key: function (jwertyCode, callbackFunction, callbackContext /*? this */, selector /*? document */, selectorContext /*? body */) {
// Because callbackContext is optional, we should check if the
// `callbackContext` is a string or element, and if it is, then the
// function was called without a context, and `callbackContext` is
// actually `selector`
var realSelector = realTypeOf(callbackContext, 'element') || realTypeOf(callbackContext, 'string') ? callbackContext : selector
// If `callbackContext` is undefined, or if we skipped it (and
// therefore it is `realSelector`), set context to `global`.
, realcallbackContext = realSelector === callbackContext ? global : callbackContext
// Finally if we did skip `callbackContext`, then shift
// `selectorContext` to the left (take it from `selector`)
, realSelectorContext = realSelector === callbackContext ? selector : selectorContext;
// If `realSelector` is already a jQuery/Zepto/Ender/DOM element,
// then just use it neat, otherwise find it in DOM using $$()
$b(realTypeOf(realSelector, 'element') ?
realSelector : $$(realSelector, realSelectorContext)
, jwerty.event(jwertyCode, callbackFunction, realcallbackContext));
},
/**
* jwerty.fire
*
* `jwerty.fire` will construct a keyup event to fire, based on
* `jwertyCode`. The event will be fired against `selector`.
* `selectorContext` is used to search for `selector` within
* `selectorContext`, similar to jQuery's
* `$('selector', 'context')`.
*
* @param {Mixed} jwertyCode can be an array, or string of key
* combinations, which includes optinals and or sequences
* @param {Mixed} selector can be a string, jQuery/Zepto/Ender object,
* or an HTML*Element on which to bind the eventListener
* @param {Mixed} selectorContext can be a string, jQuery/Zepto/Ender
* object, or an HTML*Element on which to scope the selector
*
*/
fire: function (jwertyCode, selector /*? document */, selectorContext /*? body */, i) {
jwertyCode = new JwertyCode(jwertyCode);
var realI = realTypeOf(selectorContext, 'number') ? selectorContext : i;
// If `realSelector` is already a jQuery/Zepto/Ender/DOM element,
// then just use it neat, otherwise find it in DOM using $$()
$f(realTypeOf(selector, 'element') ?
selector : $$(selector, selectorContext)
, jwertyCode[realI || 0][0]);
},
KEYS: _keys
};
}(this, (typeof module !== 'undefined' && module.exports ? module.exports : this)));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment