Duplicate an array in Knockout.js - javascript

I'm trying to make a little damage calculator for the game Diablo 3 (I know, I know).
Basically the idea is that it has a "before" and "after" array of values that represent items for your character. The "after" array should duplicate the "before" array when that's updated. However, changes to the "after" array should not update the "before" array.
Each array then displays a DPS (more of this is better) total, and it shows you the difference between the two. The idea is then it makes for easy comparison of two items when using the in-game auction house.
I have the first bit set up - the "before" array is working great. However I'm at a loss as to how to create the "after" array, and I'm wondering if I've made this a different magnitude of complexity. Should I be using two view models, replicating it in jQuery, or using the mapping plugin? I can't quite find anything that's exactly what I'm after, the UI requirements especially seem a bit of a sticking point
Fiddle of where I'm up to: http://jsfiddle.net/kimadactyl/GuMuY/8/

Here's a solution that should get you started. I refactored your HeroItem to take a config object and an optional linked Hero.
I am assuming for the moment the array is fixed length. I create the after array from the items array by mapping it to a new HeroItem, using jquery extend to do a deep copy.
When a link is passed in the HeroItem will subscribe to changes on it's observables and update one-way only as specified.
function HeroItem(config, link) {
var self = this, prop;
self.item = config.item;
self.int = ko.observable(config.int);
self.ias = ko.observable(config.ias);
self.critdmg = ko.observable(config.critdmg);
self.critpc = ko.observable(config.critpc);
self.min = ko.observable(config.min);
self.max = ko.observable(config.max);
if (link) {
for (prop in link) {
if (link.hasOwnProperty(prop) && ko.isObservable(link[prop])) {
console.log("subscribing " + prop);
link[prop].subscribe((function(p) {
return function (newValue) {
console.log("updating " + p+ " to " + newValue);
self[p](newValue);
}
})(prop));
}
}
}
}
self.after = ko.observableArray(ko.utils.arrayMap(self.items(), function(i) {
return new HeroItem($.extend({}, ko.toJS(i)), i);
}));
http://jsfiddle.net/madcapnmckay/2MNFn/1/
No custom bindings needed, it just uses the subscription capabilities all KO observables have. If you want to extend this to cope with dynamic length arrays simple subscribe to the items array and cleanup the after array accordingly.
Hope this helps.

Related

Javascript sorts object values

Hope you have been a good day. I want to ask you a question. Here is it:
So I have an object and I get values from it with Underscore.js (I will give you an example without using underscore.js to getting values).
After I get values, it puts numbers that are bigger than 0. I think it sorts bigger than smaller.
I want values exactly how they are at the object before I take them.
Here is an image for you
Here is the code
tmpReport.forEach(element => {
const tmpVal = [];
console.log("element: ", element);
tmpValList.push(_.values(element));
console.log('tmpValList: ', tmpValList);
});
Here is the code without using underscore.js:
tmpReport.forEach(element => {
const tmpVal = [];
console.log("element: ", element);
Object.entries(element).forEach(([key, value]) => {
tmpVal.push(value);
});
tmpValList.push(tmpVal);
console.log('tmpValList: ', tmpValList);
});
So any help will be appreciated. Thank you!
In JavaScript, the order of object entries is not guaranteed.
If you want to put specific entries at specific indices in your array you have to do that manually.
For example:
tmpVal[0] = element['Cell damage by placing'];
...
tmpVal[4] = element['time'];
tmpVal[5] = element['toplan'];
You can only achieve that if you are aware of your object keys. As far as I know there is now generic way to keep the initial order as there is no order to begin with.
The best you can do in that case is use a lexicographic order and sort your keys accordingly.

Why am I unable to sort list by difference of 2 fields?

