I'm working with the outputs of an Intranet I don't control.
I have this string:
let template = 'LAWYER=|FIRM=|SUIT_DESCRIPTION=|DEF_COMMENT=|PLF_COMMENT=|';
It goes on longer, but that's the pattern.
Now there's another similar string, but with data assigned, as in this example:
let current= 'FIRM=Smith and Wesson LLP|SUIT_DESCRIPTION=It\'s a royal mess|PLF_COMMENT=some freeform text|LAWYER=Bob Smith';
Now, notice that not every element in template is necessarily represented in current, and the order may be different (if the latter fact is a big deal, I can ensure the order is the same).
What I'm trying to do, is take every element that is in current, and populate the matching element in template, if it exists. (or, alternatively and potentially preferred, insert every non-matching element in template into current, but ideally in the same order as template).
Using the date above, the result I'm looking for is:
result = 'LAWYER=Bob Smith|FIRM=Smith and Wesson LLP|SUIT_DESCRIPTION=It\'s a royal mess|DEF_COMMENT=|PLF_COMMENT=some freeform text|';
I'm not very accomplished with JavaScript :(
I tried various things in JSFiddle using split() and match() but I just made a mess of it.
// Convert the template to an array of keys
const getKeys = str => str.split('|').map(entry => entry.split('=')[0]);
// Convert the data to an object
const toObj = str => Object.fromEntries(str.split('|').map(entry => entry.split('=')));
// Reconcile the data with the template
const compile = (templateStr, dataStr) => {
const keys = getKeys(templateStr);
const data = toObj(dataStr);
return keys.reduce((results, key) => {
if(key) results.push([key, data[key] ?? '']);
return results;
}, []);
};
// Convert the results back into a string
const toString = data => data.map(entry => entry.join('=')).join('|') + '|';
// And then a test
let template = 'LAWYER=|FIRM=|SUIT_DESCRIPTION=|DEF_COMMENT=|PLF_COMMENT=|';
let current = 'FIRM=Smith and Wesson LLP|SUIT_DESCRIPTION=It\'s a royal mess|PLF_COMMENT=some freeform text|LAWYER=Bob Smith';
console.log(toString(compile(template, current)));
For some reason firefox and chrome don't seem to have the same sort method algorithms in their javascript engines. Is there a clear way to know why or how these are being processed differently from one another? It seems kind of strange to have to find out in testing that a simple array sort is causing bugs in other browsers when it should be a pretty straightforward comparison, no?
Here's the code I'm using: I collect an rss feed xml file, create a shallow copy to not be dealing with Symbols or whatever just in case, I just insert episodes with titles containing "Special" because I don't really care where they get inserted. Then, instead of creating date objects and doing a bunch of needless processing, since every episode title in the rss xml starts with the episode number like "Ep.25: Episode Name" I thought I could just parse the string and use the integer value between "Ep." and ":" to sort by integer instead of datetime objects.
For some reason Firefox doesn't give me the same results as Chrome and I can't understand how that would be different or wrong no matter what engine is processing the javascript.
See live example at https://krisdriver.com/fg
const EpisodeContainer = ()=>{
// random card background image if no specific index number is selected (select by map index)
const cardbg = ()=>{
let x = [bgselect]
return x[x.length-1]
}
// collect the rss feed info and start the async chain
async function loadingdata () {
return await rssdata
}
let data // collects results from async xml data collection
const epsperpage = 12 // PRODUCTION CONSIDERATION: consider allowing options for preloading, # per page, etc
const [datastate, setDataState] = useState(data)
const [epsLoaded, setEpsLoaded] = useState(false)
useEffect( ()=>{
loadingdata().then( res =>{
// eslint-disable-next-line react-hooks/exhaustive-deps
data = [...res]
let sortedEpisodes_byDate = [...data[0]] // create shallow copy, not reference
// start sorting the episodes by their data...
sortedEpisodes_byDate.sort( (x,y)=>{
// sorting episodes so part 1 and 2 are always in order
// exclude special episodes
if(!x.title.toUpperCase().includes('SPECIAL')){
// take the episode number from the title
let _temp = x.title.split(':') // take the "Ep.xx:" out of the title
let _temp2 = _temp[0].split('.') // take the number out of "Ep.xx"
let _temp3 = y.title.split(':')
let _temp4 = _temp3[0].split('.')
let [_x, _y] = [parseInt(_temp2[1]), parseInt(_temp4[1])] // compare integer values of ep numbers
if ( _x > _y) return 1 // sort
else return -1 // sort
}
else return -1 // just insert the specials
} )
let sorted = [...sortedEpisodes_byDate]
let _tmp = { eps: sorted.reverse(), channel: data[1] }
// return the sorted final output data object, without episodes being sectioned into pages
return _tmp
}).then( response=>{ // take final sorted data and start breaking it into sections for the DOM to render as pages...
if (datastate === undefined) {
// Organize episode info into pages
let numEpsDisplayedPerPage = epsperpage // referred to as 'x' in the comments of this scope
let numOfEps = response.eps.length
let episodePages = []
let pageloop = Math.ceil(numOfEps / numEpsDisplayedPerPage)
// for each page of x eps...
for(let i = 0; i < pageloop; i++){
let pageofepisodes = []
// for each of the x eps within page range...
for(let j = 0; j < numEpsDisplayedPerPage; j++ ){
// 'i*numEpsPerPage' is the index of Episode in the data array, the '+j' selects 1 through x on current page
pageofepisodes.push(response.eps[ (i*numEpsDisplayedPerPage) + j] )
}
episodePages.push([...pageofepisodes]) // make a shallow copy of the output array
}
// remove 'undefined' elements on the last page, if they exist
let len = episodePages.length-1
episodePages[len] = episodePages[len].filter( x => {
return x !== undefined
})
// finally, set the state to the data object which includes channel info, all episodes sorted, and paginated array
let tmp_obj = { ...response }
// remove time from publication date...
tmp_obj.eps.forEach( v => {
v.published = removeTimeFromPubDate(v.published).trim()
})
let tmp_ = { ...tmp_obj, pages: [...episodePages], sortselected: 1, currentPage: 0, buttonText: 'Newest First' }
// console.log(tmp_)
// ready, set state with data object sanitized
setDataState({ ...tmp_ })
// allow for useEffect to render contents
setEpsLoaded(true)
} // This all only runs on the first load, before the state is loaded. Data is parsed and split into pages
})
}, [])
// sanitize publish date to remove time and timezone from display cards next to episode titles
const removeTimeFromPubDate = (published) => published.split(':')[0].slice(0, -2)
"EPISODE" COMPONENT ABOVE THIS LINE--------
"data" imported into "Episode" component below this line-----
class Episode{
constructor(title, link, author, description, published, enclosure, itunestags, mediatags, guid){
this.title = title.innerHTML
this.links = link.innerHTML
this.author = author.innerHTML
this.description = description.innerHTML
this.published = published.innerHTML
this.enc = enclosure.innerHTML
this.itunes = itunestags
this.media = mediatags
this.mp3url = mediatags[1].attributes[0].value // ACCURATE MP3 URL
this.id = guid.innerHTML // NOTE!!! This id is actually a text string url as a unique id in the rss document. not a good id, nor is it a good source for an accurate url string. Use "mp3url" property instead
// for accurate link to mp3 download, use this.media[1].attributes[0].value (0: url, 1: sample rate, 2:lang)
}
}
class Channel{
constructor(title, youtube_channel_url, description, language, copyright, image, itunes_tags, media_tags){
this.title = title.innerHTML
this.url = youtube_channel_url.innerHTML
this.description = description.innerHTML
this.lang = language.innerHTML
this.copyright = copyright.innerHTML
this.image = {
imgurl: image.children[0].innerHTML,
imgdesc: image.children[2].innerHTML,
width: image.children[4].innerHTML,
height: image.children[5].innerHTML
}
this.itunes = {
img: itunes_tags[0],
categories: [itunes_tags[1].innerHTML, itunes_tags[2].innerHTML, itunes_tags[3].innerHTML],
explicit: itunes_tags[4].innerHTML,
title: itunes_tags[5].innerHTML,
subtitle: itunes_tags[6].innerHTML,
summary: itunes_tags[7].innerHTML,
keywords: itunes_tags[8].innerHTML,
author: itunes_tags[9].innerHTML,
owner: itunes_tags[10].innerHTML,
link: itunes_tags[11].innerHTML
}
this.media = media_tags
}
}
// Make array of episodes, each with templated object notation data
let episodes = []
async function whenDataLoaded(){
// FOR PRODUCTION - COMMENT OUT FOR DEVELOPMENT MODE
let data = await fetch('https://krisdriver.com/feed/rssfeed.xml').then( res => {
// "data" is imported for development mode
// console.log(res)
return res.text()
})
// FOR PRODUCTION - COMMENT OUT FOR DEVELOPMENT MODE
// parse xml data and store
const parser = new DOMParser()
const xml = await parser.parseFromString(data, 'application/xml')
const getelements = await xml.getElementsByTagName('rss')
const channel = await getelements[0].getElementsByTagName('channel')[0]
// Create containers for episode data and channel data to create class objects out of
let channelData = await getChannelInfo(channel) // returns array of <channel> children until an <item> tag is traversed
let episodeData = await getEpisodeData(channel) // returns all item tags as html collection
for(let i of episodeData) {
episodes.push(new Episode(i.children[0], i.children[1], i.children[2], i.children[3], i.children[4], i.children[5], [i.children[6], i.children[7], i.children[8], i.children[9]], [i.children[10], i.children[11]], i.children[12]))
}
// Create an object with channel details in object legible notation
let i = await channelData // for less typing
const channelDetails = await new Channel(i[0], i[1], i[2], i[3], i[4], i[5], [ i[6], i[7], i[8], i[9], i[10], i[11], i[12], i[13], i[14], i[15], i[16], i[17] ], [ i[18], i[19], i[20], i[21], i[22], i[23], i[24], i[25], i[26], i[27], i[28], i[29], i[30], i[31], i[32], i[33] ] )
return [episodes, channelDetails]
}
function getEpisodeData(xml){
return xml.getElementsByTagName('item')
}
function getChannelInfo(xmlstream){
// console.log(xmlstream)
let kids = xmlstream.children
let result = []
for(let i = 0; i<kids.length-1; i++){
if(kids[i].tagName === 'item') return result // stop looping once we get to the first item tag, return completed list
else if(kids[i].nodeType === 1){
result.push(kids[i]) // add channel child elements which are not <item> tags to make a channel object with
}
}
}
let result = async function(){
let x = await whenDataLoaded();
return x
}
export default result()
https://krisdriver.com/fg/
THE RSS XML BEING PARSED BELOW------------
<item>
<title>Ep.61: Public Good</title>
<link>https://www.youtube.com/channel/UCb3cCrFqaHFBp2s7jgtJvFg</link>
<author>frivolousgravitas#gmail.com (Kristopher Driver and Jordan Roy)</author>
<description>Today Kris makes a case for government sponsored programs. This is not a narrow topic as allocation of resources is always a complicated mess of interests. Universities, corporations, the media, the general public, and those finding themselves at the bottom, all take part. Join us for this debate as we discuss what might work to lift up the less fortunate in our society so that we may all benefit. QUOTE: “The 25% of Spaniards who are presently without work simply don’t (by Milton’s presumption) want to work at the prevailing wage and are on vacation.” - Mark Blyth, Austerity: The History of a Dangerous Idea #FrivolousGravitas Links: Channel: https://www.youtube.com/channel/UCb3cCrFqaHFBp2s7jgtJvFg FB: https://www.facebook.com/Frivolous-Gravitas-Podcast-109356198202987/ Twitter: https://twitter.com/FrivolousGravi1 RSS: https://krisdriver.com/feed/rssfeed.xml If you like this kind of content and want to see more, be sure to like, share and subscribe to help keep this channel going. Use RSS url to subscribe to Frivolous Gravitas using your favourite podcast player to catch up on past episodes and listen any time you want. No commercials, no sponsors, no ads, just straight talk on demand and on the go. Original Music by Epistra </description>
<pubDate>Feb 18 2022 05:30:00 CST</pubDate>
<enclosure url="https://www.krisdriver.com/feed/podcast/ep61.mp3" type="audio/mpeg" length="187312271"/>
<itunes:author>Kristopher Driver and Jordan Roy</itunes:author>
<itunes:episodeType>full</itunes:episodeType>
<itunes:episode>61</itunes:episode>
<itunes:image>
<url>https://www.krisdriver.com/feed/rssitunesb.jpg</url>
<title>Frivolous Gravitas</title>
<link>https://www.youtube.com/channel/UCb3cCrFqaHFBp2s7jgtJvFg</link>
</itunes:image>
<media:title>Ep.61: Public Good</media:title>
<media:content url="https://www.krisdriver.com/feed/podcast/ep61.mp3" samplingrate="44.1" lang="en" type="audio/mpeg" expression="full"/>
<guid>http://www.krisdriver.com/feed/podcast/ep61.mp3</guid>
</item>
<item>
<title>Ep.60: Charity</title>
<link>https://www.youtube.com/channel/UCb3cCrFqaHFBp2s7jgtJvFg</link>
<author>frivolousgravitas#gmail.com (Kristopher Driver and Jordan Roy)</author>
<description>It is hard to argue that charity is anything but good, but it is not always evident that our offerings are indeed worthwhile. Many who donate don't give much thought beyond the act itself, but charity is not so simple. It can be seen as a gamble, a waste, or a necessity depending on the point of view. We hope you will join us as we look at what makes a good, and bad, act of charity. QUOTE: "Whoever closes his ear to the cry of the poor will himself call out and not be answered." - Proverbs 21:13 #FrivolousGravitas Links: Channel: https://www.youtube.com/channel/UCb3cCrFqaHFBp2s7jgtJvFg FB: https://www.facebook.com/Frivolous-Gravitas-Podcast-109356198202987/ Twitter: https://twitter.com/FrivolousGravi1 RSS: https://krisdriver.com/feed/rssfeed.xml If you like this kind of content and want to see more, be sure to like, share and subscribe to help keep this channel going. Use RSS url to subscribe to Frivolous Gravitas using your favourite podcast player to catch up on past episodes and listen any time you want. No commercials, no sponsors, no ads, just straight talk on demand and on the go. Original Music by Epistra </description>
<pubDate>Feb 11 2022 12:30:00 CST</pubDate>
<enclosure url="https://www.krisdriver.com/feed/podcast/ep60.mp3" type="audio/mpeg" length="133276531"/>
<itunes:author>Kristopher Driver and Jordan Roy</itunes:author>
<itunes:episodeType>full</itunes:episodeType>
<itunes:episode>60</itunes:episode>
<itunes:image>
<url>https://www.krisdriver.com/feed/rssitunesb.jpg</url>
<title>Frivolous Gravitas</title>
<link>https://www.youtube.com/channel/UCb3cCrFqaHFBp2s7jgtJvFg</link>
</itunes:image>
<media:title>Ep.60: Charity</media:title>
<media:content url="https://www.krisdriver.com/feed/podcast/ep60.mp3" samplingrate="44.1" lang="en" type="audio/mpeg" expression="full"/>
<guid>http://www.krisdriver.com/feed/podcast/ep60.mp3</guid>
</item>
A custom sort function, when passed any two array values, must adhere to the Reversal property of inequalities. In other words...
mySort( a, b ) == -mySort( b, a )
...otherwise inconsistent sort results will occur. Per my earlier comment, this appears to be the case with the sortedEpisodes_byDate.sort( (x,y)... algorithm, which does not adhere to the above property for every combination of array values. To exemplify...
a = [ 'def-SPECIAL', 'abc', 'xyz-SPECIAL' ];
function mySort( a, b ) {
if ( a.includes( 'SPECIAL' ) ) {
return -1;
} else {
return a.localeCompare( b );
}
};
console.log( 'Initial Array:' );
console.log( a );
console.log( '\nFirst time sorting...');
console.log( a.sort( mySort ).join( ',' ) );
console.log( '\nSecond time sorting...');
console.log( a.sort( mySort ).join( ',' ) );
console.log( '\nThird time sorting...');
console.log( a.sort( mySort ).join( ',' ) );
Note that mySort, not adhering to the Reversal property, returns inconsistent array sorts. This is because mySort only checks if a.includes( 'SPECIAL' ) and not b, in a similar fashion as the sortedEpisodes_byDate sort is currently coded. Case in point...
mySort( 'abc', 'xyz-SPECIAL' ) returns -1 because 'abc' < 'xyz'
mySort( 'xyz-SPECIAL', 'abc' ) returns -1 because 'xyz-SPECIAL'.includes( 'SPECIAL' )
So now it is a crapshoot as to whether the underlying browser Javascript engine is going to treat 'abc' or 'xyz-SPECIAL' as being the lesser of the other...
Bottom line, I believe the sortedEpisodes_byDate.sort( (x,y)... needs fixing...
I want to retrieve a list of products in relation to the user's position, for this I use Geofirestore and update my Flatlist
When I have my first 10 closest collections, I loop to have each of the sub-collections.
I manage to update my state well, but every time my collection is modified somewhere else, instead of updating my list, it duplicates me the object that has been modified and adds it (updated) at the end of my list and keep the old object in that list too.
For example:
const listListeningEvents = {
A: {Albert, Ducon}
B: {Mickael}
}
Another user modified 'A' and delete 'Ducon', I will get:
const listListeningEvents = {
A: {Albert, Ducon},
B: {Mickael},
A: {Albert}
}
And not:
const listListeningEvents = {
A: {Albert},
B: {Mickael},
}
That's my useEffect:
useEffect(() => {
let geoSubscriber;
let productsSubscriber;
// 1. getting user's location
getUserLocation()
// 2. then calling geoSubscriber to get the 10 nearest collections
.then((location) => geoSubscriber(location.coords))
.catch((e) => {
throw new Error(e.message);
});
//Here
geoSubscriber = async (coords) => {
let nearbyGeocollections = await geocollection
.limit(10)
.near({
center: new firestore.GeoPoint(coords.latitude, coords.longitude),
radius: 50,
})
.get();
// Empty array for loop
let nearbyUsers = [];
// 3. Getting Subcollections by looping onto the 10 collections queried by Geofirestore
productsSubscriber = await nearbyGeocollections.forEach((geo) => {
if (geo.id !== user.uid) {
firestore()
.collection("PRODUCTS")
.doc(geo.id)
.collection("USER_PRODUCTS")
.orderBy("createdDate", "desc")
.onSnapshot((product) => {
// 4. Pushing each result (and I guess the issue is here!)
nearbyUsers.push({
id: product.docs[0].id.toString(),
products: product.docs,
});
});
}
});
setLoading(false);
// 4. Setting my state which will be used within my Flatlist
setListOfProducts(nearbyUsers);
};
return () => {
if (geoSubscriber && productsSubscriber) {
geoSubscriber.remove();
productsSubscriber.remove();
}
};
}, []);
I've been struggling since ages to make this works properly and I'm going crazy.
So I'm dreaming about 2 things :
Be able to update my state without duplicating modified objects.
(Bonus) Find a way to get the 10 next nearest points when I scroll down onto my Flatlist.
In my opinion the problem is with type of nearbyUsers. It is initialized as Array =[] and when you push other object to it just add new item to at the end (array reference).
In this situation Array is not very convenient as to achieve the goal there is a need to check every existing item in the Array and find if you find one with proper id update it.
I think in this situation most convenient will be Map (Map reference). The Map indexes by the key so it is possible to just get particular value without searching it.
I will try to adjust it to presented code (not all lines, just changes):
Change type of object used to map where key is id and value is products:
let nearbyUsersMap = new Map();
Use set method instead of push to update products with particular key:
nearbyUsersMap.set(product.docs[0].id.toString(), product.docs);
Finally covert Map to Array to achieve the same object to use in further code (taken from here):
let nearbyUsers = Array.from(nearbyUsersMap, ([id, products]) => ({ id, products }));
setListOfProducts(nearbyUsers);
This should work, but I do not have any playground to test it. If you get any errors just try to resolve them. I am not very familiar with the geofirestore so I cannot help you more. For sure there are tones of other ways to achieve the goal, however this should work in the presented code and there are just few changes.
I have a method that gets a list of saved photos and determines the number of photos listed. What I wish to do is return the number of photos that contain the text "Biological Hazards" in the name. Here is my code so far
getPhotoNumber(): void {
this.storage.get(this.formID+"_photos").then((val) => {
this.photoResults = JSON.parse(val);
console.log("photoResults", this.photoResults);
// photoResults returns 3 photos
// Hazardscamera_11576868238023.jpg,
// Biological Hazardscamera_11576868238023.jpg,
// Biological Hazardscamera_11576868351915.jpg
this.photoList = this.photoResults.length;
console.log("photoList", this.photoList); // returns 3
this.photoListTwo = this.photoResults.includes('Biological Hazards').length; // I wish to return 2
}).catch(err => {
this.photoList = 0;
});
}
Any help would be greatly appreciated.
Xcode log
[
One way to do this is to .filter() the array, and then calculate the length of that array.
this.photoListTwo = this.photoResults.filter(photoString => {
return photoString === 'Biological Hazards' //or whatever comparison makes sense for your data
}).length;
Quick solution for this (sorry for the lack of better formating, posting from mobile):
const array = ["Hazardscamera_11576868238023.jpg", "Biological Hazardscamera_11576868238023.jpg", "Biological Hazardscamera_11576868351915.jpg"];
const filterBioHazards = (str) => /Biological Hazards/.test(str);
console.log(array.filter(filterBioHazards).length);
// Prints 2
The method includes returns boolean to indicate whether the array contains a value or not. What you need is to filter your array and return its length after.
You need to replace the line:
this.photoListTwo = this.photoResults.includes('Biological Hazards').length;
By this:
this.photoListTwo = this.photoResults.filter(function(result) {return result.contains("Biological Hazards");}).length;
I am building a simple todo app, and I'm trying to get the assigned users for each task. But let's say that in my database, for some reason, the tasks id starts at 80, instead of starting at 1, and I have 5 tasks in total.
I wrote the following code to get the relationship between user and task, so I would expect that at the end it should return an array containing 5 keys, each key containing an array with the assigned users id to the specific task.
Problem is that I get an array with 85 keys in total, and the first 80 keys are undefined.
I've tried using .map() instead of .forEach() but I get the same result.
let assignedUsers = new Array();
this.taskLists.forEach(taskList => {
taskList.tasks.forEach(task => {
let taskId = task.id;
assignedUsers[taskId] = [];
task.users.forEach(user => {
if(taskId == user.pivot.task_id) {
assignedUsers[taskId].push(user.pivot.user_id);
}
});
});
});
return assignedUsers;
I assume the issue is at this line, but I don't understand why...
assignedUsers[taskId] = [];
I managed to filter and remove the empty keys from the array using the line below:
assignedUsers = assignedUsers.filter(e => e);
Still, I want to understand why this is happening and if there's any way I could avoid it from happening.
Looking forward to your comments!
If your taskId is not a Number or autoconvertable to a Number, you have to use a Object. assignedUsers = {};
This should work as you want it to. It also uses more of JS features for the sake of readability.
return this.taskLists.reduce((acc, taskList) => {
taskList.tasks.forEach(task => {
const taskId = task.id;
acc[taskId] = task.users.filter(user => taskId == user.pivot.task_id);
});
return acc;
}, []);
But you would probably want to use an object as the array would have "holes" between 0 and all unused indexes.
Your keys are task.id, so if there are undefined keys they must be from an undefined task id. Just skip if task id is falsey. If you expect the task id to possibly be 0, you can make a more specific check for typeof taskId === undefined
this.taskLists.forEach(taskList => {
taskList.tasks.forEach(task => {
let taskId = task.id;
// Skip this task if it doesn't have a defined id
if(!taskId) return;
assignedUsers[taskId] = [];
task.users.forEach(user => {
if(taskId == user.pivot.task_id) {
assignedUsers[taskId].push(user.pivot.user_id);
}
});
});
});