How do I sort the existing stream in rxjs? - javascript

I'm new to RxJs. I have a response stream which is getting data from ajax. Also, I have another button to sort by. I can sort without any problem. My question is if I do the sorting and updating properly? What I'm doing is essentially just empty the child nodes and append new result.
(function($, _) {
var fetchRepoButton = $('.fetch');
var sortByButton = $('.sort-by');
var fetchRepoClickStream = Rx.Observable.fromEvent(fetchRepoButton, 'click');
var sortByClickStream = Rx.Observable.fromEvent(sortByButton, 'click');
var requestStream = fetchRepoClickStream.map(function() {
return '/api';
});
var responseStream = requestStream.flatMap(function (requestUrl) {
return Rx.Observable.fromPromise($.getJSON(requestUrl));
});
responseStream.subscribe(function (es) {
var repositories = $('.container');
repositories.empty();
var names = es.map(function (e) {
return {name: e.name};
}).forEach(function (e) {
var rep = $('<div>');
rep.html(e.name);
repositories.append(rep);
});
});
var sortByStream = sortByClickStream.combineLatest(responseStream, function (click, es) {
return _.sortBy(es, function(e) {
return e.count;
}).reverse().map(function (e) {
return {name: e.name, count: e.count};
});
});
sortByStream.subscribe(function(es) {
var repositories = $('.container');
repositories.empty();
var names = es.map(function (e) {
return {name: e.name};
}).forEach(function (e) {
var rep = $('<div>');
rep.html(e.name);
repositories.append(e);
});
});
})($, _);
I'm playing with the code right now. So there might be duplication.

There is nothing incorrect with your code, and your RxJS usage looks fine, though your DOM usage is not as optimized as it could be. Creating/deleting all those DOM elements is a relatively expensive process, so ideally you want to resume elements where possible. Your sorting code seems ripe for optimizing in this respect.
When you sort your list, you know that DOM elements already exist for each. Instead of deleting all of them, then recreating them in the right order, I would instead use detach() to remove the element from the page and return it, then later use container.append(element) to add them in the right order.
If I was implementing it, I'd do something like rep.data('listCount', e.count) when I originally create the element, so we can sort the jQuery elements directly, then sort the list with:
sortByClickStream.subscribe(function() {
var container = $('.container');
// `.children()` returns raw DOM elements, so wrap each in jQuery
_.map(container.children(), function(el) { return $(el); })
.sortBy(function(item) { return item.data('listCount'); })
.reverse()
.forEach(function(item) {
item.detach();
container.append(item);
});
});
Doing something similar with the response stream list is possible, but a lot more work, since you can't guarantee that each element in the latest list already has an element.
Overall, what you have will work fine, and should be fast enough for small/medium-sized lists. If it appears to get sluggish with your expected list size, then I'd start optimizing DOM code. Frameworks like Angular have entire libraries dedicated to 'DOM diffing' to figure out the minimal number of changes needed to modify the DOM for updated content. If you are doing a lot of this sort of content updates, I'd look into using a library/framework that has this built-in.

Related

Function with memoization, how not to evaluate each time?

