| 1 | /* |
|---|
| 2 | This is a datasource you can attach to a table. It will enable |
|---|
| 3 | the selection of rows or cells in the table. |
|---|
| 4 | |
|---|
| 5 | The data in the datasource is elements that are selected. |
|---|
| 6 | |
|---|
| 7 | $id:$ |
|---|
| 8 | */ |
|---|
| 9 | |
|---|
| 10 | SelectableTable = new Class(DataSource, { |
|---|
| 11 | |
|---|
| 12 | // options: |
|---|
| 13 | // table: what table element to attach to |
|---|
| 14 | // selectableClass: if you only want elements with a certain class to be selectable, |
|---|
| 15 | // specifiy this class with selectableClass |
|---|
| 16 | // multiple: can more than one thing be selected at once? default is true |
|---|
| 17 | // selectedClass: class to apply to selected elements |
|---|
| 18 | // checkboxClass: since there are frequently checkboxes associated with selectable elements, |
|---|
| 19 | // you can specify the class of your checkboxes to make them stay in sync |
|---|
| 20 | // selectableItem: What type of elements can be selected. Values are "cell" or "row" |
|---|
| 21 | init: function (opts) { |
|---|
| 22 | SelectableTable.superClass.init.apply(this, []); |
|---|
| 23 | |
|---|
| 24 | var table = opts.table; |
|---|
| 25 | var selectableClass = opts.selectableClass; |
|---|
| 26 | var multiple = opts.multiple; |
|---|
| 27 | var selectedClass = opts.selectedClass |
|---|
| 28 | var checkboxClass = opts.checkboxClass |
|---|
| 29 | var selectableItem = opts.selectableItem; |
|---|
| 30 | |
|---|
| 31 | selectableItem = selectableItem == "cell" ? "cell" : "row"; |
|---|
| 32 | |
|---|
| 33 | if (!defined(multiple)) multiple = true; |
|---|
| 34 | |
|---|
| 35 | this.table = table; |
|---|
| 36 | this.selectableClass = selectableClass; |
|---|
| 37 | this.multiple = multiple; |
|---|
| 38 | this.selectedClass = opts.selectedClass; |
|---|
| 39 | this.checkboxClass = opts.checkboxClass; |
|---|
| 40 | |
|---|
| 41 | this.selectedElements = []; |
|---|
| 42 | |
|---|
| 43 | // if it's not a table, die |
|---|
| 44 | if (!table || !table.tagName || table.tagName.toLowerCase() != "table") return null; |
|---|
| 45 | |
|---|
| 46 | // get selectable items |
|---|
| 47 | var tableElements = table.getElementsByTagName("*"); |
|---|
| 48 | |
|---|
| 49 | var selectableElements; |
|---|
| 50 | |
|---|
| 51 | if (selectableItem == "cell") { |
|---|
| 52 | selectableElements = DOM.filterElementsByTagName(tableElements, "td"); |
|---|
| 53 | } else { |
|---|
| 54 | selectableElements = DOM.filterElementsByTagName(tableElements, "tr"); |
|---|
| 55 | } |
|---|
| 56 | |
|---|
| 57 | var self = this; |
|---|
| 58 | selectableElements.forEach(function(ele) { |
|---|
| 59 | // if selectableClass is defined and this element doesn't have the class, skip it |
|---|
| 60 | if (selectableClass && !DOM.hasClassName(ele, selectableClass)) return; |
|---|
| 61 | |
|---|
| 62 | // attach click handler to every element inside the element |
|---|
| 63 | var itemElements = ele.getElementsByTagName("*"); |
|---|
| 64 | for (var i = 0; i < itemElements.length; i++) { |
|---|
| 65 | self.attachClickHandler(itemElements[i], ele); |
|---|
| 66 | } |
|---|
| 67 | |
|---|
| 68 | // attach click handler to the element itself |
|---|
| 69 | self.attachClickHandler(ele, ele); |
|---|
| 70 | }); |
|---|
| 71 | }, |
|---|
| 72 | |
|---|
| 73 | // stop our handling of this event |
|---|
| 74 | stopHandlingEvent: function (evt) { |
|---|
| 75 | if (!evt) return; |
|---|
| 76 | |
|---|
| 77 | // w3c |
|---|
| 78 | if (evt.stopPropagation) |
|---|
| 79 | evt.stopPropagation(); |
|---|
| 80 | |
|---|
| 81 | // ie |
|---|
| 82 | try { |
|---|
| 83 | event.cancelBubble = true; |
|---|
| 84 | } catch(e) {} |
|---|
| 85 | }, |
|---|
| 86 | |
|---|
| 87 | // attach a click handler to this element |
|---|
| 88 | attachClickHandler: function (ele, parent) { |
|---|
| 89 | if (!ele) return; |
|---|
| 90 | |
|---|
| 91 | var self = this; |
|---|
| 92 | |
|---|
| 93 | var rowClicked = function (evt) { |
|---|
| 94 | // if it was a control-click, they're probably trying to open a new tab or something. |
|---|
| 95 | // let's not handle it |
|---|
| 96 | if (evt && evt.ctrlKey) return false; |
|---|
| 97 | |
|---|
| 98 | var tagName = ele.tagName.toLowerCase(); |
|---|
| 99 | |
|---|
| 100 | // if this is a link or has an onclick handler, |
|---|
| 101 | // return true and tell other events to return true |
|---|
| 102 | if ((ele.href && tagName != "img") || ele.onclick) { |
|---|
| 103 | self.stopHandlingEvent(evt); |
|---|
| 104 | return true; |
|---|
| 105 | } |
|---|
| 106 | |
|---|
| 107 | // if this is the child of a link, propagate the event up |
|---|
| 108 | var ancestors = DOM.getAncestors(ele, true); |
|---|
| 109 | for (var i = 0; i < ancestors.length; i++) { |
|---|
| 110 | var ancestor = ancestors[i]; |
|---|
| 111 | if (ancestor.href && ancestor.tagName.toLowerCase() != "img") { |
|---|
| 112 | return true; |
|---|
| 113 | } |
|---|
| 114 | } |
|---|
| 115 | |
|---|
| 116 | // if this is an input or select element, skip it |
|---|
| 117 | if ((tagName == "select" || tagName == "input") && parent.checkbox != ele) { |
|---|
| 118 | self.stopHandlingEvent(evt); |
|---|
| 119 | return true; |
|---|
| 120 | } |
|---|
| 121 | |
|---|
| 122 | // toggle selection of this parent element |
|---|
| 123 | if (self.selectedElements.indexOf(parent) != -1) { |
|---|
| 124 | if (self.selectedClass) DOM.removeClassName(parent, self.selectedClass); |
|---|
| 125 | |
|---|
| 126 | self.selectedElements.remove(parent); |
|---|
| 127 | } else { |
|---|
| 128 | if (self.selectedClass) DOM.addClassName(parent, self.selectedClass); |
|---|
| 129 | |
|---|
| 130 | if (self.multiple) { |
|---|
| 131 | self.selectedElements.push(parent); |
|---|
| 132 | } else { |
|---|
| 133 | if (self.selectedClass && self.selectedElements.length > 0) { |
|---|
| 134 | var oldParent = self.selectedElements[0]; |
|---|
| 135 | if (oldParent) { |
|---|
| 136 | DOM.removeClassName(oldParent, self.selectedClass); |
|---|
| 137 | if (oldParent.checkbox) oldParent.checkbox.checked = ""; |
|---|
| 138 | } |
|---|
| 139 | } |
|---|
| 140 | |
|---|
| 141 | self.selectedElements = [parent]; |
|---|
| 142 | } |
|---|
| 143 | } |
|---|
| 144 | |
|---|
| 145 | // update our data |
|---|
| 146 | self.setData(self.selectedElements); |
|---|
| 147 | |
|---|
| 148 | // if there's a checkbox associated with this parent, set it's value |
|---|
| 149 | // to the parent selected value |
|---|
| 150 | if (parent.checkbox) parent.checkbox.checked = (self.selectedElements.indexOf(parent) != -1) ? "on" : ''; |
|---|
| 151 | if (parent.checkbox == ele) { self.stopHandlingEvent(evt); return true; } |
|---|
| 152 | |
|---|
| 153 | // always? not sure |
|---|
| 154 | if (evt) |
|---|
| 155 | Event.stop(evt); |
|---|
| 156 | } |
|---|
| 157 | |
|---|
| 158 | // if this is a checkbox we need to keep in sync, set up its event handler |
|---|
| 159 | if (this.checkboxClass && ele.tagName.toLowerCase() == "input" |
|---|
| 160 | && ele.type == "checkbox" && DOM.hasClassName(ele, this.checkboxClass)) { |
|---|
| 161 | |
|---|
| 162 | parent.checkbox = ele; |
|---|
| 163 | |
|---|
| 164 | // override default event handler for the checkbox |
|---|
| 165 | DOM.addEventListener(ele, "click", function (evt) { |
|---|
| 166 | return true; |
|---|
| 167 | }); |
|---|
| 168 | } |
|---|
| 169 | |
|---|
| 170 | // attach a method to the row so other people can programatically |
|---|
| 171 | // select it. |
|---|
| 172 | ele.rowClicked = rowClicked; |
|---|
| 173 | |
|---|
| 174 | DOM.addEventListener(ele, "click", rowClicked); |
|---|
| 175 | } |
|---|
| 176 | |
|---|
| 177 | }); |
|---|