/* hobo-jQuery initialization & utility functions */ (function($) { var page_data = {}; //used for javascript testing. var num_updates = 0; var methods = { /* call only once per document. */ initOnce: function() { if(typeof History === 'object') { // History.js installed $(window).on("statechange", function() { var state = History.getState(); if(state.data.length==3) { var form = $(state.data[0]); var roptions = form.hjq('buildRequestCallbacks', state.data[1], state.data[2]); $.ajax(state.url, roptions); } }) } return true; }, /* call for every new fragment */ init: function() { var top = this; this.find("[data-rapid-page-data]").each(function() { page_data = $(this).data('rapid-page-data'); }); this.find("[data-rapid]").each(function() { var that = jQuery(this); jQuery.each(jQuery(this).data('rapid'), function(tag, annotations) { tag = "hjq_"+tag.replace(/-/g, '_'); if(that[tag]) { that[tag](annotations); } }); }); return top; }, /* return the ID from the typed-id in the data-rapid-context attribute */ contextId: function() { return this.data('rapid-context').split(":")[1]; }, /* given annotations, turns the values in the _events_ object into functions, merges them into _options_ and returns _options_ */ getOptions: function(annotations) { for(var key in annotations.events) { if(annotations.events.hasOwnProperty(key)) { annotations.options[key] = methods.createFunction.call(this, annotations.events[key]); } } return annotations.options; }, /* return the global page_data: hobo_parts, form_auth_token, etc. */ pageData: function() { return page_data; }, /* return the number of current updates. Useful for javascript/integration testing */ numUpdates: function() { return num_updates; }, /* this function is called on an update of a part via Ajax. */ update: function(innerHtml) { num_updates += 1; var that=this; var replacement=this.clone().html(innerHtml).hide(); var hide_o, show_o; if(this.data('hjq-ajax')) { hide_o = this.data('hjq-ajax')['hide']; show_o = this.data('hjq-ajax')['show']; } methods.hideAndRemove.call(this, hide_o, function () { that.before(replacement); methods.show.call(replacement, show_o, function() {num_updates -= 1;}); }); methods.init.call(replacement); return replacement; }, /* this function is called on ajax update to update part context information */ updatePartContexts: function(contexts) { $.extend(page_data.hobo_parts, contexts); }, /* hide and removes the element. options is an array or * comma-separated string corresponding to the jQuery-UI hide * arguments: effect, options, speed & callback. The callback * argument is an additional callback called after the one in * the options hash. Removal is done after both callbacks. */ hideAndRemove: function(o, callback) { methods.hide.call(this, o, callback, true); }, /* hides the element. options is an array or * comma-separated string corresponding to the jQuery-UI hide * arguments: effect, options, speed & callback. The callback * argument is an additional callback called after the one in * the options hash. Removal is done after both callbacks. */ hide: function(o, callback, andRemove) { var that=this; var args = o; if(args===undefined) args=page_data.hide; if(args===undefined) args=[]; if (typeof args=='string') args=args.split(','); else if($.isArray(args)) args=args.slice(0); //shallow clone else args=[]; o_cb = args[3]; args[3] = function() { if(o_cb) methods.createFunction.call(that, o_cb).apply(this, arguments); if(callback) callback.apply(this, arguments); if(andRemove) that.remove(); }; if(args[0]) { that.hide.apply(that, args); } else { that.hide(); args[3].call(that); } return this; }, /* show the element. options is an array or * comma-separated string corresponding to the jQuery-UI show * arguments: effect, options, speed & callback. The callback * argument is an additional callback called after the one in * the options hash. */ show: function(o, callback) { var that=this; var args = o; if(args===undefined) args=page_data.show; if(args===undefined) args=[]; if (typeof args=='string') args=args.split(','); else if($.isArray(args)) args=args.slice(0); //shallow clone else args=[]; o_cb = args[3]; args[3] = function() { if(o_cb) methods.createFunction.call(that, o_cb).apply(this, arguments); if(callback) callback.apply(this, arguments); }; if(args[0]) { that.show.apply(that, args); } else { that.show(); args[3].call(that); } return this; }, /* given a global function name, find the function */ functionByName: function(name) { var descend = window; // find function by name on the root object jQuery.each(name.split("."), function() { if(descend) descend = descend[this]; }); return descend; }, /* Given a function name or javascript fragment, return a function */ createFunction: function(script) { if(!script) return function() {}; if($.isFunction(script)) return script; var f=methods.functionByName.call(this, script); if(f) return f; return function() { return eval(script); }; }, /* returns a jQuery selector for an element. One option would be to use something like http://stackoverflow.com/questions/2206958/best-way-to-reference-an-element-with-jquery However, if the DOM changes due to Ajax this isn't necessarily stable. So instead we give the element a unique ID if it doesn't already have one. */ getPath: function() { if(!this.attr("id")) { this.attr("id", Math.random().toString().replace("0.", "id")) } return "#"+this.attr("id"); }, /* Build an options object suitable for sending to * jQuery.ajax. (Note that the before & confirm callbacks * are called from this function, and the spinner is shown) * * The returned object will include a 'data' value * populated with a hash. * * This function has now been split into two parts to * better support push_state. buildRequestData is the * first part, which builds everything except the * callbacks (but it does execute the before callbacks). * buildRequestCallbacks builds the remaining callbacks. * * Options: * type: POST, GET * attrs: a hash containing the standard Hobo ajax attributes & callbacks * extra_options: merged into the hash sent to jQuery.ajax * extra_callbacks: the callbacks in attrs are generally specified by the DRYML; this allows the framework to add their own * function: passed to Hobo's ajax_update_response * preamble: passed to Hobo's ajax_update_response * postamble: passed to Hobo's ajax_update_response * content_type: passed to Hobo's ajax_update_response * */ buildRequest: function(o) { return methods.buildRequestCallbacks.call(this, methods.buildRequestData.call(this, o), o); }, buildRequestData: function(o) { var that = this; if (!o.attrs) o.attrs = {}; var result = {}; if(o.attrs.before) { if(!methods.createFunction.call(that, o.attrs.before).call(this)) { return false; } } var before_evt=jQuery.Event("rapid:ajax:before"); that.trigger(before_evt, [that]); if(before_evt.isPropagationStopped()) { return false; } if(o.attrs.confirm) { if(!confirm(o.attrs.confirm)) { return false; } } result.context = this; result.type = o.type || 'GET'; result.data = {}; /* These are now the defaults, so we don't need to send them over the wire. result.data = {"render_options[preamble]": o.preamble || '', "render_options[contexts_function]": 'hjq.ajax.updatePartContexts' }; */ if(o.preamble) result.data["render_options[preamble]"] = o.preamble; if(o.postamble) result.data["render_options[postamble]"] = o.postamble; if(o.fix_quotes) result.data["render_options[fix_quotes]"] = o.fix_quotes; if(o.content_type) result.data["render_options[content_type]"] = o.content_type; if(o.attrs['errors-ok']) result.data["render_options[errors_ok]"] = 1; result.dataType = 'script'; o.spec = jQuery.extend({'function': 'hjq.ajax.update', preamble: ''}, o.spec); var part_data = {}; if(o.attrs.hide!==undefined) part_data.hide = o.attrs.hide; if(o.attrs.show!==undefined) part_data.show = o.attrs.show; if($.isEmptyObject(part_data)) part_data = undefined; // we tell our controller which parts to return by sending it a "render" array. var ids=methods.getUpdateIds.call(this, o.attrs); for(var i=0; i