I'm writing a simple jQuery function that will swap some HTML elements for others when on certain viewports. The idea is simple:
<div data-swap-for="#element" data-swap-on="phone"></div>
Will insert the element with id #element after that line when the current media query corresponds to phone (the details about how that is done are not important).
My function looks like this:
jq.fn.swapElements = function(viewport) {
var targets = jq('[data-swap-for][data-swap-on='+viewport+']');
if (targets.length) {
console.log('Found elements to swap for', viewport);
} else {
console.log('Found no elements to swap for', viewport);
}
return {
on: function() {
console.log('Should swap elements for', viewport);
},
off: function() {
console.log('Should restore elements', viewport);
}
}
};
So whenever the screen enters the phone layout, it calls:
jq().swapElements('phone').on();
Which should do all the DOM transformations, and when it exits the phone layout, it calls:
jq().swapElements('phone').off();
Which should restore them.
My problem is that these two are creating a new evaluation of the var targets... part, resulting in:
As the output in the console, and I need this function to cache or remember the variables that it uses, so that the resulting console output is:
> Found elements to swap for phone
> Should swap elements for phone
That is, only evaluating the elements and saving the variables once per each call (a different viewport value should call for a new evaluation).
I've been looking into higher order functions and memoization, but I'm confused about how to apply this in this case and specially to a jQuery function.
Please help?
Thanks
You can use some variable (object or array) to cache already targeted elements.
var cache = {}; // Should be out of function
if (viewport in cache) {
var targets = cache[viewport];
} else {
var targets = jq('[data-swap-for][data-swap-on='+viewport+']');
cache[viewport] = targets;
}
I Would go with slightly different approach:
jq.fn.swapElements = {
var cache;
getTargets: function(viewport) {
if (viewport in this.cache) {
return cache[viewport];
} else {
var targets = jq('[data-swap-for][data-swap-on='+viewport+']');
if (targets.length) {
console.log('Found elements to swap for', viewport);
} else {
console.log('Found no elements to swap for', viewport);
}
this.cache[viewport] = targets;
return this.cache[viewport];
}
}
on: function(viewport) {
console.log('Should swap elements for', viewport);
},
off: function(viewport) {
console.log('Should restore elements', viewport);
}
};
Pseudocode might not work in particular case, but You get the idea. Whenever You need targets you call swapElements.getTargets(viewport) function.
I'm pretty sure you don't need a higher-order memoize function (although you could trivially apply it when you have written one anyway).
What you need to do is to store the result of jq().swapElements('phone') in a variable, and when the screen enters/exits the phone layout you should call the methods on that variable, instead of creating new instances.

When does a listView itemTemplate get inserted into the DOM?

I have created a custom itemTemplateFunction as described here. However I want to access attributes (such as height, or clientHeight) which are defined upon rendering in the DOM. This is to allow for a child element to be offset dynamically in the layout.
I've currently looked into two methods:
renderComplete - returned as an element in the itemPromise then return object. The event fired as expected, however the attributes (such as height) did not have set values, so it looks as though the item is not in the DOM at this point.
setInterval - although this would work it's a bad solution as I am relying on a constant time offset, something which ideally I don't want to do.
function itemTemplateFunction(itemPromise) {
return itemPromise.then(function (item) {
var div = document.createElement("div");
var img = document.createElement("img");
img.src = item.data.picture;
img.alt = item.data.title;
div.appendChild(img);
var childDiv = document.createElement("div");
var title = document.createElement("h4");
title.className += "title";
title.innerText = item.data.title;
childDiv.appendChild(title);
var desc = document.createElement("h6");
desc.innerText = item.data.text;
childDiv.appendChild(desc);
div.appendChild(childDiv);
return {
element: div,
renderComplete: itemPromise.then(function (item){
return item.ready;
}).then(function (item){
var height_a = div.querySelector(".title").clientHeight;
var height_b = WinJS.Utilities.query(".title", div).clientHeight;
})
}
});
};
With the first item created the attributes still haven't been finalised, despite the promise being passed back. If however I wrap the return of item.ready in a second promise with a timeout interval of 0 this does return as expected.
return {
element: div,
renderComplete: itemPromise.then(function (item){
return WinJS.Promise.timeout(0).then(function () {
return item.ready;
});
}).then(function (item){
var height_a = div.querySelector(".title").clientHeight;
var height_b = WinJS.Utilities.query(".title", div).clientHeight;
})
}
The hook you're looking for is the item.ready property, once you're inside the completed handler for itemPromise.then().
item.ready is itself a promise that's fulfilled when the item gets rendered, at which point all the layout-dependent attributes should be set.
To use it, return item.ready from your first completed handler for itemPromise.then, and then add another .then(function (item) {} ) to the chain. Inside that complete handler is where you can check the attributes.
I go into some detail on this in Chapter 7 of my free ebook, Programming Windows Store Apps in HTML, CSS, and JavaScript, 2nd Edition, specifically in the section at the end of the chapter called "Template Functions (Part 2): Optimized Item Rendering" and the discussion of multistage rendering. The same content also appeared on the older Windows 8 developer blog, although note that the "recycling placeholder" option discussed on the blog is for WinJS 1.0 only.

