387 lines
8.9 KiB
JavaScript
Raw Normal View History

/**********************************************************************
*
*
*
**********************************************************************/
define(function(require){ var module = {}
//var DEBUG = DEBUG != null ? DEBUG : true
/*********************************************************************/
// convert JS arguments to Array...
var args2array =
module.args2array =
function(args){
//return Array.apply(null, args)
return [].slice.call(args)
}
// Quote a string and convert to RegExp to match self literally.
var quoteRegExp =
module.quoteRegExp =
function(str){
return str.replace(/([\.\\\/\(\)\[\]\$\*\+\-\{\}\@\^\&\?\<\>])/g, '\\$1')
}
// XXX do we need to quote anything else???
var path2url =
module.path2url =
function(path){
// test if we have a schema, and if yes return as-is...
if(/^(http|https|file|[\w-]*):[\\\/]{2}/.test(path)){
return path
}
// skip encoding windows drives...
var drive = path.split(/^([a-z]:[\\\/])/i)
path = drive.pop()
drive = drive.pop() || ''
return drive + (path
.split(/[\\\/]/g)
// XXX these are too aggressive...
//.map(encodeURI)
//.map(encodeURIComponent)
.join('/')
// NOTE: keep '%' the first...
//.replace(/%/g, '%25')
.replace(/#/g, '%23')
.replace(/&/g, '%26'))
}
// NOTE: we are not using node's path module as we need this to work in
// all contexts, not only node... (???)
var normalizePath =
module.normalizePath =
function(path){
return typeof(path) == typeof('str') ? path
// normalize the slashes...
.replace(/(\/)/g, '/')
// remove duplicate '/'
.replace(/(\/)\1+/g, '/')
// remove trailing '/'
.replace(/\/+$/, '')
// take care of .
.replace(/\/\.\//g, '/')
.replace(/\/\.$/, '')
// take care of ..
.replace(/\/[^\/]+\/\.\.\//g, '/')
.replace(/\/[^\/]+\/\.\.$/, '')
: path
}
/*********************************************************************/
var _transform_parse = {
// 2d transforms:
//martix: [],
translate: ['left|0', 'top|0'],
translateX: ['left'],
translateY: ['top'],
scale: [
['scale'],
['scaleX|scale', 'scaleY|scale'],
],
scaleX: ['scaleX'],
scaleY: ['scaleY'],
rotate: ['rotate'],
skew: ['skewX', 'skewY'],
skewX: ['skewX'],
skewY: ['skewY'],
// 3d transforms:
//martix3d: [],
translate3d: ['x|0', 'y|0', 'z|0'],
translateZ: ['z'],
scale3d: ['scaleX', 'scaleY', 'scaleZ'],
scaleZ: ['scaleZ'],
// XXX
//rotate3d: [x, y, z, angle],
// rotateX
// rotateY
// rotateZ
perspective: ['perspective'],
}
var _transform_parse_rev = {}
Object.keys(_transform_parse).forEach(function(func){
var args = _transform_parse[func]
// we got multiple signatures == merge...
if(!(args[0] instanceof Array)){
args = [args]
}
args
// merge lists of args...
.reduce(function(a, b){ return [].concat.call(a, b) })
.unique()
// split alternatives...
.map(function(a){ return a.split(/\|/g) })
.forEach(function(a){
var arg = a[0]
var alt = a.slice(1)
var e = _transform_parse_rev[arg] = _transform_parse_rev[arg] || {}
e.funcs = e.funcs || []
e.funcs.indexOf(func) < 0 && e.funcs.push(func)
e.alt = e.alt || []
// XXX we explicitly support only one alternative now...
e.alt = e.alt.concat(alt).unique()
})
})
// XXX get vendor...
//
// Set element transform...
// .transform({..})
// -> element
//
// Get element transform...
// .transform()
// .transform(<attr>[, ...])
// .transform([<attr>, ...])
// -> data
//
// Supported transformations:
// x/y
// scale
// scaleX/scaleY
// origin
// originX/originY
//
// NOTE: pixel values are converted to numbers and back by default...
//
// XXX this will get/set values only on the first element, is this correct???
// XXX how do we combine translate(..) and translate3d(..)???
jQuery.fn.transform = function(){
var that = this
var args = args2array(arguments)
// XXX get the browser prefix...
var prefix = ''
// normalize...
args = args.length == 0
|| typeof(args[0]) == typeof('str') ? args
: args[0].constructor === Array
|| args.length == 1 ? args[0]
: args
var elem = $(this)[0]
var origin_str = elem.style[prefix + 'transformOrigin']
var transform_str = elem.style[prefix + 'transform']
// build the current state...
// NOTE: we'll need this for both fetching (parsing) and writing
// (checking)...
var transform = {}
var functions = {}
// origin...
var origin = origin_str
.split(/\s+/)
// XXX add this to transform...
// transform...
transform_str
// split functions...
.split(/(\w+\([^\)]*)\)/)
// remove empty strings...
.filter(function(e){ return e.trim().length > 0})
// split each function...
.map(function(e){ return e
// split args...
.split(/\s*[\(,\s]\s*/)
// cleanup...
.filter(function(e){ return e.trim().length > 0 }) })
// build the structure...
.forEach(function(data){
var func = data.shift()
var args = data
// XXX do we care about function vendor tags here???
var spec = _transform_parse[func]
functions[func] = args
// we do not know this function...
if(spec == null){
transform[func] = args
} else {
spec = spec[0] instanceof Array ? spec : [spec]
spec.forEach(function(s){
// skip non-matching signatures...
// XXX is this correct???
if(s.length != args.length){
return
}
s.forEach(function(e, i){
// this is for things that have optional arguments
// like scale(..)
// XXX should we treat this in some special way???
if(args[i] == null){
return
}
var alternatives = e.split(/\|/g)
var k = alternatives.shift().trim()
transform[k] = args[i].slice(-2) == 'px'
|| /[0-9\.]+/.test(args[i]) ?
parseFloat(args[i])
: args[i]
})
})
}
})
// get data...
if(args.constructor === Array){
var res = {}
// return the full transform...
if(args.length == 0){
return transform
}
args.forEach(function(arg){
// direct match in shorthand data...
if(arg in transform){
res[arg] = transform[arg]
// try and find an alias...
} else if(arg in _transform_parse_rev){
var funcs = _transform_parse_rev[arg].funcs
var alt = _transform_parse_rev[arg].alt[0]
// no alternatives...
if(!alt){
res[arg] = ''
// explicit number value...
} else if(/^[0-9\.]+$/.test(alt)){
res[arg] = parseFloat(alt)
// explicit string value...
} else if(/^(['"]).*\1$/.test(alt)){
res[arg] = alt.slice(1, -1)
} else {
var v = $(that).transform(alt)
res[arg] = v == '' ? alt : v
}
// collect from function...
} else if(arg in _transform_parse){
var v = res[arg] = {}
_transform_parse[arg].forEach(function(e){
var alternatives = e.split(/\|/g)
var k = alternatives.shift().trim()
v[k] = transform[k] != null ? transform[k] : ''
})
// don't know about this attr...
} else {
res[arg] = ''
}
})
// special case: we asked for a single value...
if(args.length == 1){
return res[args[0]]
}
return res
// set data...
} else {
transform = Object.create(transform)
Object.keys(args).forEach(function(key){
// the changing value already exists...
if(key in transform
// get one of the shorthand keys...
// NOTE: we might need to pack or re-pack the date but we
// can't decide here...
|| key in _transform_parse_rev
// got one of the standard keys...
|| key in _transform_parse){
transform[key] = args[key]
// key undefined...
} else {
console.warn('Ignoring key "%s".', key)
transform[key] = args[key]
}
})
console.log('>>>>', transform)
// XXX set new values and resolve new functions...
// XXX
// build the value string...
var transform_str = ''
for(var f in functions){
transform_str += f +'('
+(functions[f]
// XXX test if px value...
.map(function(e){ return typeof(e) == typeof(123) ? e + 'px' : e })
.join(', '))+') '
}
console.log(transform_str)
// XXX STUB
return functions
// set the values...
elem.style.transform = transform_str
elem.style.transformOrigin = origin_str
}
return $(this)
}
// shorthands...
jQuery.fn.scale = function(value){
if(value){
return $(this).transform({scale: value})
} else {
return $(this).transform('scale')
}
}
jQuery.fn.origin = function(value){
if(value){
return $(this).transform({origin: value})
} else {
return $(this).transform('origin')
}
}
/**********************************************************************
* vim:set ts=4 sw=4 : */
return module })