can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
279 lines (246 loc) • 7.47 kB
JavaScript
steal("can/util", "./utils.js","can/view/live",function(can, utils, live){
live = live || can.view.live;
var resolve = function (value) {
if (utils.isObserveLike(value) && utils.isArrayLike(value) && value.attr('length')) {
return value;
} else if (can.isFunction(value)) {
return value();
} else {
return value;
}
};
var resolveHash = function(hash){
var params = {};
for(var prop in hash) {
var value = hash[prop];
if(value && value.isComputed) {
params[prop] = value();
} else {
params[prop] = value;
}
}
return params;
};
var looksLikeOptions = function(options){
return options && typeof options.fn === "function" && typeof options.inverse === "function";
};
var helpers = {
"each": function(items, options){
var resolved = resolve(items),
result = [],
keys,
key,
i;
if( resolved instanceof can.List && !options.stringOnly) {
return function(el){
// make a child nodeList inside the can.view.live.html nodeList
// so that if the html is re
var nodeList = [el];
nodeList.expression = "live.list";
can.view.nodeLists.register(nodeList, null, options.nodeList, true);
// runs nest replacements
can.view.nodeLists.update(options.nodeList, [el]);
var cb = function (item, index, parentNodeList) {
return options.fn(options.scope.add({
"%index": index,
"@index": index
},{notContext: true}).add(item), options.options, parentNodeList);
};
live.list(el, items, cb, options.context, el.parentNode, nodeList, function(list, parentNodeList){
return options.inverse(options.scope.add(list), options.options, parentNodeList);
});
};
}
var expr = resolved;
if ( !! expr && utils.isArrayLike(expr)) {
var fragItems = utils.getItemsFragContent(expr, options, options.scope);
Array.prototype.push.apply(result, fragItems);
} else if (utils.isObserveLike(expr)) {
keys = can.Map.keys(expr);
// listen to keys changing so we can livebind lists of attributes.
for (i = 0; i < keys.length; i++) {
key = keys[i];
result.push(options.fn(options.scope.add({
"%key": key,
"@key": key
},{notContext: true})
.add(expr[key])));
}
} else if (expr instanceof Object) {
for (key in expr) {
result.push(options.fn(options.scope.add({
"%key": key,
"@key": key
},{notContext: true})
.add(expr[key])));
}
}
return !options.stringOnly ? result : result.join('');
},
"@index": function(offset, options) {
if (!options) {
options = offset;
offset = 0;
}
var index = options.scope.attr("@index");
return ""+((can.isFunction(index) ? index() : index) + offset);
},
'if': function (expr, options) {
var value;
// if it's a function, wrap its value in a compute
// that will only change values from true to false
if (can.isFunction(expr)) {
value = can.compute.truthy(expr)();
} else {
value = !! resolve(expr);
}
if (value) {
return options.fn(options.scope || this);
} else {
return options.inverse(options.scope || this);
}
},
'is': function() {
var lastValue, curValue,
options = arguments[arguments.length - 1];
if (arguments.length - 2 <= 0) {
return options.inverse();
}
var args = arguments;
var callFn = can.compute(function(){
for (var i = 0; i < args.length - 1; i++) {
curValue = resolve(args[i]);
curValue = can.isFunction(curValue) ? curValue() : curValue;
if (i > 0) {
if (curValue !== lastValue) {
return false;
}
}
lastValue = curValue;
}
return true;
});
return callFn() ? options.fn() : options.inverse();
},
'eq': function() {
return helpers.is.apply(this, arguments);
},
'unless': function (expr, options) {
return helpers['if'].apply(this, [expr, can.extend({}, options, {
fn: options.inverse,
inverse: options.fn
})]);
},
'with': function (expr, options) {
var ctx = expr;
expr = resolve(expr);
if ( !! expr) {
return options.fn(ctx);
}
},
'log': function (expr, options) {
if (typeof console !== "undefined" && console.log) {
if (!options) {
console.log(expr.context);
} else {
console.log(expr, options.context);
}
}
},
'data': function(attr){
// options will either be the second or third argument.
// Get the argument before that.
var data = arguments.length === 2 ? this : arguments[1];
return function(el){
can.data( can.$(el), attr, data || this.context );
};
},
'switch': function(expression, options){
resolve(expression);
var found = false;
var newOptions = options.helpers.add({
"case": function(value, options){
if(!found && resolve(expression) === resolve(value)) {
found = true;
return options.fn(options.scope || this);
}
},
"default": function(options){
if(!found) {
return options.fn(options.scope || this);
}
}
});
return options.fn(options.scope, newOptions);
},
'joinBase': function(firstExpr/* , expr... */){
var args = [].slice.call(arguments);
var options = args.pop();
var moduleReference = can.map(args, function(expr){
var value = resolve(expr);
return can.isFunction(value) ? value() : value;
}).join("");
var templateModule = options.helpers.attr("helpers.module");
var parentAddress = templateModule ? templateModule.uri: undefined;
var isRelative = moduleReference[0] === ".";
if(isRelative && parentAddress) {
return can.joinURIs(parentAddress, moduleReference);
} else {
var baseURL = can.baseURL || (typeof System !== "undefined" &&
(System.renderingLoader && System.renderingLoader.baseURL ||
System.baseURL)) ||
location.pathname;
// Make sure one of them has a needed /
if(moduleReference[0] !== "/" && baseURL[baseURL.length - 1] !== "/") {
baseURL += "/";
}
return can.joinURIs(baseURL, moduleReference);
}
},
routeUrl: function(params, merge){
// check if called like a mustache helper
if(!params) {
params = {};
}
if(typeof params.fn === "function" && typeof params.inverse === "function") {
params = resolveHash(params.hash);
}
return can.route.url(params, typeof merge === "boolean" ? merge : undefined);
},
routeCurrent: function(params){
// check if this a normal helper call
var last = can.last(arguments),
isOptions = last && looksLikeOptions(last);
if( last && isOptions && !(last.exprData instanceof can.expression.Call) ) {
if(can.route.current( resolveHash(params.hash || {}))) {
return params.fn();
} else {
return params.inverse();
}
} else {
return can.route.current(looksLikeOptions(params) ? {} : params || {});
}
}
};
// TODO ... remove as this is hacky
helpers.routeCurrent.callAsMethod = true;
helpers.eachOf = helpers.each;
var registerHelper = function(name, callback){
helpers[name] = callback;
};
return {
registerHelper: registerHelper,
registerSimpleHelper: function(name, callback) {
registerHelper(name, can.view.simpleHelper(callback));
},
getHelper: function(name, options){
var helper = options && options.get("helpers." + name,{proxyMethods: false});
if(!helper) {
helper = helpers[name];
}
if(helper) {
return {fn: helper};
}
}
};
});