Consider this example from http://baconjs.github.io/
var up = $('#up').asEventStream('click');
var down = $('#down').asEventStream('click');
var counter =
// map up to 1, down to -1
up.map(1).merge(down.map(-1))
// accumulate sum
.scan(0, function(x,y) { return x + y });
// assign observable value to jQuery property text
counter.assign($('#counter'), 'text');
What if I have one more button for resetting counter and an event stream from this button clicks. How do I switch counter stream based on reset clicks stream to reset counter? I know that I have to use .flatMapLatest method, but referring this example I dont know how to do it.
You don't need flatMapLatest here. You can use a powerful and a much simpler Bacon.update as in this example we have a simple state machine.
var up = $('#up').asEventStream('click');
var down = $('#down').asEventStream('click');
var reset = $('#reset').asEventStream('click');
var counter = Bacon.update(0,
[up], function (prev, unused) { return prev + 1; },
[down], function (prev, unused) { return prev - 1; },
[reset], function (prev, unused) { return 0; }
);
Assuming #reset is a button for resetting
var up = $('#up').asEventStream('click');
var down = $('#down').asEventStream('click');
var reset = $('#reset').asEventStream('click');
var counter = reset.flatMapLatest(function () {
return up.map(1).merge(down.map(-1))
.scan(0, function(x,y) { return x + y });
});
counter.assign($('#counter'), 'text');
Related
I am trying to increase the value for the globally defined variable highestPurposeValue in case the if condition is true, but it does not work. It returns every time 1. What am I doing wrong?
var highestPurposeValue = 0;
$('#addJobPurposeButton').on('click', function(){
var value = $('#jobPurposeInputForm').val();
$('#jobPurposeList li').map(function () {
var item = $(this).find("input[name^='jobPurpose']").attr('name');
var itemNumber = item.match(/\d+/); // returns an array.
if (itemNumber[0] > highestPurposeValue) {
var num = parseInt(itemNumber[0]);
var plusOne = num+1;
highestPurposeValue = plusOne;
}
});
I have this closure :
function CFetchNextData(ofs, pag, fetchFunction) {
var offset = ofs;
var limit = pag;
return function(options, cb) {
//do stuff to create params
fetchFunction(params, cb);
offset += limit;
};
}
I then create a variable this way:
var fetchInfo = CFetchNextData(0, 10, specificFetchFunction);
fetchInfo(options, myCB);
So that everytime I call fetchInfo, pagination is automatically set to the next set of data. That works great, althought
I'd like to have multiple instance of : "fetchInfo", each one having its own scope.
var A = fetchInfo; // I'd like a clone with its own scope, not a copy
var B = fetchInfo; // I'd like a clone with its own scope, not a copy
I could do:
var A = new CFetchNextData(ofs, pag, fetchFunction);
var B = new CFetchNextData(ofs, pag, fetchFunction);
But obviously I would have to setup "ofs" and "pag" each time, whereas by cloning fetchInfo, I'd have a stable pagination, set only once and for good.
Do you know how to achieve that ?
Thanks in advance
There isn't a concept of cloning a function in JavaScript. You need to call CFetchNextData (or another function) multiple times if you want to create multiple closures.
You could have CFetchNextData return a factory function instead of returning the actual function. But I'm not sure that's really an improvement.
function CFetchNextDataFactory(ofs, pag, fetchFunction) {
return function() {
var offset = ofs;
var limit = pag;
return function(options, cb) {
//do stuff to create params
fetchFunction(params, cb);
offset += limit;
};
};
}
var fetchInfoFactory = CFetchNextData(0, 10, specificFetchFunction);
var A = fetchInfoFactory();
var B = fetchInfoFactory();
This may not answer all of your question but just to pitch in , you could try assigning your parameters to a default / fallback value which will allow you to avoid setting ofs and pag each declaration . Below is a prototype of what I came up with . Its using oop :
class CFetchNextData {
constructor(ofs, pag){
this.OFS = 1; //default value
this.PAG = 10; //default value
this.ofs = ofs;
this.pag = pag;
if(ofs == null || ofs == undefined){
this.ofs = this.OFS;
}
if(pag = null || pag == undefined){
this.pag = this.PAG;
}
}
fetchInfo(){
var data = this.ofs += this.pag;
return data;
}
}
var task1 = new CFetchNextData(); // Falls back to default values..
var task2 = new CFetchNextData(32,31); // Uses values from specified in args...
document.write(task1.fetchInfo() + "\n")
document.write(task2.fetchInfo())
Hope this helps...
Using Web MIDI andTeoria.JS I'm trying to build a web based a chords controller.
I found a way to generate chords for a scale thanks to teoria-chord-progression and then get the midi codes for it. Now I would like to get the midi notes for the inversions of the same chord.
What I have done so far is subtracting 12 from the original midi note for the fifth for the first inversion then for the third and the fifth for the second inversion but I'm sure there is a better way.
EDIT: here the code I have, it plays only the chord in its not inverted form:
'use strict';
const teoria = require('teoria');
const chordProgression = require('teoria-chord-progression');
const Combokeys = require("combokeys");
const document = global.document;
const cSharpHMinor = teoria.scale('c#', 'harmonicminor');
const chords = chordProgression(cSharpHMinor, [1, 2, 3, 4, 5, 6, 7], 3).chords;
const combokeys = new Combokeys(document.documentElement);
global.navigator.requestMIDIAccess()
.then((midiAccess) => {
return Array.from(midiAccess.outputs.values());
})
.then((midiOutputs)=> {
chords.forEach((chord, index) => {
buildPadForChord(index + 1, chord, midiOutputs);
});
});
function createPad(index, chordName, listener) {
let button = document.createElement('button');
button.setAttribute('type', 'button');
button.textContent = `${chordName} (${index})`;
button.addEventListener('click', listener);
let autorepeat = false;
combokeys.bind(index.toString(), () => {
if (!autorepeat) {
autorepeat = true;
listener();
}
}, 'keydown');
combokeys.bind(index.toString(), () => {
autorepeat = false;
}, 'keyup');
document.documentElement.appendChild(button);
}
function buildPadForChord(index, chord, midiOutputs) {
let listener = () => {
midiOutputs.forEach((midiOutput) => {
chord.notes().forEach((note)=> {
midiOutput.send([0x90, note.midi(), 127]);
midiOutput.send([0x80, note.midi(), 127], global.performance.now() + 1000.0);
});
});
};
createPad(index, chord.name, listener);
}
According to this issue on git, inversions currently are not implemented in Teoria.js. At one point the developer was considering adding them, but nothing so far.
My implementation in my own code was this:
// Adds an "invert" function to the Teoria Chord class
teoria.Chord.prototype.invert = function(n){
// Grabs the current chord voicing. Returns array of intervals.
var voicing = this.voicing()
// Reverse the array to work top-down when n is negative
if(n < 0){
voicing = voicing.reverse()
}
// Loop through each inversion
var j = Math.abs(n);
for(let i = 0; i < j; i++){
// Find which interval we're modifying this loop.
let index = i % voicing.length;
if(n > 0){
// Move the lowest note up a perfect 8th
voicing[index] = voicing[index].add(teoria.interval('P8'))
}else{
// Move the highest note down a perfect 8th
voicing[index] = voicing[index].add(teoria.interval('P-8'))
}
}
// Change the array into a usable format
var newVoicing = arrayToSimple(voicing)
this.voicing(newVoicing)
// Return the object for chaining
return this;
}
The arrayToSimple function:
function arrayToSimple(array){
var newArray = []
for(let item of array){
newArray.push(item.toString())
}
return newArray
}
Now, you can call Chord.invert(n) on any chord, where n is your inversion number. If n is negative, the chord will be inverted downwards, whereas if it is positive, it will be inverted upwards.
Note that the above invert() function needs a chord with a voicing of [lowest note, ..., highest note], in that order. Consequently, calling Chord.invert(1).invert(1) will not be the same as calling Chord.invert(2). If you need this functionality, you might detect which notes are lowest using Chord.bass() and/or Chord.notes(), and reorder the elements. That is probably overkill, though.
I'm trying to pick a random film from an object containing film objects. I need to be able to call the function repeatedly getting distinct results until every film has been used.
I have this function, but it doesn't work because the outer function returns with nothing even if the inner function calls itself because the result is not unique.
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var moviesLength = Object.keys(movies).length;
function doPick() {
var pick = pickRandomProperty(movies);
var distinct = true;
for (var i = 0;i < watchedFilms.length; i += 1) {
if (watchedFilms[i]===pick.title) {
distinct = false;
if (watchedFilms.length === moviesLength) {
watchedFilms = [];
}
}
}
if (distinct === true) {
watchedFilms.push(pick.title);
return pick;
}
if (distinct === false) {
console.log(pick.title+' has already been picked');
doPick();
}
};
return doPick();
}
T.J. Crowder already gave a great answer, however I wanted to show an alternative way of solving the problem using OO.
You could create an object that wraps over an array and makes sure that a random unused item is returned everytime. The version I created is cyclic, which means that it infinitely loops over the collection, but if you want to stop the cycle, you can just track how many movies were chosen and stop once you reached the total number of movies.
function CyclicRandomIterator(list) {
this.list = list;
this.usedIndexes = {};
this.displayedCount = 0;
}
CyclicRandomIterator.prototype.next = function () {
var len = this.list.length,
usedIndexes = this.usedIndexes,
lastBatchIndex = this.lastBatchIndex,
denyLastBatchIndex = this.displayedCount !== len - 1,
index;
if (this.displayedCount === len) {
lastBatchIndex = this.lastBatchIndex = this.lastIndex;
usedIndexes = this.usedIndexes = {};
this.displayedCount = 0;
}
do index = Math.floor(Math.random() * len);
while (usedIndexes[index] || (lastBatchIndex === index && denyLastBatchIndex));
this.displayedCount++;
usedIndexes[this.lastIndex = index] = true;
return this.list[index];
};
Then you can simply do something like:
var randomMovies = new CyclicRandomIterator(Object.keys(movies));
var randomMovie = movies[randomMovies.next()];
Note that the advantage of my implementation if you are cycling through items is that the same item will never be returned twice in a row, even at the beginning of a new cycle.
Update: You've said you can modify the film objects, so that simplifies things:
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
var counter = keyCount * 2;
// Try a random pick
while (--counter) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
// We've done two full count loops and not found one, find the
// *first* one we haven't watched, or of course return null if
// they've all been watched
for (counter = 0; counter < keyCount; ++counter) {
candidate = movies[keys[counter]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
return null;
}
This has the advantage that it doesn't matter if you call it with the same movies object or not.
Note the safety valve. Basically, as the number of watched films approaches the total number of films, our odds of picking a candidate at random get smaller. So if we've failed to do that after looping for twice as many iterations as there are films, we give up and just pick the first, if any.
Original (which doesn't modify film objects)
If you can't modify the film objects, you do still need the watchedFilms array, but it's fairly simple:
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
if (watchedFilms.length >= keyCount) {
return null;
}
while (true) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (watchedFilms.indexOf(candidate) === -1) {
watchedFilms.push(candidate);
return candidate;
}
}
}
Note that like your code, this assumes getRandomFilm is called with the same movies object each time.
I am trying to animate through CSS properties by adding "start" and "end" classNames through javascript. I have defined a bunch of these classes in CSS (for instance starting with opacity: 0 and ending with opacity: 1
I am trying to loop through all of the elements, set a start className on an element, then an end className on the element to trigger the transition. The problem is if I do that how I normally would, by the time the function finishes there would be no actual className change.
Using setTimeout with even no delay is one way to get around this, as was mentioned here but that method does not work in this instance because of my looping. I am using a closure.
Here is my JS code:
var animation = (function () {
var a = {};
a.divIndex = -1;
a.imgIndex = startHolders.length;
a.animIndex = -1;
a.divs = startHolders;
a.run = function () {
if (++a.divIndex === a.divs.length) a.divIndex = 0;
if (++a.animIndex === animations.length) a.animIndex = 0;
if (++a.imgIndex === imageElements.length) a.imgIndex = 0;
imageElements[a.imgIndex].className = animations[a.animIndex]['start'];
setTimeout(function() {
imageElements[a.imgIndex].className = animations[a.animIndex]['end'];
}, 0);
startHolders[a.divIndex].appendChild(imageElements[a.imgIndex]);
};
setInterval(a.run, 1000);
return a;
})();
What I really wanted was to be able to set all those indexes to 0 and use this instead of a (just some placeholder object) but I couldn't do this with the setTimeout because of how it changes the value of this. Another problem here is that if I put the if(++a.index) at the bottom of the code, by the time setTimeout runs the value ofa.index` has changed (I believe) Does anybody know of a workaround here or just a better way?
If you would like access to this inside of the function passed to setTimeout you can use bind.
For example:
var obj = {}
obj.divIndex = "do you wanna build a snowman?";
obj.run = function () {
console.log(this.divIndex);
}
setTimeout(obj.run.bind(obj), 1000);
I'm thinking you might want to do something like this?:
var a = {};
a.divIndex = -1;
a.imgIndex = startHolders.length;
a.animIndex = -1;
a.divs = startHolders;
a.run = function () {
if (++this.divIndex === this.divs.length) this.divIndex = 0;
if (++this.animIndex === animations.length) this.animIndex = 0;
if (++this.imgIndex === imageElements.length) this.imgIndex = 0;
imageElements[this.imgIndex].className = animations[this.animIndex]['start'];
setTimeout(function() {
imageElements[this.imgIndex].className = animations[this.animIndex]['end'];
}.bind(this), 0);
startHolders[this.divIndex].appendChild(imageElements[this.imgIndex]);
};
setInterval(a.run.bind(a), 1000);