|
if (typeof Prototype == 'undefined')
|
|
{
|
|
warning = "ActiveScaffold Error: Prototype could not be found. Please make sure that your application's layout includes prototype.js (e.g. <%= javascript_include_tag :defaults %>) *before* it includes active_scaffold.js (e.g. <%= active_scaffold_includes %>).";
|
|
alert(warning);
|
|
}
|
|
if (Prototype.Version.substring(0, 3) != '1.6')
|
|
{
|
|
warning = "ActiveScaffold Error: Prototype version 1.6.x is required. Please update prototype.js (rake rails:update:javascripts).";
|
|
alert(warning);
|
|
}
|
|
|
|
/*
|
|
* Simple utility methods
|
|
*/
|
|
|
|
var ActiveScaffold = {
|
|
records_for: function(tbody_id) {
|
|
var rows = [];
|
|
var child = $(tbody_id).down('.record');
|
|
while (child) {
|
|
rows.push(child);
|
|
child = child.next('.record');
|
|
}
|
|
return rows;
|
|
},
|
|
stripe: function(tbody_id) {
|
|
var even = false;
|
|
var rows = this.records_for(tbody_id);
|
|
for (var i = 0; i < rows.length; i++) {
|
|
var child = rows[i];
|
|
//Make sure to skip rows that are create or edit rows or messages
|
|
if (child.tagName != 'SCRIPT'
|
|
&& !child.hasClassName("create")
|
|
&& !child.hasClassName("update")
|
|
&& !child.hasClassName("inline-adapter")
|
|
&& !child.hasClassName("active-scaffold-calculations")) {
|
|
|
|
if (even) child.addClassName("even-record");
|
|
else child.removeClassName("even-record");
|
|
|
|
even = !even;
|
|
}
|
|
}
|
|
},
|
|
hide_empty_message: function(tbody, empty_message_id) {
|
|
if (this.records_for(tbody).length != 0) {
|
|
$(empty_message_id).hide();
|
|
}
|
|
},
|
|
reload_if_empty: function(tbody, url) {
|
|
var content_container_id = tbody.replace('tbody', 'content');
|
|
if (this.records_for(tbody).length == 0) {
|
|
new Ajax.Updater($(content_container_id), url, {
|
|
method: 'get',
|
|
asynchronous: true,
|
|
evalScripts: true
|
|
});
|
|
}
|
|
},
|
|
removeSortClasses: function(scaffold_id) {
|
|
$$('#' + scaffold_id + ' td.sorted').each(function(element) {
|
|
element.removeClassName("sorted");
|
|
});
|
|
$$('#' + scaffold_id + ' th.sorted').each(function(element) {
|
|
element.removeClassName("sorted");
|
|
element.removeClassName("asc");
|
|
element.removeClassName("desc");
|
|
});
|
|
},
|
|
decrement_record_count: function(scaffold_id) {
|
|
// decrement the last record count, firsts record count are in nested lists
|
|
count = $$('#' + scaffold_id + ' span.active-scaffold-records').last();
|
|
count.innerHTML = parseInt(count.innerHTML) - 1;
|
|
},
|
|
increment_record_count: function(scaffold_id) {
|
|
// increment the last record count, firsts record count are in nested lists
|
|
count = $$('#' + scaffold_id + ' span.active-scaffold-records').last();
|
|
count.innerHTML = parseInt(count.innerHTML) + 1;
|
|
},
|
|
|
|
server_error_response: '',
|
|
report_500_response: function(active_scaffold_id) {
|
|
messages_container = $(active_scaffold_id).down('td.messages-container');
|
|
new Insertion.Top(messages_container, this.server_error_response);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* DHTML history tie-in
|
|
*/
|
|
function addActiveScaffoldPageToHistory(url, active_scaffold_id) {
|
|
if (typeof dhtmlHistory == 'undefined') return; // it may not be loaded
|
|
|
|
var array = url.split('?');
|
|
var qs = new Querystring(array[1]);
|
|
var sort = qs.get('sort')
|
|
var dir = qs.get('sort_direction')
|
|
var page = qs.get('page')
|
|
if (sort || dir || page) dhtmlHistory.add(active_scaffold_id+":"+page+":"+sort+":"+dir, url);
|
|
}
|
|
|
|
/*
|
|
* Add-ons/Patches to Prototype
|
|
*/
|
|
|
|
/* patch to support replacing TR/TD/TBODY in Internet Explorer, courtesy of http://dev.rubyonrails.org/ticket/4273 */
|
|
Element.replace = function(element, html) {
|
|
element = $(element);
|
|
if (element.outerHTML) {
|
|
try {
|
|
element.outerHTML = html.stripScripts();
|
|
} catch (e) {
|
|
var tn = element.tagName;
|
|
if(tn=='TBODY' || tn=='TR' || tn=='TD')
|
|
{
|
|
var tempDiv = document.createElement("div");
|
|
tempDiv.innerHTML = '<table id="tempTable" style="display: none">' + html.stripScripts() + '</table>';
|
|
element.parentNode.replaceChild(tempDiv.getElementsByTagName(tn).item(0), element);
|
|
}
|
|
else throw e;
|
|
}
|
|
} else {
|
|
var range = element.ownerDocument.createRange();
|
|
/* patch to fix <form> replaces in Firefox. see http://dev.rubyonrails.org/ticket/8010 */
|
|
range.selectNodeContents(element.parentNode);
|
|
element.parentNode.replaceChild(range.createContextualFragment(html.stripScripts()), element);
|
|
}
|
|
setTimeout(function() {html.evalScripts()}, 10);
|
|
return element;
|
|
};
|
|
|
|
/*
|
|
* URL modification support. Incomplete functionality.
|
|
*/
|
|
Object.extend(String.prototype, {
|
|
append_params: function(params) {
|
|
url = this;
|
|
if (url.indexOf('?') == -1) url += '?';
|
|
else if (url.lastIndexOf('&') != url.length) url += '&';
|
|
|
|
url += $H(params).collect(function(item) {
|
|
return item.key + '=' + item.value;
|
|
}).join('&');
|
|
|
|
return url;
|
|
}
|
|
});
|
|
|
|
/*
|
|
* Prototype's implementation was throwing an error instead of false
|
|
*/
|
|
Element.Methods.Simulated = {
|
|
hasAttribute: function(element, attribute) {
|
|
var t = Element._attributeTranslations;
|
|
attribute = (t.names && t.names[attribute]) || attribute;
|
|
// Return false if we get an error here
|
|
try {
|
|
return $(element).getAttributeNode(attribute).specified;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* A set of links. As a set, they can be controlled such that only one is "open" at a time, etc.
|
|
*/
|
|
ActiveScaffold.Actions = new Object();
|
|
ActiveScaffold.Actions.Abstract = function(){}
|
|
ActiveScaffold.Actions.Abstract.prototype = {
|
|
initialize: function(links, target, loading_indicator, options) {
|
|
this.target = $(target);
|
|
this.loading_indicator = $(loading_indicator);
|
|
this.options = options;
|
|
this.links = links.collect(function(link) {
|
|
return this.instantiate_link(link);
|
|
}.bind(this));
|
|
},
|
|
|
|
instantiate_link: function(link) {
|
|
throw 'unimplemented'
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A DataStructures::ActionLink, represented in JavaScript.
|
|
* Concerned with AJAX-enabling a link and adapting the result for insertion into the table.
|
|
*/
|
|
ActiveScaffold.ActionLink = new Object();
|
|
ActiveScaffold.ActionLink.Abstract = function(){}
|
|
ActiveScaffold.ActionLink.Abstract.prototype = {
|
|
initialize: function(a, target, loading_indicator) {
|
|
this.tag = $(a);
|
|
this.url = this.tag.href;
|
|
this.method = 'get';
|
|
if(this.url.match('_method=delete')){
|
|
this.method = 'delete';
|
|
} else if(this.url.match('_method=post')){
|
|
this.method = 'post';
|
|
}
|
|
this.target = target;
|
|
this.loading_indicator = loading_indicator;
|
|
this.hide_target = false;
|
|
this.position = this.tag.getAttribute('position');
|
|
this.page_link = this.tag.getAttribute('page_link');
|
|
|
|
this.onclick = this.tag.onclick;
|
|
this.tag.onclick = null;
|
|
this.tag.observe('click', function(event) {
|
|
this.open();
|
|
Event.stop(event);
|
|
}.bind(this));
|
|
|
|
this.tag.action_link = this;
|
|
},
|
|
|
|
open: function() {
|
|
if (this.is_disabled()) return;
|
|
|
|
if (this.tag.hasAttribute( "dhtml_confirm")) {
|
|
if (this.onclick) this.onclick();
|
|
return;
|
|
} else {
|
|
if (this.onclick && !this.onclick()) return;//e.g. confirmation messages
|
|
this.open_action();
|
|
}
|
|
},
|
|
|
|
open_action: function() {
|
|
if (this.position) this.disable();
|
|
|
|
if (this.page_link) {
|
|
window.location = this.url;
|
|
} else {
|
|
if (this.loading_indicator) this.loading_indicator.style.visibility = 'visible';
|
|
new Ajax.Request(this.url, {
|
|
asynchronous: true,
|
|
evalScripts: true,
|
|
method: this.method,
|
|
onSuccess: function(request) {
|
|
if (this.position) {
|
|
this.insert(request.responseText);
|
|
if (this.hide_target) this.target.hide();
|
|
} else {
|
|
request.evalResponse();
|
|
}
|
|
}.bind(this),
|
|
|
|
onFailure: function(request) {
|
|
ActiveScaffold.report_500_response(this.scaffold_id());
|
|
if (this.position) this.enable()
|
|
}.bind(this),
|
|
|
|
onComplete: function(request) {
|
|
if (this.loading_indicator) this.loading_indicator.style.visibility = 'hidden';
|
|
}.bind(this)
|
|
});
|
|
}
|
|
},
|
|
|
|
insert: function(content) {
|
|
throw 'unimplemented'
|
|
},
|
|
|
|
close: function() {
|
|
this.enable();
|
|
this.adapter.remove();
|
|
if (this.hide_target) this.target.show();
|
|
},
|
|
|
|
register_cancel_hooks: function() {
|
|
// anything in the insert with a class of cancel gets the closer method, and a reference to this object for good measure
|
|
var self = this;
|
|
this.adapter.select('.cancel').each(function(elem) {
|
|
elem.observe('click', this.close_handler.bind(this));
|
|
elem.link = self;
|
|
}.bind(this))
|
|
},
|
|
|
|
reload: function() {
|
|
this.close();
|
|
this.open();
|
|
},
|
|
|
|
get_new_adapter_id: function() {
|
|
var id = 'adapter_';
|
|
var i = 0;
|
|
while ($(id + i)) i++;
|
|
return id + i;
|
|
},
|
|
|
|
enable: function() {
|
|
return this.tag.removeClassName('disabled');
|
|
},
|
|
|
|
disable: function() {
|
|
return this.tag.addClassName('disabled');
|
|
},
|
|
|
|
is_disabled: function() {
|
|
return this.tag.hasClassName('disabled');
|
|
},
|
|
|
|
scaffold_id: function() {
|
|
return this.tag.up('div.active-scaffold').id;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Concrete classes for record actions
|
|
*/
|
|
ActiveScaffold.Actions.Record = Class.create();
|
|
ActiveScaffold.Actions.Record.prototype = Object.extend(new ActiveScaffold.Actions.Abstract(), {
|
|
instantiate_link: function(link) {
|
|
var l = new ActiveScaffold.ActionLink.Record(link, this.target, this.loading_indicator);
|
|
l.refresh_url = this.options.refresh_url;
|
|
if (link.hasClassName('delete')) l.url = l.url.replace(/\/delete(\?.*)?/, '$1');
|
|
if (l.position) l.url = l.url.append_params({adapter: '_list_inline_adapter'});
|
|
l.set = this;
|
|
return l;
|
|
}
|
|
});
|
|
|
|
ActiveScaffold.ActionLink.Record = Class.create();
|
|
ActiveScaffold.ActionLink.Record.prototype = Object.extend(new ActiveScaffold.ActionLink.Abstract(), {
|
|
close_previous_adapter: function() {
|
|
this.set.links.each(function(item) {
|
|
if (item.url != this.url && item.is_disabled() && item.adapter) item.close();
|
|
}.bind(this));
|
|
},
|
|
|
|
insert: function(content) {
|
|
this.close_previous_adapter();
|
|
|
|
if (this.position == 'replace') {
|
|
this.position = 'after';
|
|
this.hide_target = true;
|
|
}
|
|
|
|
if (this.position == 'after') {
|
|
new Insertion.After(this.target, content);
|
|
this.adapter = this.target.next();
|
|
}
|
|
else if (this.position == 'before') {
|
|
new Insertion.Before(this.target, content);
|
|
this.adapter = this.target.previous();
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
|
|
this.adapter.down('a.inline-adapter-close').observe('click', this.close_handler.bind(this));
|
|
this.register_cancel_hooks();
|
|
|
|
new Effect.Highlight(this.adapter.down('td'));
|
|
},
|
|
|
|
close_handler: function(event) {
|
|
this.close_with_refresh();
|
|
if (event) Event.stop(event);
|
|
},
|
|
|
|
/* it might simplify things to just override the close function. then the Record and Table links could share more code ... wouldn't need custom close_handler functions, for instance */
|
|
close_with_refresh: function() {
|
|
new Ajax.Request(this.refresh_url, {
|
|
asynchronous: true,
|
|
evalScripts: true,
|
|
method: this.method,
|
|
onSuccess: function(request) {
|
|
Element.replace(this.target, request.responseText);
|
|
var new_target = $(this.target.id);
|
|
if (this.target.hasClassName('even-record')) new_target.addClassName('even-record');
|
|
this.target = new_target;
|
|
this.close();
|
|
}.bind(this),
|
|
|
|
onFailure: function(request) {
|
|
ActiveScaffold.report_500_response(this.scaffold_id());
|
|
}
|
|
});
|
|
},
|
|
|
|
enable: function() {
|
|
this.set.links.each(function(item) {
|
|
if (item.url != this.url) return;
|
|
item.tag.removeClassName('disabled');
|
|
}.bind(this));
|
|
},
|
|
|
|
disable: function() {
|
|
this.set.links.each(function(item) {
|
|
if (item.url != this.url) return;
|
|
item.tag.addClassName('disabled');
|
|
}.bind(this));
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Concrete classes for table actions
|
|
*/
|
|
ActiveScaffold.Actions.Table = Class.create();
|
|
ActiveScaffold.Actions.Table.prototype = Object.extend(new ActiveScaffold.Actions.Abstract(), {
|
|
instantiate_link: function(link) {
|
|
var l = new ActiveScaffold.ActionLink.Table(link, this.target, this.loading_indicator);
|
|
if (l.position) l.url = l.url.append_params({adapter: '_list_inline_adapter'});
|
|
return l;
|
|
}
|
|
});
|
|
|
|
ActiveScaffold.ActionLink.Table = Class.create();
|
|
ActiveScaffold.ActionLink.Table.prototype = Object.extend(new ActiveScaffold.ActionLink.Abstract(), {
|
|
insert: function(content) {
|
|
if (this.position == 'top') {
|
|
new Insertion.Top(this.target, content);
|
|
this.adapter = this.target.immediateDescendants().first();
|
|
}
|
|
else {
|
|
throw 'Unknown position "' + this.position + '"'
|
|
}
|
|
|
|
this.adapter.down('a.inline-adapter-close').observe('click', this.close_handler.bind(this));
|
|
this.register_cancel_hooks();
|
|
|
|
new Effect.Highlight(this.adapter.down('td'));
|
|
},
|
|
|
|
close_handler: function(event) {
|
|
this.close();
|
|
if (event) Event.stop(event);
|
|
}
|
|
});
|