How can I remember combination states? - javascript

I'm having a logic problem with an implementation of combination rules in Javascript.
In short:
I define which checkboxes cannot be 'on' together with eachother in a JSON object.
If I click on measure 1 -> measures 7 and 8 get grayed out, this is correct.
If I click on measure 4 -> measures 8, 9, 10 and 11 get grayed out, this is correct too.
Now, if I click again on measure 4 -> measure 8 gets active again, but shouldn't be, because the JSON 'rules' state that since measure 1 is still checked, measure 8 must be grayed.
What's a smart way to solve this logic problem? Thanks in advance!
Basically, how do I remember that clicking 'measure 1' already disabled 'measure 8', so that toggling 'measure 4' won't accidentally re-enable 'measure 8' with 'measure 1' still 'on'?
All code here: https://gist.github.com/1055968
Interactive: http://jsfiddle.net/gnijholt/58zuR/

Well logically i'd do something like this:
when you tick a checkbox, you create
an object which stores the rules that
are applied.
when i untick a checkbox, i will
remove that rule from the memory
object and before unticking each
checkboxes i'll go through the memory
object to see if there are any
conflicting rules. If there is one
conflicting rules, i do not gray out
the checkbox.
I'm trying to implement this.
EDIT - i'v implemented my idea. here is a fiddle http://jsfiddle.net/Tvs7E/1/
function createObjectsWithRules(rulesApplied){
var presentBlock = {};
for (var checkboxid in rulesApplied){
var presentCombos = rulesApplied[checkboxid];
for(var key in presentCombos) {
var obj = presentCombos[key];
for(var prop in obj) {
var s = obj[prop];
presentBlock[s] = true;
}
}
}
return presentBlock;
}
$(document).ready(function() {
var rulesApplied= {};
$('input').change(function() {
current_cb = this;
if ($(this).attr("checked")) {
// toggle on
console.log("You ticked on " + current_cb.id);
combos = JSONSelect.match("." + current_cb.id, combinations);
rulesApplied[current_cb.id] = combos;
for(var key in combos) {
var obj = combos[key];
for(var prop in obj) {
var s = obj[prop];
$('#' + s).attr('disabled', 'disabled');
console.log("I disable " + obj[prop]);
}
}
console.log(rulesApplied);
return;
}
// toggle off
console.log("You ticked off " + current_cb.id);
combos = JSONSelect.match("." + current_cb.id, combinations);
console.log(combos);
delete rulesApplied[current_cb.id];
console.log(rulesApplied);
presentRules = createObjectsWithRules(rulesApplied);
console.log(presentRules);
for(var key in combos) {
var obj = combos[key];
for(var prop in obj) {
var s = obj[prop];
if (!presentRules[s]){
$('#' + s).removeAttr('disabled');
console.log("I enable " + obj[prop]);
}
}
}
return;
});
});

Try changing your .change event to this
http://paste.pocoo.org/show/424704/
It does what it did before, only without using empty returns to emulate an if-else.
Then when it is done with the clicked input, it loops through all the input to "re-disable" whatever needs to be disabled.
Only tested it lightly
And I can see that while I was writing this, davin was making the same suggestion in the comments. It's the same thing, just with a code example.

Related

How can I do this? change checkbox from readonly to available for checking

I created a list of 5 checkbox using HTML and CSS. The first one is always able to be checked but the other four are initianlly readonly. Then, when the first is checked, the second should change from readonly to be available for checking. Once the second is checked, the third must change as well, and so on. How could I do this?
Short answer:
Javascript
more explanation:
What you want is to listen to the first checkbox so you can change a property on the next checkbox.
const box1 = document.querySelector('#box1')
const box2 = document.querySelector('#box2')
box1.addEventListener('change', () => {
box2.readonly = false
})
I didn't test this code but this should get you started
this code works for more or less common case
for (let i = 1; i < 5; i++) {
const item = document.querySelector('#cbk' + i);
item.addEventListener('change', () => {
var newId = +item.id.replace('cbk', '') + 1;
document.querySelector('#cbk' + newId).readonly = false;
})
}

Handsontable - afterChangeEvent how to detect when a whole row is deleted or added

