I needed much the same thing for a project I was working on. This is
a code dump, w/o any good explanations or guarantees. I haven''t
tested it except with tabular data. It it very heavily based on
Script.aculo.us'' InPlaceEditor.
I use it to add/edit/remove an arbitrary number of addresses
associated with an entity.
It''ll use the form''s action attribute by default for its AJAX
calls,
and expects certain td id patterns.
Dive in. Perhaps you will find it useful.
TAG
==== page specific js ===
var states =
["AK","AL","AR","AZ","CA","CO","CT","DC","DE","FL","GA","HI","IA","ID","
IL","IN","KS","KY","LA","MA","MD","ME","MI","MN","MO","MS","MT","NC","ND
","NE","NH","NJ","NM","NV","NY","OH","OK","OR","PA","RI","SC","SD","TN",
"TX","UT","VA","VT","WA","WI","WV","WY"];
var addrTypes =
[{"value":1,"text":"Home"},{"value":
3,"text":"Mailing"},{"value":2,"text":"Work"}];
//TODO: Could be
better written to use key/val pairs
var addrFields = {
addr1: {attributes:{size:12}},
addr2: {attributes:{size:12}},
city: {attributes: {size: 8}},
state: {type: ''select'', options: states},
zip: {attributes: {size: 5, ''class'':
''required''}},
''type'': {type: ''select'', options:
addrTypes}
};
Event.observe(window,''load'',
function (e) {
var addrLipe = new LineInPlaceEditor(''frmAddress'',
''addrList'', {
hoverClass: '''',
fieldElements: [''td''],
rowElements: [''tr''],
fieldOptions: addrFields,
ajaxOptions: {method: ''POST''}
});
// ...
}
}
====== HTML snippet =======<table id="addr"
class="datatable"><thead>
<tr><th scope="col">Address Line 1</th><th
scope="col">Address Line
2</th><th scope="col">City</th><th
scope="col">State</th><th
scope="col">Zip Code</th><th
scope="col">Type</th><th scope="col"></
th></tr>
</thead>
<tbody id="addrList"><tr id="a1"><td
id="a1_addr1">1983 N 1120 W</
td><td id="a1_addr2"></td><td
id="a1_city">Somewhere</td><td
id="a1_state">UT</td><td
id="a1_zip">84604</td><td
id="a1_type">Mailing</td><td><input
id="a1_edit" value="Edit"
class="edit" type="button"><input
id="a1_delete" value="Delete"
class="edit" onclick="deleteAddress(''a1'')"
type="button"></td></tr>
</tbody>
<tfoot id="newaddrFields"><tr
id="newaddr"><td id="newaddr_addr1"></
td><td id="newaddr_addr2"></td><td
id="newaddr_city"></td><td
id="newaddr_state"></td><td
id="newaddr_zip"></td><td
id="newaddr_type"></td><td
id="newaddrTools"><input id="newaddr_edit"
value="Add Address" class="edit"
type="button"></td></tr></tfoot>
</table>
====== line_controls.js ======var LineInPlaceEditor = Class.create();
LineInPlaceEditor.createField = function (element, name, options) {
var text;
element = $(element);
options = Object.extend({
name: name,
''type'': ''text'',
attributes: {},
onLoad: function (element, field) {}
}, options);
if (options.exclude) {return null;}
var tmp = element.innerHTML;
var field;
switch (options.type) {
case ''textarea'':
field = document.createElement(''textarea'');
field.innerHTML = tmp;
break;
case ''select'':
field = document.createElement(''select'');
if (options.options) {
//may be array or function that returns array
var opt = (typeof options.options == ''function'' ?
options.options(element) : options.options);
opt.each(function(o) {
var el = document.createElement(''option'');
var str = '''';
if (o.text) {
el.value = (typeof o.value == ''undefined'' ?
'''' : o.value);
str = o.text;
} else {
el.value = o;
str = o;
}
el.appendChild(document.createTextNode(str));
if (str == tmp) {
el.selected = true;
}
field.appendChild(el);
});
}
break;
case ''text'':
case ''password'':
default:
field = document.createElement(''input'');
field.type = options.type;
field.value = tmp.unescapeHTML();
break;
}
field._oldValue = tmp;
field.name = options.name;
var a;
if (options.attributes) {
for (a in options.attributes) {
field.setAttribute(a, options.attributes[a]);
}
}
while (element.firstChild) {
element.removeChild(element.firstChild)
}
element.appendChild(field);
if (options.onload) {options.onload(element, field);}
return field;
};
LineInPlaceEditor.prototype = {
active: {},
initialize: function(form, element, options) {
this.form = $(form);
this.element = $(element);
this.options = Object.extend({
url: this.form.action,
editClass: ''editing'',
formEditClass: ''editing'',
hoverClass: ''hover'',
//TODO: clickToEdit: true,
rowElements: [''tr'',''li''],
fieldElements: [''td'',''span''],
fieldOptions: {},
ajaxOptions: {
method: this.form.method
},
submitCallback: function() {
var tmp = Form.serialize(this.form);
if (tmp) {tmp = ''&'' + tmp;}
tmp = ''__row='' + this.active.row.id + tmp;
return tmp;
},
editElementMask: ''_edit'',
getEditElement: function (row) {
return $(row.id + ''_edit'');
},
getUpdateElement: function(row) {
var el = document.createElement(''input'');
el.setAttribute(''type'', ''submit'');
el.value = ''Update'';
return el;
},
getCancelElement: function(row) {
var el = document.createElement(''input'');
el.setAttribute(''type'', ''button'');
el.value = ''Cancel'';
return el;
},
onEnterEditRow: function (row) {},
onExitEditRow: function (evt) {
if (this.active.update) {
this.active.update.parentNode.removeChild(this.active.update);
}
if (this.active.cancel) {
this.active.cancel.parentNode.removeChild(this.active.cancel);
}
}
}, options || {});
if (this.options.onExitEditRow) {
this.options.onExitEditRow = this.options.onExitEditRow.bind(this);
}
Event.observe(this.form, ''submit'',
this.onSubmit.bindAsEventListener
(this));
//.bindAsEventListener(this)
this.options.submitCallback this.options.submitCallback.bind(this);
if (!this.options.rowElements) {return;}
var re = new RegExp(''^('' +
this.options.rowElements.join(''|'') + '')
$'',''i'');
var hc = this.options.hoverClass;
var hoverFunc = function (evt) {
if (this.parentNode._hoveredRow) {
if (this.parentNode._hoveredRow == this) {return;}
Element.removeClassName(this.parentNode._hoveredRow, hc);
}
Element.addClassName(this, hc);
this.parentNode._hoveredRow = this;
}
var tmp = this.element.firstChild;
var self = this;
var editEl;
while (tmp) {
if (tmp.nodeName && tmp.nodeName.match(re) && tmp.id) {
if (hc) {tmp.onmouseover = hoverFunc.bindAsEventListener(tmp);}
if (editEl = $(tmp.id + this.options.editElementMask)) {
editEl.onclick = function () {
self.enterEditRow(this);
}.bind(tmp);
}
}
tmp = tmp.nextSibling;
}
if (hc) {
this.element.onmouseout = function (evt) {
if (!this._hoveredRow) {return;}
var from = Event.element(evt);
var to = evt.relatedTarget || evt.toElement;
while (to != from && to.nodeName != ''BODY'') {
to = to.parentNode
if (to == from) return;
}
Element.removeClassName(this._hoveredRow, hc);
this._hoveredRow = null;
}.bindAsEventListener(this.element);
}
},
enterEditRow: function (row) {
if (this.options.onEnterEditRow) {
this.options.onEnterEditRow(row);
}
if (this.active && this.active.row) {
this.cancelEditRow();
}
row = $(row);
if (!row) {return;}
this.active = {
row: row,
edit: null,
update: null,
cancel: null,
fields: []
};
this.options.fieldElements.each(function (el) {
var els = row.getElementsByTagName(el);
var re = new RegExp(row.id+''_(.+)'');
for (var i = 0; i < els.length; i++) {
if (els[i].id && els[i].id.match(re)) {
var f = LineInPlaceEditor.createField(els[i], RegExp.$1,
this.options.fieldOptions[RegExp.$1] || {});
if (f) {this.active.fields.push(f);}
}
}
}.bind(this));
// Handle the edit/update/cancel elements
if (this.options.getEditElement) {
if (this.active.edit = this.prepareElement
(this.options.getEditElement, row)) {
Element.hide(this.active.edit);
}
}
if (this.options.getUpdateElement) {
this.active.update = this.prepareElement
(this.options.getUpdateElement, row);
}
if (this.options.getCancelElement) {
this.active.cancel = this.prepareElement
(this.options.getCancelElement, row);
this.active.cancel.onclick = this.cancelEditRow.bindAsEventListener
(this);
}
Element.addClassName(this.form, this.options.formEditClass);
},
cancelEditRow: function (evt) {
this.exitEditRow(evt);
},
exitEditRow: function (evt) {
Element.removeClassName(this.form, this.options.formEditClass);
if (this.active.edit) {
Element.show(this.active.edit);
}
this.options.onExitEditRow(evt);
this.active.fields.each(function (el) {
if (el && el.parentNode) {el.parentNode.innerHTML = el._oldValue;}
});
this.active = {};
},
onSubmit: function (evt) {
Event.stop(evt);
if (!this.active.row) {return false;}
var data = this.options.submitCallback();
new Ajax.Request(this.options.url,
Object.extend({
parameters: data,
onComplete: this.onComplete.bind(this),
onSuccess: this.onSuccess.bind(this),
onFailure: this.onFailure.bind(this),
asynchronous:true
}, this.options.ajaxOptions));
return false;
},
onComplete: function (transport) {
this.exitEditRow();
},
onSuccess: function (transport, xJson) {
if (!xJson) {xJson = eval(transport.responseText);}
if (xJson.__row) {
var row = xJson.__row + ''_'';
delete xJson.__row;
var el, i;
for (i in xJson) {
el = $(row + i);
if (el) {
el.innerHTML = xJson[i];
}
}
}
},
onFailure: function (transport) {
var str = ''Your request could not be performed. An XMLHTTPRequest
error has occurred.'';
str += '' ('' + transport.status + '': '' +
transport.statusText + '')'';
if (transport.responseText) {
str += ''\n'' + transport.responseText.stripTags();
}
alert(str);
transport = false;
},
prepareElement: function (el, row) {
el = (typeof el == ''function'' ? el(row) : $(el));
if (!el) {return null;}
if (!el.parentNode) {
if (this.active.edit && this.active.edit.parentNode) {
this.active.edit.parentNode.appendChild(el);
} else if (this.active.update && this.active.update.parentNode) {
this.active.update.parentNode.appendChild(el);
}
}
return el;
},
createRow: function (rowId, fields, values) {
if (this.options.rowElements.length < 1
|| this.options.fieldElements.length < 1) {return;}
var row = document.createElement(this.options.rowElements[0]);
if (rowId) {row.id = rowId;}
var ftype = this.options.fieldElements[0];
fields.each(
function (i) {
var f = document.createElement(ftype);
f.id = row.id + ''_'' + i;
if (values[i]) {f.innerHTML = values[i];}
row.appendChild(f);
}
);
this.element.appendChild(row);
return row;
}
}
On Feb 18, 2007, at 9:40 AM, Michael Williams wrote:
>
> Hi all,
>
> I''m looking for a sensible/streamlined manner in which to
implement
> an "in-place editable" html table with cells that, upon click,
will
> present the proper edit option (e.g. textbox, drop-down, etc). Are
> there any plugins that already implement this type of item, or is it
> something I''ll need to do from scratch? Any tips, tricks, or
> insights would be appreciated.
>
> Thanks in advance,
> Michael
>
> >
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"Ruby on Rails: Spinoffs" group.
To post to this group, send email to
rubyonrails-spinoffs-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org
To unsubscribe from this group, send email to
rubyonrails-spinoffs-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org
For more options, visit this group at
http://groups.google.com/group/rubyonrails-spinoffs?hl=en
-~----------~----~----~----~------~----~------~--~---