aced
Version:
php+node light FE/BE framework
325 lines (305 loc) • 7.8 kB
JavaScript
/*
Notes
- Keeps querystrings but removes hashes
- Can force https but does not treat this as a new resource
- Cb still called if css fails to load
-- SIMPLE --
ace.resource.fetch('https://s3.example.com/assets/SNotifs.js');
-- WITH CALLBACK --
ace.resource.fetch('https://s3.example.com/assets/SNotifs.js',function(){
console.log('js and css loaded');
});
-- MULTIPLE, CALLBACK FOR JS AND CSS SEPARATE --
ace.resource.fetch([
'//s3.example.com/assets/SNotifs.js#sup'
,'//s3.example.com/assets/SActivity.pv2.js'
,'//s3.example.com/assets/SLeaderboards.pv2.js'
,'s3.example.com/assets/SNotifs.css'
,'s3.example.com/assets/SActivity.pv2.css'
,'s3.example.com/assets/SLeaderboards.pv2.css'
],function(){
console.log('jsdone2');
},function(){
console.log('cssdone2');
});
-- MULTIPLE, CALLBACK FOR JS ONLY --
ace.resource.fetch([
'//s3.example.com/assets/SActivity.pv2.js'
,'s3.example.com/assets/SActivity.pv2.css'
],true,function(){
console.log('jsdone3');
},new Function); // currently you need to pass the css cb as an empty function to split up the cbs
-- LOAD JS SERIALLY (use case: homepages with global namespace functions) --
ace.resource.fetch([
'https://s3.example.com/assets/SNotifs.js'
,'//s3.example.com/assets/SActivity.pv2.js'
],true,function(){
console.log('alldone');
});
-- OVERRIDE AUTO-DETECT FILETYPE --
ace.resource.fetch([
{
url: '//start.jewelmint.com/k/api/test/temp'
,type: 'js'
}
,'//s3.example.com/assets/SActivity.pv2.js'
],function(){
console.log('alldone');
});
-- LOAD PACKS --
ace.resource.fetch([
'pack:notifs'
,'pack:activity'
]);
*/
ace.resource = {
config: {
key: 'resource'
,versions: {
// if asset is not listed here, it is versioned with defaultVersion via ace.getAppVersion()
// setting explicit version here is better for caching, especially if you release often
/* examples...
's3.example.com/assets/SActivity.js': [3, 'activity'] // <-- 'activity' == pack key
,'s3.example.com/assets/SActivity.css': [3, 'activity'] // <-- 'activity' == pack key
,'s3.example.com/assets/SMysteryWheel.pv2.js': 3 // <-- 3 == version
,'s3.example.com/assets/Polls.js': 5
,'s3.example.com/assets/reviews.css': 20
*/
}
,maxWaitCss: 1500
,styleSheetsCheckInterval: 300
}
,resources: {}
,packs: null
,defaultVersion: null
,fetch: function(/*resources,serial,opts,cb,jsCb,cssCb*/){
var z = this
,resources = arguments[0]
,cbs = []
,serial = false
,opts = {}
,n = 0
,cb,jsCb,cssCb,undef,i
;
cb = jsCb = cssCb = function(){};
for (i=1;i<arguments.length;++i) {
switch (typeof arguments[i]) {
case 'function': cbs.push(arguments[i]); break;
case 'boolean': serial = arguments[i]; break;
case 'object': opts = arguments[i]; break;
}
}
if (cbs.length == 2) {
jsCb = cbs[0];
cssCb = cbs[1];
} else if (cbs[0]) {
cb = cbs[0];
}
ace.ready(function(){
if (z.packs === null)
z.buildPacks();
if (z.defaultVersion === null)
return ace.getAppVersion(function(err,appVersion){
z.defaultVersion = appVersion;
goGetEm();
});
goGetEm();
});
function goGetEm(){
resources = z.parseResources(resources);
//console.log('resources',resources);
z.fetchCss(resources.css,opts.maxWaitCss==undef?z.config.maxWaitCss:opts.maxWaitCss,function(){
setTimeout(function(){
cssCb();
if (++n == 2)
cb();
},0);
});
z.fetchJs(resources.js,serial,function(){
setTimeout(function(){
jsCb();
if (++n == 2)
cb();
},0);
});
}
}
,fetchJs: function(items,serial,cb){
var z = this
,i = 0
,n = 0
;
if (!items.length)
return cb();
if (serial) {
(function next(){
fetchItem(items[i++],function(){
if (++n == items.length)
return cb();
next();
});
}());
} else {
$.each(items,function(i,item){
fetchItem(item,function(){
if (++n == items.length)
cb();
});
});
}
function fetchItem(item,cb){
var r = z.getResource(item.key);
if (r.status == 'fetched')
return cb();
if (r.status == 'fetching')
return z.subscribe(r,cb);
r.status = 'fetching';
$.ajax({
dataType: 'script'
,cache: true
,url: item.url
,success: function(){
cb();
z.gotResource(r);
}
});
}
}
,fetchCss: function(items,maxWait,cb){
var z = this
,n = 0
;
if (!items.length)
return cb();
$.each(items,function(i,item){
z.fetchSingleCss(item,maxWait,function(){
if (++n == items.length)
cb();
});
});
}
,fetchSingleCss: function(item,maxWait,cb){
var z = this
,r = z.getResource(item.key)
,once = false
,interval = null
,$head = z.$head = z.$head || $('head')
,lUrl = item.url.replace(/^https?:/,'')
,done = function(){
if (!once) {
once = true;
//if (typeof arguments[0] != 'undefined') // why did i write this? what if maxWait is null?
z.gotResource(r);
cb();
}
if (interval !== null) {
clearInterval(interval);
interval = null;
}
}
;
if (r.status == 'fetched')
return done();
if (r.status == 'fetching')
return z.subscribe(r,done);
r.status = 'fetching';
$('<link>').bind('load',done).bind('error',done).appendTo($head).attr({type:'text/css',rel:'stylesheet'}).attr('href',item.url);
if (document.styleSheets) {
interval = setInterval(function(){
$.each(document.styleSheets,function(i,v){
if ((v.href||'').replace(/^https?:/,'') == lUrl)
return done();
});
},z.config.styleSheetsCheckInterval);
}
if (typeof maxWait == 'number')
setTimeout(function(){done(true);},maxWait);
}
,getResource: function(key){
if (!this.resources[key])
this.resources[key] = {key:key};
return this.resources[key];
}
,subscribe: function(resource,cb){
(resource.subs = resource.subs || []).push(cb);
}
,gotResource: function(resource){
resource.status = 'fetched';
if (resource.subs) {
$.each(resource.subs,function(i,cb){
cb();
});
delete resource.subs;
}
}
,parseResources: function(input){
var z = this
,_input = []
,resources = {
js: []
,css: []
};
input = input instanceof Array ? input : [input];
$.each(input,function(i,item){
var pkey;
if (typeof item == 'string' && item.indexOf('pack:') == 0) {
pkey = item.substr(5);
if (z.packs[pkey])
_input.push.apply(_input,z.packs[pkey]);
} else {
_input.push(item);
}
});
$.each(_input,function(i,item){
var type,url,key;
if (item instanceof Object) {
url = z.cleanUrl(item.url);
type = item.type;
} else {
url = z.cleanUrl(item);
type = z.fileType(item);
}
key = z.getKeyFromUrl(url);
resources[type].push({
key: key
,url: url+(url.indexOf('?')<0?'?':'&')+z.version(key)
});
});
return resources;
}
,cleanUrl: function(url){
url = url.replace(/(#.*)/,'');
if (url[0] != '/' && !/^((https?:)?\/\/)/.test(url))
url = '//'+url;
return url;
}
,fileType: function(fn){
// default to js to trigger errors
return fn.toLowerCase().replace(/(\?.*)/,'').split('.').pop() == 'css' ? 'css' : 'js';
}
,getKeyFromUrl: function(url){
return url.replace(/^(https?:)?\/\//i,'');
}
,version: function(resource){
var v = this.config.versions[resource];
if (v instanceof Array)
v = v[0];
return v == null ? this.defaultVersion : v;
}
,buildPacks: function(){
var z = this;
z.packs = {};
$.each(z.config.versions,function(key,ver){
if (!(ver instanceof Array))
return true;
var i,pkey;
for (i=1;i<ver.length;++i) {
pkey = ver[i];
if (!z.packs[pkey])
z.packs[pkey] = [];
z.packs[pkey].push(key);
}
});
}
};
//ace.resource.init();