I have create a custom function that executes on the afterChange cell event as per the instructions in the following Handsontable link.
https://docs.handsontable.com/0.15.0-beta3/Hooks.html
And I can perform actions based on each cell that is changed fine. As per the following code.
var hot = new Handsontable(container, {
data: data(),
minSpareRows: 1,
rowHeaders: true,
colHeaders: ['some array of headers']
afterChange: function (changes, source) {
onChangeHandler(this, changes, source);
}
});
var onChangeHandler = function (hot_instance, changes, source) {
var changesLength;
var changePayloadtoServer = [];
if (changes != null) {
changesLength = changes.length;
} else {
changesLength = 0;
}
for (var i = 0; i < changesLength; i++) {
var row = changes[i][0],
col = changes[i][1],
oldVal = changes[i][2],
newVal = changes[i][3],
var trigger = false
// How to I determine that a whole row has been deleted/added here?
// trigger = some check that a whole row has been deleted/added.
if (trigger == true) {
// Perform action on row deletion
// i.e. CASCADE DELETE query on database
// Perform action on row addition
// i.e. INSERT INTO query on database
} else {
// Perform normal on cell change
// i.e. UPDATE query on database
}
}
}
However, I cant figure out how to determine whether a entire row has been deleted or added in the changes. Given that the change data provided in this afterChange event is in the follow format:
[
{changedRowNumber, chnagedColumnName, oldCellValue, newCellValue}
, {changedRowNumber, chnagedColumnName, oldCellValue, newCellValue}
, {changedRowNumber, chnagedColumnName, oldCellValue, newCellValue}
, {changedRowNumber, chnagedColumnName, oldCellValue, newCellValue}
, ... etc.
]
I have created an example fiddle. See link below.
https://jsfiddle.net/JoshAdams/go898kq6/
Does anyone know how I can detect when a whole row is added or deleted?
What you're looking for is two other events also defined in the documentation:
afterCreateRow
afterRemoveRow
As you can imagine, you will have to defiene this two events in your handsontable configuration and not in your afterChange event.
For example, see your JSFiddle edited :
afterRemoveRow: function (index, amount) {
console.log(amount + " row(s) removed starting line "+ index)
},
afterCreateRow: function (index, amount) {
console.log(amount + " row(s) added starting line "+ index)
},
EDIT : Based on your comment, I made an example for the case you want; knowing when every value from a row became empty after a change.
You can find a new simplified JSFiddle here containg the following afterChange event definition :
hot.addHook('afterChange', function(changes) {
if (!changes) {
return;
}
$.each(changes, function (index, element) {
var
rowEmpty = true,
row = element[0],
currentRowArray = hot.getData(row,0,row,hot.countCols());
for(var i=0; i<currentRowArray[0].length-1; i++) {
if(currentRowArray[0][i] != "") {
rowEmpty = false;
break;
}
}
if(rowEmpty) {
console.log("Row " + row + " is empty !");
}
});
});
Every time a change is made in a cell, the code within the event will check if there is at least one cell on the same row which contain a value (and finish the event if it sees one). If not, it prints in console that the row is empty. Hence the result you're looking for is the boolean rowEmpty.
Also, note two things :
The jQuery function $.each allows you to make it works when a user empty two (or more) rows at the same time.
The -1 in the condition of the for loop. If you want to compare the length of your array versus the number of columns, don't forget that the number of columns obviously don't start to count from 0. So if there is 6 columns, this will be column 0 to 5, hence the -1.

If else condition Javascript

I have four TextFields on my UI page.I get the input values of the user from all textfields for example values Textfield1.getvalue(),Textfield2.getvalue(),Textfield3.getvalue(),Textfield4.getvalue().
Now on a button click, I need to check which textfields are actually filled by the user and attach those values I need to send a http request to the server to query on the database. These values are used to filter values from a table. SO basically the values are "TABLE COLUMN" values. For this, I thought of using old school combinations like:
if (Textfield1.getvalue()=="" && Textfield2.getvalue()!=""){
//do something
}
else if (Textfield2.getvalue()=="" && Textfield3.getvalue()!=""){
//do something
}
else if (Textfield3.getvalue()=="" && Textfield4getvalue()!=""){
//do something
}
......
and so on.
This, I personally feel is not efficient and not a good programming way. I am pretty sure there might be some other way of doing it which I am not aware of and couldnt find googling it either. Can anyone share some ideas for a better solution.
Thanks in advance.
If you want to do something based on first field that has a value, at least that is what it looks like from your sample, you could do something like:
» Simple Fiddle. «
var do_something = {
0 : function(val) { console.log("Doing x width " + val)},
1 : function(val) { console.log("Doing y width " + val)},
2 : function(val) { console.log("Doing z width " + val)},
3 : function(val) { console.log("Doing w width " + val)},
}
$("#post").on("click", function() {
var val;
$(".test").each(function(i) {
val = $(this).val();
if (val) {
do_something[i](val);
return false; // Break
// (Or omit the break if you want to "do_something" with all fields
// having a value.)
}
});
});
Or, depending on various, a better solution could be:
var do_something2 = {
act1 : function(k, val) { console.log("Doing x width " + val + " k=" + k) },
act2 : function(k, val) { console.log("Doing y width " + val + " k=" + k) },
act3 : function(k, val) { console.log("Doing z width " + val + " k=" + k) }
};
$("#post2").on("click", function() {
var val;
$(".test").each(function(i) {
val = $(this).val();
if (val) {
do_something2[$(this).data("act")](i, val);
return false; // Break
}
});
});
Where you have input fields like this (dynamically or otherwise created):
<input type="text" data-act="act1" class="test" value="one" />
<input type="text" data-act="act2" class="test" value="two" />
This way you can also easily change what action is taken per field simply by setting the data-act value to wanted function.
One idea - check individual fields once and combine into a single unique value:
var c=0;
if (condition1) c+=1;
if (condition2) c+=2;
if (condition3) c+=4;
Etc. now every combination of conditions has a unique value associated with it and you can use a switch statement for cleaner flow.
Think of data instead of control flow. I'd suggest thinking of the problem this way:
Data -> Validation -> Side effects
All those steps must be uncoupled. Here's example, you may have to re-think your data to adapt your code:
// Data
var fields = [Textfield1, Textfield2, Textfield3, Textfield4];
// Validation
var valid = fields.filter(function(x) {
return x.getvalue();
});
// Side effects
valid.forEach(function(field) {
var value = field.getvalue();
// do something
});