javascript item splice self out of list

If I have an array of objects is there any way possible for the item to splice itself out of the array that contains it?
For example: If a bad guy dies he will splice himself out of the array of active enemies.
I probably sound crazy but that ability would simplify my code dramatically, so I hope for something cool =)
The way you would do it is as follows:
var game_state = { active_enemies: [] };
function Enemy() {
// Various enemy-specific things go here
}
Enemy.prototype.remove = function() {
// NOTE: indexOf is not supported in all browsers (IE < 8 most importantly)
// You will probably either want to use a shim like es5-shim.js
// or a utility belt like Underscore.js
var i = game_state.active_enemies.indexOf(this);
game_state.active_enemies.splice(i, 1);
}
See:
Es5-Shim
Underscore.js
Notta bene: There are a couple of issues here with this manner of handling game state. Make sure you are consistent (i.e. don't have enemies remove themselves from the list of active enemies, but heroes remove enemies from the map). It will also make things more difficult to comprehend as the code gets more complex (your Enemy not only is an in-game enemy, but also a map state manager, but it's probably not the only map state manager. When you want to make changes to how you manage map state, you want to make sure that code is structured in such a way that you only need to change it in one place [preferably]).
Assuming the bad guy knows what list he's in, why not?
BadGuy.prototype.die = function()
{
activeEnemies.splice(activeEnemies.indexOf(this), 1);
}
By the way, for older browsers to use indexOf on Arrays, you'll need to add it manually.
You kind of want to avoid circular references
I would suggest creating an object/class that represents the active enemies list. Create methods on that instance for adding/removing a given item from the list - abstracting the inner workings of the data structure from the outside world. If the active enemies list is global (e.g. there's only one of them), then you can just reference it directly to call the remove function when you die. If it's not global, then you'll have to give each item a reference to the list so it can call the function to remove itself.
You can also use an object and instead of splice, delete the enemy:
var activeEnemies = {};
function Enemy() {
this.id = Enemy.getId(); // function to return unique id
activeEnemies[this.id] = this;
// ....
}
Enemy.getId = (function() {
var count = 0;
return function() {
return 'enemyNumber' + count++;
}
}());
Enemy.prototype.exterminate = function() {
// do tidy up
delete activeEnemies[this.id];
}
Enemy.prototype.showId = function() {
console.log(this.id);
}
Enemy.prototype.showEnemies = function() {
var enemyList = [];
for (var enemy in activeEnemies) {
if (activeEnemies.hasOwnProperty(enemy)) {
enemyList.push(enemy);
}
}
return enemyList.join('\n');
}
var e0 = new Enemy();
var e1 = new Enemy();
console.log( Enemy.prototype.showEnemies() ); // enemyNumber0
// enemyNumber1
e0.exterminate();
console.log( Enemy.prototype.showEnemies() ); // enemyNumber1

How should I implement OOP patterns to interactive web applications (with the aide of jQuery)?

Sometimes, using jQuery induces you to abuse its power (at least for me because of its selector matching capability). Event handlers here and there. Utility functions here and everywhere. Code coherence can almost seem nonexistent. I want to alleviate that problem by implementing OOP patterns, but since I have C++ and python background, implementing it in javascript is weirding me out a little bit.
The code below uses OOP patterns, but I'm not entirely sure if my implementations are good practices. The reason I'm doubting my implementations is because of the 3rd comment in my last stackoverflow question. I know it's only one certain detail in my code he commented on, but it also makes me wonder about the other patterns I'm implementing in my code.
I would really appreciate if you could point out the flaws and pitfalls in my patterns and/or if you have any suggestions. Many thanks in advance.
(this code is an simplification of something I'm developing, but the idea is similar)
Live Example
$(function(){
var stream = new Stream();
});
/* Stream Class
------------------------------------------*/
function Stream(){
// Disables multiple Stream objects
if (this.singleton)
return
else
this.__proto__.singleton = true;
this.elements = jQueryMapping(this.selectors) // Converts a map of selectors to a map of jQuery objects
this.initEvents();
}
Stream.prototype.singleton = false;
Stream.prototype.selectors = {
stream : '#stream',
post_content : '#post-content',
add_post: '#add-post',
// ... more action selectors
}
Stream.prototype.initEvents = function(){
this.elements.add_post.click(this, this.addPost);
// ... more action event-listeners
}
Stream.prototype.addPost = function(e){
var self = e.data;
var post_content = self.elements.post_content.val();
if (post_content)
self.elements.stream.append(new Post(post_content));
}
/* Post Class
------------------------------------------*/
function Post(post_content){
this.$element = $('<li>')
.text(post_content)
.append('<button class="delete-post">Delete</button>');
this.elements = jQueryMapping(this.selectors, this.$element);
this.initEvents();
return this.$element;
}
Post.prototype.selectors = {
delete_post: 'button.delete-post',
// ... more action selectors
}
Post.prototype.initEvents = function(){
this.elements.delete_post.click(this.deletePost);
// ... more action event-listeners
}
Post.prototype.deletePost = function(){
$(this).parent().slideUp();
}
/* Utils
------------------------------------------*/
function jQueryMapping(map, context){
// Converts a map of selectors to a map of jQuery objects
var $map = {};
$.each(map, function(key, value){
$map[key] = (context) ? $(value, context) : $(value);
});
return $map;
}
I believe your code is over engineered. I've re factored and it simplified it as can be seen here. If you really want a heavy OOP setup I recommend you use a clientside MVC (Backbone, knockout, etc) construct to do it properly or keep it light instead.
I'll proceed with general feedback on your code.
/* Stream Class
------------------------------------------*/
function Stream(){
// Disables multiple Stream objects
if (this.singleton)
return
else
this.__proto__.singleton = true;
this.elements = jQueryMapping(this.selectors) // Converts a map of selectors to a map of jQuery objects
this.initEvents();
}
There is no reason to use a singleton like this. It's also very bad to use .__proto__
I would recommend pattern like this instead.
var Stream = (function() {
var Stream = function() { ... };
// prototype stuff
var stream = new Stream();
return function() {
return stream;
};
})());
Storing a hash of data like that on the prototype is unneccesary.
Stream.prototype.selectors = {
stream : '#stream',
post_content : '#post-content',
add_post: '#add-post',
// ... more action selectors
}
You can include this as a defaults hash instead.
(function() {
var defaults = {
stream : '#stream',
post_content : '#post-content',
add_post: '#add-post',
// ... more action selectors
}
function Stream() {
...
this.elements = jQueryMapping(defaults);
}
}());
Your utility function could be optimised slightly.
$map[key] = (context) ? $(value, context) : $(value);
This could be rewritten as
$map[key] = $(value, context)
Since if context is undefined you just pass in an undefined paramater which is the same as passing in no parameter.
The title of this reads "for beginners", but I've found this section on design patterns, and this section on design patterns using jQuery useful.

Creating methods on the fly

Hi I'm trying to author a jQuery plugin and I need to have methods accessible to elements after they are initialized as that kind of object, e.g.:
$('.list').list({some options}); //This initializes .list as a list
//now I want it to have certain methods like:
$('.list').find('List item'); //does some logic that I need
I tried with
$.fn.list = function (options) {
return this.each(function() {
// some code here
this.find = function(test) {
//function logic
}
}
}
and several other different attempts, I just can't figure out how to do it.
EDIT:
I'll try to explain this better.
I'm trying to turn a table into a list, basically like a list on a computer with column headers and sortable items and everything inbetween. You initiate the table with a command like
$(this).list({
data: [{id: 1, name:'My First List Item', date:'2010/06/26'}, {id:2, name:'Second', date:'2010/05/20'}]
});
.list will make the <tbody> sortable and do a few other initial tasks, then add the following methods to the element:
.findItem(condition) will allow you to find a certain item by a condition (like findItem('name == "Second"')
.list(condition) will list all items that match a given condition
.sort(key) will sort all items by a given key
etc.
What's the best way to go about doing this?
If you want these methods to be available on any jQuery object, you will have to add each one of them to jQuery's prototype. The reason is every time you call $(".list") a fresh new object is created, and any methods you attached to a previous such object will get lost.
Assign each method to jQuery's prototype as:
jQuery.fn.extend({
list: function() { .. },
findItem: function() { .. },
sort: function() { .. }
});
The list method here is special as it can be invoked on two occasions. First, when initializing the list, and second when finding particular items by a condition. You would have to differentiate between these two cases somehow - either by argument type, or some other parameter.
You can also use the data API to throw an exception if these methods are called for an object that has not been initialized with the list plugin. When ('xyz').list({ .. }) is first called, store some state variable in the data cache for that object. When any of the other methods - "list", "findItem", or "sort" are later invoked, check if the object contains that state variable in its data cache.
A better approach would be to namespace your plugin so that list() will return the extended object. The three extended methods can be called on its return value. The interface would be like:
$('selector').list({ ... });
$('selector').list().findOne(..);
$('selector').list().findAll(..);
$('selector').list().sort();
Or save a reference to the returned object the first time, and call methods on it directly.
var myList = $('selector').list({ ... });
myList.findOne(..);
myList.findAll(..);
myList.sort();
I found this solution here:
http://www.virgentech.com/blog/2009/10/building-object-oriented-jquery-plugin.html
This seems to do exactly what I need.
(function($) {
var TaskList = function(element, options)
{
var $elem = $(element);
var options = $.extend({
tasks: [],
folders: []
}, options || {});
this.changed = false;
this.selected = {};
$elem.sortable({
revert: true,
opacity: 0.5
});
this.findTask = function(test, look) {
var results = [];
for (var i = 0,l = options.tasks.length; i < l; i++)
{
var t = options['tasks'][i];
if (eval(test))
{
results.push(options.tasks[i]);
}
}
return results;
}
var debug = function(msg) {
if (window.console) {
console.log(msg);
}
}
}
$.fn.taskList = function(options)
{
return this.each(function() {
var element = $(this);
if (element.data('taskList')) { return; }
var taskList = new TaskList(this, options);
element.data('taskList', taskList);
});
}
})(jQuery);
Then I have
$('.task-list-table').taskList({
tasks: eval('(<?php echo mysql_real_escape_string(json_encode($tasks)); ?>)'),
folders: eval('(<?php echo mysql_real_escape_string(json_encode($folders)); ?>)')
});
var taskList = $('.task-list-table').data('taskList');
and I can use taskList.findTask(condition);
And since the constructor has $elem I can also edit the jQuery instance for methods like list(condition) etc. This works perfectly.
this.each isn't needed. This should do:
$.fn.list = function (options) {
this.find = function(test) {
//function logic
};
return this;
};
Note that you'd be overwriting jQuery's native find method, and doing so isn't recommended.
Also, for what it's worth, I don't think this is a good idea. jQuery instances are assumed to only have methods inherited from jQuery's prototype object, and as such I feel what you want to do would not be consistent with the generally accepted jQuery-plugin behaviour -- i.e. return the this object (the jQuery instance) unchanged.

Categories

Resources