I have a form with a dynamic number of inputs, controlled by AngularJS.
<body ng-app="mainApp" ng-controller="CreatePollController" ng-init="init(3)">
<form id="createPollForm">
<input class="create-input" ng-repeat="n in questions" id="q_{{$index}}" name="q_{{$index}}" type="text" ng-keypress="createInputKeypress($event);"/>
Add Question
</form>
</body>
This is being controlled by the following angular code:
app.controller('CreatePollController', function($scope) {
$scope.questions = [];
$scope.init = function(numOfInputs){
for(var i = 0; i < numOfInputs; i++){
$scope.questions.push({
"questionText":""
});
}
};
$scope.addQuestion = function(){
$scope.questions.push({
"questionText":""
});
};
$scope.createInputKeypress = function(e){
if(e.keyCode === 13){
e.preventDefault();
var idx = Number(e.target.id.replace("q_", ""));
if(idx === this.questions.length - 1){
this.addQuestion();
}
// Wait for angular update ????
var nextId = "#q_" + (++idx);
$(nextId).focus();
}
};
});
Currently, when the user hits the Enter key while focused on a text input, the createInputKeypress function is called and the browser focuses the next input in the form. However, if you are currently focused on the last element in the form, it adds a new question to the questions array, which will cause another input to be generated in the DOM.
However, when this new element is created, the focus() call isn't working. I suspect this is because angular doesn't add the new element right away, so trying to use jQuery to locate and focus the new element isn't working.
Is there a way to wait for the DOM to be updated, and THEN focus the new element?
As you might already know, javascript is turn based, that means that browsers will execute JS code in turns (cycles). Currently the way to prepare a callback in the next javascript cycle is by setting a callback with the code we want to run on that next cycle in a timeout, we can do that by calling setTimeout with an interval of 0 miliseconds, that will force the given callback to be called in the next javascript turn, after the browser finishes (gets free from) the current one.
Trying to keep it simple, one browser cycle executes these actions in the given order:
Scripting (where JS turn happen)
Rendering (HTML and DOM renderization)
Painting (Painting the rendered DOM in the window)
Other (internal browser's stuff)
Take a look at this example:
console.log(1);
console.log(2);
setTimeout(function () {
console.log(3);
console.log(4);
}, 0);
console.log(5);
console.log(6);
/** prints in the console
* 1 - in the current JS turn
* 2 - in the current JS turn
* 5 - in the current JS turn
* 6 - in the current JS turn
* 3 - in the next JS turn
* 4 - in the next JS turn
**/
3 and 4 are printed after 5 and 6, even knowing that there is no interval
(0) in the setTimeout, because setTimeout basically prepares the given callback to be called only after the current javascript turn finishes. If in the next turn, the difference between the current time and the time the callback was binded with the setTimeout instruction is lower than the time interval, passed in the setTimeout, the callback will not be called and it will wait for the next turn, the process repeats until the time interval is lower than that difference, only then the callback is called!
Since AngularJS is a framework wrapping all our code, angular updates generally occur after our code execution, in the end of each javascript turn, that means that angular changes to the HTML will only occur after the current javascript turn finishes.
AngularJS also has a timeout service built in, it's called $timeout, the difference between the native setTimeout and angular's $timeout service is that the last is a service function, that happens to call the native setTimeout with an angular's internal callback, this callback in its turn, is responsible to execute the callback we passed in $timeout and then ensure that any changes we made in the $scope will be reflected elsewhere! However, since in our case we don't actually want to update the $scope, we don't need to use this service, a simple setTimeout happens to be more efficient!
Knowing all this information, we can use a setTimeout to solve our problem. like this:
$scope.createInputKeypress = function(e){
if(e.keyCode === 13){
e.preventDefault();
var idx = Number(e.target.id.replace("q_", ""));
if(idx === this.questions.length - 1){
this.addQuestion();
}
// Wait for the next javascript turn
setTimeout(function () {
var nextId = "#q_" + (++idx);
$(nextId).focus();
}, 0);
}
};
To make it more semantic, we can wrap the setTimeout logic
in a function with a more contextualized name, like runAfterRender:
function runAfterRender (callback) {
setTimeout(function () {
if (angular.isFunction(callback)) {
callback();
}
}, 0);
}
Now we can use this function to prepare code execution in the next javascript turn:
app.controller('CreatePollController', function($scope) {
// functions
function runAfterRender (callback) {
setTimeout(function () {
if (angular.isFunction(callback)) {
callback();
}
}, 0);
}
// $scope
$scope.questions = [];
$scope.init = function(numOfInputs){
for(var i = 0; i < numOfInputs; i++){
$scope.questions.push({
"questionText":""
});
}
};
$scope.addQuestion = function(){
$scope.questions.push({
"questionText":""
});
};
$scope.createInputKeypress = function(e){
if(e.keyCode === 13){
e.preventDefault();
var idx = Number(e.target.id.replace("q_", ""));
if(idx === this.questions.length - 1){
this.addQuestion();
}
runAfterRender(function () {
var nextId = "#q_" + (++idx);
$(nextId).focus();
});
}
};
});
Related
What I am doing
I am in the middle of building a turtle graphics app using Blockly. The user can build a code from blocks, then the Blockly engine generates JS code, which draws to a canvas.
What my problem is
The Blockly engine generates the JS code, but returns it as a string, which I have to eval() to draw to the canvas.
I can change the code of the blocks to generate different output, but it's important to keep it as simple as possible, because the users can read the actual code behind the block input. So I would like not to mess it up.
What I would like to do
I have full control over the atomic operations (go, turn, etc.), so I would like to insert a small piece of code to the beginning of the functions, which delays the execution of the rest of the bodies of the functions. Something like:
function go(dir, dist) {
// wait here a little
// do the drawing
}
I think it should be something synchronous, which keeps the delay in the flow of the execution. I've tried to use setTimeout (async, fail), a promise (fail), timestamp checks in a loop (fail).
Is it even possible in JS?
You must not make the code wait synchronously. The only thing you will get is a frozen browser window.
What you need is to use the js interpreter instead of eval. This way you can pause the execution, play animations, highlight currently executing blocks, etc... The tutorial has many examples that will help you get started. Here is a working code, based on the JS interpreter example:
var workspace = Blockly.inject("editor-div", {
toolbox: document.getElementById('toolbox')
});
Blockly.JavaScript.STATEMENT_PREFIX = 'highlightBlock(%1);\n';
Blockly.JavaScript.addReservedWords('highlightBlock');
Blockly.JavaScript['text_print'] = function(block) {
var argument0 = Blockly.JavaScript.valueToCode(
block, 'TEXT',
Blockly.JavaScript.ORDER_FUNCTION_CALL
) || '\'\'';
return "print(" + argument0 + ');\n';
};
function run() {
var code = Blockly.JavaScript.workspaceToCode(workspace);
var running = false;
workspace.traceOn(true);
workspace.highlightBlock(null);
var lastBlockToHighlight = null;
var myInterpreter = new Interpreter(code, (interpreter, scope) => {
interpreter.setProperty(
scope, 'highlightBlock',
interpreter.createNativeFunction(id => {
id = id ? id.toString() : '';
running = false;
workspace.highlightBlock(lastBlockToHighlight);
lastBlockToHighlight = id;
})
);
interpreter.setProperty(
scope, 'print',
interpreter.createNativeFunction(val => {
val = val ? val.toString() : '';
console.log(val);
})
);
});
var intervalId = setInterval(() => {
running = true;
while (running) {
if (!myInterpreter.step()) {
workspace.highlightBlock(lastBlockToHighlight);
clearInterval(intervalId);
return;
}
}
}, 500);
}
#editor-div {
width: 500px;
height: 150px;
}
<script src="https://rawgit.com/google/blockly/master/blockly_compressed.js"></script>
<script src="https://rawgit.com/google/blockly/master/blocks_compressed.js"></script>
<script src="https://rawgit.com/google/blockly/master/javascript_compressed.js"></script>
<script src="https://rawgit.com/google/blockly/master/msg/js/en.js"></script>
<script src="https://rawgit.com/NeilFraser/JS-Interpreter/master/acorn_interpreter.js"></script>
<xml id="toolbox" style="display: none">
<block type="text"></block>
<block type="text_print"></block>
<block type="controls_repeat_ext"></block>
<block type="math_number"></block>
</xml>
<div>
<button id="run-code" onclick="run()">run</button>
</div>
<div id="editor-div"></div>
EDIT
Added variable running to control the interpreter. Now it steps over until the running variable is set to false, so the running = false statement inside the highlightBlock function essentially works as a breakpoint.
EDIT
Introduced lastBlockToHighlight variable to delay the highlighting, so the latest run statement is highlighted, not the next one. Unfortunately the JavaScript code generator doesn't have a STATEMENT_SUFFIX config similar to STATEMENT_PREFIX.
Recently I published a library that allows you to interact asynchronously with blockly, I designed this library for games like that.
In fact in the documentation you can find a game demo that is a remake of the maze game.
The library is called blockly-gamepad 🎮, I hope it's what you were looking for.
blockly-gamepad 🎮
live demo
Here is a gif of the demo.
How it works
This is a different and simplified approach compared to the normal use of blockly.
At first you have to define the blocks (see how to define them in the documentation). You don't have to define any code generator, all that concerns the generation of code is carried out by the library.
Each block generate a request.
// the request
{ method: 'TURN', args: ['RIGHT'] }
When a block is executed the corresponding request is passed to your game.
class Game{
manageRequests(request){
// requests are passed here
if(request.method == 'TURN')
// animate your sprite
turn(request.args)
}
}
You can use promises to manage asynchronous animations, as in your case.
class Game{
async manageRequests(request){
if(request.method == 'TURN')
await turn(request.args)
}
}
The link between the blocks and your game is managed by the gamepad.
let gamepad = new Blockly.Gamepad(),
game = new Game()
// requests will be passed here
gamepad.setGame(game, game.manageRequest)
The gamepad provides some methods to manage the blocks execution and consequently the requests generation.
// load the code from the blocks in the workspace
gamepad.load()
// reset the code loaded previously
gamepad.reset()
// the blocks are executed one after the other
gamepad.play()
// play in reverse
gamepad.play(true)
// the blocks execution is paused
gamepad.pause()
// toggle play
gamepad.togglePlay()
// load the next request
gamepad.forward()
// load the prior request
gamepad.backward()
// use a block as a breakpoint and play until it is reached
gamepad.debug(id)
You can read the full documentation here.
EDIT: I updated the name of the library, now it is called blockly-gamepad.
If i understood you!
You can build a new class to handle the executing of go(dir, dist) functions, and override the go function to create new go in the executor.
function GoExecutor(){
var executeArray = []; // Store go methods that waiting for execute
var isRunning = false; // Handle looper function
// start runner function
var run = function(){
if(isRunning)
return;
isRunning = true;
runner();
}
// looper for executeArray
var runner = function(){
if(executeArray.length == 0){
isRunning = false;
return;
}
// pop the first inserted params
var currentExec = executeArray.shift(0);
// wait delay miliseconds
setTimeout(function(){
// execute the original go function
originalGoFunction(currentExec.dir, currentExec.dist);
// after finish drawing loop on the next execute method
runner();
}, currentExec.delay);
}
this.push = function(dir, dist){
executeArray.push([dir,dist]);
run();
}
}
// GoExecutor instance
var goExec = new GoExecutor();
// Override go function
var originalGoFunction = go;
var go = function (dir, dist, delay){
goExec.push({"dir":dir, "dist":dist, "delay":delay});
}
Edit 1:
Now you have to call callWithDelay with your function and params,
the executor will handle this call by applying the params to the specified function.
function GoExecutor(){
var executeArray = []; // Store go methods that waiting for execute
var isRunning = false; // Handle looper function
// start runner function
var run = function(){
if(isRunning)
return;
isRunning = true;
runner();
}
// looper for executeArray
var runner = function(){
if(executeArray.length == 0){
isRunning = false;
return;
}
// pop the first inserted params
var currentExec = executeArray.shift(0);
// wait delay miliseconds
setTimeout(function(){
// execute the original go function
currentExec.funcNam.apply(currentExec.funcNam, currentExec.arrayParams);
// after finish drawing loop on the next execute method
runner();
}, currentExec.delay);
}
this.push = function(dir, dist){
executeArray.push([dir,dist]);
run();
}
}
// GoExecutor instance
var goExec = new GoExecutor();
var callWithDelay = function (func, arrayParams, delay){
goExec.push({"func": func, "arrayParams":arrayParams, "delay":delay});
}
After clearTimeout(tt);, tt = setTimeout(function () { is stopped to work. Why? I have the Recursion and when i execute getByClients in manually i need to remove current queue of setTimeout(if exists) and execute new setTimeout, instead after clearTimeout(tt); i am getting nothing.
var tt = undefined;
// in default select = null, only by btn click='getByClients(1)'
function getByClients(select) {
if (select == 1 || select == 2) {
clearTimeout(tt); // after, setTimeout is not executed
}
dashboardService.getByClients($scope.clientsMode).then(function(response) {
tt = setTimeout(function() {
getByClients(null);
}, refreshTime);
});
};
HELP, someone, please (
I don't see the need of recursive code. Perhaps your sample is too simplified ?
If I understand correctly, you need to automatically call a service every X seconds, but you want to be able to force immediate call.
In this case here is how I would do that :
var tt = null;
// - Stop current timer (is started)
function stopTimeout()
{
if(tt)
{
clearTimeout(tt);
tt = null;
}
}
// - Start a new Timer (and stops the previous one if any)
function startTimeout()
{
stopTimeout(); // - to clean if already started (security)
tt = setTimeout(function() {
getByClients();
}, refreshTime);
}
function getByClients()
{
// - Stop automatic Timer
stopTimeout();
// - Executes Query
dashboardService.getByClients($scope.clientsMode).then(function(response) {
// - "Read response "
// - Restart timer
startTimeout();
}
);
}
// - Start automatic calls
getByClients();
There is no need in this case for a parameter : each call stops current timeOut.
I think a better way could be to use a setInterval, but I wanted to be near your original code.
My example code restarts automatic updates after a manual one. I don't know if this is your need ?
EDIT : I see you use AngularJS, perhaps $timeout would better suit your needs ?
I have a game in which two people play against each other. After the clock runs down I call the function below, which is supposed to increase the current question by 1. However, it increases it by 1 TWICE.
increaseQuestion: function() {
GameCollection.update({current:true}, { $inc: { currentQuestion: 1}});
},
Here is specifically the code where it is called:
Template.gamePage.clock = function () {
var game = GameCollection.findOne({current: true});
var currentQuestion = game.currentQuestion;
var question = game.gameQuestions[currentQuestion];
var clockQuestion = Clocks.findOne({gameId: game._id, questionId: question._id});
var clock = clockQuestion.clock;
if(clock === 0) {
Meteor.call('increaseQuestion');
} else {
Meteor.call('windDown', clockQuestion, clock);
}
// format into M:SS
var min = Math.floor(clock / 60);
var sec = clock % 60;
return min + ':' + (sec < 10 ? ('0' + sec) : sec);
};
Here is the method within the code above (which could be causing problems)
Meteor.methods({
windDown: function(clockQuestion, clock) {
var interval = Meteor.setInterval(function () {
clock -= 1;
Clocks.update(clockQuestion._id, {$set: {clock: clock}});
// end of game
if (clock === 0) {
// stop the clock
Meteor.clearInterval(interval);
// declare zero or more winners
}
}, 1000);
}
});
Why is the function being called twice? I tried moving the method from both client and server folder to a server only folder and it is still called twice.
I'm surprised it's only being called twice, to be honest.
The clock helper has a dependency on the ClockQuestion document which has the same gameId as the current game. However, when that helper is run, the windDown method is called, which is going to update that ClockQuestion document, which is going to cause the helper to be reactively rerun, which is going to call the windDown method again, etc...
To be honest, this sort of control logic really should not be included in a helper function for exactly this reason - it's going to be impossible to control. windDown needs to be called by whatever it is that's making the gamePage template render in the first place, or else a Deps.autorun block, the reactivity of which you can have much more control over, as opposed to a reactive UI element which is (by design) being managed by Meteor rather than your own app explicitly.
My suspicion is this: Template.gamePage.clock uses GameCollection, so when that collection is updated, then the function will be re-run reactively.
Since increaseQuestion doesn't depend on any arguments from the client, why not just move it to the // end of game if block in windDown?
I have a search box that hides all lines in a list that don't contain the entered text.
This worked great until the list became 10,000 lines long. One keystroke is fine but if the user types a several letter word, the function is iterated for each keypress.
What I want to do is to abandon any previous execution of the function if a new key is pressed.
The function is very simple, as follows:
$("#search").keyup(function(e) {
goSearch();
});
function goSearch()
{
var searchString = $("#search").val().toLowerCase();
$(".lplist").each(function(index, element) {
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(searchString,0) != -1)
$(row).show();
else
$(row).hide();
});
}
Thanks
You can't directly. Javascript is not multi-threaded so your function will run and block any key-presses until it is done.
The way this is made tolerable from a user-experience point of view is to not trigger a function immediately on a key event, but to wait a short period of time and then fire the event.
While the user is typing, the timeout function will continually be set and reset and so the gosearch function won't be called, and so the user won't have their typing interrupted.
When the user pauses typing, the timeout will countdown to zero and call the search function, which will run and block typing until it completes. But that's okay (so long as it completes within a second or so) as the user is probably not currently trying to type.
You can also do what you actually asked by breaking up your gosearch function into chunks, where each call to the function: * Reads a counter of the number of lines processed so far, and then processes another 500 lines and increments the counter. * Calls another gosearch using setTimeout with a value of zero for the time. This yields events to other 'threads', and allows for fast changing of search terms.
var goSearchTimeout = null;
var linesSearched = 0;
function keySearch(e){
if(goSearchTimeout != null){
clearTimeout(goSearchTimeout);
linesSearched = 0;
}
goSearchTimeout = setTimeout(goSearch, 500);
}
$("#search").keyup(keySearch);
function highLight(index, element) {
if(index >= linesSearched){
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(searchString,0) != -1){
$(row).show();
else{
$(row).hide();
}
if(index > linesSearched + 500){
linesSearched = index;
goSearchTimeout = setTimeout(goSearch);
return;
}
}
function goSearch(){
goSearchTimeout = null;
var searchString = $("#search").val().toLowerCase();
$(".lplist").each(highLight);
}
If you're going to use timeout callbacks like this, I'd strongly recommend wrapping your code up into jQuery widgets, so that you can use variables on the object to store the variables goSearchTimeout etc rather than having them float around as global variables.
Introduce a counter var keypressCount that is being increased by your keypress event handler. at the start of goSearch() write its value into a buffer. Then at each run of your $(".lplist").each() you ask if the current keypressCount is the same as the buffered one; if not, you return. I would suggest you use a for() though since it is easier to break; than $.each().
Update:
You will have to make sure that there is time for new keypress events to be fired/received, so wrap the anonymous function of your $.each() inside a timeout.
Reference: http://www.garrickcheung.com/javascript/what-i-learned-about-multi-threading-in-javascript/
You can use a global variable to save search string and stop execution when search string changes.
IMPORTANT: You must set a timeout in each iteration so that function execution is paused and global variables are updated, as JavaScript is single-threaded.
Your code would look like this:
var searchString;
$("#search").keyup(function(e) {
// Update search string
searchString = $("#search").val().toLowerCase();
// Get items to be searched
var items = $(".lplist");
// Start searching!
goSearch(items, searchString, 0);
});
function goSearch(items, filter, iterator)
{
// Exit if search changed
if (searchString != filter) {
return;
}
// Exit if there are no items left
if (iterator >= items.length) {
return;
}
// Main logic goes here
var element = items[iterator];
var row = "#row-" + element.id.substr(5);
if ($(element).text().toLowerCase().indexOf(filter, 0) != -1)
$(row).show();
else
$(row).hide();
// Schedule next iteration in 5 ms (tune time for better performance)
setTimeout(function() {
goSearch(items, filter, iterator + 1);
}, 5);
}
I am trying to create the following functionality in my javascript:
$("mySelector").each(function(){
// Do something (e.g. change div class attribute)
// call to MyFunction(), the iteration will stop here as long as it will take for myFunction to complete
});
function myFunction()
{
// Do something for e.g. 5 seconds
}
My question is how can I stop every iteration for the duration of the myFunction()?
No, that isnt possible. You'll have to code it differently, possibly with a setTimeout based on the current index of .each.
$("mySelector").each(function(i){
// Do something (e.g. change div class attribute)
// call to MyFunction(), the iteration will stop here as long as it will take for myFunction to complete
setTimeout(myFunction,i*5000);
});
function myFunction()
{
// Do something for e.g. 5 seconds
}
Edit: You can also do it with queuing: http://jsfiddle.net/9Bm9p/6/
$(document).ready(function () {
var divs = $(".test");
var queue = $("<div />");
divs.each(function(){
var _this = this;
queue.queue(function(next) {
myFunction.call(_this,next);
});
});
});
function myFunction(next) {
// do stuff
$(this).doSomething();
// simulate asynchronous event
var self = this;
setTimeout(function(){
console.log(self.id);
// go to next item in the queue
next();
},2000);
}
​
Here's a jsFiddle that I think will do what you need:
http://jsfiddle.net/9Bm9p/2/
You would just need to replace the selector with what you use.
The "loop" that is occurring will wait for myFunction to finish before moving on to the next element. I added the setTimeout inside of myFunction to simulate it taking a period of time. If you are using asynchronous things, such as an AJAX request, you would need to put the call to myFunction inside of the complete method...or in the callback of an animation.
But as someone already commented, if everything in myFunction is synchronous, you should be able to use it as you are. If you are looking for this process to be asynchronous, or if things in myFunction are asynchronous, you cannot use a for loop or .each().
(function () {
"use strict";
var step = 0;
var content = $("mySelector");
var max = content.length;
var speed = 5000; // ms
var handle = setInterval(function () {
step++;
if (step >= max) {
clearInterval(handle);
} else {
var item = content[step];
// do something
}
}, speed);
}());
setInterval will do it once-every-n-miliseconds, and clearInterval will stop it when you're done. This won't lock up the browser (provided your "do something" also doesn't). FRAGILE: it assumes that the results of $("mySelector") are valid for the duration of the task. If that isn't the case then inside do something then validate item again.