Reputation: 43427
I've got a matrix of drop down lists, and I'd like to be able to show the user when they have edited a setting visually (for example by setting the background of the table cell to red). If they switch it back to the original value, it will go back to the default background color. This way it will be unambiguously clear what the state of the system is.
To do this I have come up with setting an onchange
handler on the <select>
element like this: select.setAttribute('onchange','chSel(this,'+select.selectedIndex+');');
where select
is a <select>
element.
It will call the function chSel
with a reference to itself and the current setting, so it will be able to set classes which will determine display properties via CSS:
function chSel(node, origSelIndex) {
if (node.selectedIndex == origSelIndex) // switching back to the set state
node.parentNode.removeAttribute('class');
else node.parentNode.setAttribute('class','cell_changed');
}
That doesn't require the retrieval of dynamic data, though the saved selectedIndex
value in the onchange
handler serves as element-specific static data.
I wanted to extend this functionality I have described by making a border around my table, which I intend to color red when ANY of its contents are modified, so that if a table is huge it is still possible to tell at a glance if any of its entries are modified.
There is a bad way and a better way to implement this. The bad way, which I do know how to implement in compliant HTML/XHTML, is to have chSel
(which is executed any time the user interacts with a drop down menu) run a full search through the entire table to count edits, and mark the table border accordingly. This may not actually be a performance problem but I anticipate that it will be. I don't think it would scale that well.
The better way is to keep a count of the number of edited elements. When that number drops back to zero (user changes all his edits back to original values) I can re-mark the table as unedited.
However this requires that I have some method of determining if a select menu has been changed from a new value to a different new value, as opposed to changing from the original value to a new value, i.e. it is now relevant what the value being changed from is. There does not appear to be a way to do this. This discussion seems to indicate that I cannot create and retrieve custom attributes while keeping my document valid. When I have tried to assign arbitrary attributes I've had difficulty retrieving their values.
What's left? Can I build an object which I will use as a hash from elements to integers? Will javascript even let me do that?
Upvotes: 4
Views: 4949
Reputation: 147363
Most form controls have a defaultValue property. To determine if any have changed from their default, cycle through them and compare the current value with the defaultValue. For select elements, always set one to selected, and check if the currently selected option has its defaultSelected property set to true. Similarly for radio buttons and checkboxes.
That way you don't have to store any values, it's done by the DOM elements themselves.
Upvotes: 1
Reputation: 707228
I'm not sure I totally follow what you're trying to do, but you can keep track of state either in javascript variables or in custom DOM attributes. Javascript variables are slightly easier, but you have to give them an appropriate scope so they survive the duration of your operation. Custom DOM attributes are sometimes easier to make object oriented and to avoid any global javascript variables. jQuery's .data()
capability is actually in between the two. It uses one custom data attribute to tag the DOM object with a unique tag and then stores all the actual data in a javascript map.
Once you store the state appropriately, anytime you get a change event, you can compare to the previous value and see what actually changed from the previous state, update your new state and save the new value as the previous value or do whatever else you need to do.
For example, in this way, you could keep a count of how many edited items there were and anytime that value goes from 0 to non-zero or non-zero to 0, you would change the overall table look as desired. Likewise, anytime an individual cell changes, you could update it's look.
If your DOM elements are your only data structure, then it's probably easiest to just create a custom attribute and keep additional data there. The HTML5 way of doing this is to preprend "data-" to your attribute names as in obj.setAttribute("data-origSelectIndex", x)
or obj["data-origSelectIndex"] = x
to keep them clear of any name conflicts with standard attributes.
If there's any reason you don't want to use custom attributes, then you would need to make a parallel javascript data structure (probably an array where each item in the array corresponds to one DOM element in some way that you can map between the two) that held the data.
Upvotes: 4
Reputation: 2786
Working off of @jfriend00 's suggestion using dom attributes as a sort of global variable:
CSS
.selected { border: 1px solid red;}
HTML
<div id="table1">
<select id="sel1">
<option>1</option>
<option>2</option>
<option selected>3</option>
</select>
<select id="sel2">
<option selected>1</option>
<option>2</option>
<option>3</option>
</select>
</div>
<div id="table2">
<select id="sel3">
<option>1</option>
<option selected>2</option>
<option>3</option>
</select>
<select id="sel4">
<option selected>1</option>
<option>2</option>
<option>3</option>
</select>
</div>
Javascript:
(function(){
var selects = document.getElementsByTagName('select'),
i = 0,
selectLength = selects.length;
var applyParentChange = function(node, val){
var currentEdits = node.getAttribute('data-edits') || 0;
currentEdits = parseFloat(currentEdits, 10) + val;
if(currentEdits <= 0) {
currentEdits = 0;
node.className = '';
} else {
node.className = 'selected';
}
node.setAttribute('data-edits',currentEdits);
};
var onSelectChange = function(){
var defaultIndex = parseFloat(this.getAttribute('data-default'),10);
if(defaultIndex === this.selectedIndex){
applyParentChange(this.parentNode, -1);
this.className = '';
} else {
applyParentChange(this.parentNode, 1);
this.className = 'selected';
}
};
for(i=0; i<selectLength; i+=1){
selects[i].setAttribute('data-default',selects[i].selectedIndex);
selects[i].onchange = onSelectChange;
}
})();
To walk through the code a bit...
You get a collection of all your select
elements and give them a custom property with their default selected index and you give it a onchange
event directly.
On change it checks the new index, versus the original (default) index and applies the CSS to the element if needed. Then it passes it off to the parent node (div in this case, but can be whatever).
In the parent we keep a similar custom attribute for the number of edits. The min of 0 is to keep it set if someone keeps re-selecting the default over and over. It applies the CSS if it has 1 or more edits, removing if it is 0.
Note that this is only one approach, you can keep a collection inside a variable that you keep references/counts in, some use hidden (offscreen) input elements that store values...
Upvotes: 0