On click, loop through each object key - javascript

I'm still learning JS and something is harder to understand than others.
Like so:
I am trying to change the theme of google maps by allowing users to click on a custom button.
I was using if else which works great but i wanted to add more themes and using a loop. Each time a user clicks, it selects:
object key 0,
then click again object key 2
and object key 3
and repeat
I can get the object keys and values how I'm lost after that.
This is the theme object
let theme = {
default: null,
night: [multiple objects with nested arrays],
dark: [multiple objects with nested arrays]
}
creating button inside google maps then addEventListener
let themeToggle = document.createElement('button');
themeToggle.classList.add('controlUI');
themeToggle.innerHTML = ('Mode');
themeToggle.title = 'Change map theme';
map.controls[google.maps.ControlPosition.TOP_LEFT].push(themeToggle);
let mode = true;
themeToggle.addEventListener('click', () => {
if (mode) {
map.setOptions({styles: theme.night});
} else {
map.setOptions({styles: theme.default});
}
mode = !mode;
});
Above Works Fine
Im struggling to convert the if else to a loop and select each object key and then adding that to:
map.setOptions({styles: theme.night})
and then on click it loops through each key and repeat
themeToggle.addEventListener('click', () => {
for ( let key in theme) {
map.setOptions({styles: theme[key]});
console.log(theme[key])
}
});
it selects the last one by default and i cant toggle.
Any help would e really appreciated, just trying add all the puzzle together.

Collect the object values into an array, then increment an index with modulo on every click:
const vals = Object.values(theme);
let i = 0;
themeToggle.addEventListener('click', () => {
map.setOptions({styles: vals[i]});
i = (i + 1) % vals.length;
});
While most environments will result in an object's Object.values in ascending numeric followed by insertion order, it's not guaranteed. If you need a guaranteed predictable ordering, use Reflect.ownKeys (or Object.getOwnPropertyNames) instead:
const vals = Reflect.ownKeys(theme)
.map(key => theme[key]);

You can loop through an object like this
var invoice = {
name: 'anik',
age: 29,
designation: 'Full Stack Developer'
}
Object.keys(invoice).map((d,i)=>{
console.log(d +' : '+invoice[d]);
})

Related

Vue computed property overwriting global state without vuex

I have a list of people who have scores. In state I have them listed in an array, one of the items in the array is 'scoreHistory' which is an array of objects containing their scores at different points in time. I want to filter this set for different time periods i.e. -5 days, -30 days so instead of just seeing the overall score I can see the scores if everyone started at 0 say 30 days ago.
I have it (kind of) working. See my code below:
filteredScores () {
if(!this.people) {
return
}
// Here I was trying to ensure there was a copy of the array created in order to not change the original array. I thought that might have been the problem.
let allPeople = this.people.slice(0) // this.people comes from another computed property with a simple getter. Returns an array.
let timeWindow = 30 //days
const windowStart = moment().subtract(timeWindow,'days').toDate()
for (const p of allPeople ) {
let filteredScores = inf.scoreHistory.filter(score => moment(score.date.toDate()).isSameOrAfter(windowStart,'day'))
p.scoreHistory=filteredScores
//calculate new score
p.score = inf.scoreHistory.reduce(function(sum,item) {
return sum + item.voteScore
},0)
}
return allInf
}
I expected it to return to me a new array where each person's score is summed up over the designated time period. It seems to do that OK. The problem is that it is altering the state that this.people reads from which is the overall data set. So once it filters all that data is gone. I don't know how I am altering global state without using vuex??
Any help is greatly appreciated.
Your problem isn't that you're modifying the array, but that you're modifying the objects within the array. You change the scoreHistory and score property of each item in the array. What you want to do instead is create a new array (I recommend using map) where each item is a copy of the existing item plus a new score property.
filteredScores () {
if(!this.people) {
return
}
let timeWindow = 30 //days
const windowStart = moment().subtract(timeWindow,'days').toDate()
return this.people.map(p => {
let filteredScores = p.scoreHistory.filter(score => moment(score.date.toDate()).isSameOrAfter(windowStart,'day'))
//calculate new score
let score = filteredScores.reduce(function(sum, item) {
return sum + item.voteScore
}, 0)
// Create a new object containing all the properties of p and adding score
return {
...p,
score
}
}
})