Doing key combos with jQuery / JavaScript

I'm curious how i, with the following jQuery plugin code im writing at the bottom of this question, could implement key combos. How it's working so far is it allows a user to create key commands simply by doing a normal jQuery like syntax and provide an event for the key command, like so:
$(window).jkey('a',function(){
alert('you pressed the a key!');
});
or
$(window).jkey('b c d',function(){
alert('you pressed either the b, c, or d key!');
});
and lastly what i want is the ability to do, but can't figure out:
$(window).jkey('alt+n',function(){
alert('you pressed alt+n!');
});
I know how to do this outside of the plugin (on keyup set a var false and on keydown set the var true and check if the var is true when you press the other key), but i don't know how to do this when you dont know what keys are going to be pressed and how many. How do I add this support? I want to be able to allow them to do things like alt+shift+a or a+s+d+f if they wanted. I just can't get my head around how to implement this. Any ideas?
I'm going to release this as an open source plugin and i'd love to give whoever gives me the right, working, answer some credit on the blog post and in the code it's self. Thanks in advance!
(function($) {
$.fn.jkey = function(keyCombo,callback) {
if(keyCombo.indexOf(' ') > -1){ //If multiple keys are selected
var keySplit = keyCombo.split(' ');
}
else{ //Else just store this single key
var keySplit = [keyCombo];
}
for(x in keySplit){ //For each key in the array...
if(keySplit[x].indexOf('+') > -1){
//Key selection by user is a key combo... what now?
}
else{
//Otherwise, it's just a normal, single key command
}
switch(keySplit[x]){
case 'a':
keySplit[x] = 65;
break;
case 'b':
keySplit[x] = 66;
break;
case 'c':
keySplit[x] = 67;
break;
//And so on for all the rest of the keys
}
}
return this.each(function() {
$this = $(this);
$this.keydown(function(e){
if($.inArray(e.keyCode, keySplit) > -1){ //If the key the user pressed is matched with any key the developer set a key code with...
if(typeof callback == 'function'){ //and they provided a callback function
callback(); //trigger call back and...
e.preventDefault(); //cancel the normal
}
}
});
});
}
})(jQuery);
Use keypress instead of keyup/keydown because the latter two do not accurately protray the keycode (reference, see last paragraph). You can reference the altKey ctrlKey and shiftKey boolean properties of the event object in this case...
$(document).keypress(function(e) {
var key = String.fromCharCode(e.which);
var alt = e.altKey;
var ctrl = e.ctrlKey
var shift = e.shiftKey;
alert("Key:" + key + "\nAlt:" + alt + "\nCtrl:" + ctrl + "\nShift:" + shift);
});
Also, you can use String.fromCharCode to translate the key code to an actual letter.
You can't trap multiple keys aside from combinations with Ctrl, Alt, and Shift. You simply can't do it in a single event. So throw the a+s+d+f idea out the window.
Note: Obviously there are certain key combinations that are used by the browser. For instance, Alt + F usually brings up the File menu in Windows. Ctrl + N usually launches a new window/tab. Do not attempt to override any of these combinations.
Here's a live demo for your testing pleasure.
Here's what I came up with. Essentially what I did was created a JSON object that stores all the key codes. I then replace all the provided keys with the codes. If the keys are using the '+' to make a key combo, I then create an array of the codes out of it.
We then create another array that stores all the keys that are being pressed (keyDown add the item, keyUp removes it). On keyDown, we check if it's a single key command or combo. If it's a combo, we check it against all the currently active key presses. If they all match, we execute the callback.
This will work with any number of key combos. Only time I saw that it wasn't working is when you use the 'alert()' to display a message on the key combo because it will no longer remove the items from the active key press array.
(function($) {
$.fn.jkey = function(keyCombo,callback) {
// Save the key codes to JSON object
var keyCodes = {
'a' : 65,
'b' : 66,
'c' : 67,
'alt' : 18
};
var x = '';
var y = '';
if(keyCombo.indexOf(' ') > -1){ //If multiple keys are selected
var keySplit = keyCombo.split(' ');
}
else{ //Else just store this single key
var keySplit = [keyCombo];
}
for(x in keySplit){ //For each key in the array...
if(keySplit[x].indexOf('+') > -1){
//Key selection by user is a key combo
// Create a combo array and split the key combo
var combo = Array();
var comboSplit = keySplit[x].split('+');
// Save the key codes for each element in the key combo
for(y in comboSplit){
combo[y] = keyCodes[ comboSplit[y] ];
}
keySplit[x] = combo;
} else {
//Otherwise, it's just a normal, single key command
keySplit[x] = keyCodes[ keySplit[x] ];
}
}
return this.each(function() {
$this = $(this);
// Create active keys array
// This array will store all the keys that are currently being pressed
var activeKeys = Array();
$this.keydown(function(e){
// Save the current key press
activeKeys[ e.keyCode ] = e.keyCode;
if($.inArray(e.keyCode, keySplit) > -1){ // If the key the user pressed is matched with any key the developer set a key code with...
if(typeof callback == 'function'){ //and they provided a callback function
callback(); //trigger call back and...
e.preventDefault(); //cancel the normal
}
} else { // Else, the key did not match which means it's either a key combo or just dosn't exist
// Check if the individual items in the key combo match what was pressed
for(x in keySplit){
if($.inArray(e.keyCode, keySplit[x]) > -1){
// Initiate the active variable
var active = 'unchecked';
// All the individual keys in the combo with the keys that are currently being pressed
for(y in keySplit[x]) {
if(active != false) {
if($.inArray(keySplit[x][y], activeKeys) > -1){
active = true;
} else {
active = false;
}
}
}
// If all the keys in the combo are being pressed, active will equal true
if(active === true){
if(typeof callback == 'function'){ //and they provided a callback function
callback(); //trigger call back and...
e.preventDefault(); //cancel the normal
}
}
}
}
} // end of if in array
}).keyup(function(e) {
// Remove the current key press
activeKeys[ e.keyCode ] = '';
});
});
}
})(jQuery);
This is just a shot in the dark but maybe it'll help you down the right path.
If it's possible to have that function recognize a hexadecimal value for the key that you entered instead of the literal key (such as 0x6E for the letter 'n'), you could derive what "alt+n" translates to in hex and have the function look out for that value.
If you're looking for something that will let a user easily enter and define key combos using a plain input box, I wrote a plugin that does it for you: http://suan.github.com/jquery-keycombinator/

