I am building a weather app for practice. I get to that point that I have to make an autocomplete input field with data from JSON object. When someone makes an input, it displays the matched data, but on click I want to get two properties from the object. I need to get the longitude and latitude properties from JSON object to make an API request to return the object with the weather data. The content displays properly but I can't make that onClick event listener work. I tried very different things and failed, either was a scope problem or something else. It is one of my first projects and I am in a downfall right now. Please help me. :)
P.S. You can find it on this link: https://objective-edison-1d6da6.netlify.com/
// Testing
const search = document.querySelector('#search');
const matchList = document.querySelector('#match-list');
let states;
// Get states
const getStates = async () => {
const res = await fetch('../data/bg.json');
states = await res.json();
};
// Filter states
const searchStates = searchText => {
// Get matches to current text input
let matches = states.filter(state => {
const regex = new RegExp(`^${searchText}`, 'gi');
return state.city.match(regex);
});
// Clear when input or matches are empty
if (searchText.length === 0) {
matches = [];
matchList.innerHTML = '';
}
outputHtml(matches);
};
// Show results in HTML
const outputHtml = matches => {
if (matches.length > 0) {
const html = matches
.map(
match => `<div class="card match card-body mb-1">
<h4>${match.city}
<span class="text-primary">${match.country}</span></h4>
<small>Lat: ${match.lat} / Long: ${match.lng}</small>
</div>`
)
.join('');
matchList.innerHTML = html;
document.querySelector('.match').addEventListener('click', function() {});
//Wconsole.log(matches);
//let test = document.querySelectorAll('#match-list .card');
//const values = matches.values(city);
}
};
window.addEventListener('DOMContentLoaded', getStates);
search.addEventListener('input', () => searchStates(search.value));
If I understand correctly, you're trying to access the lat and lng values of the clicked match, if that is the case, here is one way of doing it:
const outputHtml = matches => {
if (matches.length > 0) {
const html = matches
.map(
match => `<div class="card match card-body mb-1" data-lat="`${match.lat}" data-lng="`${match.lng}">
<h4>${match.city}
<span class="text-primary">${match.country}</span></h4>
<small>Lat: ${match.lat} / Long: ${match.lng}</small>
</div>`
)
.join('');
matchList.innerHTML = html;
document.querySelectorAll('.match').forEach(matchElm => {
matchElm.addEventListener('click', function(event) {
const { currentTarget } = event;
const { lat, lng } = currentTarget.dataset;
});
});
}
};
I've used the data-lat and data-lng attributes to store the required values in the element's dataset and I've used document.querySelectorAll('.match') to get all the elements that have the class match not just the first one.
Related
I'm trying to store the page Id in an array stored in local storage every time a user load a page.
I have my array, it create one if needed but for some reasons it does not update the array in new page load and keeps the first page Id.
I want to add the page id in that array on every page load if the id is not already in that array.
I've tried a lot of things but it seems like I don't understand something, any help ? Thanks
Here is my code
const [isPostId, setItems] = useState([postId]);
useEffect(() => {
//const items = JSON.parse(localStorage.getItem('items'));
if (JSON.parse(localStorage.getItem('isPostId')) == null) {
localStorage.setItem('isPostId', JSON.stringify(isPostId));
}
if (!isPostId.includes(postId)) {
JSON.parse(localStorage.getItem('isPostId'))
localStorage.setItem('isPostId', JSON.stringify(isPostId));
} },[isPostId]);
EDIT: It works now, looks like I was confused about how localStorage works, now it's clear thanks for your help everyone
Both are working:
useEffect(() => {
const storageKey = "isPostId";
const json = localStorage.getItem("isPostId");
const previousPosts = json ? JSON.parse(json) : [];
const filtered = previousPosts.filter((it) => it !== postId);
const updatedPosts = [...filtered, postId];
const stringifyed = JSON.stringify(updatedPosts);
localStorage.setItem("isPostId", stringifyed);
console.log('heu',filtered)
}, [])
useEffect(() => {
// options a - full replace
localStorage.setItem('isPostId', JSON.stringify(isPostId));
// option b - only add unique, don't remove previous
var currentIds = JSON.parse(localStorage.getItem('isPostId')) || [];
isPostId.map((e) => {
if (!currentIds.includes(e) {
currentIds.push(e);
}
})
localStorage.setItem('isPostId', JSON.stringify(currentIds));
}, [isPostId])
Right now the code in the first if statement will put ONE id in local storage if there isn't one already, but not as an array. The code in the second if statement will also only set one id. You need to be setting an array value as shown below
If isPostId is declared as an array:
useEffect(() => {
// options a - full replace
localStorage.setItem('isPostId', JSON.stringify(isPostId));
// option b - only add unique, don't remove previous
var currentIds = JSON.parse(localStorage.getItem('isPostId')) || [];
isPostId.map((e) => {
if (!currentIds.includes(e) {
currentIds.push(e);
}
})
localStorage.setItem('isPostId', JSON.stringify(currentIds));
}, [isPostId])
If isPostId is declared as a string:
If you are certain there will not be single string values in localStorage and there will only be null values or arrays, you can do this as such:
useEffect(() => {
var currentIds = JSON.parse(localStorage.getItem('isPostId')) || [];
if (!currentIds.includes(isPostId) {
currentIds.push(isPostId);
}
localStorage.setItem('isPostId', JSON.stringify(currentIds));
}, [isPostId])
If there is a possibility that there could be individual string values, you will need an additional check for the code inside the useEffect
var currentIds = JSON.parse(localStorage.getItem('isPostId'));
if (!currentIds?.length) {
currentIds = [];
} else if (typeof currentIds !== 'object') {
// value in localStorage is a single string/number rather than an array
currentIds = [currentIds]
);
if (!currentIds.includes(isPostId) {
currentIds.push(isPostId);
}
localStorage.setItem('isPostId', JSON.stringify(currentIds));
Could simplify the second chunk further if desired
If I understood the question correctly, then you need something like this solution.
useEffect(() => {
const storageKey = "isPostId";
const json = localStorage.getItem("isPostId");
const previousPosts = json ? JSON.parse(json) : [];
const updatedPosts = [...previousPosts, ...isPostId];
const uniquePosts = Array.from(new Set(updatedPosts))
const stringifyed = JSON.stringify(uniquePosts);
localStorage.setItem("isPostId", stringifyed);
}, [])
I'm creating a NoteEditor, using react. I have 2 textarea in my popup, and when i'm trying to add my array of strings into object, i have a mistake, that my variable, which is contains this arrayOfStrings returns 'undefined', when i'm clicking the button add note.
There is my function onDescriptionChange, i take the e.target.value from my textarea and add to variable arrayOfStrings, where split this string into array with words:
let onDescriptionChange = (e) => {
setTextAreaHeight(e, '100px');
let stringToSplit = e.target.value;
let arrayOfStrings = stringToSplit.split(' ');
return arrayOfStrings;
};
There is a function addArrayToNote, where I'm trying to add this arrayOfStrings into description:
let addArrayToNote = (arrayOfStrings) => {
setNote({
...note,
description: arrayOfStrings,
});
addNote();
};
I will be very grateful if you help...
I believe you want to invoke the method addArrayToNote after generating the arrayOfStrings.
let onDescriptionChange = (e) => {
setTextAreaHeight(e, '100px');
let stringToSplit = e.target.value;
let arrayOfStrings = stringToSplit.split(' ');
// return arrayOfStrings; instead of returning the value
addArrayToNote(arrayOfStrings) // invoke the addArrayToNote with the strings.
};
let addArrayToNote = (arrayOfStrings) => {
setNote({
...note,
description: arrayOfStrings,
});
addNote();
};
I hope this helps.
I implement WYSIWYG editor with draftjs and I have a set of rules for typography fixing. I use Modifier.replaceText to replace what I want, but the problem is, when I call it, it removes inlineStyles in replaced text.
Here is a block of code that I use for typography. Inputs are rules (array with rules) and editorState.
rules.forEach(({ toReplace, replace }) => {
const blockToReplace = [];
let contentState = editorState.getCurrentContent();
const blockMap = contentState.getBlockMap();
blockMap.forEach(contentBlock => {
const text = contentBlock.getText();
let matchArr;
while ((matchArr = toReplace.exec(text)) !== null) {
const start = matchArr.index;
const end = start + matchArr[0].length;
const blockKey = contentBlock.getKey();
const blockSelection = SelectionState.createEmpty(blockKey).merge({
anchorOffset: start,
focusOffset: end,
});
blockToReplace.push(blockSelection);
}
});
blockToReplace.reverse().forEach((selectionState) => {
contentState = Modifier.replaceText(
contentState,
selectionState,
text.replace(search, replace)
);
});
editorState = EditorState.push(editorState, contentState);
});
So, my input is: *bold...*
The wrong output is: *bold*…
Should be: *bold…*
Note: asterisks are for bold designation, change is three dots to horizontal ellipsis (U+2026)
Anybody any idea? I google it for two days and nothing...
I have my previous question in this link my question
I asked to push all values into an array and show to the HTML. They responded well but it showing only one value(zip1) into an array and get them to HTML.
So i want to get that all values like zip1,zip2, distance, weight based on the group number.
I tried but answer not came
my code altered from previous answer.
const array = [[{"loc":{}},{"distance":6.4},{"zip1":"06120"},{"zip2":"06095"},{"group":1},{"weight":1119}],[{"loc":{}},{"distance":6.41},{"zip1":"06095"},{"zip2":"06120"},{"group":2},{"weight":41976}],[{"loc":{}},{"distance":6.41},{"zip1":"06095"},{"zip2":"06120"},{"group":1},{"weight":41976}]];
const merged = array.map((r, a) =>{
const { group } = a.find(n => n.group)
const { zip1 } = a.find(n => n.zip1)
r[group] = r[group] || []
r[group].push({Zip1:zip1})
const { zip2 } = a.find(n => n.zip2)
r[group].push({Zip2:zip2})
const { weight } = a.find(n => n.weight)
r[group].push({weight:weight})
const { distance } = a.find(n => n.distance)
r[group].push({distance:distance})
return r;
},{})
const output = document.getElementById('output');
Object.entries(merged).forEach(([group, zips]) => {
const h1 = document.createElement('h1');
h1.innerHTML = "group " + group
const span = document.createElement('span');
span.innerHTML = `Zip1 - ${zips.zip1},${zips.zip2},${zips.weight},${zips.distance} (in group - ${group})`;
output.appendChild(h1)
output.appendChild(span)
})
My expected output(but I need to show this in google map infowindow.I just showing example content)
Methodology
Convert your 2D array into a 1D array, so instead of having arrays as inner items you will have objects. This is done through the arrToObj function
Convert your zip values from string to array. This is done to facilitate their _concatenation in the future. Done through the zipToArr function
Group your array of objects under one object. In order to do that we promote the group key and concatenate zip1/zip2 with other objects from the same group. Refer to the grouper function
Get the grouped objects using Object.values on the previous aggregate. We already have the group key in them so we don't need the parent key anymore
Format your values into HTML elements based on their respective keys. This will facilitate generating the HTML in the end since we'll have the elements ready. Done with html and format functions
Render your HTML by iterating on the previously generated array. In each iteration create a container div that will hold the group. The container will help styling its first element group
Implementation
const array = [[{"loc":{}},{"distance":6.4},{"zip1":"06120"},{"zip2":"06095"},{"group":1},{"weight":1119}],[{"loc":{}},{"distance":6.41},{"zip1":"06095"},{"zip2":"06120"},{"group":2},{"weight":41976}],[{"loc":{}},{"distance":6.41},{"zip1":"06095"},{"zip2":"06120"},{"group":1},{"weight":41976}]];
// Data processing functions
const arrToObj = arr => arr.reduce((a, c) => ({ ...a, ...c}), {});
const zipToArr = x => ({...x, zip1: [x.zip1], zip2: [x.zip2]});
const grouper = (a, c) => {
delete c.loc;
delete c.distance;
if (a[c.group]) {
a[c.group].zip1.push(...c.zip1);
a[c.group].zip2.push(...c.zip2);
return a;
} else {
return {...a, [c.group]: c}
}
};
// HTML utilities
const html = (k, v) => {
const it = document.createElement('p');
it.innerHTML = `${k} ${v}`;
return it;
}
const format = g => Object.keys(g).sort().reduce((a, c) => ({...a, [c]: html(c, g[c])}), {});
// Actual processing
const data = array.map(arrToObj).map(zipToArr).reduce(grouper, {});
const dataWithHTML = Object.values(data).map(format);
// Rendering
const display = document.getElementById('display');
dataWithHTML.forEach(it => {
const container = document.createElement('div');
Object.values(it).forEach(v => container.appendChild(v));
display.appendChild(container);
});
p:first-of-type {
font-size: 36px;
font-weight: bold;
margin: 0;
}
p {
text-transform: capitalize;
}
<div id="display"></div>
I need to search a string, and if it has any values that match my array, I need to add <span></span> tags to them to add custom CSS. I am using reactJS.
How do I search the string for objects from my array?
Example:
let string = 'this is a message with many inputs, {{input1}}, {{input2}}, and again {{input1}}'
let array = [{parameter: '{{input1}}'},{parameter: '{{input2}}'},...]
findAllOccurrances = () => {???}
Then systematically replace them '{{inputX}}' with <span className='bizarre-highlight'>{{inputX}}</span>
My intent is to add custom CSS to any text in the div which matches my array, so if you got any ideas please shoot! Again, using reactJS if that helps.
I created a component that will replace the elements that need to be highlighted with a span you can test it here
The component is:
import React from 'react';
export default ({ terms, children }) => {
const result = []
const regex = terms.map(escapeRegExp).join('|');
const re = new RegExp(regex);
let text = (' ' + children).slice(1); // copy
let match = re.exec(text);
while (match != null) {
const str = match.toString();
result.push(text.slice(0, match.index));
result.push(<span className="highlighted">{str}</span>);
text = text.slice(match.index + str.length);
match = re.exec(text);
}
result.push(text);
return result;
}
function escapeRegExp (str) {
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
}
And you should use it like this:
import React from 'react';
import Highlighter from './Highlighter';
const terms = [ '{{input1}}', '{{input2}}' ]
const App = () => (
<div>
<Highlighter terms={terms}>
{'this is a message with many inputs, {{input1}}, {{input2}}, and again {{input1}}'}
</Highlighter>
</div>
);
Use String#replace with a RegExp to find all instances of '{{inputX}}', and wrap the matches with the span:
const string = 'this is a message with many inputs, {{input1}}, {{input2}}, and again {{input3}}'
const array = [{parameter: '{{input1}}'},{parameter: '{{input2}}'}]
const pattern = new RegExp(array.map(({ parameter }) => parameter).join('|'), 'g');
const result = string.replace(pattern, (match) =>
`<span className='bizarre-highlight'>${match}</span>`
)
console.log(result)
use Array#map to extract values for wrapping in <span> and then cycle on them for replacement:
let string = 'this is a message with many inputs, {{input1}}, {{input2}}, and again {{input1}}';
let array = [{parameter: '{{input1}}'},{parameter: '{{input2}}'}];
array.map(el => { return el.parameter }).forEach(str => {
string = string.split(str).join("<span className=\'bizarre-highlight\'>" + str + "</span>");
});
console.log(string);