Delete Session Storage - Shopping Cart Project

I hope this is a good question. I am working on a shopping cart project. I have been scouring the internet through different tutorials on shopping carts. I am attempting to write mine in Vanilla Javascript. I am having a problem with removing shopping cart items from session storage.
Below is what is currently in my session storage. As you can see it is an Array of objects.
[{"id":"8","name":"Candy
Skull","price":"20000","image":"../images/candyskull-
min.JPG","qty":"1"},{"id":"5","name":"Upsidedown
House","price":"20000","image":"../images/upsidedownhouse-
min.JPG","qty":"1"},{"id":"6","name":"Brooklyn
Window","price":"30000","image":"../images/brooklynwindow-
min.JPG","qty":"1"},{"id":"4","name":"Hand That
Feeds","price":"40000","image":"../images/handthatfeeds-
min.JPG","qty":"1"}]
I want to loop through the array and remove the matching object from the cart storage.
Below is the JS Code used to generate the .remove-from-cart buttons. As you can see it includes all the dataset information.
<td>
<span class="remove-from-cart">
<b data-id="${value.id}" data-name="${value.name}" data-
price="${value.price}" data-image="${value.image}" data-
qty="${value.qty}">X</b>
</span>
</td>
To test the functionality of what I have done so far you can visit www.dancruzstudio.com/shop
The function that I can't get to work properly is the removeFromStorage() function. For some reason when comparing an object to the objects in the array I'm never getting back a true boolean value, even when there are items in the cart that should match. Where am I going wrong? I hope someone can help. Below is a copy of my JS code.
The method I am using is having an identical dataset value in the remove item button generated by JS and then parsing that dataset into an object and comparing it to the objects in the session storage array which is called shopItems inside the removeFromStorage() function. I hope this information is enough for someone to see my problem. Thank you in advance.
// Remove item from DOM
document.querySelector('#cart-list').addEventListener('click',
removeFromCart)
function removeFromCart(e) {
if(e.target.parentElement.classList.contains('remove-from-cart')) {
//Remove from DOM
e.target.parentElement.parentElement.parentElement.remove();
//Remove from Session Storage
removeFromStorage(e.target.dataset);
}
}
// remove from Session storage
function removeFromStorage(removedItem){
let shopItems;
if(sessionStorage['sc'] == null){
shopItems = [];
} else {
shopItems = JSON.parse(sessionStorage['sc'].toString());
}
var compare = JSON.parse(JSON.stringify(removedItem))
shopItems.forEach(function(item, index){
if(compare === item){
console.log(compare);
console.log(item);
// shopItems.splice(index, 1);
}
});
sessionStorage['sc'] = JSON.stringify(shopItems);
}
You can not compare objects like this.
let a = {p:1};
let b = {p:1};
console.log(`a ===b ? ${a===b}`);
If your objects are fairly simple you can try comparing their stringify representation:
let a = {p:1};
let b = {p:1};
const compare = (x,y) => {
return JSON.stringify(x) === JSON.stringify(y);
}
console.log(`a === b ? ${compare(a,b)}`);
or write your custom compare function (that may be challenging):
Compare JavaScript objects
Since your objects are decorated with an id, the easisest way would be to idntify them by that:
let storage = [{"id":"8","name":"Candy Skull", "price":"20000","image":"../images/candyskull- min.JPG","qty":"1"},{"id":"5","name":"Upsidedown House","price":"20000","image":"../images/upsidedownhouse- min.JPG","qty":"1"},{"id":"6","name":"Brooklyn Window","price":"30000","image":"../images/brooklynwindow- min.JPG","qty":"1"},{"id":"4","name":"Hand That Feeds","price":"40000","image":"../images/handthatfeeds- min.JPG","qty":"1"}];
let items = [{"id":"6","name":"Brooklyn Window","price":"30000","image":"../images/brooklynwindow- min.JPG","qty":"1"}, {"id":"5","name":"Upsidedown House","price":"20000","image":"../images/upsidedownhouse- min.JPG","qty":"1"}];
const pluck = (acc, crt) => {
acc.push(crt.id);
return acc;
};
let storageIndexes = storage.reduce(pluck, []);
let itemsIndexes = items.reduce(pluck, []);
let removeIndexes = [];
itemsIndexes.forEach(id => removeIndexes.push(storageIndexes.indexOf(id)));
console.log('storage', storage);
console.log('removed items', items);
removeIndexes.sort().reverse().forEach(index => storage.splice(index,1));
console.log('remaining storage', storage);

RxJS Filter / Search Subject, Observable, or BehaviorSubject

I just started learning RxJS. One thing I have tried to do without much luck is, figuring out how to search/filter a Subject, or create an observed array that I can search on.
I've tried piping and filtering a Subject and BehaviorSubject, but the values in the predicate are RxJS specific.
From what I've read on various posts, the way is to observe an array to use a Subject.
I can easily have two variables, one array and the Subject, and search the array. But I'd like to accomplish this one variable.
In Knockout its possible to search an observed array.
Is this possible in RxJS?
Thanks in advance.
Example:
layers: Rx.Subject<any> = new Rx.Subject<any>();
toggleLayer (layerId, visible) {
//find layer we need to toggle
// How do I search the subject to get the values added in next()?
// tried from(this.layers), pipe does not fire
const source = of(this.layers);
const example = source.pipe(filter((val, index) => {
//val is subject, and only iterates once, even if more than one value in subject
// tslint:disable-next-line:no-debugger
debugger;
return false;
}));
const sub = example.subscribe((val) => {
// tslint:disable-next-line:no-debugger
debugger;
});
}
private addLayer = (layerName, layerObj, layerType) => {
// component class is subscribed to layers subject. Update UI when layer is added
this.layers.next({
layerId: this.layerId,
name: `${layerName}_${this.layerId}`,
layerObj: layerObj,
visible: true,
layerType: layerType
});
}
I'm not 100% clear on the specifics of your ask, but maybe this example will help you.
const filterSubject = new BehaviorSubject<string>('b');
const dataSubject = new BehaviorSubject<string[]>(['foo', 'bar', 'baz', 'bat']);
const dataObservable = combineLatest(filterSubject, dataSubject).pipe(
// given an array of values in the order the observables were presented
map(([filterVal, data]) => data.filter(d => d.indexOf(filterVal) >= 0))
);
dataObservable.subscribe(arr => console.log(arr.join(',')));
// bar, baz, bat
Using combineLatest, you can have the value in dataObservable updated whenever either your filter value or your data array changes.

How to update or add to immutable.js List

I'm using Redux and Immutable.js in my React-based project (built on React Boilerplate) and I'm looking for an idiomatic way to update or add to an Immutable.js List.
My current setup. State initially looks like this:
const initialState = fromJS({
accounts: [],
activeAccount: null,
loadedAccounts: [],
});
I have an Immutable Record for an account object:
const account = new Record({
description: '',
id: '',
name: '',
status: '',
});
And my reducer:
function reducer(state = initialState, action) {
switch (action.type) {
case LOAD_ACCOUNT_SUCCESS:
return state
.set('activeAccount', new account(action.account))
default:
return state;
}
}
This works fine - when a LOAD_ACCOUNT_SUCCESS action is fired, activeAccount is updated to the value of action.account.
I can amend this so that every new LOAD_ACCOUNT_SUCCESS action pushes the newly-loaded account data to loadedAccounts instead:
function reducer(state = initialState, action) {
switch (action.type) {
case LOAD_ACCOUNT_SUCCESS:
const loadedAccount = new account(action.account);
return state
.update('loadedAccounts', (loadedAccounts) => loadedAccounts.push(loadedAccount));
default:
return state;
}
}
However, at the moment loading the same account data twice will result in new Records being pushed to my List each time (duplicating data). What I want to do instead is either add action.account to loadedAccounts (as happens now) or update the Record in the List if there is a matching ID. I'm looking at similar questions and the lamentable Immutable.js documentation and I can't see how to do this: no syntax I've tried works as I expect here.
So, what do you need here is nested update.
At first, you have to check your list of loadedAccounts whether it has this account or not.
Secondly, you have to change activeAccount field.
And, lastly, add (or update) account to loadedAccounts.
The caveat here is how you pass account property. If you derive it from somewhere and pass around as a Record, you can just compare by === (or by .equals()), but it seems that it is just a plain javascript object – I'll suppose it later.
In terms of code it would be something like:
// we can do it by different ways, it is just one of them
const listWithLoadedAccounts = state.get('loadedAccounts');
const isAccountAlready = Boolean(
listWithLoadedAccounts.filter(
account => account.get('id') === action.account.id
).size
);
const patchedState = state.set('activeAccount', action.account.id);
return isAccountAlready
? patchedState.updateIn(['loadedAccounts'], list => list.map(account => account.get('id') === account.action.id ? new account(action.account) : account))
: patchedState.updateIn(['loadedAccounts'], list => list.concat(new account(action.account)))
It is not the ideal code, something can be deduplicated, but you get the idea – always use deep merge / update if you need to change nested fields or data structures.
You also can set new field directly, like:
const oldList = state.get('loadedAccounts');
const newList = oldList.concat(action.account);
const patchedState = state.set('loadedAccounts', newList);
But I personally find that it is not that flexible and also not consistent, because it is quite common operation to perform deep merge.
i hope this example will help, i am creating a new immutable list and first performing an update and then adding a new element. i am not passing the object which i want to replace with, but you can also pass your existing object, Also in update method you have access to current item
class Test {
a = null;
b = null;
constructor(a,b){
this.a=a;
this.b=b;
}
}
$("#test").html("");
function logme(item){
$("#test").append("<br/>"+JSON.stringify(item));
}
function logmeNewLine(){
$("#test").append("<br/>");
}
function listAddUpadte(key){
var index= list.get('data').findIndex(listing => {
return listing.a === key;
});
logme(' found index (-1 for not found) : ' + index);
if(index >= 0){
logme("upadte");
list = list.set("data",list.get("data").update(index,function(item){
return new Test(key,"go");
}));
}else {
logme("add");
list = list.set("data",list.get("data").push(new Test(key,"go")));
}
list.get('data').forEach(item=>{
logme(item);
});
}
var list = Immutable.fromJS({
"data":[new Test(6,"abhi"),new Test(4,"raj"),new Test(1,"ajay")]
});
logme("intial data");
list.get('data').forEach(item=>{
logme(item);
});
logmeNewLine();
logme("testing replace with a = 4 ")
logmeNewLine();
listAddUpadte(4);
logmeNewLine();
logme("testing add with a = 8 ")
logmeNewLine();
listAddUpadte(8);
logmeNewLine();
logmeNewLine();
logmeNewLine();
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.7.2/immutable.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="test"></div>
Since this is the top search result for what is in the title of the question
https://immutable-js.github.io/immutable-js/docs/#/List/push
An example of how to add to a list using immutable.js:
let oldList = List([ 1, 2, 3, 4 ])
let newList = oldList.push(5)
https://immutable-js.com/docs/v4.0.0/List/#insert()
insert()
Returns a new List with value at index with a size 1 more than this List. Values at indices above index are shifted over by 1.
insert(index: number, value: T): List
Discussion
This is synonymous with list.splice(index, 0, value).
List([ 0, 1, 2, 3, 4 ]).insert(6, 5)
// List [ 0, 1, 2, 3, 4, 5 ]

How to manage state without using Subject or imperative manipulation in a simple RxJS example?

I have been experimenting with RxJS for two weeks now, and although I love it in principle I just cannot seem to find and implement the correct pattern for managing state. All articles and questions appear to agree:
Subject should be avoided where possible in favor of just pushing state through via transformations;
.getValue() should be deprecated entirely; and
.do should perhaps be avoided except for DOM manipulation?
The problem with all such suggestions is that none of the literature appears to directly say what you should be using instead, besides "you'll learn the Rx way and stop using Subject".
But I cannot find a direct example anywhere that specifically indicates the correct way to perform both additions and removals to a single stream/object, as the consequence of multiple other stream inputs, in a stateless and functional manner.
Before I get pointed in the same directions again, problems with uncovered literature are:
The Introduction to Reactive Programming You've been missing: great starting text, but does not specifically address these questions.
The TODO example for RxJS comes with React and involves explicit manipulation of Subjects as proxies for React Stores.
http://blog.edanschwartz.com/2015/09/18/dead-simple-rxjs-todo-list/ : explicitly uses a state object for addition and removal of items.
My perhaps 10th rewrite of the standard TODO follows - My prior iterations covered include:
starting with a mutable 'items' array - bad as state is explicit and imperatively managed
using scan to concatenate new items to an addedItems$ stream, then branching another stream where the removed items were deleted - bad as the addedItems$ stream would grow indefinitely.
discovering BehaviorSubjectand using that - seemed bad since for each new updatedList$.next() emission, it requires the previous value to iterate, meaning that Subject.getValue() is essential.
trying to stream the result of the inputEnter$ addition events into filtered removal events - but then every new stream creates a new list, and then feeding that into the toggleItem$ and toggleAll$ streams means that each new stream is dependent on the previous, and so causing one of the 4 actions (add, remove, toggle item or toggle all) requires the whole chain to be unnecessarily run through again.
Now I have come full circle, where I am back to using both Subject (and just how is it supposed to be successively iterated upon in any way without using getValue()?) and do, as show below. Myself and my colleague agree this is the clearest way, yet it of course seems the least reactive and most imperative. Any clear suggestions on the correct way for this would be much appreciated!
import Rx from 'rxjs/Rx';
import h from 'virtual-dom/h';
import diff from 'virtual-dom/diff';
import patch from 'virtual-dom/patch';
const todoListContainer = document.querySelector('#todo-items-container');
const newTodoInput = document.querySelector('#new-todo');
const todoMain = document.querySelector('#main');
const todoFooter = document.querySelector('#footer');
const inputToggleAll = document.querySelector('#toggle-all');
const ENTER_KEY = 13;
// INTENTS
const inputEnter$ = Rx.Observable.fromEvent(newTodoInput, 'keyup')
.filter(event => event.keyCode === ENTER_KEY)
.map(event => event.target.value)
.filter(value => value.trim().length)
.map(value => {
return { label: value, completed: false };
});
const inputItemClick$ = Rx.Observable.fromEvent(todoListContainer, 'click');
const inputToggleAll$ = Rx.Observable.fromEvent(inputToggleAll, 'click')
.map(event => event.target.checked);
const inputToggleItem$ = inputItemClick$
.filter(event => event.target.classList.contains('toggle'))
.map((event) => {
return {
label: event.target.nextElementSibling.innerText.trim(),
completed: event.target.checked,
};
})
const inputDoubleClick$ = Rx.Observable.fromEvent(todoListContainer, 'dblclick')
.filter(event => event.target.tagName === 'LABEL')
.do((event) => {
event.target.parentElement.classList.toggle('editing');
})
.map(event => event.target.innerText.trim());
const inputClickDelete$ = inputItemClick$
.filter(event => event.target.classList.contains('destroy'))
.map((event) => {
return { label: event.target.previousElementSibling.innerText.trim(), completed: false };
});
const list$ = new Rx.BehaviorSubject([]);
// MODEL / OPERATIONS
const addItem$ = inputEnter$
.do((item) => {
inputToggleAll.checked = false;
list$.next(list$.getValue().concat(item));
});
const removeItem$ = inputClickDelete$
.do((removeItem) => {
list$.next(list$.getValue().filter(item => item.label !== removeItem.label));
});
const toggleAll$ = inputToggleAll$
.do((allComplete) => {
list$.next(toggleAllComplete(list$.getValue(), allComplete));
});
function toggleAllComplete(arr, allComplete) {
inputToggleAll.checked = allComplete;
return arr.map((item) =>
({ label: item.label, completed: allComplete }));
}
const toggleItem$ = inputToggleItem$
.do((toggleItem) => {
let allComplete = toggleItem.completed;
let noneComplete = !toggleItem.completed;
const list = list$.getValue().map(item => {
if (item.label === toggleItem.label) {
item.completed = toggleItem.completed;
}
if (allComplete && !item.completed) {
allComplete = false;
}
if (noneComplete && item.completed) {
noneComplete = false;
}
return item;
});
if (allComplete) {
list$.next(toggleAllComplete(list, true));
return;
}
if (noneComplete) {
list$.next(toggleAllComplete(list, false));
return;
}
list$.next(list);
});
// subscribe to all the events that cause the proxy list$ subject array to be updated
Rx.Observable.merge(addItem$, removeItem$, toggleAll$, toggleItem$).subscribe();
list$.subscribe((list) => {
// DOM side-effects based on list size
todoFooter.style.visibility = todoMain.style.visibility =
(list.length) ? 'visible' : 'hidden';
newTodoInput.value = '';
});
// RENDERING
const tree$ = list$
.map(newList => renderList(newList));
const patches$ = tree$
.bufferCount(2, 1)
.map(([oldTree, newTree]) => diff(oldTree, newTree));
const todoList$ = patches$.startWith(document.querySelector('#todo-list'))
.scan((rootNode, patches) => patch(rootNode, patches));
todoList$.subscribe();
function renderList(arr, allComplete) {
return h('ul#todo-list', arr.map(val =>
h('li', {
className: (val.completed) ? 'completed' : null,
}, [h('input', {
className: 'toggle',
type: 'checkbox',
checked: val.completed,
}), h('label', val.label),
h('button', { className: 'destroy' }),
])));
}
Edit
In relation to #user3743222 very helpful answer, I can see how representing state as an additional input can make a function pure and thus scan is the best way to represent a collection evolving over time, with a snapshot of its previous state up to that point as an additional function parameter.
However, this was already how I approached my second attempt, with addedItems$ being a scanned stream of inputs:
// this list will now grow infinitely, because nothing is ever removed from it at the same time as concatenation?
const listWithItemsAdded$ = inputEnter$
.startWith([])
.scan((list, addItem) => list.concat(addItem));
const listWithItemsAddedAndRemoved$ = inputClickDelete$.withLatestFrom(listWithItemsAdded$)
.scan((list, removeItem) => list.filter(item => item !== removeItem));
// Now I have to always work from the previous list, to get the incorporated amendments...
const listWithItemsAddedAndRemovedAndToggled$ = inputToggleItem$.withLatestFrom(listWithItemsAddedAndRemoved$)
.map((item, list) => {
if (item.checked === true) {
//etc
}
})
// ... and have the event triggering a bunch of previous inputs it may have nothing to do with.
// and so if I have 400 inputs it appears at this stage to still run all the previous functions every time -any- input
// changes, even if I just want to change one small part of state
const n$ = nminus1$.scan...
The obvious solution would be to just have items = [], and manipulate it directly, or const items = new BehaviorSubject([]) - but then the only way to iterate on it appears to be using getValue to expose the previous state, which Andre Stalz (CycleJS) has commented on in the RxJS issues as something that shouldn't really be exposed (but again, if not, then how is it usable?).
I guess I just had an idea that with streams, you weren't supposed to use Subjects or represent anything via a state 'meatball', and in the first answer I'm not sure how this doesn't introduce mass chained streams which are orphaned/grow infinitely/have to build on each other in exact sequence.
I think you already found a good example with : http://jsbin.com/redeko/edit?js,output.
You take issue with the fact that this implementation
explicitly uses a state object for addition and removal of items.
However, thas is exactly the good practice you are looking for. If you rename that state object viewModel for example, it might be more apparent to you.
So what is state?
There will be other definitions but I like to think of state as follows:
given f an impure function, i.e. output = f(input), such that you can have different outputs for the same input, the state associated to that function (when it exists) is the extra variable such that f(input) = output = g(input, state) holds and g is a pure function.
So if the function here is to match an object representing a user input, to an array of todo, and if I click add on a todo list with already have 2 todos, the output will be 3 todos. If I do the same (same input) on a todo list with only one todo, the output will be 2 todos. So same input, different outputs.
The state here that allows to transform that function into a pure function is the current value of the todo array. So my input becomes an add click, AND the current todo array, passed through a function g which give a new todo array with a new todo list. That function g is pure. So f is implemented in a stateless way by making its previously hidden state explicit in g.
And that fits well with functional programming which revolves around composing pure functions.
Rxjs operators
scan
So when it comes to state management, with RxJS or else, a good practice is to make state explicit to manipulate it.
If you turn the output = g(input, state) into a stream, you get On+1 = g(In+1, Sn) and that's exactly what the scan operator does.
expand
Another operator which generalizes scan is expand, but so far I had very little use of that operator. scan generally does the trick.
Sorry for the long and mathy answer. It took me a while to get around those concepts and that's the way I made them understandable for me. Hopefully it works for you too.

Categories

Resources