Javascript reference by value/by reference problem

I'm creating a jQuery plugin to do paging and encountered the following problem.
When I click on a page link created by the plugin below, it will always give we the value of the last index passed into the value i at the last iterator of the code below. If there are 4 pages, I will always get 4 if I press link 1, 2, 3 or 4. It seems that the reference to the delegate onclick also keeps a reference to the value of i instead of just the value.
Any Ideas? It's the options.onclick(i) that's acting strange.
$.fn.pager = function(options) {
var defaults = {
resultSet: undefined,
onclick: function(page) { alert(page); return false; },
};
return this.each(function () {
var rnd = Math.floor(Math.random()*9999)
var result = '';
for(var i = 1; i <= options.resultSet.PageCount; i++)
{
if(i == options.resultSet.PageCount)
result += '' + i + '';
else
result += '' + i + '' + options.separator;
}
$(this).html(result);
for(var i = 1; i <= options.resultSet.PageCount; i++)
{
$('#' + rnd + '_pagerPage_' + i).click(function() { options.onclick(i) });
}
});
}
I reduced the above code to just the problem case. So some checks re missing ;)
It seems that the reference to the delegate onclick also keeps a reference to the value of i instead of just the value.
What you are experiencing is your first (unexpected) encounter with closures. It's not even a reference that is being passed, it's weirder than that. To understand what's going on (and it's critical that you do if you program in javascript, this is considered basic stuff these days) read my answers to the following related questions:
Please explain the use of JavaScript closures in loops
Hidden Features of JavaScript?
This is a classic problem: the value of i that gets used inside the option click event handler function is whatever value i has at the point at which the event fires (which will be 4, the final value it has in the loop), not the value it had at the point at which you assigned the event handler. The way round it is to create an extra function each time through the loop that has its own variable containing a copy of the value of i at the point it was called:
var createClickHandler = function(n) {
return function() { options.onclick(n); };
};
$('#' + rnd + '_pagerPage_' + i).click( createClickHandler(i) );

Categories