/*!
* Copyright (c) 2009 Francesco Mele jsbeans@francescomele.com
*
* This Software is licenced under the LGPL Licence (GNU Lesser General
* Public License).
* In addition to the LGPL Licence the Software is subject to the
* following conditions:
*
* i every modification must be public and comunicated to the Author
* ii every "jsbean" added to this library must be self consistent
* except for the dependence from jsbeans-x.x.x.js
* iii copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* Table utilities
* @namespace jsbeans
* @class Table
* @static
*/
jsbeans.Table = {
/**
* @config _options
* @type JSON
* @private
* @static
* */
_options: {
select: {
className: "selected"
}
},
/**
* @method _internalSelect
* @param node {DOM}
* @param options {JSON}
* @param mode {String}
* @return {DOM}
* @private
* @static
* */
_internalSelect: function(obj, options, mode) {
if (obj == null || typeof(obj) == "undefined") {
return null;
}
var isValidObject = obj.tagName.toLowerCase() == "tr" || obj.tagName.toLowerCase() == "td" || obj.tagName.toLowerCase() == "th";
if (isValidObject) {
var isSelected = function(o,cn) {
var sp = (o.className ? o.className : "").split(" ");
for (var i = 0, c; c = sp[i]; i++) {
if (c == cn) {
return true;
}
}
return false || o.className.indexOf(cn) >= 0;
};
var add = function(o,cn) {
var sp = (o.className ? o.className : "").split(" ");
for (var i = 0, c; c = sp[i]; i++) {
if (c == cn) {
return;
}
}
o.className = o.className + " " + cn;
};
var del = function(o,cn) {
var sp = (o.className ? o.className : "").split(" ");
var res = "";
for (var i = 0, c; c = sp[i]; i++) {
if (c != cn) {
res += c + " ";
}
}
o.className = res;
};
var sw = function(o,cn) {
var sp = (o.className ? o.className : "").split(" ");
var res = "";
var found = false;
for (var i = 0, c; c = sp[i]; i++) {
if (c == cn) {
found = true;
}
else {
res += c + " ";
}
}
if (found || o.className.indexOf(cn) >= 0) {
o.className = res;
}
else {
o.className = res + " " + cn;
}
};
switch ("" + options.what) {
case "tr":
if (obj.tagName.toLowerCase() == "th" || obj.tagName.toLowerCase() == "td") {
obj = obj.parentNode;
}
if (mode == "a") {
add(obj, options.className);
}
else if (mode == "r") {
del(obj, options.className);
}
else {
sw(obj, options.className);
}
break;
case "td":
if (mode == "a") {
add(obj, options.className);
}
else if (mode == "r") {
del(obj, options.className);
}
else {
sw(obj, options.className);
}
break;
case "column":
if (obj.tagName.toLowerCase() != "th" && obj.tagName.toLowerCase() != "td") {
return null;
}
var o = obj;
if (obj.tagName.toLowerCase() == "th") {
var table = obj.parentNode.tagName.toLowerCase() == "table" ? obj.parentNode : obj.parentNode.parentNode;
table = table.tagName.toLowerCase() == "table" ? table : table.parentNode;
o = table.getElementsByTagName("td")[0];
}
var sel = isSelected(o, options.className);
var trs = o.parentNode.parentNode.getElementsByTagName("tr");
for (var i = 0, tr; tr = trs[i]; i++) {
var td = tr.cells[obj.cellIndex];
if (mode == "a") {
add(td, options.className);
}
else if (mode == "r") {
del(td, options.className);
}
else {
if (sel) {
del(td, options.className);
}
else {
add(td, options.className);
}
}
}
break;
case "both":
if (obj.tagName.toLowerCase() != "th" && obj.tagName.toLowerCase() != "td") {
return null;
}
var o = obj;
if (obj.tagName.toLowerCase() == "th") {
var table = obj.parentNode.tagName.toLowerCase() == "table" ? obj.parentNode : obj.parentNode.parentNode;
table = table.tagName.toLowerCase() == "table" ? table : table.parentNode;
o = table.getElementsByTagName("td")[0];
}
var sel = isSelected(o, options.className);
var trs = o.parentNode.parentNode.getElementsByTagName("tr");
for (var i = 0, tr; tr = trs[i]; i++) {
var td = tr.cells[obj.cellIndex];
if (mode == "a") {
add(td, options.className);
}
else if (mode == "r") {
del(td, options.className);
}
else {
if (sel) {
del(td, options.className);
}
else {
add(td, options.className);
}
}
}
var row = o.parentNode;
if (mode == "a") {
add(row, options.className);
}
else if (mode == "r") {
del(row, options.className);
}
else {
if (sel) {
del(row, options.className);
}
else {
add(row, options.className);
}
}
break;
default:
if (obj.tagName.toLowerCase() == "th" || obj.tagName.toLowerCase() == "td") {
obj = obj.parentNode;
}
if (mode == "a") {
add(obj, options.className);
}
else if (mode == "r") {
del(obj, options.className);
}
else {
sw(obj, options.className);
}
}
return obj;
}
return null;
},
/**
* Selects table element {@param} <code class="param">node</code> (column, row, cell or column+row) based on value of {@param} <code class="param">options</code>.<br/>
* It switches selection on every invocation. Use {@method} <code class="methd">selectOn</code> or {@method} <code class="methd">selectOff</code> to force a selection mode.
* @method select
* @param node {DOM} a table DOM element
* @param [options] {JSON} a JSON
* <pre>
* what: ['column' | 'tr' | 'td' | 'both'], // optional, default 'td'
* className: a_class_name // optional, define style class to use on selection, default 'selected'
* </pre>
* @return {DOM} selected DOM Object
* @static
* */
select: function(obj /*, options*/) {
var options = arguments[1] || jsbeans.Table._options.select;
var className = options.className || this._options.select.className;
var what = options.what || obj.tagName.toLowerCase();
return this._internalSelect(obj,{className:className,what:what},"s");
},
/**
* Selects table element {@param} <code class="param">node</code> (column, row, cell or column+row) based on value of {@param} <code class="param">options</code>.
* @method selectOn
* @param node {DOM} a table DOM element
* @param [options] {JSON} a JSON
* <pre>
* what: ['column' | 'tr' | 'td' | 'both'], // optional, default 'td'
* className: a_class_name // optional, define style class to use on selection, default 'selected'
* </pre>
* @return {DOM} selected DOM Object
* @static
* */
selectOn: function(obj /*, options*/) {
var options = arguments[1] || jsbeans.Table._options.select;
var className = options.className || this._options.select.className;
var what = options.what || obj.tagName.toLowerCase();
return this._internalSelect(obj,{className:className,what:what},"a");
},
/**
* Turns off a previous selection by {@method} <code class="methd">select</code> or {@method} <code class="methd">selectOn</code>
* @method selectOff
* @param node {DOM} a table DOM element
* @param [options] {JSON} a JSON
* <pre>
* what: ['column' | 'tr' | 'td' | 'both'], // optional, default 'td'
* className: a_class_name // optional, define style class to use on selection
* </pre>
* @return {DOM} selected DOM Object
* @static
* */
selectOff: function(obj /*, options*/) {
var options = arguments[1] || jsbeans.Table._options.select;
var className = options.className || this._options.select.className;
var what = options.what || obj.tagName.toLowerCase();
return this._internalSelect(obj,{className:className,what:what},"r");
},
/**
* Hides rows of a Table based on {@param} <code class="param">search</code>
* @method filter
* @param search {String} the search term or phrase
* @param head {DOM | String} the TH DOM Element, or its id, containing the filter
* @param [min] {Integer} filter fired if <code>value.length >= min</code>. Default <code>0</code>
* @static
* */
filter: function(value, head) {
var h = typeof(head) == "string" ? document.getElementById(head) : head;
var cn = h.parentNode.childNodes;
var pos = -1;
for (var i = 0, c; c = cn[i]; i++) {
if (c.nodeName && (c.nodeName.toLowerCase() == "td" || c.nodeName.toLowerCase() == "th")) {
pos++;
if (c.id == h.id) {
break;
}
}
}
// h is a TD or TH, so TABLE isn't that far!
var tb = h.parentNode.parentNode.nodeName.toLowerCase() == "table" ? h.parentNode.parentNode : h.parentNode.parentNode.parentNode;
if (tb.getElementsByTagName("tbody")[0] == null) {
tb.appendChild(document.createElement("tbody"));
}
var min = arguments[2] || 0;
if (value.length >= min || value.length == 0) {
var trs = tb.getElementsByTagName("tbody")[0].getElementsByTagName("tr");
for (var i = 0, tr; tr = trs[i]; i++) {
var cn = tr.childNodes;
var pos2 = -1;
for (var j = 0, c; c = cn[j]; j++) {
if (c.nodeName && (c.nodeName.toLowerCase() == "td" || c.nodeName.toLowerCase() == "th")) {
pos2++;
if (pos2 == pos) {
if (c.firstChild.data.toLowerCase().indexOf(value) > -1) {
try {
c.parentNode.style.display = "table-row";
}
catch (e) {
c.parentNode.style.display = "";
}
}
else {
c.parentNode.style.display = "none";
}
}
}
}
}
}
},
/**
* An easy way to set rows with alternate style and a mouseover effect.
* @method zebra
* @param table {String | DOM} could be the id of a Table or the DOM Table Object itself
* @param options {JSON} a JSON. Note that at least one of these options have to be used to make this method have sense
* <pre>
* odd: a_class_name, // a style class for odd rows
* even: a_class_name, // a style class for even rows
* over: a_class_name, // a style class to applied on mouseover event fired. You can even use 'hover'.
* </pre>
* @static
* */
zebra: function(table, options) {
if (table == null || typeof table == "undefined") {
return;
}
var tableObj = null;
if (typeof table == "string") {
tableObj = document.getElementById(table);
}
else {
tableObj = table;
}
if (tableObj != null && typeof tableObj != "undefined" && tableObj.nodeName && tableObj.nodeName.toUpperCase() == "TABLE") {
var opts = options || {};
var odd = opts.odd || "";
var even = opts.even || "";
// both 'over' and 'hover' are valid
var over = opts.over || (opts.hover ? opts.hover : "");
if (odd == "" && even == "" && over == "") {
return;
}
var addHandler = function(element, name, handler) {
if (element.addEventListener) {
element.addEventListener(name, handler, false);
}
else if (element.attachEvent) {
element["e" + name + handler] = handler;
element[name + handler] = function() {
element["e" + name + handler](window.event);
};
element.attachEvent('on' + name, element[name + handler]);
}
};
var getTarget = function(e) {
try {
e = e || event;
var object = e.srcElement || e.target;
if (object != null) {
return object;
}
}
catch(e) {
return null;
}
};
var base = tableObj.getElementsByTagName("tbody").length > 0 ? tableObj.getElementsByTagName("tbody")[0] : tableObj;
var trs = base.getElementsByTagName("tr");
var cn;
for (var i = 0, tr; tr = trs[i]; i++) {
cn = tr.className;
if (cn == null || typeof cn == "undefined") {
cn = "";
}
if (i % 2 == 0) {
tr.className = cn + " " + odd;
}
else {
tr.className = cn + " " + even;
}
if (over != "") {
addHandler(tr, "mouseover", function(e) {
var el = getTarget(e);
if (el != null) {
if (el.nodeName.toUpperCase() == "TD") {
el = el.parentNode;
}
cn = el.className;
if (cn == null || typeof cn == "undefined") {
cn = "";
}
el.className = cn + " " + over;
}
el = null;
});
addHandler(tr, "mouseout", function(e) {
var el = getTarget(e);
if (el != null) {
if (el.nodeName.toUpperCase() == "TD") {
el = el.parentNode;
}
cn = el.className;
if (cn == null || typeof cn == "undefined") {
// should never happen as this means that mouseover did nothing!
return;
}
var sp = cn.split(" ");
var s = "";
for (var j = 0; j < sp.length; j++) {
c = sp[j];
if (c != over) {
s += " " + c;
}
}
el.className = s;
}
el = null;
});
}
tr = null;
}
}
}
};