ImmutableJs - How to Retrieve Key Based On Position in Map - javascript

Im using immutableJs
My state object looks like this:
export const initialToolbarState = Map({
temSelectionCtrl : Map({
temSelect : true,
}),
functionalCtrl : Map({
addEle : true,
grpSelect : true,
drawShape : true
}),
operationalCtrl : Map({
zoomIn : true,
zoomOut : true,
pan : true,
temSide : true
}),
referenceCtrl : Map({
saveCtrl : true
})
});
So there are objects with keys which have boolean values.
I want to map (loop) over these objects & get their keys. The boolean values tell whether to render the key or not. Immutable lets us map over Maps using its custom map function. So, the following works, however not as intended:
// map over the initialToolbarState Map object
let ctrls = initialToolbarState.map(( eachToolbar ) => {
// map over the child Map objects like temSelectionCtrl, functionalCtrl, operationalCtrl etc
return eachToolbar.map(( eachCtrl, i ) => {
// get the key corresponding to 'eachCtrl' value
let propKey = eachToolbar.keyOf( eachCtrl );
// propKey is always the first property (1st prop) of 'eachToolbar'
console.log( propKey );
...
Using immutableJs, is there a way to get the correct key corresponding to the currect 'eachCtrl' value within the loop? Could I make sure of the i to help pointing it towards the correct value for which to match the key?

You can use .map again on your objects. The second argument is the key, with the full argument signature being (mapper (value: V, key: K, iter: this))
So, this snippet:
initialToolbarState.map(( eachToolbar ) => {
eachToolbar.map((value, key) => {
console.log(key, ' ==> ', value);
});
});
Will log:
temSelect ==> true
addEle ==> true
grpSelect ==> true
drawShape ==> true
// etc…
Now just chain your returns to create the data structure that you need or do whatever with your keys.
Also, reconsider if this “Map of Maps” is the best structure for the problems you are solving. Perhaps a “List of Maps” is better if you need to iterate often. You won’t have instant read/update for individual items, but if your list consists of only a couple of items, then the performance will not suffer.

Related

How can I create a JavaScript filter function for multiple filters at once

My Problem:
I'm having a website where I can compare products stored inside an array (with objects). I want to add different filters from array inside of an object that get applied together.
For two filters I can easily do it (see my code below). I just compare two objects and use a filter depending on their content.
But what would be a good approach to use the filter if there are more than two objects. Can I loop through the object and compare if the arrays are empty?
With my current approach I would have to extend my code for every new filter and it would balloon.
What I'm trying to do:
I want to check which filter objects have any data in their "feature" array (that array gets filled after the user clicks a filter on the site) and if they have I want to use these arrays to filter the main filteredArray array.
My current Object:
features_collection: {
aspect_ratio_object: {
features: [],
value: "Aspect Ratio",
},
performance_rating_object: {
features: [],
value: "Performance Rating",
},
},
My Filter Function:
if (
features_collection.aspect_ratio_object.features.length &&
features_collection.performance_rating_object.features.length
) {
return filteredArray.filter(
(obj) =>
features_collection.aspect_ratio_object.features.includes(
obj[features_collection.aspect_ratio_object.value]
) &&
features_collection.performance_rating_object.features.includes(
obj[features_collection.performance_rating_object.value]
)
);
} else if (
features_collection.aspect_ratio_object.features.length ||
features_collection.performance_rating_object.features.length
) {
return filteredArray.filter(
(obj) =>
features_collection.aspect_ratio_object.features.includes(
obj[features_collection.aspect_ratio_object.value]
) ||
features_collection.performance_rating_object.features.includes(
obj[features_collection.performance_rating_object.value]
)
);
}
},
Further Notes:
I can also change my object. I could change it into an array of objects if that would make things easier?
Making your filters an array seems more practical. Here's an example on how to
filter a set of objects against your feature_collection.
function filter_by_features(targets, feature_collection) {
// Start right of to filter the `filteredArray`
return targets.filter(obj => {
// go through every feature and test it against the current object.
// every() returns either true or false and the targets array is filtered
// by that condition supplied within the callback of `every()`
return feature_collection.every(filter => {
// If for a given feature no filter is available, return true
// so the test for this filter passes.
if(filter.features.length === 0) {
return true
}
// there are features, check if any applies.
return filter.features.includes(obj[filter.value])
})
})
}
Usage
// feature collection (as array)
const feature_collection = [
{
features: [],
value: "Aspect Ratio",
},
{
features: [],
value: "Performance Rating",
}
]
// the objects you want to filter.
const objects_to_filter = [/* ... */]
const filtered = filter_by_features(objects_to_filter, feature_collection)
docs
every()
You obviously have too loop through your object.
Here is your loop code for features_collection:
features_collection.forEach(function (item, index) {
console.log(item, index);
});

Store array from firebase database

l am trying to build a project using Angular and angularfire. My project is about drawing polygons using leaflet map, then pushing coordinates of a polygon to a database. I have successfully pushed an array to the database, but I have a problem to retrieve coordinates from database.
Database Structure :
{
"Locations" : {
"-M0M9kqEbE0FVMzIZAg0" : {
"text" : [ [ {
"lat" : 35.97800618085566,
"lng" : 42.03369140625001
}, {
"lat" : 33.88865750124075,
"lng" : 41.46240234375001
} ] ]
}
}
}
I am trying to store array above in class ts not in html.
My code :
export class WeatherNowComponent {
itemsRef: AngularFireList<any>;
items: Observable<any[]>;
coords :any
constructor(private db: AngularFireDatabase) {
this.itemsRef = this.db.list('Locations')
// Use snapshotChanges().map() to store the key
this.items = this.itemsRef.snapshotChanges().pipe(
map(changes =>
changes.map(c => ({ key: c.payload.key, ...c.payload.val() }))
)
);
this.db.list('Locations').snapshotChanges().subscribe((markers: any) => {
console.log(markers)
markers.forEach(singlemarker => {
console.log(singlemarker)
// ADD COORDS to MAP POLYGON
var polygon = L.polygon([singlemarker.text.lat,singlemarker.text.lng], {color: 'red'}).addTo(this.map);
});
});
console.log(this.items)
}
}
So I have only key array in console log without rest of array objects.
If you calling a spread operator on purpose because it is an array try assigning it to a property value in the object literal definition.
{ key: c.payload.key, values: ...c.payload.val() }
Edit: Based on your comment I would still say still just assign the array of coordinates (lat/lng property pairs objects) to the values property to iterate over later. I assume the .val() method must return the array of those values. Otherwise please post what .val() returns.
Also, I noticed something else you should check.
The observable is calling the RxJs pipe() method to map but I didn't notice a subscribe() anywhere. While .pipe() lets you map and filter the callback needs a subscribe.
Edit #2: Based on your second comment below. What you are asking for is still unclear to me. So I will give two more possible answers based on the comment: "okay , my question is how i can get array directly ?"
// Possibility #1
// Simplest is to store off the returned data into the coords variable for later processing.
// You can then get the array directly at any later point just by accessing the coords
this.db.list('Locations').snapshotChanges().subscribe((markers: any) => {
coords = markers;
});
// Possibility #2
// Since the data structure you are using works like a dictionary data structure
// Define some structures to store off the data and get at the array data directly later
// Define this somewhere outside your class
interface ICoordinate {
lat: string;
lng: string;
}
// Initialize coordinate dictionary
let coordinates: { [key: string] : ICoordinate[]; } = {};
// Retrieve locations and store off coordinates to get at the array data directly
this.db.list('Locations')
.snapshotChanges()
.subscribe((markers: any) => {
coordinates[markers.key] = ICoordinate[];
markers.forEach(singlemarker => {
coordinates[markers.key].push({ lat: singlemarker.text.lat, lng: singlemarker.text.lng });
});
});
Edit #3: Oh yes, I see. The array needed to be initialized before adding to it.
Updated code. Also, as an additional help to you on future questions it is very helpful if you have a snippet. S.O. question to refer to: I've been told to create a "runnable" example with "Stack Snippets", how do I do that?
Hopefully that helps.
Happy coding!

How to filter and modify an array of objects with a single line of code?

I have the following array of objects coming from my database:
dataFromDB = [
{ src: 'stringWithSource1',
selected: true
},
{ src: 'stringWithSource2',
selected: false
},
{ src: 'stringWithSource3',
selected: true
},
AND SO ON...
];
When I fetch it from the database I need to store on client-side in a state that must be an array of strings, containing only the src property of the objects that are selected: true.
Example:
myState = [
'stringWithSource1',
'stringWithSource3',
AND SO ON...
]
QUESTION
The line where I'm assigning this, is like the following (see code below):
I tried but that doesn't work because I'm keeping the unselected src as null instead of just skipping them.
setProductDetails({
// ... other properties,
images: dataFromDB.images.map((item) => {
return item.selected ? item.src : null;
}
});
How can I achieve this behavior in a single line like this? I know I could create an auxiliar variable and handle this. But I would like a one-liner in this case. Is this possible? It feels like I should filter and map at the same time?
You need two parts, because the filtering does not map a value, but the original items.
You could filter by selected and then map src.
images: dataFromDB.images
.filter(({ selected }) => selected)
.map(({ src }) => src)

How to compare multiple keys with distinctUntilKeyChanged?

I'm still learning rxjs, and I'm a little stuck on how to write a custom compare function for the operator distinctUntilChanged.
I looked into using distinctUntilKeyChanged, which works great for a single key...But I have two keys that I need to compare.
It seems I might need to incorprate the scan operator to compare the current value emitted to the last value emitted....?
Ok, here is my code. I'm streaming map center changes from google maps. I don't need the map center GeoLocation to be very precise, so I'm rounding off most of the decimals returned by google maps.
searchStream$
.map((value)=>{
return {
lat: round(value[1].lat, 1),
lng: round(value[1].lng, 1)
}
}).distinctUntilKeyChanged('lat')
.do((position)=>{console.log(position)})
.subscribe((position)=>{ this._store.dispatch(new QueryUpdateGeoPositionAPIAction({latitude: position.lat, longitude: position.lng})) });
So back to my question, how can I compare both properties(lat & lng) to ensure that it's only emitting values when either one of the values change?
Thanks for the help!
I was having the same problem and this situation isn't covered by the docs.
this will add the .distinctUntilKeysChanged operator. just pass it any keys to "watch" for
const {
Observable,
Subject
} = Rx;
Observable.prototype.distinctUntilKeysChanged = function(...keys) {
return this.distinctUntilChanged((old, current) =>
// if no value changed,
// the array will only have true values,
// includes(false) will be false,
// convert to oposite (!),
// returns true;
// => true = do nothing
// if any value changed,
// the array will include some false,
// includes(false) will be true,
// convert to oposite (!),
// returns false;
// => false = emit
!keys
.map(key => old[key] === current[key]) // converts to array of boolean
.includes(false) // if any value changed
);
};
const stream = new Subject();
stream
.distinctUntilKeysChanged('prop', 'prop2')
.subscribe(obj => console.log(obj));
// should log
stream.next({
prop: 42,
prop2: 54,
});
// should log
stream.next({
prop: 12346,
prop2: 54,
});
// shouldn't log because neither prop nor prop2 changed
stream.next({
prop: 12346,
prop2: 54,
});
// should log
stream.next({
prop: 12346,
prop2: 5454665654645,
});
<script src="https://unpkg.com/#reactivex/rxjs#5.0.3/dist/global/Rx.js"></script>
the only downside to this is that you can't specify a custom comparison function. if you do want to specify one you could use this instead (which is more similar to the implementation of .distinctUntilKeyChanged where the first argument is the key and the second is the comparison function). Notice that this one takes an array of keys where the first one took the keys as separate arguments
const {
Observable,
Subject
} = Rx;
Observable.prototype.distinctUntilKeysChanged = function(keys, compare) {
return this.distinctUntilChanged((old, current) =>
// if no value changed,
// the array will only have true values,
// includes(false) will be false,
// convert to oposite (!),
// returns true;
// => true = do nothing
// if any value changed,
// the array will include some false,
// includes(false) will be true,
// convert to oposite (!),
// returns false;
// => false = emit
!keys
.map(key => compare ? compare(old[key], current[key]) : old[key] === current[key]) // converts to array of boolean
.includes(false) // if any value changed
);
};
const stream = new Subject();
stream
.distinctUntilKeysChanged(['prop', 'prop2'])
.subscribe(obj => console.log(obj));
// should log
stream.next({
prop: 42,
prop2: 54,
});
// should log
stream.next({
prop: 12346,
prop2: 54,
});
// shouldn't log because neither prop nor prop2 changed
stream.next({
prop: 12346,
prop2: 54,
});
// should log
stream.next({
prop: 12346,
prop2: 5454665654645,
});
<script src="https://unpkg.com/#reactivex/rxjs#5.0.3/dist/global/Rx.js"></script>
hope you find it useful
From the RxJS section of the distinct documentation:
In RxJS, the distinct operator has two optional parameters:
a function that accepts an item emitted by the source Observable and returns a key which will be used instead of the item itself when comparing two items for distinctness
a function that accepts two items (or two keys) and compares them for distinctness, returning false if they are distinct (an equality function is the default if you do not supply your own function here)
So it looks to me (with no testing) that you could simply pass something like
(a, b) => a.lat === b.lat && a.lon === b.lon
I'm not sure about RxJS conventions to know how you should pass this (second optional) parameter.

SimpleSchema match any type but null

I'm planning to make a collection to hold different app-wide settings, like, say, amount of logged in users today, Google analytics tracking ID, etc. So I made a schema like this:
options_schema = new SimpleSchema({
key: {
type: String,
unique: true
},
value: {
},
modified: {
type: Date
}
});
Now the main problem is that I want value to be of any type: Number, String, Date, or even custom Objects. Though it has to be present, can't be null.
But of course it gets angry about not specifying the type. Is there a workaround for this?
You can use Match patterns for your fields' type which allow you to do pretty much anything :
const notNullPattern = Match.Where(val => val !== null)
value : {
type : notNullPattern
}
(See Arrow functions)
Note that this will allow everything but null, including undefined.
Defining patterns this way allow you to use them everywhere in your application including in check :
check({
key : 'the key',
modified : Date.now(),
value : {} // or [], 42, false, 'hello ground', ...
}, optionsSchema)
Match.test(undefined, notNullPattern) //true
Match.test({}, notNullPattern) //true
Match.test(null, notNullPattern) //false
A more general solution to exclude one value would simply be:
const notValuePattern =
unwantedValue => Match.Where(val => val !== unwantedValue))
The use of which is similar to the above:
Match.test(42, notValuePattern(null)) // true
Note that due to the use of the identity operator === it will notably fail for NaN:
Match.test(NaN, notValuePattern(NaN)) // true :(
A solution could be:
const notValuePattern =
unwantedValue => Match.Where(val => Number.isNaN(unwantedValue)?
!Number.isNaN(val)
: val !== unwantedValue
)
Should you want a solution to exclude some specific values in a schema (kind of the contrary of Match.OneOf), you could use the following:
const notOneOfPattern = (...unwantedValues) =>
Match.Where(val => !unwantedValues.includes(val)
)
This uses Array.prototype.includes and the ... spread operator. Use as follow:
Match.test(42, notOneOfPattern('self-conscious whale', 43)) // true
Match.test('tuna', notOneOfPattern('tyranny', 'tuna')) // false
Match.test('evil', notOneOfPattern('Plop', 'kittens')) // true
const disallowedValues = ['coffee', 'unicorns', 'bug-free software']
Match.test('bad thing', notOneOfPattern(...disallowedValues)) // true

Categories

Resources