Object.extend = function(destination) { $A(arguments).slice(1).each(function (src) { for (var property in src) { destination[property] = src[property]; } }) return destination } Object.merge = function() { return Object.extend.apply(this, [{}].concat($A(arguments))) } var Hobo = { searchRequest: null, uidCounter: 0, ipeOldValues: {}, spinnerMinTime: 500, // milliseconds uid: function() { Hobo.uidCounter += 1 return "uid" + Hobo.uidCounter }, updatesForElement: function(el) { el = $(el) var updates = Hobo.getClassData(el, 'update') return updates ? updates.split(':') : [] }, ajaxSetFieldForElement: function(el, val, options) { var updates = Hobo.updatesForElement(el) var params = Hobo.fieldSetParam(el, val) var p = el.getAttribute("hobo-ajax-params") if (p) params = params + "&" + p var opts = Object.merge(options || {}, { params: params, message: el.getAttribute("hobo-ajax-message")}) Hobo.ajaxRequest(Hobo.putUrl(el), updates, opts) }, ajaxUpdateParams: function(updates, resultUpdates) { var params = [] var i = 0 if (updates.length > 0) { updates.each(function(id_or_el) { var el = $(id_or_el) if (el) { // ignore update of parts that do not exist var partDomId = el.id if (!hoboParts[partDomId]) { throw "Update of dom-id that is not a part: " + partDomId } params.push("render["+i+"][part_context]=" + encodeURIComponent(hoboParts[partDomId])) params.push("render["+i+"][id]=" + partDomId) i += 1 } }) params.push("page_path=" + hoboPagePath) } if (resultUpdates) { resultUpdates.each(function (resultUpdate) { params.push("render["+i+"][id]=" + resultUpdate.id) params.push("render["+i+"][result]=" + resultUpdate.result) if (resultUpdate.func) { params.push("render["+i+"][function]=" + resultUpdate.func) } i += 1 }) } return params.join('&') }, ajaxRequest: function(url_or_form, updates, options) { options = Object.merge({ asynchronous:true, evalScripts:true, resetForm: false, refocusForm: false, message: "Saving..." }, options) if (typeof url_or_form == "string") { var url = url_or_form var form = false } else { var form = url_or_form var url = form.action } var params = [] if (typeof(formAuthToken) != "undefined") { params.push(formAuthToken.name + "=" + formAuthToken.value) } updateParams = Hobo.ajaxUpdateParams(updates, options.resultUpdate) if (updateParams != "") { params.push(updateParams) } if (options.params) { params.push(options.params) delete options.params } if (form) { params.push(Form.serialize(form)) } if (options.message != false) Hobo.showSpinner(options.message, options.spinnerNextTo) var complete = function() { if (options.message != false) Hobo.hideSpinner(); if (options.onComplete) options.onComplete.apply(this, arguments) if (form && options.refocusForm) Form.focusFirstElement(form) Event.addBehavior.reload() } var success = function() { if (options.onSuccess) options.onSuccess.apply(this, arguments) if (form && options.resetForm) form.reset(); } if (options.method && options.method.toLowerCase() == "put") { delete options.method params.push("_method=PUT") } if (!options.onFailure) { options.onFailure = function(response) { alert(response.responseText) } } new Ajax.Request(url, Object.merge(options, { parameters: params.join("&"), onComplete: complete, onSuccess: success })) }, hide: function() { for (i = 0; i < arguments.length; i++) { if ($(arguments[i])) { Element.addClassName(arguments[i], 'hidden') } } }, show: function() { for (i = 0; i < arguments.length; i++) { if ($(arguments[i])) { Element.removeClassName(arguments[i], 'hidden') } } }, toggle: function() { for (i = 0; i < arguments.length; i++) { if ($(arguments[i])) { if(Element.hasClassName(arguments[i], 'hidden')) { Element.removeClassName(arguments[i], 'hidden') } else { Element.addClassName(arguments[i], 'hidden') } } } }, onFieldEditComplete: function(el, newValue) { el = $(el) var oldValue = Hobo.ipeOldValues[el.id] delete Hobo.ipeOldValues[el.id] var blank = el.getAttribute("hobo-blank-message") if (blank && newValue.strip().length == 0) { el.update(blank) } else { el.update(newValue) } var modelId = Hobo.getModelId(el) if (oldValue) { $$(".model:" + modelId).each(function(e) { if (e != el && e.innerHTML == oldValue) e.update(newValue) }) } }, _makeInPlaceEditor: function(el, options) { var old var updates = Hobo.updatesForElement(el) var id = el.id if (!id) { id = el.id = Hobo.uid() } var updateParams = Hobo.ajaxUpdateParams(updates, [{id: id, result: 'new_field_value', func: "Hobo.onFieldEditComplete"}]) var opts = {okButton: false, cancelLink: false, submitOnBlur: true, evalScripts: true, htmlResponse: false, ajaxOptions: { method: "put" }, onEnterHover: null, onLeaveHover: null, callback: function(form, val) { old = val el.setAttribute("hobo-edit-text", val) return (Hobo.fieldSetParam(el, val) + "&" + updateParams) }, onFailure: function(_, resp) { alert(resp.responseText); el.innerHTML = old }, onEnterEditMode: function() { var blank_message = el.getAttribute("hobo-blank-message") var editable_text = el.getAttribute("hobo-edit-text") if (editable_text) { el.innerHTML = editable_text } if (el.innerHTML.gsub(" ", " ") == blank_message) { el.innerHTML = "" } else { Hobo.ipeOldValues[el.id] = el.innerHTML } } } Object.extend(opts, options) return new Ajax.InPlaceEditor(el, Hobo.putUrl(el), opts) }, doSearch: function(el) { el = $(el) var spinner = $(el.getAttribute("search-spinner") || "search-spinner") var search_results = $(el.getAttribute("search-results") || "search-results") var search_results_panel = $(el.getAttribute("search-results-panel") || "search-results-panel") var url = el.getAttribute("search-url") || (urlBase + "/search") var clear = function() { Hobo.hide(search_results_panel); el.clear() } // Close window on [Escape] Event.observe(el, 'keypress', function(ev) { if (ev.keyCode == 27) clear() }); Event.observe(search_results_panel.down('.close-button'), 'click', clear) var value = $F(el) if (Hobo.searchRequest) { Hobo.searchRequest.transport.abort() } if (value.length >= 3) { if (spinner) Hobo.show(spinner); Hobo.searchRequest = new Ajax.Updater(search_results, url, { asynchronous:true, evalScripts:true, onSuccess:function(request) { if (spinner) Hobo.hide(spinner) if (search_results_panel) { Hobo.show(search_results_panel) } }, method: "get", parameters:"query=" + value }); } else { Hobo.updateElement(search_results, '') Hobo.hide(search_results_panel) } }, putUrl: function(el) { var spec = Hobo.modelSpecForElement(el) return urlBase + "/" + Hobo.pluralise(spec.name) + "/" + spec.id + "?_method=PUT" }, urlForId: function(id) { var spec = Hobo.parseModelSpec(id) var url = urlBase + "/" + Hobo.pluralise(spec.name) if (spec.id) { url += "/" + spec.id } return url }, fieldSetParam: function(el, val) { var spec = Hobo.modelSpecForElement(el) var res = spec.name + '[' + spec.field + ']=' + encodeURIComponent(val) if (typeof(formAuthToken) != "undefined") { res = res + "&" + formAuthToken.name + "=" + formAuthToken.value } return res }, fadeObjectElement: function(el) { var fadeEl = Hobo.objectElementFor(el) new Effect.Fade(fadeEl, { duration: 0.5, afterFinish: function (ef) { ef.element.remove() } }); Hobo.showEmptyMessageAfterLastRemove(fadeEl) }, removeButton: function(el, url, updates, options) { if (options.fade == null) { options.fade = true; } if (options.confirm == null) { options.confirm = "Are you sure?"; } if (options.confirm == false || confirm(options.confirm)) { var objEl = Hobo.objectElementFor(el) Hobo.showSpinner('Removing'); function complete() { if (options.fade) { Hobo.fadeObjectElement(objEl) } Hobo.hideSpinner() } if (updates && updates.length > 0) { new Hobo.ajaxRequest(url, updates, { method:'delete', message: "Removing...", onComplete: complete}); } else { var ajaxOptions = {asynchronous:true, evalScripts:true, method:'delete', onComplete: complete} if (typeof(formAuthToken) != "undefined") { ajaxOptions.parameters = formAuthToken.name + "=" + formAuthToken.value } new Ajax.Request(url, ajaxOptions); } } }, ajaxUpdateField: function(element, field, value, updates) { var objectElement = Hobo.objectElementFor(element) var url = Hobo.putUrl(objectElement) var spec = Hobo.modelSpecForElement(objectElement) var params = spec.name + '[' + field + ']=' + encodeURIComponent(value) new Hobo.ajaxRequest(url, updates, { method:'put', message: "Saving...", params: params }); }, showEmptyMessageAfterLastRemove: function(el) { var empty var container = $(el.parentNode) if (container.getElementsByTagName(el.nodeName).length == 1 && (empty = container.next('.empty-collection-message'))) { new Effect.Appear(empty, {delay:0.3}) } }, getClassData: function(el, name) { var match = el.className.match(new RegExp("(^| )" + name + "::(\\S+)($| )")) return match && match[2] }, getModelId: function(el) { return Hobo.getClassData(el, 'model') }, modelSpecForElement: function(el) { var id = Hobo.getModelId(el) return id && Hobo.parseModelSpec(id) }, parseModelSpec: function(id) { m = id.gsub('-', '_').match(/^([^:]+)(?::([^:]+)(?::([^:]+))?)?$/) if (m) return { name: m[1], id: m[2], field: m[3] } }, objectElementFor: function(el) { var m while(el.getAttribute) { id = Hobo.getModelId(el) if (id) m = id.match(/^[^:]+:[^:]+$/); if (m) break; el = el.parentNode; } if (m) return el; }, modelIdFor: function(el) { var e = Hobo.objectElementFor(el) return e && Hobo.getModelId(e) }, showSpinner: function(message, nextTo) { clearTimeout(Hobo.spinnerTimer) Hobo.spinnerHideAt = new Date().getTime() + Hobo.spinnerMinTime; if (t = $('ajax-progress-text')) { if (!message || message.length == 0) { t.hide() } else { Element.update(t, message); t.show() } } if (e = $('ajax-progress')) { if (nextTo) { var e_nextTo = $(nextTo); var pos = e_nextTo.cumulativeOffset() e.style.top = pos.top - e_nextTo.offsetHeight + "px" e.style.left = (pos.left + e_nextTo.offsetWidth + 5) + "px" } e.style.display = "block"; } }, hideSpinner: function() { if (e = $('ajax-progress')) { var remainingTime = Hobo.spinnerHideAt - new Date().getTime() if (remainingTime <= 0) { e.visualEffect('Fade') } else { Hobo.spinnerTimer = setTimeout(function () { e.visualEffect('Fade') }, remainingTime) } } }, updateElement: function(id, content) { // TODO: Do we need this method? Element.update(id, content) }, getStyle: function(el, styleProp) { if (el.currentStyle) var y = el.currentStyle[styleProp]; else if (window.getComputedStyle) var y = document.defaultView.getComputedStyle(el, null).getPropertyValue(styleProp); return y; }, partFor: function(el) { while (el) { if (el.id && hoboParts[el.id]) { return el } el = el.parentNode } return null }, pluralise: function(s) { return pluralisations[s] || s + "s" }, addUrlParams: function(params, options) { params = $H(window.location.search.toQueryParams()).merge(params) if (options.remove) { var remove = (options.remove instanceof Array) ? options.remove : [options.remove] remove.each(function(k) { params.unset(k) }) } return window.location.href.sub(/(\?.*|$)/, "?" + params.toQueryString()) }, fixSectionGroup: function(e) { rows = e.childElements().map(function(e, i) { cells = e.childElements().map(function(e, i) { return e.outerHTML.sub("