I'm new to JavaScript, coming over from Swift. Trying it out code-learning challenges at http://play.elevatorsaga.com/
and some behavior is tough to grasp. In the following code, I setup floor & elevator objects. I am trying to get the elevator to get the floor it is about to pass's button request (if someone has pressed that floor's up or down button to call the elevator)
- in the code console.log(" (x) passing_floor - Same direction requested");
However the logs I get tell me that the up/downRequests are undefined
passing_floor 2 up
upRequest: undefined
downRequest: undefined
Is the issue with initialization? scoping? What is the proper way to achieve what I am trying to do?
{
init: function(elevators, floors) {
function initializeElevator(elevator){
elevator.on("floor_button_pressed", function(floorNum) {
elevator.goToFloor(floorNum);
});
elevator.on("idle", function() {
elevator.goToFloor(0);
});
elevator.on("passing_floor", function(floorNum, direction) {
if ((floorNum.upRequest) && (direction =='up')) {
floorNum.upRequest = false;
console.log(" (x) passing_floor - Same direction requested");
} else if ((floorNum.downRequest) && (direction == 'down')) {
floorNum.downRequest = false;
console.log(" (x) passing_floor - Same direction requested");
} else {
console.log("passing_floor " + floorNum + " " + direction);
console.log("upRequest: " + floorNum.upRequest);
console.log("downRequest: " + floorNum.downRequest);
}
});
}
function initializeFloor(floor){
var upRequest = false;
var downRequest = false;
floor.on("up_button_pressed", function() {
this.upRequest = true;
});
floor.on("down_button_pressed", function() {
this.downRequest = true;
});
}
elevators.forEach(initializeElevator);
floors.forEach(initializeFloor);
},
update: function(dt, elevators, floors) { }
}
Thank you for taking the time to help me understand Javascript a bit more, trying out W3School to get around it, but let me know if you have better sites I should look at..
I was able to proceed with the desired behavior by creating an "array" of floors that were global - I'm not sure if this was a scoping issue or something else though!
{
init: function(elevators, floors) {
var floorArray = new Array(floors.length);
var elevatorOnFloor = new Array(floors.length);
//the init function populates the listeners for the elevator objects
function initializeElevator(elevator){
elevator.on("floor_button_pressed", function(floorNum) {
elevator.goToFloor(floorNum, true);
});
elevator.on("idle", function() {
elevator.goToFloor(0);
});
elevator.on("passing_floor", function(floorNum, direction) {
if ((floorArray[floorNum] == 1) && (direction =='up')) {
floorArray[floorNum] = 0;
elevator.goToFloor(floorNum, true);
console.log("picking someone else going UP");
elevatorOnFloor[floorNum]+=1;
console.log("elevators per floor:"+ elevatorOnFloor );
} else if ((floorArray[floorNum] = 2) && (direction == 'down')) {
floorArray[floorNum] = 0;
elevator.goToFloor(floorNum, true);
console.log("picking someone else going DOWN");
elevatorOnFloor[floorNum]-=1;
console.log("elevators per floor:" + elevatorOnFloor + floorArray );
} else {
console.log("passing_floor " + floorNum + " " + direction);
if (direction =='up') {
elevatorOnFloor[floorNum]+=1;
} else {
elevatorOnFloor[floorNum]-=1;
}
}
});
}
function initializeFloor(floor){
floor.on("up_button_pressed", function() {
floorArray[floor] = 1;
});
floor.on("down_button_pressed", function() {
floorArray[floor] = 2;
});
}
function initializeElevatorsOnFloor(floor){
elevatorOnFloor[floor] = 0;
}
// we initialize all the elevators
elevators.forEach(initializeElevator);
// initialize the floors
floors.forEach(initializeFloor);
floors.forEach(initializeElevatorsOnFloor);
},
update: function(dt, elevators, floors) {
// We normally don't need to do anything here
}
}
Related
I have a function that uses setInterval, and it keeps running and it doesn't want to stop. The code I wrote is
let findGrid = setInterval(function () {
if (grid == null) {
grid = $('#QuickEntryGrid').getKendoGrid();
}
else {
clearFindGrid;
console.log("Found Grid");
console.log(grid.dataSource.view());
}
}, 100);
let clearFindGrid = function () {
clearInterval(findGrid);
};
if (grid != null) {
grid.setOptions({
width: (newInnerVerticalWidth - 2) + "px"
});
$("#QuickEntryGrid").find("table").on("keydown", onGridKeydown);
}
It keeps hitting the console.log(grid.dataSource.view());
You must call the funcion with the brakets clearFindGrid()
I'm currently trying to implement shortcuts feature on the menu of my web based project. I had already implemented single or double shortcut key combination (like F1, CTRL + Q etc..,).
$("#MyField").keydown(function (eventData) {
if (eventData.keyCode == 112) {
eventData.preventDefault();
myFunction_ToCall();
}
});
But now I'm moving towards the combination of 3-keys, to access a sub-subMenu, because my menu is look like this:
Menu1
SubMenu1
Sub-SubMenu1
Sub-Sub-Menu2
SubMenu2
SubMenue3
Menu2
Menu3
Menu4
So, to access the 1. Sub-SubMenu1 the path will be like 1. Menu1 > 1. SubMenu1 > 1. Sub-SubMenu1, the key combination will be like CTRL + 1 + 1 + 1`.
I searched a lot, but couldn't find any better solution. And now I'm confused how to achieve it. Anyone can help me!!
I would use KeyboardEvent.key, KeyboardEvent.ctrlKey and a tree where each sequence of keystrokes forms a branch :
step = shortcuts = {
"1": {
"1": sayHello,
"2": sayGoodbye
}
};
document.addEventListener("keydown", function (ev) {
if (ev.key === "Control") {
step = shortcuts; // go back to the root
ev.preventDefault();
}
});
document.addEventListener("keyup", function (ev) {
if (ev.ctrlKey && step[ev.key]) {
step = step[ev.key]; // a node was reached
if (typeof step === "function") {
step(); // a leaf was reached
}
}
});
function sayHello () {
console.log("Hello :-)");
}
function sayGoodbye () {
console.log("Goodbye :-(");
}
<p>Click here before trying shortcuts.</p>
Here is an improved version of the previous snippet :
step = shortcuts = {
"1": {
"1": "sayHello",
"2": "sayGoodbye"
}
};
commands = {
"sayHello": function () {
console.log("Hello :-)");
},
"sayGoodbye": function () {
console.log("Goodbye :-(");
}
};
printShortcuts(shortcuts, "CTRL");
document.addEventListener("keydown", function (ev) {
if (ev.key === "Control") {
step = shortcuts; // go back to the root
ev.preventDefault();
}
});
document.addEventListener("keyup", function (ev) {
if (ev.ctrlKey && step[ev.key]) {
step = step[ev.key]; // a node was reached
if (commands[step]) {
commands[step](); // a leaf was reached
}
}
});
function printShortcuts (node, prefix) {
if (typeof node === "string") {
document.body.innerHTML += prefix + " : " + node + "()<br>";
} else {
for (var child in node) {
printShortcuts(node[child], prefix + "-" + child);
}
}
}
<p>Click here before trying shortcuts.</p>
How about this example from Keyboard events with Ctrl, Alt, Shift keys
Take a look at how it captures combinations..
function handleKeyDown(e) {
var ctrlPressed=0;
var altPressed=0;
var shiftPressed=0;
var evt = (e==null ? event:e);
shiftPressed=evt.shiftKey;
altPressed =evt.altKey;
ctrlPressed =evt.ctrlKey;
self.status=""
+ "shiftKey="+shiftPressed
+", altKey=" +altPressed
+", ctrlKey=" +ctrlPressed
if ((shiftPressed || altPressed || ctrlPressed)
&& (evt.keyCode<16 || evt.keyCode>18))
alert ("You pressed the "+fromKeyCode(evt.keyCode)
+" key (keyCode "+evt.keyCode+")\n"
+"together with the following keys:\n"
+ (shiftPressed ? "Shift ":"")
+ (altPressed ? "Alt " :"")
+ (ctrlPressed ? "Ctrl " :"")
)
return true;
}
document.onkeydown = handleKeyDown;
I have several functions that use this given for loop below.
function startClaw(dir){
var readCount = 0;
for(var isRead in qdata){
readCount++;
if(qdata[isRead]['reading'] == true){
return;
}else if(readCount == 5){
isAnimating = $("#claw").is(':animated');
if(!isAnimating){// prevents multiple clicks during animation
if(isMoving || isDropping){ return; }
MCI = setInterval(function(){ moveClaw(dir); },10);
//console.log("startClaw:" + dir);
stopSwingClaw();
}
}
}
}
//.................................................................
function dropClaw(){
var readCount = 0;
for(var isRead in qdata){
readCount++;
if(qdata[isRead]['reading'] == true){
return;
}else if(readCount == 5){
if(isDropping){ return; } //prevent multiple clicks
stopSwingClaw();
isDropping = true;
MCI = setInterval(moveDown,20); //start heartbeat
}
}
}
Everything in the else if statement is different within the various functions. I'm wondering if there is any way to place the "pieces" of the for loop on the outside of the else if into its very own function. I feel like I've seen this or had done this a very long time ago, but it escapes me and I couldn't find any examples. Thanks everyone!
Previewing, I see this is similar to the above. Two differences (it looks like) are here the count gets passed to the function in case they needed to ever have different checks in the if statement, and, it's checking what the return value is since it looks like you return out of the loop if the condition is met. There are notes in comments in the code below.
function startClaw(dir) {
// Pass a function as a callback to the method which expects to receive the count as a param
doReadCount(qdata, function(theCount) {
if (theCount === 5) {
isAnimating = $("#claw").is(':animated');
if (!isAnimating) { // prevents multiple clicks during animation
if (isMoving || isDropping) {
return true;
}
MCI = setInterval(function() { moveClaw(dir); }, 10);
//console.log("startClaw:" + dir);
stopSwingClaw();
}
return false;
});
}
//.................................................................
function dropClaw() {
// Pass a function as a callback to the method which expects to receive the count as a param
doReadCount(qdata, function(theCount) {
if (theCount === 5) {
if (isDropping) {
return;
} //prevent multiple clicks
stopSwingClaw();
isDropping = true;
MCI = setInterval(moveDown,20); //start heartbeat
}
});
}
function doReadCount(qdata, elseFunction) {
var readCount = 0;
var elseReturn;
for (var isRead in qdata) {
readCount++;
if (qdata[isRead]['reading'] == true) {
return;
} else {
// call the function that was sent and pass it the current read count. If the return is true, then also return true here
elseReturn = elseFunction(readCount);
if (elseReturn) {
return;
}
}
}
}
You can pass a function into another function to achieve this. I've done it for dropClaw, and it should be clear from my example how to do also extract startClaw.
function operateClaw(func){
var readCount = 0;
for(var isRead in qdata){
readCount++;
if(qdata[isRead]['reading'] == true){
return;
}else if(readCount == 5){
func();
}
}
}
function drop () {
if(isDropping){ return; } //prevent multiple clicks
stopSwingClaw();
isDropping = true;
MCI = setInterval(moveDown,20); //start heartbeat
}
function dropClaw () {
operateClaw(drop);
}
I am trying to use the history api to make some rudimentary filtering a bit more usable for people using my site.
I have it working quite well for the most part but I am stuck on some edge cases: hitting the start of the history chain (and avoiding infinte back) and loosing the forward button.
The full source with working examples can be found here: http://jsfiddle.net/YDFCS/
The JS code:
$(document).ready(function () {
"use strict";
var $noResults, $searchBox, $entries, searchTimeout, firstRun, loc, hist, win;
$noResults = $('#noresults');
$searchBox = $('#searchinput');
$entries = $('#workshopBlurbEntries');
searchTimeout = null;
firstRun = true;
loc = location;
hist = history;
win = window;
function reset() {
if (hist.state !== undefined) { // Avoid infinite loops
hist.pushState({"tag": undefined}, "theMetaCity - Workshop", "/workshop/");
}
$noResults.hide();
$entries.fadeOut(150, function () {
$('header ul li', this).removeClass('searchMatchTag');
$('header h1 a span', this).removeClass('searchMatchTitle'); // The span remains but it is destroyed when filtering using the text() function
$(".workshopentry", this).show();
});
$entries.fadeIn(150);
}
function filter(searchTerm) {
if (searchTerm === undefined) { // Only history api should push undefined to this, explicitly taken care of otherwise
reset();
} else {
var rePattern = searchTerm.replace(/[.?*+^$\[\]\\(){}|]/g, "\\$&"), searchPattern = new RegExp('(' + rePattern + ')', 'ig'); // The brackets add a capture group
$entries.fadeOut(150, function () {
$noResults.hide();
$('header', this).each(function () {
$(this).parent().hide();
// Clear results of previous search
$('li', this).removeClass('searchMatchTag');
// Check the title
$('h1', this).each(function () {
var textToCheck = $('a', this).text();
if (textToCheck.match(searchPattern)) {
textToCheck = textToCheck.replace(searchPattern, '<span class="searchMatchTitle">$1</span>'); //capture group ($1) used so that the replacement matches the case and you don't get weird capitolisations
$('a', this).html(textToCheck);
$(this).closest('.workshopentry').show();
} else {
$('a', this).html(textToCheck);
}
});
// Check the tags
$('li', this).each(function () {
if ($(this).text().match(searchPattern)) {
$(this).addClass('searchMatchTag');
$(this).closest('.workshopentry').show();
}
});
});
if ($('.workshopentry[style*="block"]').length === 0) {
$noResults.show();
}
$entries.fadeIn(150);
});
}
}
$('header ul li a', $entries).on('click', function () {
hist.pushState({"tag": $(this).text()}, "theMetaCity - Workshop - " + $(this).text(), "/workshop/tag/" + $(this).text());
$searchBox.val('');
filter($(this).text());
return false; // Using the history API so no page reloads/changes
});
$searchBox.on('keyup', function () {
clearTimeout(searchTimeout);
if ($(this).val().length) {
searchTimeout = setTimeout(function () {
var searchVal = $searchBox.val();
hist.pushState({"tag": searchVal}, "theMetaCity - Workshop - " + searchVal, "/workshop/tag/" + searchVal);
filter(searchVal);
}, 500);
}
if ($(this).val().length === 0) {
searchTimeout = setTimeout(function () {
reset();
}, 500);
}
});
$('#reset').on('click', function () {
$searchBox.val('');
reset();
});
win.addEventListener("popstate", function (event) {
console.info(hist.state);
if (event.state === null) { // Start of history chain on this page, direct entry to page handled by firstRun)
reset();
} else {
$searchBox.val(event.state.tag);
filter(event.state.tag);
}
});
$noResults.hide();
if (firstRun) { // 0 1 2 3 4 (if / present)
var locArray = loc.pathname.split('/'); // '/workshop/tag/searchString/
if (locArray[2] === 'tag' && locArray[3] !== undefined) { // Check for direct link to tag (i.e. if something in [3] search for it)
hist.pushState({"tag": locArray[3]}, "theMetaCity - Workshop - " + locArray[3], "/workshop/tag/" + locArray[3]);
filter(locArray[3]);
} else if (locArray[2] === '') { // Root page and really shouldn't do anything
hist.pushState({"tag": undefined}, "theMetaCity - Workshop", "/workshop/");
} // locArray[2] === somepagenum is an actual page and what should be allowed to happen by itself
firstRun = false;
// Save state on first page load
}
});
I feel that there is something I am not quite getting with the history api. Any help would be appreciated.
You need to use onpopstate event handler for the back and forward capabilities:
https://developer.mozilla.org/en-US/docs/Web/API/window.onpopstate
Check out this question I answered a while back, I believe they had the same issues you are facing:
history.pushstate fails browser back and forward button
I have the following code (these codes are in separated js files ) :
Code 1 (home.js):
alert('1');
BeginGetDashboardsMethod();
alert('5');
Code 2(script.js) :
function BeginGetDashboardsMethod(){
var stop = 'false';
alert('2');
try {
Service.GetDashboardsMobile("" + curr_cod_user, SuccessGetDashboardMethod, ErrorGetDashboardMethod);
}
catch (e) {
}
function SuccessGetDashboardMethod(result) {
alert('3');
json = result;
json = JSON.parse(json);
ListDashboards(json);
}
function ErrorGetDashboardMethod(err) {
alert(JSON.stringify(err));
}
function ListDashboards(json) {
alert('4');
for (var i = 0; i < json.Dashboards.length; i++) {
if (json.Dashboards.length === 1)
Items = "[{key:\'" + json.Dashboards[i].OBV_ST_TITULO + "\', title:\'" + json.Dashboards[i].OBV_ST_TITULO + "\'}]";
else {
if (i == 0) {
Items += "[{key:\'" + json.Dashboards[i].OBV_ST_TITULO + "\', title:\'" + json.Dashboards[i].OBV_ST_TITULO + "\'} ";
}
else if (i + 1 == json.Dashboards.length) {
Items += ",{key:\'" + json.Dashboards[i].OBV_ST_TITULO + "\', title:\'" + json.Dashboards[i].OBV_ST_TITULO + "\'}] ";
}
else {
Items += ",{key:\'" + json.Dashboards[i].OBV_ST_TITULO + "\', title:\'" + json.Dashboards[i].OBV_ST_TITULO + "\'} ";
}
}
}
obj = eval(Items);
} }
My code works asynchronous. After Service.GetDashboardsMobile call the code "skip" Success callback and execute alert(5); while executing callback. Is there a way to make that functions synchronous?
To be more exactly, I want that sequence : alert('1');-->alert('2');-->alert('3');-->alert('4');-->alert('5')
Welcome to the new world of Promises.
script.js
alert("1"); // script 1 loaded
function $dashboard(user) {
function getDashboard(start, limit) {
var user = user;
return new Promise(function(pass, fail) {
if( Math.random()*3 > 2 ) {
fail(new Error("boo hoo"));
} else {
window.setTimeout(pass, 5000, '{"time": 5000, "content": "foo"}');
}
});
}
function parseData(json) {
return new Promise(function(pass, fail) {
try {
pass(JSON.parse(json));
} catch(e) {
fail(e);
}
});
}
function printData(data) {
alert(JSON.stringify(data));
return true;
}
return {
get: getDashboard,
parse: parseData,
print: printData
};
}
home.js
(function(d,w,$) {
alert("2"); // script 2
var dashboard = $("mcfoobar");
dashboard
.get(0, 100)
.then(function(sData) {
alert("3"); // data retrieved
return dashboard.parse(sData);
})
.then(function(oData) {
alert("4"); // data parsed
return dashboard.print(oData);
})
.then(function(result) {
alert("5"); // data printed
})
.catch(function(err) {
alert(JSON.stringify(err)); // Something went wrong
});
}(document, window, $dashboard));
Notes:
Why all the alerts? Surely console.log(..) is a much better solution. Certainly a lot less clicking.
You will have to make sure script.js is loaded. Hopefully home.js has some sort of el.onload event on the <script> element of script.js.
For backward compatibility you will need to have a definition of Promises just in case. But that should be easy to implement. You can use v8 version if it doesn't already exist in some compatibility library.
Make sure you note how values are changed in the chain by returning different values in the .then() function. To keep original values simply forward parameters.