,]
// }
// - 'origin' (default)
//
// This expects:
// - the block is directly nested in the container
// - the block can be scaled
// - the block has an origin set
//
function getRelativeOffset(container, block, point){
point = point == null ? {} : point
var l = point.left
var t = point.top
var scale = point.scale
// get the input data...
var s = getElementScale(block)
var o = getElementOrigin(block)
// get only the value we need...
var W = container.width()
var H = container.height()
// we need this to make everything relative to the container...
var co = container.offset()
var offset = getElementOffset(block)
var bo = block.offset()
scale = scale == 'screen' ? 1
: scale == 'elem' ? s
: scale == null ? s
: scale
// normalize the l,t to element scale...
if(l != null && t != null){
// get only the value we need...
// NOTE: width and height are used to calculate the correction
// due to origin/scale...
var w = block.width()
var h = block.height()
o = {
// target offset scale...
top: t*scale
// set origin to top left corner of element (compensate
// for scaling)...
+ (h - h*s) / (h / o.top),
left: l*scale
+ (w - w*s) / (w / o.left),
}
}
return {
top: offset.top + (H/2 - offset.top) - o.top,
left: offset.left + (W/2 - offset.left) - o.left,
}
}
// NOTE: at this point this works only on the X axis...
function setElementTransform(elem, offset, scale, duration){
elem = $(elem)
//var t3d = USE_3D_TRANSFORM ? 'translateZ(0)' : ''
var t3d = USE_3D_TRANSFORM ? 'translate3d(0,0,0)' : ''
//var translate = USE_3D_TRANSFORM ? 'translate3d' : 'translate'
var translate = 'translate'
if(offset == null){
offset = getElementOffset(elem)
// number -- only the x coord...
} else if(typeof(offset) == typeof(1)){
offset = {
left: offset,
top: 0
}
// array...
} else if(offset.indexOf){
offset = {
left: offset[0] ? offset[0] : 0,
top: offset[1] ? offset[1] : 0
}
}
if(scale == null){
var scale = getElementScale(elem)
}
if(USE_TRANSFORM){
var transform = translate+'('+
Math.round(offset.left) +'px, '+
//Math.round(offset.top) +'px'+ (USE_3D_TRANSFORM && ', 0px' || '') +') '
Math.round(offset.top) +'px) '
+'scale('+ scale +') '
+ t3d
elem.css({
'-ms-transform' : transform,
'-webkit-transform' : transform,
'-moz-transform' : transform,
'-o-transform' : transform,
'transform' : transform,
// XXX can we avoid this here??
left: 0,
// XXX is this correct???
top: ''
}, duration)
} else {
//var transform = translate+'(0px, 0px'+ (USE_3D_TRANSFORM && ', 0px' || '') +') '
var transform = translate+'(0px, 0px) '
+'scale('+ scale +') '
+ t3d
elem.css({
// NOTE: this will be wrong during a transition, that's why we
// can pass the pre-calculated offset as an argument...
left: Math.round(offset.left),
top: Math.round(offset.top),
// XXX can we avoid this here??
'-ms-transform' : transform,
'-webkit-transform' : transform,
'-moz-transform' : transform,
'-o-transform' : transform,
'transform' : transform,
}, duration)
}
return elem
}
// Run a function controllably in an animation frame
//
// NOTE: we do not need to make this run several callbacks as the
// browser already does this and will do the loop faster...
function animationFrameRunner(func){
var next
var _nop = function(){ return this }
var frame
if(this === window){
self = new animationFrameRunner
} else {
self = this
}
self.func = func
var _tick = function(){
func(Date.now())
frame = getAnimationFrame(next)
}
// main user interface...
var start = function(){
next = _tick
this.start = _nop
this.stop = stop
// start things up...
// NOTE: we are not calling _tick here directly to avoid stray,
// off-frame call to func...
frame = getAnimationFrame(next)
return this
}
var stop = function(){
if(frame != null){
cancelAnimationFrame(frame)
frame = null
}
next = _nop
this.start = start
this.stop = _nop
return this
}
// setup the ticker in stopped state...
stop.call(self)
return self
}
// XXX make this a drop-in replacement for setElementTransform...
// XXX cleanup, still flacky...
function animateElementTo(elem, to, duration, easing, speed, callback, use_transitions){
// stop all ongoing animations on the current elem...
stopAnimation(elem)
use_transitions = use_transitions != null ?
use_transitions
: USE_TRANSITIONS_FOR_ANIMATION
// use transition for animation...
if(use_transitions){
setTransitionEasing(elem, easing)
duration == null && setTransitionDuration(elem, duration)
setElementTransform(elem, to)
// manually animate...
} else {
if(typeof(to) == typeof(1)){
to = {
left: to,
top: 0,
}
}
if(typeof(speed) == typeof(2)){
speed = {
x: speed,
y: 0,
}
}
if(duration == null){
duration = getElementTransitionDuration(elem)
}
setTransitionDuration(elem, 0)
var start = Date.now()
var then = start + duration
var from = getElementOffset(elem)
var cur = {
top: from.top,
left: from.left
}
var dist = {
top: to.top - from.top,
left: to.left - from.left,
}
// XXX are we using this...
elem.animating = true
elem.next_frame = null
// remember step start position...
var s_t = cur.top
var s_l = cur.left
function animate(){
// prevent running animations till next call of animateElementTo(..)
if(elem.next_frame === false){
return
}
var t = Date.now()
// end of the animation...
if(t >= then){
setElementTransform(elem, to)
return
}
if(!elem.animating){
// XXX jittery...
setElementTransform(elem, cur)
return
}
// remember step start position...
s_t = cur.top
s_l = cur.left
// animate a step with speed...
if(speed != null){
// NOTE: these are almost identical, they are inlined
// for speed...
if(Math.abs(dist.top) >= 1){
dy = ((t - start) * speed.y)
if(Math.abs(dist.top) > Math.abs(dy)){
dist.top -= dy
cur.top = Math.round(cur.top + dy)
// normalize...
cur.top = Math.abs(dist.top) <= 1 ? to.top : cur.top
// calc speed for next step...
speed.y = dist.top / (duration - (t - start))
} else {
cur.top = to.top
}
}
if(Math.abs(dist.left) >= 1){
dx = ((t - start) * speed.x)
if(Math.abs(dist.left) > Math.abs(dx)){
dist.left -= dx
cur.left = Math.round(cur.left + dx)
// normalize...
cur.left = Math.abs(dist.left) <= 1 ? to.left : cur.left
// calc speed for next step...
speed.x = dist.left / (duration - (t - start))
} else {
cur.left = to.left
}
}
// liner animate...
} else {
var r = (t - start) / duration
cur.top = Math.round(from.top + (dist.top * r))
cur.left = Math.round(from.left + (dist.left * r))
}
setElementTransform(elem, cur)
callback != null && callback({
x: cur.left - s_l,
y: cur.top - s_t,
})
// sched next frame...
elem.next_frame = getAnimationFrame(animate)
}
animate()
}
}
function stopAnimation(elem){
if(elem.next_frame){
cancelAnimationFrame(elem.next_frame)
elem.next_frame = false
return
}
}
// XXX account for other transitions...
// XXX make a sync version...
function setElementOffset(elem, l, t, scale){
return setElementTransform(elem, [l, t], scale)
}
function setElementScale(elem, scale){
return setElementTransform(elem, null, scale)
}
function setElementOrigin(elem, x, y, z){
x = x == null ? '50%' : x
y = y == null ? '50%' : y
z = z == null ? '0' : z
var value = x +' '+ y +' '+ z
return $(elem).css({
'transform-origin': value,
'-o-transform-origin': value,
'-ms-transform-origin': value,
'-moz-transform-origin': value,
'-webkit-transform-origin': value,
})
}
// a sync version of setElementOrigin(..), this will not trigger transforms...
function setElementOriginSync(elem, x, y, z){
x = x == null ? '50%' : x
y = y == null ? '50%' : y
z = z == null ? '0' : z
var value = x +' '+ y +' '+ z
elem = $(elem)
var e = elem[0]
e.style.display = 'none'
// now kick the browser into recognition of our changes NOW ;)
getComputedStyle(e).display
e.style['-o-transform-origin'] = value
e.style['-ms-transform-origin'] = value
e.style['-moz-transform-origin'] = value
e.style['-webkit-transform-origin'] = value
e.style['transform-origin'] = value
e.style.display = ''
getComputedStyle(e).display
return $(elem)
}
// this is like setElementOrigin(..) but will compensate for element
// shift when scaled...
// NOTE: this will work only of translate is used for positioning...
function shiftOriginTo(elem, l, t, scale){
var o = getElementOrigin(elem)
var scale = scale || getElementScale(elem)
var offset = getElementOffset(elem)
// calculate the offset change and compensate...
var cl = offset.left + ((o.left - o.left*scale) - (l - l*scale))
var ct = offset.top + ((o.top - o.top*scale) - (t - t*scale))
setElementOffset(elem, cl, ct)
return setElementOriginSync(elem, l+'px', t+'px')
}
function setTransitionEasing(elem, ease){
if(typeof(ms) == typeof(0)){
ms = ms + 'ms'
}
return $(elem).css({
'transition-timing-function': ease,
'-moz-transition-timing-function': ease,
'-o-transition-timing-function': ease,
'-ms-transition-timing-function': ease,
'-webkit-transition-timing-function': ease
})
}
function setTransitionDuration(elem, ms){
if(typeof(ms) == typeof(0)){
ms = ms + 'ms'
}
return elem.css({
'transition-duration': ms,
'-moz-transition-duration': ms,
'-o-transition-duration': ms,
'-ms-transition-duration': ms,
'-webkit-transition-duration': ms
})
}
/************************************************ jQuery extensions **/
jQuery.fn.reverseChildren = function(){
return $(this).each(function(_, e){
return $(e).append($(e).children().detach().get().reverse())
})
}
jQuery.fn.sortChildren = function(func){
return $(this).each(function(_, e){
return $(e).append($(e).children().detach().get().sort(func))
})
}
/************************************************** Deferred utils ***/
// Deferred worker pool...
//
// makeDeferredPool([size][, paused]) -> pool
//
//
// This will create and return a pooled queue of deferred workers.
//
//
// The pool can be in one of the folowing states:
//
// - filling
// This state prevents .depleted() from triggering until the pool
// exits the filling state.
// This helps us to prevent premature depletion of the pool in
// cases where the queue is depleted faster than it is being filled.
//
// - paused
// This state prevents any new queued workers from starting.
//
//
// Public interface:
//
// .enqueue(obj, func, args) -> deferred
// Add a worker to queue.
// If the pool is not filled and not paused, this will run the
// worker right away.
// If the pool is full the worker is added to queue (FIFO) and
// ran in its turn.
//
// .dropQueue() -> pool
// Drop the queued workers.
// NOTE: this will not stop the already running workers.
//
//
// .filling()
// Enter the filling state
//
// .doneFilling()
// Exit the filling state
// NOTE: this will trigger .depleted() if at the time of call
// both the pool and queue are empty.
//
//
// .pause() -> pool
// Pause the queue.
// NOTE: this also has a second form: .pause(func), see below.
//
// .resume() -> pool
// Restart the queue.
//
//
// .isFilling() -> bool
// Test if the pool is being filled -- filling state.
//
// .isRunning() -> bool
// Test if any workers are running in the pool.
// NOTE: this will return false ONLY when the pool is empty.
//
// .isPaused() -> bool
// Test if pool is in a paused state.
// NOTE: some workers may sill be finishing up so if you want
// to test whether any workers are still running use
// .isRunning()
//
//
// Event handler/callback registration:
//
// .on(evt, func) -> pool
// Register a handler (func) for an event (evt).
//
// .off(evt[, func]) -> pool
// Remove a handler (func) form and event (evt).
// NOTE: if func is omitted, remove all handlers from the given
// event...
//
// .progress(func) -> pool
// Register a progress handler.
// The handler is called after each worker is done and will get
// passed:
// - workers done count
// - workers total count
// Short hand for:
// .on('progress', func) -> pool
// NOTE: the total number of workers can change as new workers
// are added or the queue is cleared...
//
// .fail(func) -> pool
// Register a worker fail handler.
// The handler is called when a worker goes into the fail state.
// This will get passed:
// - workers done count
// - workers total count
// Short hand for:
// .on('fail', func) -> pool
// NOTE: this will not stop the execution of other handlers.
//
// .pause(func) -> pool
// Register a pause handler.
// This handler is called after the last worker finishes when
// the queue is paused.
// Short hand for:
// .on('progress', func) -> pool
//
// .resume(func) -> pool
// Short hand for:
// .on('resume', func) -> pool
//
// .depleted(func) -> pool
// Register a depleted pool handler.
// The handler will get called when the queue and pool are empty
// (depleted) and the last worker is done.
// Short hand for:
// .on('deplete', func) -> pool
//
// XXX should this be an object or a factory???
function makeDeferredPool(size, paused){
size = size == null ? POOL_SIZE : size
size = size < 0 ? 1
: size > 512 ? 512
: size
paused = paused == null ? false : paused
var Pool = {
pool: [],
queue: [],
size: size,
// XXX do we need to hide or expose them and use their API???
_event_handlers: {
deplete: $.Callbacks(),
progress: $.Callbacks(),
pause: $.Callbacks(),
resume: $.Callbacks(),
fail: $.Callbacks()
},
_paused: paused,
}
// Run a worker...
//
// This will:
// - create and add a worker to the pool, which will:
// - run an element from the queue
// - remove self from pool
// - if the pool is not full, create another worker (call
// ._run(..)) else exit
// - call ._fill() to replenish the pool
Pool._run = function(deferred, func, args){
var that = this
var pool = this.pool
var pool_size = this.size
var queue = this.queue
var run = this._run
// run an element from the queue...
var worker = func.apply(null, args)
pool.push(worker)
// NOTE: this is explicitly after the pool push to avoid the
// possible race condition of the worker exiting and
// triggering .always(..) before being added to the pool...
worker
.always(function(){
// prepare to remove self from pool...
var i = pool.indexOf(this)
Pool._event_handlers.progress.fire(pool.length - pool.len, pool.length + queue.length)
// remove self from queue...
delete pool[i]
// shrink the pool if it's overfilled...
// i.e. do not pop another worker and let the "thread" die.
if(pool.len > pool_size){
// remove self...
return
}
// pause the queue -- do not do anything else...
if(that._paused == true){
// if pool is empty fire the pause event...
if(pool.len == 0){
Pool._event_handlers.pause.fire()
}
return
}
// get the next queued worker...
var next = queue.splice(0, 1)[0]
// run the next worker if it exists...
if(next != null){
run.apply(that, next)
// empty queue AND empty pool mean we are done...
} else if(pool.len == 0){
var l = pool.length
// NOTE: potential race condition -- something can be
// pushed to pool just before it's "compacted"...
pool.length = 0
if(!that._filling){
that._event_handlers.deplete.fire(l)
}
}
// keep the pool full...
that._fill()
})
.fail(function(){
Pool._event_handlers.fail.fire(pool.length - pool.len, pool.length + queue.length)
deferred.reject.apply(deferred, arguments)
})
.progress(function(){
deferred.notify.apply(deferred, arguments)
})
.done(function(){
deferred.resolve.apply(deferred, arguments)
})
return worker
}
// Fill the pool...
//
Pool._fill = function(){
var that = this
var pool_size = this.size
var run = this._run
var l = this.pool.len
if(this._paused != true
&& l < pool_size
&& this.queue.length > 0){
this.queue.splice(0, pool_size - l)
.forEach(function(e){
run.apply(that, e)
})
}
return this
}
// Public methods...
// Add a worker to queue...
//
Pool.enqueue = function(func){
var deferred = $.Deferred()
// add worker to queue...
this.queue.push([deferred, func, args2array(arguments).slice(1)])
// start work if we have not already...
this._fill()
//return this
return deferred
}
// Drop the queued workers...
//
// NOTE: this will not stop the running workers...
// XXX should this return the pool or the dropped queue???
Pool.dropQueue = function(){
this.queue.splice(0, this.queue.length)
return this
}
// Filling state...
//
// When this mode is set, it will prevent the queue from triggering
// the depleated action until .doneFilling() is called...
//
// This is to prevent the pool depleting before the queue is filled
// in the case of tasks ending faster than they are added...
Pool.filling = function(){
this._filling = true
return this
}
Pool.doneFilling = function(){
delete this._filling
// trigger depleted if we are empty...
if(this.pool.len == 0 && this.queue.length == 0){
that._event_handlers.deplete.fire(l)
}
return this
}
Pool.isFilling = function(){
return this._filling == true
}
// Paused state...
//
// NOTE: this will not directly cause .isRunning() to return false
// as this will not directly spot all workers, it will just
// pause the queue and the workers that have already started
// will keep running until they are done, and only when the
// pool is empty will the .isRunning() return false.
//
// XXX test...
Pool.pause = function(func){
if(func == null){
this._paused = true
} else {
this.on('pause', func)
}
return this
}
// XXX test...
Pool.resume = function(func){
if(func == null){
this._paused = false
this._event_handlers['resume'].forEach(function(f){ f() })
this._fill()
} else {
this.on('resume', func)
}
return this
}
Pool.isPaused = function(){
return this._paused
}
Pool.isRunning = function(){
return this.pool.len > 0
}
// Generic event handlers...
Pool.on = function(evt, handler){
this._event_handlers[evt].add(handler)
return this
}
// NOTE: if this is not given a handler, it will clear all handlers
// from the given event...
Pool.off = function(evt, handler){
if(handler != null){
this._event_handlers[evt].remove(handler)
} else {
this._event_handlers[evt].empty()
}
return this
}
// Register a queue depleted handler...
//
// This occurs when a populated queue is depleted and the last worker
// is done.
//
// NOTE: this is similar to jQuery.Deferred().done(..) but differs in
// that the pool can fill up and get depleted more than once,
// thus, the handlers may get called more than once per pool
// life...
// NOTE: it is recommended to fill the queue faster than the workers
// finish, as this may get called after last worker is done and
// the next is queued...
Pool.depleted = function(func){
return this.on('deplete', func)
}
// Deferred compatibility...
//
// NOTE: the key difference between this and the deferred is that this
// does not have memory and can get called multiple times...
// XXX is this correct???
//Pool.done = Pool.depleted
// Register queue progress handler...
//
// This occurs after each worker is done.
//
// handler will be passed:
// - the pool object
// - workers done
// - total workers (done + queued)
Pool.progress = function(func){
return this.on('progress', func)
}
// Register worker fail handler...
//
Pool.fail = function(func){
return this.on('fail', func)
}
return Pool
}
/**************************************************** JS utilities ***/
// Get screen dpi...
//
// This will calculate the value and save it to screen.dpi
//
// if force is true this will re-calculate the value.
//
// NOTE: this needs the body loaded to work...
// NOTE: this may depend on page zoom...
// NOTE: yes, this is a hack, but since we have no other reliable way to
// do this...
function getDPI(force){
if(screen.dpi == null || force){
var e = $('')
.css({
position: 'absolute',
width: '1in',
left: '-100%',
top: '-100%'
})
.appendTo($('body'))
var res = e.width()
e.remove()
screen.dpi = res
return res
} else {
return screen.dpi
}
}
// XXX is this correct???
$(getDPI)
// return 1, -1, or 0 depending on sign of x
function sign(x){
return (x > 0) - (x < 0)
}
// convert JS arguments to Array...
function args2array(args){
//return Array.apply(null, args)
return [].slice.call(args)
}
var getAnimationFrame = (window.requestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.oRequestAnimationFrame
|| window.msRequestAnimationFrame
|| function(callback){
setTimeout(callback, 1000/60)
})
var cancelAnimationFrame = (window.cancelRequestAnimationFrame
|| window.webkitCancelAnimationFrame
|| window.webkitCancelRequestAnimationFrame
|| window.mozCancelRequestAnimationFrame
|| window.oCancelRequestAnimationFrame
|| window.msCancelRequestAnimationFrame
|| clearTimeout)
// NOTE: repatching a date should not lead to any side effects as this
// does not add any state...
function patchDate(date){
date = date || Date
date.prototype.toShortDate = function(){
var y = this.getFullYear()
var M = this.getMonth()+1
M = M < 10 ? '0'+M : M
var D = this.getDate()
D = D < 10 ? '0'+D : D
var H = this.getHours()
H = H < 10 ? '0'+H : H
var m = this.getMinutes()
m = m < 10 ? '0'+m : m
var s = this.getSeconds()
s = s < 10 ? '0'+s : s
return ''+y+'-'+M+'-'+D+' '+H+':'+m+':'+s
}
date.prototype.getTimeStamp = function(no_seconds){
var y = this.getFullYear()
var M = this.getMonth()+1
M = M < 10 ? '0'+M : M
var D = this.getDate()
D = D < 10 ? '0'+D : D
var H = this.getHours()
H = H < 10 ? '0'+H : H
var m = this.getMinutes()
m = m < 10 ? '0'+m : m
var s = this.getSeconds()
s = s < 10 ? '0'+s : s
return ''+y+M+D+H+m+s
}
date.prototype.setTimeStamp = function(ts){
ts = ts.replace(/[^0-9]*/g, '')
this.setFullYear(ts.slice(0, 4))
this.setMonth(ts.slice(4, 6)*1-1)
this.setDate(ts.slice(6, 8))
this.setHours(ts.slice(8, 10))
this.setMinutes(ts.slice(10, 12))
this.setSeconds(ts.slice(12, 14))
return this
}
date.timeStamp = function(){
return (new this()).getTimeStamp()
}
date.fromTimeStamp = function(ts){
return (new this()).setTimeStamp(ts)
}
// convert string time period to milliseconds...
date.str2ms = function(str, dfl){
dfl = dfl || 'ms'
if(typeof(str) == typeof(123)){
var val = str
str = dfl
} else {
var val = parseFloat(str)
str = str.trim()
// check if a unit is given...
str = str == val ? dfl : str
}
var c = /(m(illi)?(-)?s(ec(ond(s)?)?)?)$/i.test(str) ? 1
: /s(ec(ond(s)?)?)?$/i.test(str) ? 1000
: /m(in(ute(s)?)?)?$/i.test(str) ? 1000*60
: /h(our(s)?)?$/i.test(str) ? 1000*60*60
: /d(ay(s)?)?$/i.test(str) ? 1000*60*60*24
: null
return c ? val * c : NaN
}
return date
}
// patch the root date...
patchDate()
function logCalls(func, logger){
var that = this
var _func = function(){
logger(func, arguments)
return func.apply(that, arguments)
}
_func.name = func.name
return _func
}
function assyncCall(func){
var that = this
var _func = function(){
var res = $.Deferred()
setTimeout(function(){
res.resolve(func.apply(that, arguments))
}, 0)
return res
}
_func.name = func.name
return _func
}
// Quote a string and convert to RegExp to match self literally.
function quoteRegExp(str){
return str.replace(/([\.\\\/\(\)\[\]\$\*\+\-\{\}\@\^\&\?\<\>])/g, '\\$1')
}
/**********************************************************************
* vim:set ts=4 sw=4 : */