So I have a list of items.
Each item has a like or dislike button. I wish to sort it by total score, which is = # likes less # dislikes.
I am trying to understand why:
The below handlebar + sort is not working on client side and how to make it work?
If a server side solution would be better and why? (server side takes up unnecessary disk space from what I have learnt in other posts and premature optimization - Yes, Im still in the early stages)
This is how I store it in the list in items.js within collections (ground 0 form).
var item = _.extend(itemAttributes, {
lovers: [],
likes: 0,
haters: [],
dislikes: 0
//popularity: likes - dislikes I tried inserting in collection but doesnt work too
});
So Im sorting it via a handlebar helper + extension of the main list controller
This is the handlebar segment
Template.registerHelper('popularity', function(likes, dislikes) {
var popularity = likes - dislikes;
return popularity;
This is the sorter in router.js
BestItemController = ItemListController.extend({
sort: {popularity: -1},
nextPath: function() {
return Router.routes.BestItem.path({itemsLimit: this.itemsLimit() + this.increment})
}
});
So the handlebar popularity calculations actually does work, the popularity score appears on addition of {{ popularity 123numbersxx }}
However the sorting doesnt work, probably because the sorting does not sort "on the surface" calculations, but rather on the actual item and its fields?
I tried to insert an additional schema field (see above commented line). However that causes errors which states likes are not defined.
Would anyone help guide me a little on this?
Also if you think my method of doing things is bad, appreciate any other ways? For example if sorting on the individual template helper.js files rather than on the main router.js files.
Many thanks!
You can achieve that client-side. Here is how I would proceed:
You probably display your item to be sorted using an {{#each item}} iteration. I would replace item in the #each by a custom helper and create a function, using a cursor or an array as an argument, that will sort your items using the current sorting settings.
Your helper could look like that:
Template.items.helpers({
sortedItems: function(){
return sortMyItems(items.find()) //add .fetch() if you need an array,
//or directly your array if you already have it in a variable.
}
});
And at the beginning of your file, you add the sortMyItems function where you return the sorted list of items.
sortMyItems = function(cursor) {
if(!cursor) {
return [];
}
var sortBy = Session.get("sortBy");// in your case, it would be set to "popularity"
var sortAscending = Session.get("sortAscending ");
if(typeof(sortAscending) == "undefined") sortAscending = true;
var sorted = [];
var raw = cursor.fetch();
// sort
if(sortBy) {
sorted= _.sortBy(raw, sortBy);
// descending?
if(!sortAscending) {
sorted= sorted.reverse();
}
}
return sorted;
}
here I use Session vars, but I advise you to rather use reactive variables or reactive dictionary, since this is a feature related to the current view only.

Creating a filterable list with RxJS

I'm trying to get into reactive programming. I use array-functions like map, filter and reduce all the time and love that I can do array manipulation without creating state.
As an exercise, I'm trying to create a filterable list with RxJS without introducing state variables. In the end it should work similar to this:
I would know how to accomplish this with naive JavaScript or AngularJS/ReactJS but I'm trying to do this with nothing but RxJS and without creating state variables:
var list = [
'John',
'Marie',
'Max',
'Eduard',
'Collin'
];
Rx.Observable.fromEvent(document.querySelector('#filter'), 'keyup')
.map(function(e) { return e.target.value; });
// i need to get the search value in here somehow:
Rx.Observable.from(list).filter(function() {});
Now how do I get the search value into my filter function on the observable that I created from my list?
Thanks a lot for your help!
You'll need to wrap the from(list) as it will need to restart the list observable again every time the filter is changed. Since that could happen a lot, you'll also probably want to prevent filtering when the filter is too short, or if there is another key stroke within a small time frame.
//This is a cold observable we'll go ahead and make this here
var reactiveList = Rx.Observable.from(list);
//This will actually perform our filtering
function filterList(filterValue) {
return reactiveList.filter(function(e) {
return /*do filtering with filterValue*/;
}).toArray();
}
var source = Rx.Observable.fromEvent(document.querySelector('#filter'), 'keyup')
.map(function(e) { return e.target.value;})
//The next two operators are primarily to stop us from filtering before
//the user is done typing or if the input is too small
.filter(function(value) { return value.length > 2; })
.debounce(750 /*ms*/)
//Cancel inflight operations if a new item comes in.
//Then flatten everything into one sequence
.flatMapLatest(filterList);
//Nothing will happen until you've subscribed
source.subscribe(function() {/*Do something with that list*/});
This is all adapted from one of the standard examples for RxJS here
You can create a new stream, that takes the list of people and the keyups stream, merge them and scans to filter the latter.
const keyup$ = Rx.Observable.fromEvent(_input, 'keyup')
.map(ev => ev.target.value)
.debounce(500);
const people$ = Rx.Observable.of(people)
.merge(keyup$)
.scan((list, value) => people.filter(item => item.includes(value)));
This way you will have:
-L------------------ people list
------k-----k--k---- keyups stream
-L----k-----k--k---- merged stream
Then you can scan it. As docs says:
Rx.Observable.prototype.scan(accumulator, [seed])
Applies an accumulator function over an observable sequence and returns each
intermediate result.
That means you will be able to filter the list, storing the new list on the accumulator.
Once you subscribe, the data will be the new list.
people$.subscribe(data => console.log(data) ); //this will print your filtered list on console
Hope it helps/was clear enough
You can look how I did it here:
https://github.com/erykpiast/autocompleted-select/
It's end to end solution, with grabbing user interactions and rendering filtered list to DOM.
You could take a look at WebRx's List-Projections as well.
Live-Demo
Disclosure: I am the author of the Framework.

How to work with javascript object methods in Angularfire

I have an object that represents a restaurant order:
function order () {
this.customer_name = ''
this.menu = // menu object
}
extended with some object methods for business logic, like:
order.prototype.value = function() {
var total = 0;
for (var i = 0; i < this.menu.length; i++) {
// calculate the order value
}
return total;
}
In the angular controller orders get pushed onto an array when submitted (via ng-click from a button in the view):
var ref = new Firebase('https://myfirebase.firebaseio.com');
$scope.orders = [];
angularFire(ref, $scope, 'orders');
$scope.currentOrder = orderService;
$scope.submitOrder = function() {
$scope.orders.push($scope.currentOrder);
};
Once orders are pushed into the array, properties like orders[0].customer_name work, but methods like orders[0].value() don't.
It seems reasonable that Firebase/Angularfire would only be syncing JSON, but is there an approach that would allow me to keep order-related logic included with the order object, i.e without having to write $scope.getOrderValue(orders[0])?
There isn't a great way to do exactly what you want, since according to the Firebase FAQ:
At a low-level, we support basically the same data types as JSON: Strings, Numbers, Booleans, and Objects (which in turn contain Strings, Numbers, Booleans, and more Objects).
Which means you can store data but not functions. It seems like a clean way to accomplish the same thing would be to store the latest order value as a property of your order object, and have a method as part of your orderService that updates it whenever menu items are added or removed. Alternatively, do what you suggested and have a getOrderValue somewhere, but it probably still makes sense to put that in a service.
I actually had the same issue.
I wanted to add a method to my firebase object.
After looking in the latest angularfire docs I found that $extend can do just that
I didn't test it yet, but I think this is the way to go about it.

Knockout: Dynamically controlling number of objects in an array?

So, I'm grabbing a number from a model, and trying to use that number (the length of an observable array) to dynamically control the number of objects in a "sub-model." In this case, when I add or remove "meta headings" in the parent model, the child model will automatically have the right number of corresponding "meta fields."
So this grabs the length of the array I'm basing things on:
self.metaCount = ko.computed(function() {
return parent.metaHeadings().length;
});
This is the array I want to dynamically push objects into:
self.metaColumns = ko.observableArray([]);
And this is how I'm trying to dynamically push items into the array:
self.columnUpdate = ko.computed(function() {
for (i=0; i<self.metaCount(); i++) {
self.metaColumns.push({heading: ko.observable()});
}
});
Now, I'm doing all of this in the model. The reason is that I have several instances of models and sub-models, and it makes more sense to have each one handle its own updating when a change takes place.
Am I going about this all wrong?
I would say it depends on your requirements. Firstly does your current code work correctly? If not what are the problems?
Is the metaColumns array required to to editable independently? If the answer is no then why maintain them as a separate property when you could simply do:
self.metaColumns = ko.computed(function() {
var result = [];
for (i=0; i<self.metaCount(); i++) {
self.metaColumns.push({heading: ko.observable()});
}
return result;
});
I notice that you currently aren't clearing the metaColumns array when the columnUpdate is recomputed so it will keep adding to the array.
Hope this helps.

Categories

Resources