Using edge animate, I don't seem to have the control over orders of operations that I am looking for. I have a function that operates a switch and another that tests the conditions of one or many switches. The problem I am running into is edge keeps wanting to run the test before the switch.
I can have the switch launch the test but run into the issue that I load the objects in an array inside of edge. What I am thinking is I need a way of pre-loading variables that the function can use but they don't need to be global since they are only used in this one function.
Here is what I have so far.
Inside Edge Animate:
twoPhaseSwitch('.btn1','on'); //sets up switch 1
twoPhaseSwitch('.swtch1','off'); //sets up switch 2
conditionsArray(['.btn1','.swtch1']); // tells the test script what the buttons are
In JavaScript file:
function twoPhaseSwitch(object,state)
{
var obj = $(object);
stage.getSymbol(obj).stop(state);
obj.data('state',state);
obj.mousedown(function(e)
{
if(obj.state == 'off')
{
stage.getSymbol(obj).stop('on');
obj.state = 'on';
obj.data('state','on');
}else{
stage.getSymbol(obj).stop('off');
obj.state = 'off';
obj.data('state','off');
};
});
};
function conditionsArray(obj)
{
var answers = ['on','on'];
// compare lengths
if (obj.length != answers.length)
return 'Argument Miscount';
var challengResults = challangeArray();
if (challengResults == true)
{
lightOn('.LED1','on');
}else if(challengResults == false)
{
lightOn('.LED1','off');
};
console.log(challengResults);
function challangeArray()
{
for (var i = 0, l=obj.length; i < l; i++)
{
if ($(obj[i]).data('state') != answers[i]) {
return false;
}
}
return true;
};
};
function lightOn(lightOBJ,state)
{
lightOBJ = $(lightOBJ);
stage.getSymbol(lightOBJ).stop(state);
};
I use mousedown and mouseup currently to fake the order of operations but it brings some pretty unacceptable issues so I am trying to do this right.
did you try wrapping your code in
$( document ).ready(){
your code here
}
to prevent any javascript from running until the page loads?
Related
I'm working on a text game in javascript right now and have a function to pick up a sword.
var takeSword = function() {
if (currentRoom.hasSword) {
$("<p>You picked up a sword.</p>").properDisplay();
}
else {
$("<p>The sword is not here.</p>").properDisplay();
}
};
My problem is, as long as you're in the same room as the sword, you can pick it up over and over. How can you set the function so that once you pick up the sword you can't pick it up again?
My original thought was setting a variable like var sword = false; and then when the function runs to set sword = true; but that didn't work.
This isn't my entire code, there's an object further up that sets `hasSword = true;' so that the sword can be picked up in the first place and can NOT be picked up in different rooms of the game.
Something like this perhaps?
var GameState = {
worldHasSword: true,
playerHasSword: false,
takeSword: function() {
if( this.worldHasSword ) {
this.worldHasSword = false;
this.playerHasSword = true;
$("<p>You picked up a sword.</p>").properDisplay();
}
else {
$("<p>The sword is not here.</p>").properDisplay();
}
}
}
GameState.takeSword();
The best solution here is not to touch your original function at all, but simply wrap it in a general function that will prevent its being called more than once. This general function can be used anywhere else in your code that you need to "once-ify" something:
function once(fn, context) {
var result;
return function() {
if(fn) {
result = fn.apply(context || this, arguments);
fn = null;
}
return result;
};
}
Now you simply do:
var takeSwordOnce = once(takeSword);
and use takeSwordOnce in your code. Or, you can do:
takeSword = once(takeSword);
Please see this article for further explanation of the once function.
#Kenney got me thinking in the right direction. Here's how I fixed it without totally rewriting the function:
var takeSword = function() {
if (currentRoom.hasSword) {
$("<p>You picked up a sword.</p>").properDisplay();
currentRoom.hasSword = false;
}
else {
$("<p>The sword is not here.</p>").properDisplay();
}
};
The first mistake I made was thinking "hasSword" was the variable by itself. I needed to add the currentRoom to it then it worked fine. I've tested it and also tried it in rooms where the sword does not exist and it seems to be working fine.
So, I have this little code in my js file:
window.onload = function Equal() {
var a = 'b1'
var b = 'box1'
var bookstorname = localStorage.getItem(a)
if (bookstorname == 1) {
document.getElementById(b).setAttribute('checked','checked');
}
if (bookstorname == 0) {
document.getElementById(b).removeAttribute('checked','checked');
}
var a = 'b2'
var b = 'box2'
var bookstorname = localStorage.getItem(a)
if (bookstorname == 1) {
document.getElementById(b).setAttribute('checked','checked');
}
if (bookstorname == 0) {
document.getElementById(b).removeAttribute('checked','checked');
}
}
The function itself is not important (it equals checkboxvalues set in the localstorage), but I execute it 2 times. First time with var a & b set to 'b1' & 'box1'. Then I run the script again (same script), but with var a & b set to 'b2' & 'box2'. Now, this code works, but my question is if there is a shorter way to write this? I can imagine some sort of array with a loop, but I could not get it to work for some reason. The 2 variables are pairs, and I know this might be a dumb question, but I can't find the answer anywhere.
You can use a second function which will accept the local storage key and the checkbox id like
window.onload = function Equal() {
setCheckboxState('box1', 'b1');
setCheckboxState('box2', 'b2');
}
function setCheckboxState(id, key) {
document.getElementById(id).checked = 1 == localStorage.getItem(key);
}
You might separate common logic into another function
window.onload = function Equal() {
function extractFromStorage(a, b) {
var bookstorname = localStorage.getItem(a)
if (bookstorname == 1) {
document.getElementById(b).setAttribute('checked','checked');
}
if (bookstorname == 0) {
document.getElementById(b).removeAttribute('checked','checked');
}
}
extractFromStorage('b1', 'box1');
extractFromStorage('b2', 'box2');
}
function doTheStuff(a, b) {
var bookstorname = localStorage.getItem(a)
if (bookstorname == 1) {
document.getElementById(b).setAttribute('checked','checked');
}
if (bookstorname == 0) {
document.getElementById(b).removeAttribute('checked','checked');
}
}
window.onload = function Equal() {
doTheStuff('b1', 'box1');
doTheStuff('b2', 'box2');
}
?
This is how I would do it.
There are several problems with your code.
You do not check that the element you are stetting an attribute to
exists. You do not check if the localStorage item you get is
defined.
You pollute the global name space with the function name Equal.
That function should not be named with a capital as it is not a Object generator.
There is no need to use setAttribute and removeAttribute, in
fact removeAttribute makes no sense in this case as you can not
remove the checked attribute from the element. BTW why use setAttribute here and not for window.onload?
The checked attribute is either true or false, it does not use the
string "checked"
Binding the load event via the onload attribute is not safe as you may
block 3rd party code, or worse 3rd party code may block you.
There is no error checking. DOM pages are dynamic environments, pages
have adverts and content from many places that can interfer with your
code. Always code with this in mind. Check for possible errors and deal with them in a friendly way for the end user. In this case I used an alert, not friendly for a normal user but for you the coder.
My solution.
// add an event listener rather than replace the event listener
window.addEventListener(
"load", // for the load event
function(){
// the update function that is called for each item;
var update = function(item){
// the right hand side equates to true if the localstorage
// is equal to "1". LocalStorage allways returns a string or
// undefined if the key is not defined.
item.element.checked = localStorage[item.storageName] === "1";
}
// safe element getter
var getElement = function(eId){
var e = document.getElementById(eId); // try and get the element
if(e === null){ // does it exist?
throw "Missing element:"+eId; // no then we can not continue
// the program stops here unless
// you catch the error and deal with
// it gracefully.
}
return e; //ok return the element.
}
// Item creator. This creates a new item.
// sName is the local storage name
// eId id the element ID
var item = function(sName, eId){
return {
storageName: sName, // set the loaclStorage name
element:getElement(eId); // get the element and check its safe
};
}
// make it all safe
try{
// create an array of items.
var items = [
item("b1","box1"),
item("b2","box2")
];
// for each item update the element status
items.forEach(update);
}catch(e){
alert("Could not update page?");
}
}
);
I came across this in some code I am reworking and am curious if there is any reason for doing it this way besides personal preference. It seems unnecessarily obfuscated to me, considering how easy it would be to use several small functions with descriptive names. The purpose of the code is to validate a number of variables to ensure the data is properly formatted and within acceptable ranges while generating business data reports. The report is mainly just a tool to bring attention to scheduling issues.
There is a single function that gets passed several values, runs a test on each one, then passes all the results back as a boolean array.
function testAll(test1, ..., test10) {
var results = [false, ..., false];
if (test1 condition == true) {
results[0] = true;
}
...
if (test10 condition == true) {
results[9] = true;
}
return results;
}
That function is then called and used like this.
var tblData = getCurrentData(); // function that gets database info through AJAX
for (i = 0; i < tblData.Rows.Count; i++) {
// some code to append table element
var results = testAll(strStartDate, ..., strTotalHours);
if (results[0] == true) {
$('#startDate' + i.toString()).css('background-color', 'red');
}
...
if (results[9] == true) {
$('#projectTime' + i.toString()).css('background-color', 'red');
}
}
The original writer is gone and did not comment his code
While this may not technically address the question I posed, it is the solution to cleaning up the code in this case.
After further review it made more sense to use a non value returning function that performs the css formatting because of how simple the the tasks after evaluation are.
-Modified function
function verifyData(startDate, ..., timeSpent) {
if (startDate isValid != true) {
$('#startDate' + i.toString()).css('background-color', 'red');
}
...
if (timeSpent isValid != true) {
$('#projectTime' + i.toString()).css('background-color', 'red');
}
}
-New method of calling and using
var tblData = getCurrentData(); // function that gets database info through AJAX
for (i = 0; i < tblData.Rows.Count; i++) {
// some code to append table element
verifyData(strStartDate, ..., strTotalHours, i);
}
For context, I am writing a plugin for omniture (adobe) sitecatalyst for video tracking. Before I write the plugin in the sitecatalyst format, I want to confirm it is working. I have tested the same code but using jQuery, and it works fine with how jQuery handles variables/scope. But doing it with Javascript directly is proving to be a little more difficult. Here is where I am at:
var vsa = new Array();
var vp = new Array();
vsa = document.getElementsByTagName('video');
if(vsa.length>0){
for(var vvv=0;vvv<vsa.length;vvv++) {
vsa[vvv].addEventListener('seeked',function () { if(vp[vsa[vvv].id]) { s.Media.play(vsa[vvv].id,vsa[vvv].currentTime); }},false);
vsa[vvv].addEventListener('seeking',function () { if(vp[vsa[vvv].id]) { s.Media.play(vsa[vvv].id,vsa[vvv].currentTime); }},false);
vsa[vvv].addEventListener('play',function () {
if(!vp[vsa[vvv].id]) {
vp[vsa[vvv].id] = true;
s.Media.open(vsa[vvv].id,vsa[vvv].duration,s.Media.playerName);
s.Media.play(vsa[vvv].id,vsa[vvv].currentTime);
} else {
s.Media.play(vsa[vvv].id,vsa[vvv].currentTime);
}},false);
vsa[vvv].addEventListener('pause',function () { if(vp[vsa[vvv].id]) { s.Media.stop(vsa[vvv].id,vsa[vvv].currentTime); }},false);
vsa[vvv].addEventListener('ended',function () { vp[vsa[vvv].id] = false; s.Media.stop(vsa[vvv].id,vsa[vvv].currentTime); s.Media.close(vsa[vvv].id); },false);
if (typeof vsa[vvv].error != 'undefined' && vsa[vvv].error) {
var scvt_msg = 'Error Not Captured';
if(typeof vsa[vvv].error.code != 'undefined') {
switch (vsa[vvv].error.code) {
case MEDIA_ERR_ABORTED:
scvt_msg = 'vsa[vvv]eo stopped before load.';
break;
case MEDIA_ERR_NETWORK:
scvt_msg = 'Network error';
break;
case MEDIA_ERR_DECODE:
scvt_msg = 'vsa[vvv]eo is broken';
break;
case MEDIA_ERR_SRC_NOT_SUPPORTED:
scvt_msg = 'Codec is unsupported by this browser';
break;
}
}
s.tl(this,'o','video: ' + scvt_msg);
}
}
}
On load, there is no error (meaning the eventlisteners are attaching correctly). When I press play on the video, I get a "vsa[vvv] is undefined". on the line of code that starts with
if(!vp[vsa[vvv].id])
Any ideas how to get the "global" vars of vsa, vp, and s accessible from the event listener function?
Thank you!
The variables are accessible. The problem is that you fell into the (very common) closures inside for loops trap.
Basically, all the event listeners are sharing the same vvv variable. By the time the event listeners run, vvv has gone through all the loop and is set to vsa.length, making vsa[vvv] undefined. (Check this out by adding a console.log(vvv) call to your event listeners)
The usual fix for this is creating the event listeners in a function outside the loop, to make each event listener get its own reference to a videotag variable.
function addMyEvents(node){
//the inner functions here get their own separate version of "node"
// instead of sharing the vvv
node.addEventListener('seeked',function () { if(vp[node.id]) { s.Media.play(node.id, node.currentTime); }},false);
node.addEventListener('seeking',function () { if(vp[node.id]) { s.Media.play(node.id, node.currentTime); }},false);
//...
}
for(var vvv=0;vvv<vsa.length;vvv++) {
addMyEvents(vsa[vvv]);
}
By the way, you don't need the if(vsa.length) test in the beginning since the loop wont run if the length is zero anyway...
To aboid this error in the future, run your code through a linter like JSHint or JSLint. They give warnings if you create functions inside a for loop.
Can I do the following?
function contains(element) {
// if the element is a Vertex object, do this
if (element instanceof Vertex) {
var vertex = element;
for ( var index in self.verticies) {
if (self.verticies[index].id == vertex.id) {
return true;
}
}
return false;
}
// else if the element is an Edge object, do this
else if (element instanceof Edge) {
var edge = element;
for ( var index in self.verticies) {
if (self.verticies[index].id == edge.id) {
return true;
}
}
return false;
} else {
// shouldn't come here
return false;
}
};
Basically... I want to be able to call contains() and pass it either a Vertex object or an Edge object but I don't want to have duplicate code. Is this the right way to do it? Furthermore, am I handling the assignment var vertex = element / var edge = element correctly? I want to assign element to another Vertex/Edge object and use that for my look up.
Let me know if I need to clarify.
Thanks,
Hristo
Your code should work fine.
Note, however, that there is no point (other than clarity, which is a good thing) in writing var edge = element.
Javascript variables are untyped; there is no difference between edge and element.
Also, you should probably throw an exception instead of
// shouldn't come here
return false;
Finally, why are you searching self.verticies for an Edge?
Note, by the way, that you still have duplicate code.
You can rewrite your function like this:
function contains(element) {
var searchSet;
// if the element is a Vertex object, do this
if (element instanceof Vertex)
searchSet = self.verticies;
else if (element instanceof Edge)
searchSet = self.edges;
else
throw Error("Unexpected argument");
for (var i = 0; i < searchSet.length; i++) {
if (searchSet[i].id == element.id)
return true;
}
return false;
}
Here's an approach that has a couple of advantages:
Smaller functions (no big if/else if chain)
Produces an appropriate error for missing functions without any additional coding
See what you think:
function contains(element) {
window['contains_' + typeof element](element);
};
contains_string = function(element) {
alert('string: ' + element);
};
contains('hi!'); // produces alert
contains(3); // error: 'undefined is not a function'
It has some downsides too.
The error message isn't terribly informative (not much worse than default behavior though)
You 'pollute' the 'window' object here a little (it'd work better as part of an object)
etc