I have an observable of blog posts, converted from an array:
let posts = Rx.Observable.fromArray(blogPosts)
And two observables nextClicks and prevClicks of button click events for next/previous navigate respectively:
What I want is:
Initially the posts observable emits the very first post and
then wait for click events
posts emits next post when nextClicks emits (repeat current post
if no next available)
posts emits prev post when prevClicks emits (repeat current post
if no prev available)
And I'm not sure if this is possible:
Two observables postNextable and postPrevable, derived from
posts, indicating whether there is a next/prev post available in
posts respectively
Thanks in advance!
There are several ways to do 1, 2, 3 and 4. For number 4, I wonder how you plan to use those observables.
One way which only uses pure functions and no subjects is :
var blogPosts$ = Rx.Observable.just(blogPosts);
var index$ = Rx.Observable.merge(nextClicks, prevClicks)
.withLatestFrom(blogPosts$, function(ev, blogPosts){return {posts: blogPosts, ev : ev};})
.scan(function(index, posts_and_ev){
return isNextClick(posts_and_ev.ev) && isNextable(posts_and_ev.posts, index) ? index + 1
:isPrevClick(posts_and_ev.ev) && isPrevable(posts_and_ev.posts, index) ? index - 1
:index
}, 0);
var currentPost$ = index$.withLatestFrom(blogPosts$, function (index, blogPost){return blogPost[index]; }).startWith(blogPosts[0]);
var postNextable = index$.withLatestFrom(blogPosts$, function (index, blogPost){return isNextable(blogPost, index);});
var postPrevable = index$.withLatestFrom(blogPosts$, function (index, blogPost){return isNextable(blogPost, index);})
The output you seek is named here currentPost$.
Code test included thereafter. isNextable, isNextClick, isPrevable, isPrevClick have obvious semantics, and are easy to write (for example discriminating on key codes or button ids or else for your next and prev events). sample code
Related
SAPUI5 - I have an array of objects and one of the properties in those is 'Category'.
For example say I have 2 different types of Category, 'Front Shop' and 'Production Area', what I need to do is to be able to get the first value of each and the last value of each, and then set the enabled property of a button as enabled/disabled.
I'm currently using undercore js (_.each) to loop through to perform some other logic, so can include additional logic here.
Not sure if Underscore has a built in function for this?
Or could someone point me in the right direction on how to do this?
I've got my first pass at what was wanted where I get the very first result and the last result, but now need to set this for each unique category.
Example code below:
// Set view data
oViewData.Questions = oQuestions.results;
oViewData.Questions.TotalNumberOfQuestions = oQuestions.results.length;
// Loop Questions, to get Category Desc and Competency Desc values from relevant Sets
_.each(oViewData.Questions, function (result, index) {
// Read and set Category Desc
this.getView().getModel("Survey").read("/CategorySet", {
filters: [new Filter("CategoryId", FilterOperator.EQ, result.CategoryId)],
success: function (oData) {
oViewData.Questions[index]._CategoryDesc = oData.results[0].CategoryDesc;
this.setViewData(oViewData);
}.bind(this),
error: function (oError) {}.bind(this)
});
// Read and set Competency Desc
this.getView().getModel("Survey").read("/CompetencySet", {
filters: [new Filter("CompetencyId", FilterOperator.EQ, result.CompetencyId)],
success: function (oData) {
oViewData.Questions[index]._CompetencyDesc = oData.results[0].CompetencyDesc;
this.setViewData(oViewData);
}.bind(this),
error: function (oError) {}.bind(this)
});
// Set all move up / down buttons to enabled
oViewData.Questions[index]._MoveUpBtn = true;
oViewData.Questions[index]._MoveDownBtn = true;
// if category id is the first one in the list
}.bind(this));
// Overwrite first move up button and last move down btn to disabled
oViewData.Questions[0]._MoveUpBtn = false;
oViewData.Questions.slice(-1)[0]._MoveDownBtn = false;
// Set view data
this.setViewData(oViewData);
First, you can iterate through arrays with native JavaScript.
_.each(array, function(item) {}) is the same as array.forEach(function(item) {}).
Second, you can use the built-in filter function for your actual question:
const aFrontShopItems = oViewData.Questions.filter(function(oItem) {
return oItem.Category === "Front Shop";
}
If oViewData.Questions is an array then the function passed to filter is applied to every element. If the condition (e.g. oItem.Category === "Front Shop") is true then the element is added to the new array aFrontShopItems. Obviously you need to call filter a second time to get the Production Area items. You can then apply your logic to the first and last items of your new arrays.
var self = this;
var firebaseRef = new Firebase(baseUrl + '/sparks');
firebaseRef.limitToLast(5).on('child_added', function(childSnapshot, prevChildKey) {
self.addChild(childSnapshot); // adds post to a <div>
});
My code currently loads the last 5 posts and will load any new posts. However, I'd also like to be able to load older posts as well. I have a button that when clicked will call a function (that I'm unsure of how to implement) that loads older posts. How do I retrieve these older posts?
(The arrow just signifies that I want to retrieve posts starting from the bottom and working my way up to the top)
You need to think a bit backwards to do this. When you get the results for your query for the first page, remember the first item in the results:
firebaseRef.endAt().limitToLast(5).on('child_added', function(childSnapshot, prevChildKey) {
self.addChild(childSnapshot); // adds post to a <div>
});
While you cannot access child items by index with Firebase, you can store the key of an item and use that to start a next query.
var firstKnownKey;
firebaseRef.orderByKey().limitToLast(5).on('child_added', function(childSnapshot, prevChildKey) {
if (!firstKnownKey) {
firstKnownKey = childSnapshot.key;
}
self.addChild(childSnapshot); // adds post to a <div>
});
Now you have a variable firstKnownKey that has the first key you've ever seen. To get the previous batch of children, you pass that value in to endAt() when you fire your next query:
firebaseRef.orderByKey().endAt(firstKnownKey).limitToLast(5).on('child_added', function(childSnapshot, prevChildKey) {
if (!firstKnownKey) {
firstKnownKey = childSnapshot.key;
}
self.addChild(childSnapshot); // adds post to a <div>
});
Answers to similar questions of the past few days:
Can I get the nth item of a firebase "query"?
Firebase results range using startAt and endAt
Since endAt() is inclusive, the last item gets repeated every time I do the infinite scroll, so I did a little modification to frank van puffen 's answer.
I initiate a list childrenVal to store all the values, another list childrenKey to store all the keys and a var firstKnownKey, as frank van puffen sugests.
var childrenVal=[];
var childrenKey=[];
var firstKnownKey = "";
For the first time you make the query, you get the last 5 elements:
getFirst(){
firebaseRef.orderByKey().limitToLast(5).once('value')
.then((snap)=>{
snap.forEach(childSnap => {
childrenVal.unshift(childSnap.val());
childrenKey.unshift(childSnap.key);
});
firstKnownKey = childrenKey[childrenKey.length-1];
});
}
In your next query, you won't want your firstKnownKey to get repeated, so I did the following function:
exclude(key){
return key.substring(0, key.length - 1) + String.fromCharCode(key.charCodeAt(key.length - 1) - 1)
}
and for the query itself, the following function:
getNext() {
firebaseRef.orderByKey().endAt(exclude(firstKnownKey)).limitToLast(5).once('value')
.then((snap) => {
snap.forEach(childSnap => {
childrenVal.unshift(childSnap.val());
childrenKey.unshift(childSnap.key);
});
firstKnownKey = childrenKey[childrenKey.length - 1];
});
}
I'm playing with Reactive Programming, using RxJS, and stumbled upon something I'm not sure how to solve.
Let's say we implement a vending machine. You insert a coin, select an item, and the machine dispenses an item and returns change. We'll assume that price is always 1 cent, so inserting a quarter (25 cents) should return 24 cents back, and so on.
The "tricky" part is that I'd like to be able to handle cases like user inserting 2 coins and then selecting an item. Or selecting an item without inserting a coin.
It seems natural to implement inserted coins and selected items as streams. We can then introduce some sort of dependency between these 2 actions — merging or zipping or combining latest.
However, I quickly ran into an issue where I'd like coins to be accumulated up until an item is dispensed but not further. AFAIU, this means I can't use sum or scan since there's no way to "reset" previous accumulation at some point.
Here's an example diagram:
coins: ---25---5-----10------------|->
acc: ---25---30----40------------|->
items: ------------foo-----bar-----|->
combined: ---------30,foo--40,bar--|->
change:------------29------39------|->
And a corresponding code:
this.getCoinsStream()
.scan(function(sum, current) { return sum + current })
.combineLatest(this.getSelectedItemsStream())
.subscribe(function(cents, item) {
dispenseItem(item);
dispenseChange(cents - 1);
});
25 and 5 cents were inserted and then "foo" item was selected. Accumulating coins and then combining latest would lead to "foo" being combined with "30" (which is correct) and then "bar" with "40" (which is incorrect; should be "bar" and "10").
I looked through all of the methods for grouping and filtering and don't see anything that I can use.
An alternative solution I could use is to accumulate coins separately. But this introduces state outside of a stream and I'd really like to avoid that:
var centsDeposited = 0;
this.getCoinsStream().subscribe(function(cents) {
return centsDeposited += cents;
});
this.getSelectedItemsStream().subscribe(function(item) {
dispenseItem(item);
dispenseChange(centsDeposited - 1);
centsDeposited = 0;
});
Moreover, this doesn't allow for making streams dependent on each other, such as to wait for coin to be inserted until selected action can return an item.
Am I missing already existing method? What's the best way to achieve something like this — accumulating values up until the moment when they need to be merged with another stream, but also waiting for at least 1 value in 1st stream before merging it with the one from the 2nd?
You could use your scan/combineLatest approach and then finish the stream with a first followed up with a repeat so that it "starts over" the stream but your Observers would not see it.
var coinStream = Rx.Observable.merge(
Rx.Observable.fromEvent($('#add5'), 'click').map(5),
Rx.Observable.fromEvent($('#add10'), 'click').map(10),
Rx.Observable.fromEvent($('#add25'), 'click').map(25)
);
var selectedStream = Rx.Observable.merge(
Rx.Observable.fromEvent($('#coke'), 'click').map('Coke'),
Rx.Observable.fromEvent($('#sprite'), 'click').map('sprite')
);
var $selection = $('#selection');
var $change = $('#change');
function dispense(selection) {
$selection.text('Dispensed: ' + selection);
console.log("Dispensing Drink: " + selection);
}
function dispenseChange(change) {
$change.text('Dispensed change: ' + change);
console.log("Dispensing Change: " + change);
}
var dispenser = coinStream.scan(function(acc, delta) { return acc + delta; }, 0)
.combineLatest(selectedStream,
function(coins, selection) {
return {coins : coins, selection : selection};
})
//Combine latest won't emit until both Observables have a value
//so you can safely get the first which will be the point that
//both Observables have emitted.
.first()
//First will complete the stream above so use repeat
//to resubscribe to the stream transparently
//You could also do this conditionally with while or doWhile
.repeat()
//If you only will subscribe once, then you won't need this but
//here I am showing how to do it with two subscribers
.publish();
//Dole out the change
dispenser.pluck('coins')
.map(function(c) { return c - 1;})
.subscribe(dispenseChange);
//Get the selection for dispensation
dispenser.pluck('selection').subscribe(dispense);
//Wire it up
dispenser.connect();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.6/rx.all.js"></script>
<button id="coke">Coke</button>
<button id="sprite">Sprite</button>
<button id="add5">5</button>
<button id="add10">10</button>
<button id="add25">25</button>
<div id="change"></div>
<div id="selection"></div>
Generally speaking you have the following set of equations:
inserted_coins :: independent source
items :: independent source
accumulated_coins :: sum(inserted_coins)
accumulated_paid :: sum(price(items))
change :: accumulated_coins - accumulated_paid
coins_in_machine :: when items : 0, when inserted_coins : sum(inserted_coins) starting after last emission of item
The hard part is coins_in_machine. You need to switch the source observable based on some emissions from two sources.
function emits ( who ) {
return function ( x ) { console.log([who, ": "].join(" ") + x);};
}
function sum ( a, b ) {return a + b;}
var inserted_coins = Rx.Observable.fromEvent(document.getElementById("insert"), 'click').map(function ( x ) {return 15;});
var items = Rx.Observable.fromEvent(document.getElementById("item"), 'click').map(function ( x ) {return "snickers";});
console.log("running");
var accumulated_coins = inserted_coins.scan(sum);
var coins_in_machine =
Rx.Observable.merge(
items.tap(emits("items")).map(function ( x ) {return {value : x, flag : 1};}),
inserted_coins.tap(emits("coins inserted ")).map(function ( x ) {return {value : x, flag : 0};}))
.distinctUntilChanged(function(x){return x.flag;})
.flatMapLatest(function ( x ) {
switch (x.flag) {
case 1 :
return Rx.Observable.just(0);
case 0 :
return inserted_coins.scan(sum, x.value).startWith(x.value);
}
}
).startWith(0);
coins_in_machine.subscribe(emits("coins in machine"));
jsbin : http://jsbin.com/mejoneteyo/edit?html,js,console,output
[UPDATE]
Explanations:
We merge the insert_coins stream with the items stream while attaching a flag to them to know which one of the two emitted when we receive a value in the merged stream
When it is the items stream emitting, we want to put 0 in coins_in_machine. When it is the the insert_coins we want to sum the incoming values, as that sum will represent the new amount of coins in the machine. That means the definition of insert_coins switches from one stream to another under the logic defined before. That logic is what is implemented in the switchMapLatest.
I use switchMapLatest and not not switchMap as otherwise the coins_in_machine stream would continue to receive emission from former switched streams, i.e. duplicated emission as in the end there are ever only two streams to and from which we switch. If I may, I would say this is a close and switch that we need.
switchMapLatest has to return a stream, so we jump through hoops to make a stream that emits 0 and never ends (and does not block the computer, as using the repeat operator would in that case)
we jump through some extra hoops to make the inserted_coins emit the values we want. My first implementation was inserted_coins.scan(sum,0) and that never worked. The key and I found that quite tricky, is that when we get to that point in the flow, inserted_coins already emitted one of the values that is a part of the sum. That value is the one passed as a parameter of flatMapLatest but it is not in the source anymore, so calling scan after the fact won-t get it, so it is necessary to get that value from the flatMapLatest and reconstitute the correct behaviour.
You can also use Window to group together multiple coin events, and use item selection as the window boundary.
Next we can use zip to acquire the item value.
Notice we instantly try to give out items. So the user does have to insert coins before he decide on an item.
Notice i decided to publish both selectedStream and dispenser for safety reasons, we don't want to cause a race-condition where events fire while we're building up the query and zip becomes unbalanced. That would be a very rare condition, but notice that when our sources had been cold Observables, they pretty much start generating as soon as we subscribe, and we must use Publish to safeguard ourselves.
(Shamelessly stolen paulpdaniels example code).
var coinStream = Rx.Observable.merge(
Rx.Observable.fromEvent($('#add5'), 'click').map(5),
Rx.Observable.fromEvent($('#add10'), 'click').map(10),
Rx.Observable.fromEvent($('#add25'), 'click').map(25)
);
var selectedStream = Rx.Observable.merge(
Rx.Observable.fromEvent($('#coke'), 'click').map('Coke'),
Rx.Observable.fromEvent($('#sprite'), 'click').map('Sprite')
).publish();
var $selection = $('#selection');
var $change = $('#change');
function dispense(selection) {
$selection.text('Dispensed: ' + selection);
console.log("Dispensing Drink: " + selection);
}
function dispenseChange(change) {
$change.text('Dispensed change: ' + change);
console.log("Dispensing Change: " + change);
}
// Build the query.
var dispenser = Rx.Observable.zip(
coinStream
.window(selectedStream)
.flatMap(ob => ob.reduce((acc, cur) => acc + cur, 0)),
selectedStream,
(coins, selection) => ({coins : coins, selection: selection})
).filter(pay => pay.coins != 0) // Do not give out items if there are no coins.
.publish();
var dispose = new Rx.CompositeDisposable(
//Dole out the change
dispenser
.pluck('coins')
.map(function(c) { return c - 1;})
.subscribe(dispenseChange),
//Get the selection for dispensation
dispenser
.pluck('selection')
.subscribe(dispense),
//Wire it up
dispenser.connect(),
selectedStream.connect()
);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.6/rx.all.js"></script>
<button id="coke">Coke</button>
<button id="sprite">Sprite</button>
<button id="add5">5</button>
<button id="add10">10</button>
<button id="add25">25</button>
<div id="change"></div>
<div id="selection"></div>
I am playing around with Node and Express. My current problem is not "how to do things", as I have my paging and filtering/searching working in my "mini API" that I have made from scratch and that I am playing with. My question is more about "good practices" and "proper way" of doing things.
I will put some snippets of code below, which I am sure will bring some critics. The API is memory based, no database involved. I have an array of hard-coded users that I am pulling data from and pushing data in.
Below is my code (as you can see, I have also implemented basic authentication using passport):
//This array contains all my user data...
var users = [
{
"id": "1",
"firstName": "john",
"lastName": "doe"
}
];
//This is the route I have configured in order to retrieve all users.
//I am retrieving the users with the getUsers() function and then returning it.
//in the response object.
router.get('/users', passport.authenticate('basic', { session: false }),
function(req, res, next) {
var result = users.getUsers(req);
res.status(200).json({ users: result });
});
//This method will get the page and items parameters and will try to parse
//them. After that, it will call the search function that will filter the data
//Finally, I am passing the result array, page param and items param to the
//sliceUsers() function that will take care of slicing the result array depending
//on the values of page and items.
exports.getUsers = function(req) {
console.log(req.query);
var page = req.query.page;
items = req.query.items;
page = page !== 'undefined' ? parseInt(page, 10) : undefined;
items = items !== 'undefined' ? parseInt(items, 10) : undefined;
//The search method will filter the data
var searchResults = exports.search(req.query);
//Then, I call sliceUsers(), passing the filtered data, page and items parameters
return exports.sliceUsers(searchResults , page, items);
}
//This method will slice the array to return the page and # of items specified
//The "data" array that is passed as the first parameters is the array that contains
//the data that have already been filtered.
exports.sliceUsers= function(data, page, items) {
page = (page < 1 ? 1 : page) || 1;
items = (items < 1 ? 5 : items) || 5;
console.log('page', page, 'items', items);
var indexStart, indexEnd;
indexStart = (page - 1) * items;
indexEnd = indexStart + items;
return data.slice(indexStart, indexEnd);
};
//Those 2 methods take care of filtering
exports.search = function(query) {
return users.filter(search(query));
}
function search(query) {
console.log('search function');
return function(element) {
for(var i in query) {
//Please note here how I am checking the the parameter I am currently
//checking is NOT 'page' nor 'items'
if(query[i] != element[i] && i !== 'page' && i !== 'items') {
return false;
}
}
return true;
}
}
A few questions arise here:
Is the way I am dealing with filter/search and THEN, dealing with paging the right way?
In the search() function, when I am looping over req.query, I know that the way I check if the current param is different than 'page' or 'items' is not very efficient, but I don't know I could do that differently.
My goal here is to learn node and express and get better at javascript, what would you advice me to do next in order to pursue that goal? Any resources greatly appreciated, as the only stuff I found on APIs are basic operations that don't really deal with search/filtering. When I do find those, it's never in addition to paging for example. I have never found a complete example.
I have heard that underscore could help me do the filtering, but once again, did not really find any good example, any snippets somewhere?
Any critic GREATLY appreciated.
P.S: I apologize in advance for any grammatical error in this question.
There's a standard called OData for thing like filtering, searching, selecting and of course REST. There are few options for using it at the moment. For node backend part it's node-odata. For more, see here: http://www.odata.org/libraries/
I have a stream holding an array, each element of which has an id. I need to split this into a stream per id, which will complete when the source stream no longer carries the id.
E.g. input stream sequence with these three values
[{a:1}, {b:1}] [{a:2}, {b:2}, {c:1}] [{b:3}, {c:2}]
should return three streams
a -> 1 2 |
b -> 1 2 3
c -> 1 2
Where a has completed on the 3rd value, since its id is gone, and c has been created on the 2nd value, since its id has appeared.
I'm trying groupByUntil, a bit like
var input = foo.share();
var output = input.selectMany(function (s) {
return rx.Observable.fromArray(s);
}).groupByUntil(
function (s) { return s.keys()[0]; },
null,
function (g) { return input.filter(
function (s) { return !findkey(s, g.key); }
); }
)
So, group by the id, and dispose of the group when the input stream no longer has the id. This seems to work, but the two uses of input look odd to me, like there could a weird order dependency when using a single stream to control the input of the groupByUntil, and the disposal of the groups.
Is there a better way?
update
There is, indeed, a weird timing problem here. fromArray by default uses the currentThread scheduler, which will result in events from that array being interleaved with events from input. The dispose conditions on the group are then evaluated at the wrong time (before the groups from the previous input have been processed).
A possible workaround is to do fromArray(.., rx.Scheduler.immediate), which will keep the grouped events in sync with input.
yeah the only alternative I can think of is to manage the state yourself. I don't know that it is better though.
var d = Object.create(null);
var output = input
.flatMap(function (s) {
// end completed groups
Object
.keys(d)
.filter(function (k) { return !findKey(s, k); })
.forEach(function (k) {
d[k].onNext(1);
d[k].onCompleted();
delete d[k];
});
return Rx.Observable.fromArray(s);
})
.groupByUntil(
function (s) { return s.keys()[0]; },
null,
function (g) { return d[g.key] = new Rx.AsyncSubject(); });