can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
277 lines (275 loc) • 10.8 kB
JavaScript
steal('jquery', 'can/util', 'can/view', function ($, can) {
$ = $ || window.$;
//---- ADD jQUERY HELPERS -----
//converts jquery functions to use views
var convert, modify, isTemplate, isHTML, isDOM, getCallback,
// text and val cannot produce an element, so don't run hookups on them
noHookup = {
'val': true,
'text': true
};
convert = function (func_name) {
// save the old jQuery helper
var old = $.fn[func_name];
// replace it with our new helper
$.fn[func_name] = function () {
var args = can.makeArray(arguments),
callbackNum, callback, self = this,
result;
// if the first arg is a deferred
// wait until it finishes, and call
// modify with the result
if (can.isPromise(args[0])) {
args[0].done(function (res) {
modify.call(self, [res], old);
});
return this;
} //check if a template
else if (isTemplate(args)) {
// if we should operate async
if (callbackNum = getCallback(args)) {
callback = args[callbackNum];
args[callbackNum] = function (result) {
modify.call(self, [result], old);
callback.call(self, result);
};
can.view.apply(can.view, args);
return this;
}
// call view with args (there might be deferreds)
result = can.view.apply(can.view, args);
// if we got a string back
if (!can.isPromise(result)) {
// we are going to call the old method with that string
args = [result];
} else {
// if there is a deferred, wait until it is done before calling modify
result.done(function (res) {
modify.call(self, [res], old);
});
return this;
}
}
return noHookup[func_name] ? old.apply(this, args) : modify.call(this, args, old);
};
};
// modifies the content of the element
// but also will run any hookup
modify = function (args, old) {
var res;
//check if there are new hookups
for (var hasHookups in can.view.hookups) {
break;
}
//if there are hookups, turn into a frag
// and insert that
// by using a frag, the element can be recursively hooked up
// before insterion
if (hasHookups && args[0] && isHTML(args[0])) {
args[0] = can.view.frag(args[0])
.childNodes;
}
//then insert into DOM
res = old.apply(this, args);
return res;
};
// returns true or false if the args indicate a template is being used
// $('#foo').html('/path/to/template.ejs',{data})
// in general, we want to make sure the first arg is a string
// and the second arg is data
isTemplate = function (args) {
// save the second arg type
var secArgType = typeof args[1];
// the first arg is a string
return typeof args[0] === "string" &&
// the second arg is an object or function
(secArgType === 'object' || secArgType === 'function') &&
// but it is not a dom element
!isDOM(args[1]);
};
// returns true if the arg is a jQuery object or HTMLElement
isDOM = function (arg) {
return arg.nodeType || arg[0] && arg[0].nodeType;
};
// returns whether the argument is some sort of HTML data
isHTML = function (arg) {
if (isDOM(arg)) {
// if jQuery object or DOM node we're good
return true;
} else if (typeof arg === 'string') {
// if string, do a quick sanity check that we're HTML
arg = can.trim(arg);
return arg.substr(0, 1) === '<' && arg.substr(arg.length - 1, 1) === '>' && arg.length >= 3;
} else {
// don't know what you are
return false;
}
};
//returns the callback arg number if there is one (for async view use)
getCallback = function (args) {
return typeof args[3] === 'function' ? 3 : typeof args[2] === 'function' && 2;
};
/**
* @function jQuery.fn.hookup
* @parent can.view.modifiers
* Called on a jQuery collection that was rendered with can.View with pending hookups. can.View can render a
* template with hookups, but not actually perform the hookup, because it returns a string without actual DOM
* elements to hook up to. So hookup performs the hookup and clears the pending hookups, preventing errors in
* future templates.
*
* ```
* $(can.View('//views/recipes.ejs',recipeData)).hookup()
* ```
*/
$.fn.hookup = function () {
can.view.frag(this);
return this;
};
can.each([
/**
* @function jQuery.fn.prepend
* @parent can.view.modifiers
*
* Extending the original [http://api.jquery.com/prepend/ jQuery().prepend()]
* to render [can.view] templates inserted at the beginning of each element in the set of matched elements.
*
* $('#test').prepend('path/to/template.ejs', { name : 'canjs' });
*
* @param {String|Object|Function} content A template filename or the id of a view script tag
* or a DOM element, array of elements, HTML string, or can object.
* @param {Object} [data] The data to render the view with.
* If rendering a view template this parameter always has to be present
* (use the empty object initializer {} for no data).
* @param {Function} [callback] A success callback to load the view asynchronously
*
* @return {jQuery|can.Deferred} The jQuery object or a [can.Deferred] if a deferred has
* been passed in data.
*/
"prepend",
/**
* @function jQuery.fn.append
* @parent can.view.modifiers
*
* Extending the original [http://api.jquery.com/append/ jQuery().append()]
* to render [can.view] templates inserted at the end of each element in the set of matched elements.
*
* $('#test').append('path/to/template.ejs', { name : 'canjs' });
*
* @param {String|Object|Function} content A template filename or the id of a view script tag
* or a DOM element, array of elements, HTML string, or can object.
* @param {Object} [data] The data to render the view with.
* If rendering a view template this parameter always has to be present
* (use the empty object initializer {} for no data).
* @param {Function} [callback] A success callback to load the view asynchronously
*
* @return {jQuery|can.Deferred} The jQuery object or a [can.Deferred] if a deferred has
* been passed in data.
*/
"append",
/**
* @function jQuery.fn.after
* @parent can.view.modifiers
*
* Extending the original [http://api.jquery.com/after/ jQuery().after()]
* to render [can.view] templates inserted after each element in the set of matched elements.
*
* $('#test').after('path/to/template.ejs', { name : 'canjs' });
*
* @param {String|Object|Function} content A template filename or the id of a view script tag
* or a DOM element, array of elements, HTML string, or can object.
* @param {Object} [data] The data to render the view with.
* If rendering a view template this parameter always has to be present
* (use the empty object initializer {} for no data).
* @param {Function} [callback] A success callback to load the view asynchronously
*
* @return {jQuery|can.Deferred} The jQuery object or a [can.Deferred] if a deferred has
* been passed in data.
*/
"after",
/**
* @function jQuery.fn.before
* @parent can.view.modifiers
*
* Extending the original [http://api.jquery.com/before/ jQuery().before()]
* to render [can.view] templates inserted before each element in the set of matched elements.
*
* $('#test').before('path/to/template.ejs', { name : 'canjs' });
*
* @param {String|Object|Function} content A template filename or the id of a view script tag
* or a DOM element, array of elements, HTML string, or can object.
* @param {Object} [data] The data to render the view with.
* If rendering a view template this parameter always has to be present
* (use the empty object initializer {} for no data).
* @param {Function} [callback] A success callback to load the view asynchronously
*
* @return {jQuery|can.Deferred} The jQuery object or a [can.Deferred] if a deferred has
* been passed in data.
*/
"before",
/**
* @function jQuery.fn.text
* @parent can.view.modifiers
*
* Extending the original [http://api.jquery.com/text/ jQuery().text()]
* to render [can.View] templates as the content of each matched element.
* Unlike [jQuery.fn.html] jQuery.fn.text also works with XML, escaping the provided
* string as necessary.
*
* $('#test').text('path/to/template.ejs', { name : 'canjs' });
*
* @param {String|Object|Function} content A template filename or the id of a view script tag
* or a DOM element, array of elements, HTML string, or can object.
* @param {Object} [data] The data to render the view with.
* If rendering a view template this parameter always has to be present
* (use the empty object initializer {} for no data).
* @param {Function} [callback] A success callback to load the view asynchronously
*
* @return {jQuery|can.Deferred} The jQuery object or a [can.Deferred] if a deferred has
* been passed in data.
*/
"text",
/**
* @function jQuery.fn.html
* @parent can.view.modifiers
*
* Extending the original [http://api.jquery.com/html/ jQuery().html()]
* to render [can.view] templates as the content of each matched element.
*
* $('#test').html('path/to/template.ejs', { name : 'canjs' });
*
* @param {String|Object|Function} content A template filename or the id of a view script tag
* or a DOM element, array of elements, HTML string, or can object.
* @param {Object} [data] The data to render the view with.
* If rendering a view template this parameter always has to be present
* (use the empty object initializer {} for no data).
* @param {Function} [callback] A success callback to load the view asynchronously
*
* @return {jQuery|can.Deferred} The jQuery object or a [can.Deferred] if a deferred has
* been passed in data.
*/
"html",
/**
* @function jQuery.fn.replaceWith
* @parent can.view.modifiers
*
* Extending the original [http://api.jquery.com/replaceWith/ jQuery().replaceWith()]
* to render [can.view] templates replacing each element in the set of matched elements.
*
* $('#test').replaceWith('path/to/template.ejs', { name : 'canjs' });
*
* @param {String|Object|Function} content A template filename or the id of a view script tag
* or a DOM element, array of elements, HTML string, or can object.
* @param {Object} [data] The data to render the view with.
* If rendering a view template this parameter always has to be present
* (use the empty object initializer {} for no data).
* @param {Function} [callback] A success callback to load the view asynchronously
*
* @return {jQuery|can.Deferred} The jQuery object or a [can.Deferred] if a deferred has
* been passed in data.
*/
"replaceWith", "val"
], function (func) {
convert(func);
});
return can;
});