view release on metacpan or search on metacpan
share/js/hal/browser.js view on Meta::CPAN
HAL.Browser = Backbone.Router.extend({
initialize: function(opts) {
opts = opts || {};
var vent = _.extend({}, Backbone.Events),
$container = opts.container || $('#browser');
this.entryPoint = opts.entryPoint || '/';
// TODO: don't hang currentDoc off namespace
vent.bind('response', function(e) {
window.HAL.currentDocument = e.resource || {};
});
vent.bind('location-go', _.bind(this.loadUrl, this));
HAL.client = new HAL.Http.Client({ vent: vent });
var browser = new HAL.Views.Browser({ vent: vent, entryPoint: this.entryPoint });
browser.render()
$container.html(browser.el);
vent.trigger('app:loaded');
if (window.location.hash === '') {
share/js/hal/views/embedded_resource.js view on Meta::CPAN
HAL.Views.EmbeddedResource = Backbone.View.extend({
initialize: function(opts) {
this.vent = opts.vent;
this.resource = opts.resource;
this.propertiesView = new HAL.Views.Properties({});
this.linksView = new HAL.Views.Links({
vent: this.vent
});
_.bindAll(this, 'onToggleClick');
_.bindAll(this, 'onDoxClick');
},
events: {
'click a.accordion-toggle': 'onToggleClick',
'click span.dox': 'onDoxClick'
},
className: 'embedded-resource accordion-group',
onToggleClick: function(e) {
share/js/hal/views/embedded_resources.js view on Meta::CPAN
HAL.Views.EmbeddedResources = Backbone.View.extend({
initialize: function(opts) {
this.vent = opts.vent;
_.bindAll(this, 'render');
},
className: 'embedded-resources accordion',
render: function(resources) {
var self = this,
resourceViews = [],
buildView = function(resource) {
return new HAL.Views.EmbeddedResource({
resource: resource,
share/js/hal/views/inspector.js view on Meta::CPAN
HAL.Views.Inspector = Backbone.View.extend({
initialize: function(opts) {
this.vent = opts.vent;
_.bindAll(this, 'renderDocumentation');
_.bindAll(this, 'renderResponse');
this.vent.bind('show-docs', this.renderDocumentation);
this.vent.bind('response', this.renderResponse);
},
className: 'inspector span6',
render: function() {
this.$el.html(this.template());
},
renderResponse: function(response) {
var responseView = new HAL.Views.Response({ vent: this.vent });
share/js/hal/views/location_bar.js view on Meta::CPAN
HAL.Views.LocationBar = Backbone.View.extend({
initialize: function(opts) {
this.vent = opts.vent;
_.bindAll(this, 'render');
_.bindAll(this, 'onButtonClick');
this.vent.bind('location-change', this.render);
this.vent.bind('location-change', _.bind(this.showSpinner, this));
this.vent.bind('response', _.bind(this.hideSpinner, this));
},
events: {
'submit form': 'onButtonClick'
},
className: 'address',
render: function(e) {
e = e || { url: '' };
share/js/hal/views/non_safe_request_dialog.js view on Meta::CPAN
HAL.Views.NonSafeRequestDialog = Backbone.View.extend({
initialize: function(opts) {
this.href = opts.href;
this.vent = opts.vent;
this.uriTemplate = uritemplate(this.href);
_.bindAll(this, 'submitQuery');
},
events: {
'submit form': 'submitQuery'
},
className: 'modal fade',
submitQuery: function(e) {
e.preventDefault();
share/js/hal/views/properties.js view on Meta::CPAN
HAL.Views.Properties = Backbone.View.extend({
initialize: function(opts) {
this.vent = opts.vent;
_.bindAll(this, 'render');
},
className: 'properties',
render: function(props) {
this.$el.html(this.template({ properties: props }));
},
template: _.template($('#properties-template').html())
});
share/js/hal/views/query_uri_dialog.js view on Meta::CPAN
HAL.Views.QueryUriDialog = Backbone.View.extend({
initialize: function(opts) {
this.href = opts.href;
this.uriTemplate = uritemplate(this.href);
_.bindAll(this, 'submitQuery');
_.bindAll(this, 'renderPreview');
},
className: 'modal fade',
events: {
'submit form': 'submitQuery',
'keyup textarea': 'renderPreview',
'change textarea': 'renderPreview'
},
share/js/hal/views/request_headers.js view on Meta::CPAN
HAL.Views.RequestHeaders = Backbone.View.extend({
initialize: function(opts) {
var self = this;
this.vent = opts.vent;
_.bindAll(this, 'updateRequestHeaders');
this.vent.bind('app:loaded', function() {
self.updateRequestHeaders();
});
},
className: 'request-headers',
events: {
'blur textarea': 'updateRequestHeaders'
},
share/js/hal/views/resource.js view on Meta::CPAN
HAL.Views.Resource = Backbone.View.extend({
initialize: function(opts) {
var self = this;
this.vent = opts.vent;
this.vent.bind('response', function(e) {
self.render(new HAL.Models.Resource(e.resource));
});
this.vent.bind('fail-response', function(e) {
try {
resource = JSON.parse(e.jqxhr.responseText);
} catch(err) {
resource = null;
}
self.vent.trigger('response', { resource: resource, jqxhr: e.jqxhr });
});
},
className: 'resource',
share/js/hal/views/response.js view on Meta::CPAN
HAL.Views.Response = Backbone.View.extend({
initialize: function(opts) {
this.vent = opts.vent;
this.headersView = new HAL.Views.ResponseHeaders({ vent: this.vent });
this.bodyView = new HAL.Views.ResponseBody({ vent: this.vent });
_.bindAll(this, 'render');
this.vent.bind('response', this.render);
},
className: 'response',
render: function(e) {
this.$el.html();
this.headersView.render(e);
this.bodyView.render(e);
share/vendor/js/backbone.js view on Meta::CPAN
return;
case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1]);
return;
case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1], args[2]);
return;
default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
}
};
// A module that can be mixed in to *any object* in order to provide it with
// custom events. You may bind with `on` or remove with `off` callback
// functions to an event; `trigger`-ing an event fires all callbacks in
// succession.
var Events = Backbone.Events = {
// Bind one or more space separated events, or an events map,
// to a `callback` function. Passing `"all"` will bind the callback to
// all events fired.
on: function(name, callback, context) {
if (!(eventsApi(this, 'on', name, [callback, context]) && callback)) return this;
this._events || (this._events = {});
var list = this._events[name] || (this._events[name] = []);
list.push({callback: callback, context: context, ctx: context || this});
return this;
},
// Bind events to only be triggered a single time. After the first time
share/vendor/js/backbone.js view on Meta::CPAN
for (var id in listeners) {
listeners[id].off(name, callback, this);
}
this._listeners = {};
}
return this;
}
};
// Aliases for backwards compatibility.
Events.bind = Events.on;
Events.unbind = Events.off;
// Allow the `Backbone` object to serve as a global event bus, for folks who
// want global "pubsub" in a convenient place.
_.extend(Backbone, Events);
// Backbone.Model
// --------------
// Create a new model, with defined attributes. A client id (`cid`)
// is automatically generated and assigned for you.
share/vendor/js/backbone.js view on Meta::CPAN
sort: function(options) {
if (!this.comparator) {
throw new Error('Cannot sort a set without a comparator');
}
options || (options = {});
// Run sort based on type of `comparator`.
if (_.isString(this.comparator) || this.comparator.length === 1) {
this.models = this.sortBy(this.comparator, this);
} else {
this.models.sort(_.bind(this.comparator, this));
}
if (!options.silent) this.trigger('sort', this, options);
return this;
},
// Pluck an attribute from each model in the collection.
pluck: function(attr) {
return _.invoke(this.models, 'get', attr);
},
share/vendor/js/backbone.js view on Meta::CPAN
});
// Backbone.Router
// ---------------
// Routers map faux-URLs to actions, and fire events when routes are
// matched. Creating a new one sets its `routes` hash, if not set statically.
var Router = Backbone.Router = function(options) {
options || (options = {});
if (options.routes) this.routes = options.routes;
this._bindRoutes();
this.initialize.apply(this, arguments);
};
// Cached regular expressions for matching named param parts and splatted
// parts of route strings.
var optionalParam = /\((.*?)\)/g;
var namedParam = /(\(\?)?:\w+/g;
var splatParam = /\*\w+/g;
var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
// Set up all inheritable **Backbone.Router** properties and methods.
_.extend(Router.prototype, Events, {
// Initialize is an empty function by default. Override it with your own
// initialization logic.
initialize: function(){},
// Manually bind a single named route to a callback function.
route: function(route, name, callback) {
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (!callback) callback = this[name];
Backbone.history.route(route, _.bind(function(fragment) {
var args = this._extractParameters(route, fragment);
callback && callback.apply(this, args);
this.trigger.apply(this, ['route:' + name].concat(args));
this.trigger('route', name, args);
Backbone.history.trigger('route', this, name, args);
}, this));
return this;
},
// Simple proxy to `Backbone.history` to save a fragment into the history.
navigate: function(fragment, options) {
Backbone.history.navigate(fragment, options);
return this;
},
// Bind all defined routes to `Backbone.history`. We have to reverse the
// order of the routes here to support behavior where the most general
// routes can be defined at the bottom of the route map.
_bindRoutes: function() {
if (!this.routes) return;
var route, routes = _.keys(this.routes);
while ((route = routes.pop()) != null) {
this.route(route, this.routes[route]);
}
},
// Convert a route string into a regular expression, suitable for matching
// against the current location hash.
_routeToRegExp: function(route) {
share/vendor/js/backbone.js view on Meta::CPAN
});
// Backbone.History
// ----------------
// Handles cross-browser history management, based on URL fragments. If the
// browser does not support `onhashchange`, falls back to polling.
var History = Backbone.History = function() {
this.handlers = [];
_.bindAll(this, 'checkUrl');
// Ensure that `History` can be used outside of the browser.
if (typeof window !== 'undefined') {
this.location = window.location;
this.history = window.history;
}
};
// Cached regex for stripping a leading hash/slash and trailing space.
var routeStripper = /^[#\/]|\s+$/g;
share/vendor/js/backbone.js view on Meta::CPAN
this._wantsPushState = !!this.options.pushState;
this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
var fragment = this.getFragment();
var docMode = document.documentMode;
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
// Normalize root to always include a leading and trailing slash.
this.root = ('/' + this.root + '/').replace(rootStripper, '/');
if (oldIE && this._wantsHashChange) {
this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
this.navigate(fragment);
}
// Depending on whether we're using pushState or hashes, and whether
// 'onhashchange' is supported, determine how we check the URL state.
if (this._hasPushState) {
Backbone.$(window).on('popstate', this.checkUrl);
} else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
Backbone.$(window).on('hashchange', this.checkUrl);
} else if (this._wantsHashChange) {
share/vendor/js/backbone.js view on Meta::CPAN
// *{"event selector": "callback"}*
//
// {
// 'mousedown .title': 'edit',
// 'click .button': 'save'
// 'click .open': function(e) { ... }
// }
//
// pairs. Callbacks will be bound to the view, with `this` set properly.
// Uses event delegation for efficiency.
// Omitting the selector binds the event to `this.el`.
// This only works for delegate-able events: not `focus`, `blur`, and
// not `change`, `submit`, and `reset` in Internet Explorer.
delegateEvents: function(events) {
if (!(events || (events = _.result(this, 'events')))) return;
this.undelegateEvents();
for (var key in events) {
var method = events[key];
if (!_.isFunction(method)) method = this[events[key]];
if (!method) throw new Error('Method "' + events[key] + '" does not exist');
var match = key.match(delegateEventSplitter);
var eventName = match[1], selector = match[2];
method = _.bind(method, this);
eventName += '.delegateEvents' + this.cid;
if (selector === '') {
this.$el.on(eventName, method);
} else {
this.$el.on(eventName, selector, method);
}
}
},
// Clears all callbacks previously bound to the view with `delegateEvents`.
share/vendor/js/jquery-1.10.2.js view on Meta::CPAN
context = fn;
fn = tmp;
}
// Quick check to determine if target is callable, in the spec
// this throws a TypeError, but we will just return undefined.
if ( !jQuery.isFunction( fn ) ) {
return undefined;
}
// Simulated bind
args = core_slice.call( arguments, 2 );
proxy = function() {
return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
};
// Set the guid of unique handler to the same of original handler, so it can be removed
proxy.guid = fn.guid = fn.guid || jQuery.guid++;
return proxy;
},
share/vendor/js/jquery-1.10.2.js view on Meta::CPAN
return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
ret :
elem[ name ];
}
},
propHooks: {
tabIndex: {
get: function( elem ) {
// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
// Use proper attribute retrieval(#12072)
var tabindex = jQuery.find.attr( elem, "tabindex" );
return tabindex ?
parseInt( tabindex, 10 ) :
rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
0 :
-1;
}
}
}
});
// Hooks for boolean attributes
boolHook = {
share/vendor/js/jquery-1.10.2.js view on Meta::CPAN
// There *must* be a type, no attaching namespace-only handlers
if ( !type ) {
continue;
}
// If event changes its type, use the special event handlers for the changed type
special = jQuery.event.special[ type ] || {};
// If selector defined, determine special event api type, otherwise given type
type = ( selector ? special.delegateType : special.bindType ) || type;
// Update special based on newly reset type
special = jQuery.event.special[ type ] || {};
// handleObj is passed to all event handlers
handleObj = jQuery.extend({
type: type,
origType: origType,
data: data,
handler: handler,
share/vendor/js/jquery-1.10.2.js view on Meta::CPAN
}
// Once for each type.namespace in types; type may be omitted
types = ( types || "" ).match( core_rnotwhite ) || [""];
t = types.length;
while ( t-- ) {
tmp = rtypenamespace.exec( types[t] ) || [];
type = origType = tmp[1];
namespaces = ( tmp[2] || "" ).split( "." ).sort();
// Unbind all events (on this namespace, if provided) for the element
if ( !type ) {
for ( type in events ) {
jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
}
continue;
}
special = jQuery.event.special[ type ] || {};
type = ( selector ? special.delegateType : special.bindType ) || type;
handlers = events[ type ] || [];
tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
// Remove matching events
origCount = j = handlers.length;
while ( j-- ) {
handleObj = handlers[ j ];
if ( ( mappedTypes || origType === handleObj.origType ) &&
( !handler || handler.guid === handleObj.guid ) &&
share/vendor/js/jquery-1.10.2.js view on Meta::CPAN
eventPath.push( tmp.defaultView || tmp.parentWindow || window );
}
}
// Fire handlers on the event path
i = 0;
while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
event.type = i > 1 ?
bubbleType :
special.bindType || type;
// jQuery handler
handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
if ( handle ) {
handle.apply( cur, data );
}
// Native handler
handle = ontype && cur[ ontype ];
if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
share/vendor/js/jquery-1.10.2.js view on Meta::CPAN
}
// Create a timestamp if incoming event doesn't have one
this.timeStamp = src && src.timeStamp || jQuery.now();
// Mark it as fixed
this[ jQuery.expando ] = true;
};
// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
isDefaultPrevented: returnFalse,
isPropagationStopped: returnFalse,
isImmediatePropagationStopped: returnFalse,
preventDefault: function() {
var e = this.originalEvent;
this.isDefaultPrevented = returnTrue;
if ( !e ) {
share/vendor/js/jquery-1.10.2.js view on Meta::CPAN
}
};
// Create mouseenter/leave events using mouseover/out and event-time checks
jQuery.each({
mouseenter: "mouseover",
mouseleave: "mouseout"
}, function( orig, fix ) {
jQuery.event.special[ orig ] = {
delegateType: fix,
bindType: fix,
handle: function( event ) {
var ret,
target = this,
related = event.relatedTarget,
handleObj = event.handleObj;
// For mousenter/leave call the handler if related is outside the target.
// NB: No relatedTarget if the mouse left/entered the browser window
if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
share/vendor/js/jquery-1.10.2.js view on Meta::CPAN
} else {
// Serialize scalar item.
add( prefix, obj );
}
}
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
// Handle event binding
jQuery.fn[ name ] = function( data, fn ) {
return arguments.length > 0 ?
this.on( name, null, data, fn ) :
this.trigger( name );
};
});
jQuery.fn.extend({
hover: function( fnOver, fnOut ) {
return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
},
bind: function( types, data, fn ) {
return this.on( types, null, data, fn );
},
unbind: function( types, fn ) {
return this.off( types, null, fn );
},
delegate: function( selector, types, data, fn ) {
return this.on( types, selector, data, fn );
},
undelegate: function( selector, types, fn ) {
// ( namespace ) or ( selector, types [, fn] )
return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
}
share/vendor/js/jquery-1.10.2.js view on Meta::CPAN
* 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
* 2) These are called:
* - BEFORE asking for a transport
* - AFTER param serialization (s.data is a string if s.processData is true)
* 3) key is the dataType
* 4) the catchall symbol "*" can be used
* 5) execution will start with transport dataType and THEN continue down to "*" if needed
*/
prefilters = {},
/* Transports bindings
* 1) key is the dataType
* 2) the catchall symbol "*" can be used
* 3) selection will start with transport dataType and THEN go to "*" if needed
*/
transports = {},
// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
allTypes = "*/".concat("*");
// #8138, IE may throw an exception when accessing
share/vendor/js/jquery-1.10.2.min.js view on Meta::CPAN
/*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license
//@ sourceMappingURL=jquery-1.10.2.min.map
*/
(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[...
}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid+...
u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x...
share/vendor/js/jquery-1.10.2.min.map view on Meta::CPAN
{"version":3,"file":"jquery-1.10.2.min.js","sources":["jquery-1.10.2.js"],"names":["window","undefined","readyList","rootjQuery","core_strundefined","location","document","docElem","documentElement","_jQuery","jQuery","_$","$","class2type","core_dele...
share/vendor/js/underscore.js view on Meta::CPAN
nativeMap = ArrayProto.map,
nativeReduce = ArrayProto.reduce,
nativeReduceRight = ArrayProto.reduceRight,
nativeFilter = ArrayProto.filter,
nativeEvery = ArrayProto.every,
nativeSome = ArrayProto.some,
nativeIndexOf = ArrayProto.indexOf,
nativeLastIndexOf = ArrayProto.lastIndexOf,
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
share/vendor/js/underscore.js view on Meta::CPAN
};
var reduceError = 'Reduce of empty array with no initial value';
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
} else {
memo = iterator.call(context, memo, value, index, list);
}
});
if (!initial) throw new TypeError(reduceError);
return memo;
};
// The right-associative version of reduce, also known as `foldr`.
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
var length = obj.length;
if (length !== +length) {
var keys = _.keys(obj);
length = keys.length;
}
each(obj, function(value, index, list) {
index = keys ? keys[--length] : --length;
if (!initial) {
share/vendor/js/underscore.js view on Meta::CPAN
start += step;
}
return range;
};
// Function (ahem) Functions
// ------------------
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
// available.
_.bind = function(func, context) {
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
var args = slice.call(arguments, 2);
return function() {
return func.apply(context, args.concat(slice.call(arguments)));
};
};
// Partially apply a function by creating a version that has had some of its
// arguments pre-filled, without changing its dynamic `this` context.
_.partial = function(func) {
var args = slice.call(arguments, 1);
return function() {
return func.apply(this, args.concat(slice.call(arguments)));
};
};
// Bind all of an object's methods to that object. Useful for ensuring that
// all callbacks defined on an object belong to it.
_.bindAll = function(obj) {
var funcs = slice.call(arguments, 1);
if (funcs.length === 0) funcs = _.functions(obj);
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
return obj;
};
// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
var memo = {};
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));