I want to make a simple web page using bacon.js. It should have a button which toggles a boolean state by mouse click.
After setting up the streams, the app should be initialized by sending an object to an init stream. Here i just send the desired initial boolean state of the toggle (false).
The code looks like this (i faked the button clicks and initialization):
// Helper function
var neg = function (a) { return !a; };
// Fake of initialization stream. End after one element
var init = Bacon.sequentially(500, [false]);
// Fake of button click event stream.
var click = Bacon.repeatedly(1000, [{}]);
var toggle = init.concat(click).scan(null,
function(a, b) { return a === null ? b : neg(a); }).skip(1).toEventStream();
toggle.log();
// Output: false, true, false, true, ....
The above code (jsFiddle) works as expected, but i do not like that the toggle stream creation line is so complex.
If it would be possible to omit the seed value (take it from the stream) and if scan returns an EventStream instead of a Property, i could write:
var toggle = init.concat(click).scan(neg);
This could be read much nicer. Or if it would be possible to give the seed as stream i could just write:
var toggle = click.scan(init, neg);
Do you have any suggestions to make the code more clear?
Is there an alternative solution to using scan?
Should i make me an own scan method?
If you read what I wrote previously, ignore. it. :) This situation is exactly what flatMap is for: streams that create streams. Use flatMap.
// Helper function
var neg = function (a) { return !a; };
// Fake of initialization stream. End after one element
// Note the use of `later` instead of `sequentially`, by the way
var init = Bacon.later(500, false);
// Fake of button click event stream.
var click = Bacon.repeatedly(1000, [null]);
var toggle = init
.flatMap(function(initVal) { return click.scan(initVal, neg) });
toggle.log();
Related
I have put together a fun API for game creation. In the code I create a prototype for Mover and then extend it with several specific prototypes (Gold, Monster, and Hero). Each one is based on a img tag with a given ID. I use type-specific information in the constructor and a single template method for each type. Most of the functional code in Mover depends on those type-specific details. I have included one example for simplicity.
I use method calls in a separate script to create and destroy instances of the Mover child types. When I create and destroy one instance at a time everything works as intended. The image updates, the sound plays and it is removed after the correct delay. If I create two or more, however, only the last one works as expected. So if I make gold, moster, hero. Only the hero will remove correctly. The other two will play the audio, but don't appear to update.
I ran into the same problem when I tried to attach a function to the onclick event for more than one instance. Only the last one worked and the others did nothing. Obviously I'm missing something about the way java handles method assignments. Any explanation you can offer would help.
Thanks,
BSD
function Mover()
{
}
Mover.prototype.InitTag = function()
{
this.HTMLtag.src=this.imageURL;
this.HTMLtag.style.position="absolute";
this.HTMLtag.style.width=characterSize;
this.HTMLtag.style.height=characterSize;
this.Position(Math.floor(Math.random()*(MaxW-characterSize)+(characterSize/2)),Math.floor(Math.random()*(MaxH-characterSize)+(characterSize/2)));
}
Mover.prototype.Destroy = function()
{
var disp = this.HTMLtag.display;
this.HTMLtag.src=this.destroyURL
this.HTMLtag.display = disp;
this.destroyAudio.play();
this.RemoveTag();
}
function Monster(id)
{
this.MonsterID = id;
this.HTMLtag = document.getElementById("monster"+id);
this.imageURL = "monster1.jpg";
this.destroyURL = "monster2.jpg";
this.destroyAudio = monsterAudio;
}
Monster.prototype = new Mover();
Monster.prototype.RemoveTag = function()
{
var mID = this.MonsterID;
setTimeout(function() {field.DeleteMonster(mID)}, 1000);
}
function Hero()
{
this.HTMLtag = document.getElementById("hero");
this.imageURL = "hero1.jpg";
this.destroyURL = "hero2.jpg";
this.destroyAudio = heroAudio;
}
Hero.prototype = new Mover();
Hero.prototype.RemoveTag = function()
{
setTimeout(function() {field.DeleteHero()}, 5000);
}
function Gold(id)
{
this.GoldID = id;
this.HTMLtag = document.getElementById("gold"+id);
this.imageURL = "gold1.jpg";
this.destroyURL = "gold2.jpg";
this.destroyAudio = goldAudio;
}
Gold.prototype = new Mover();
Gold.prototype.RemoveTag = function()
{
var mID = this.GoldID;
setTimeout(function() {field.DeleteGold(mID)}, 1000);
}
---------UPDATE UPDATE UPDATE-----------
I have at least partially fixed the problem. I have gotten it to work, but I still don't know why it didn't function as intended. I noticed that while my browser's (Chrome) developer tools could visually identify the most-recently-added Mover when it was being destroyed, it could not do so with the any other movers.
Tag of most recently added Mover can be identified in Chrome developer tools.
This suggested that Mover.HTMLtag was not actually the same as document.getElementById('mover1'). I was able to confirm this by looking at the variables in the GoldField.DeleteMover. At the line indicated mover.src has not changed, but movers[id].HTMLtag.src has been correctly updated. In the most-recently-added case they were both the same.
GoldField.prototype.DeleteMover = function(id)
{
var isHero = false;
if(this.Hero!=null && id==this.Hero.myID)
{
this.Hero = null;
isHero = true;
}
else if(this.Tower!=null && id==this.Tower.myID)
{
this.Tower = null;
}
var mover = document.getElementById("mover"+id);
if(!isHero)
{
this.tag.removeChild(mover);//<<< HERE HERE HERE HERE
delete this.movers[id];
}
}
So, I changed one line in Mover.Destroy. By finding the tag by ID and setting the src. I was able to reliable behavior. It would appear that Mover.HTMLtag is not reliable the same after the second Mover is added. Any explanation?
Mover.prototype.Destroy = function()
{
document.getElementById(this.HTMLtag.id).src=this.destroyURL;
this.HTMLtag.src=this.destroyURL;//old method
this.destroyAudio.play();
this.RemoveTag();
}
On suspicion that this might extend to other updates to this.HTMLtag I set up some basic movement of the Hero. It works great, but if you add one additional Mover of any kind it no longer moves. That narrows down the question considerably. Why would constructing a second Mover cause the prototype members to change?
So I debug your code and I found the cause of your problem. The problem was when you create a new instance of monster you storing a reference to it on the monster var. And when you delete it you don't delete / update the reference to it. So your delete function myField.DeleteMover(id) try to delete a monster already deleted. How to solve this.
// create an array to keep ref to our instances
var monsters= [];
// var monster = null;
function addMonster()
{
// monster = goldField.AddMonster();⏎
// push every monster in array
monsters.push(goldField.AddMonster());
}
function killMonster()
{
// if array.length is true
if (monsters.length) {
// call the destroy function on the last ref
monsters[monsters.length - 1].Destroy();
// remove the last ref from array using pop
monsters.pop();
}
//monster.Destroy();
}
This is working however I think all of this should be done in the objects itself. And you should not care about it here.
Another advice try to use more array methods. Avoid using delete on array index because it mess with index and count instead use splice(index, 1) same for add item in array use push instead of arbitrary index.
Anyway funny game! Good luck to finish it.
Edit, after your answer I go back an test.
To make it work I do this.
// First go inGoldField.prototype.DeleteMover and replace the ugly delete index by
this.movers.splice(id, 1);
// Then in the Mover.prototype.Destroy
// This part is a a little blurred for me.
// the current HTMLtag looks good but when I console.log like this
console.log('before', this.HTMLtag);
this.HTMLtag = document.querySelector("#mover" + this.myID);
console.log('after', this.HTMLtag);
// They are not equal look like the first is outdated
You should convert all your delete and add to splice and push methods.
This is just a quick debug I don't know why the selector is outdated.
So I check the code again and I make it work without refreshing the selector. The problem is caused by the creation of dom element with innerHTML.
First reset
this.HTMLtag.src=this.destroyURL
Then instead of
//Mover.prototype.Destroy
this.tag.innerHTML+="<img id='mover"+this.moverCount+"'>";
I create a img dom el.
var img = document.createElement("img");
img.setAttribute('id', 'mover' + this.moverCount);
this.tag.appendChild(img);
All Monsters are now deleted with the image.
I don't check for the hero but first you should update your innerHTML and reply if there is still a problem. I don't think there is any problem with some prototype.
How do I focus an input with Cycle? Do I need to reach inside the DOM and call .focus() either with or without jQuery, or is there some other way with Cycle/RxJS?
Yes, you do need to reach inside the DOM and call .focus() either with or without jQuery. However this is a side-effect and it is Cycle.js convention to move these kinds of side effects to a so-called driver.
The two questions the driver needs to know are:
which element do you want to focus?
when do you want to focus the element?
The answer to both questions can be provided by a single stream of DOM elements.
Create the driver
First make your driver. Let's call it SetFocus. We'll make it a so-called read-only driver. It will read from the app's sinks but it will not provide a source to the app. Because it is reading, the driver's function will need to accept a formal parameter that will be a stream, call it elem$:
function makeSetFocusDriver() {
function SetFocusDriver(elem$) {
elem$.subscribe(elem => {
elem.focus();
});
}
return SetFocusDriver;
}
This driver takes whatever DOM element arrives in the stream and calls .focus() on it.
Use the Driver
Add it to the list of drivers provided to the Cycle.run function:
Cycle.run(main, {
DOM: makeDOMDriver('#app'),
SetFocus: makeSetFocusDriver() // add a driver
});
Then in your main function:
function main({DOM}) {
// setup some code to produce the elem$ stream
// that will be read by the driver ...
// [1]: say _when_ we want to focus, perhaps we need to focus when
// the user clicked somewhere, or maybe when some model value
// has changed
// [2]: say _what_ we want to focus
// provide the textbox dom element as actual value to the stream
// the result is:
// |----o-----o-----o--->
// where each o indicates we want to focus the textfield
// with the class 'field'
const textbox$ = DOM.select('.field').observable.flatMap(x => x); // [2]
const focusNeeded = [
clickingSomewhere$, // [1]
someKindofStateChange$ // [1]
];
const focus$ = Observable.merge(...focusNeeded)
.withLatestFrom(textbox$, (_, textbox) => textbox); // [2]
// ...
// [*]: Add driver to sinks, the driver reads from sinks.
// Cycle.js will call your driver function with the parameter
// `elem$` being supplied with the argument of `focus$`
return {
DOM: vtree$,
SetFocus: focus$, // [*]
};
}
You can then configure focusNeeded to say when you want .field to be focused.
You can tailor for your own situation, but this should illustrate how to solve your problem. Let's assume you have a text input and a button. When the button is clicked, you want the focus to remain on the text input.
First write the intent() function:
function intent(DOMSource) {
const textStream$ = DOMSource.select('#input-msg').events('keyup').map(e => e.target);
const buttonClick$ = DOMSource.select('#send-btn').events('click').map(e => e.target);
return buttonClick$.withLatestFrom(textStream$, (buttonClick, textStream) => {
return textStream;
});
}
Then the main which has a sink to handle the lost focus side effect
function main(sources) {
const textStream$ = intent(sources.DOM);
const sink = {
DOM: view(sources.DOM),
EffectLostFocus: textStream$,
}
return sink;
}
Then the driver to handle this side effect would look something like
Cycle.run(main, {
DOM: makeDOMDriver('#app'),
EffectLostFocus: function(textStream$) {
textStream$.subscribe((textStream) => {
console.log(textStream.value);
textStream.focus();
textStream.value = '';
})
}
});
The entire example is in this codepen.
Here's one example, written by Mr. Staltz himself: https://github.com/cyclejs/cycle-examples/blob/master/autocomplete-search/src/main.js#L298
In OpenLayer 3 I create a Draw interaction using the 'draw-feature' sample code they have on their website.
The only difference is that I supply my own condition function to the Draw constructor.
I would like to know if there is a way to determine within the condition function if the interaction/drawing has started?
Basically my goal is to change the behavior slightly so drawing a box is initiated with a CTRL-click rather than a click. But ending the drawing can be done with a simple click. So my approach would be something like this (in TypeScript)
var condition = (e: ol.MapBrowserEvent): boolean => {
return (myDraw.isStarted() ? true : e.originalEvent['ctrlKey']);
}
As far as I can see there's nothing like an isStarted() method in OL Draw class. If I had access to internal members I would resolve it by checking the length of myDraw.sketchCoords_ (haven't checked this but if 0 the drawing is not started yet). But I don't want to rely on private members, furthermore I'm using the minified version of OL where members names are transformed.
Try something like this:
var start_drawing = false;
function drawCondition(evt){
var ctrl = ol.events.condition.platformModifierKeyOnly(evt);
// this should be ol.events.condition.click
// but for some reason always returns false
var click = evt.type == 'pointerdown';
// to finish draw with click
if(start_drawing) return click;
// start drawing only with Ctrl + click
return ctrl && click;
}
// draw is a reference to ol.interaction.Draw
draw.on('drawstart', function(evt){
start_drawing = true;
});
draw.on('drawend', function(evt){
start_drawing = false;
});
Im curious about what might be a larger question than I think.
I am using the following code to listen for 'keyup' on a group of text input fields. If the user stops typing for a given amount of time, I send the data to a controller using AJAX.
I decided to try my hand at OOP in javascript to accomplish this. This is because I want a new instance of the timer method for each input field. (To be absolutely clear, Im very new to OOP in javascript so this might be dreadful. Let me know.)
Here is the main class with its methods:
function FieldListener(entity){
t = this;
t.typingTimer; // Timer identifier
t.doneTypingInterval = 1000; // Time in ms. e.g.; 5000 = 5secs
t.entity = entity;
entity.bind("keyup", function(){t.setTimer();});
}
FieldListener.prototype.setTimer = function(){
t = this;
// User is still typing, so clear the timer.
clearTimeout(t.typingTimer);
// Get the field name, e.g.; 'username'
t.entityType = t.entity.attr("name");
// If the value is empty, set it to a single space.
if(!(t.val = t.entity.val())){
t.val = ' ';
}
t.noticeSpan = t.entity.siblings("span");
// Display 'waiting' notice to user.
t.noticeSpan.html('...')
t.typingTimer = setTimeout(function(){t.doneTyping();},t.doneTypingInterval);
}
FieldListener.prototype.doneTyping = function(){
// Encode for passing to ajax route.
t = this;
valueType = encodeURIComponent(t.entityType);
value = encodeURIComponent(t.val);
$.ajax({
url: '/check/'+valueType+'/'+value,
type: 'GET',
processData: false
})
.done(function(validationMessage){
t.noticeSpan.html(validationMessage);
})
.fail(function(){
t.noticeSpan.html("Something went wrong. Please try again.");
});
}
So from here I'd like to be able to create an object of the FieldListener class for every input field.
I know I can do it easily if I have an id for each like so:
var fieldListener = new FieldListener($("#someFieldID"));
But I'd like to iterate over every field with a given class name. Something close to this perhaps?:
i = 0;
$(".info-field").each(function(){
i = new FieldListener($(this));
});
But that doesn't work (and doesn't look very nice).
Any thoughts? (Im also curious about critiques/improvements to the class/methods code as well.)
edit: As per #ChrisHerring's question: The issue is that it seems to create the object but only for the last element in the each() method. So the span associated with the last input field with the class '.info-field' displays the validationMessage returned from AJAX regardless of which field I am typing in.
UPDATE:
It seems like something is wrong with the creation of new objects. For example, if, rather than iterating through the each() method, I simply follow one class initiation with another, like so:
var fieldListener1 = new FieldListener($("#someFieldID"));
var fieldListener2 = new FieldListener($("#someOtherFieldID"));
that fieldListener2 overwrites variables being saved when initiating fieldListener1. This means that when I type into the input field with id "#someFieldID", it behaves as if I am typing into the input field with id "#someOtherFieldID". Thoughts?
UPDATE #2 (solved for now):
It seems that I have solved the issue for now. I needed to add 'var' before 't = this;' in the FieldListener class. Any comments/critiques are still welcome of course. ;)
The t variable is global. The function for the "keyup" event is evaluated dynamically which means it picks up the last value of t.
Change
t = this;
to
var t = this;
I think you want an array of FieldListener objects.
var myListeners = [];
i = 0;
$(".info-field").each(function(){
myListeners[i] = new FieldListener($(this));
i++
});
This'll give you a list of FieldListeners, where myListeners[0] is the listener for the first .info-field on the page, myListeners[1] is the listener for the second, etc.
Edit: It would appear you have solved the problem. This answer may still come in handy later on, though, so I won't delete it. =)
I think you should be using jquery's .on() to handle the binding.
$(body).on({
keyup: function () { HandleKeyUpEvent($(this)); },
keydown: function () { HandleKeyDownEvent($(this)); }
}, ".info-field");
I realize this is a departure from your original coding idea (using prototypes) but it will still be OOP, if that's what you intented to do.
I need to pause a JavaScript function execution in the middle and then resume it after a button click. Please help me.
This isn't possible.
Break the function up in to two parts, run one, and have the other assigned to the click event handler of the button.
You could use a pop up box.
alert("Pausing to get Coffee");
Like David said, it is not possible to stop execution of a function in Javascript (well, not at the moment anyway). One solution would be this :
** EDITED ** after you added some precision to what you wanted to do
// pass the reference of the button id to the function
function showConfirm(message, callback) {
// 1. check if the lightbox is not already created, if not create it
// 2. keep a reference to your key elements, for example, your buttons
var btnOk = document.getElementById('btnOk'); // ...for example
var btnCancel = document.getElementById('btnCancel'); // ...for example
// 3. have a 'cleanup' function so you can dismiss your lightbox, unregister
// any events/callbacks, etc.
var cleanup = function() {
// 6. hide lightbox
// 7. remove events/callbacks, etc.
btnOk.click = null; // for example
btnCancel.click = null; // for example
// etc.
};
// 4. update your lightbox with the message given in argument
// 5. register some events to your buttons...
btnOk.click = function() {
callback(true); // ok was pressed
cleanup();
};
btnCancel.click = function() {
callback(false); // cancel was pressed
cleanup();
}
}
All you have to remember is that, in Javascript, everything should be asynchronous. If your function should return a value, it should be a function that does not require long to execute. As soon as you read "user input" with Javascript, you need callbacks. You might want to take a look at how other lightbox implementations are done, especially in frameworks like JQuery, etc.