Creating a property inside of an object that can be continuously used - javascript

I'm the type of person who loves to do a lot of projects especially if it involves only JavaScript since that is my strong point.
I thought of a little fun idea. Writing little pieces of CSS with JavaScript. These CSS pieces could then be used in a Blob or implemented into the webpage some other way.
Most of the time, I do projects just for FUN and for build up in experience.
Let's get more of a feel for what we are working with. One of these JavaScript stylesheets could look like this:
var sheet = {
"h1": {
"font-size": "24px",
"color": "blue",
children: {
"a": {
"font-size": "15px"
}
}
},
"a": {
color: "red"
}
};
This would return:
h1{font-size:24px;color:blue}h1 a{font-size:15px}a{color:red}
Note the children propert in the h1 element.
This is my way of nesting, making the h1 a.
My question however is, how could I make a continuous nesting so I could end up with something like:
"h1 div span a"
Meaning that each nested child will need be able to use the children property.
The script I have so far is below (belongs with the variable sheet).
var to = "";
for (var el in sheet) {
var props = [];
for (var prop in sheet[el]) {
if(prop != "children") {
props.push(prop + ":" + sheet[el][prop]);
}
}
to += el + "{" + props.join(";") + "}";
//----
if (sheet[el].children) {
for (var el2 in sheet[el].children) {
var props = [];
for (var prop in sheet[el].children[el2]) {
props.push(prop + ":" + sheet[el].children[el2][prop]);
}
to += el + " " + el2 + "{" + props.join(";") + "}"
}
}
//----
}
The sections in between the comments is the code I use for the 1 time nesting.
I'm not sure how difficult this would be to add. But I understand that it probable wouldn't be easy.
My full example is here: http://jsfiddle.net/shawn31313/2tfnz/1

You can pretty easily make your code recursive:
function buildCSS(stub, node){
var to = "";
for (var el in node) {
var rule = stub + " " + el;
var props = [];
for (var prop in node[el]) {
if(prop != "children") {
props.push(prop + ":" + node[el][prop]);
}
}
to += rule + "{" + props.join(";") + "}";
if (node[el].children) {
to += buildCSS(rule, node[el].children);
}
}
return to;
}
var to = buildCSS("", sheet);
This can definitely be cleaned up, but it illustrates the idea.
http://jsfiddle.net/2tfnz/3/
You might also consider a tweak to your object structure to make the code a bit cleaner:
var sheet = {
"h1": {
rules: {
"font-size": "24px",
"color": "blue"
},
children: {
"a": {
rules: {
"font-size": "15px"
}
}
}
},
"a": {
rules: {
color: "red"
}
}
};
In this way, you wouldn't need to distinguish between properties named children and those which aren't.

http://jsfiddle.net/2tfnz/6/
Put the code in a method so it can recursively call itself when it finds children. This one allows for both children and descendant properties and outputs nicely formatted CSS.
var sheet = {
"h1": {
"font-size": "24px",
"color": "blue",
children: {
"a": {
"font-size": "15px",
descendants: {
"span": {
"font-weight": "bold"
}
}
}
}
},
"a": {
color: "red"
}
};
function toCSS(obj, pre) {
var str = '', pre = pre || '';
for (var selector in obj) {
str += pre + selector + ' {\n';
var rules = obj[selector];
for (var ruleKey in rules) {
if (['descendants', 'children'].indexOf(ruleKey) > -1) continue;
str += ' ' + ruleKey + ': ' + rules[ruleKey] + ';\n';
}
str += '}\n\n';
if ('descendants' in rules) str += toCSS(rules.descendants, pre + selector + ' ');
if ('children' in rules) str += toCSS(rules.children, pre + selector + ' > ');
}
return str;
}
console.log(toCSS(sheet));

I like all of you guy's code. However, I told the idea of making my code recursive.
So I've made:
var to = "";
for (var el in sheet) {
var props = [];
var nest = [el];
var nestLookUp = {
"children": ">",
"desendents": ""
};
var nests = /children|desendents/;
var addNest = function (shh) {
for (var found in nestLookUp) {
if (shh.hasOwnProperty(found)) {
for (var el2 in shh[found]) {
var props = [];
nest.push(nestLookUp[found]);
nest.push(el2);
for (var prop in shh[found][el2]) {
if (!prop.match(nests)) {
props.push(prop + ":" + shh[found][el2][prop]);
}
}
if (props.length > 0) {
to += nest.join(" ").replace(/\s{2}/, " ") + "{" + props.join(";") + "}";
}
addNest(shh[found][el2]);
nest.pop();
nest.pop();
};
}
}
};
for (var prop in sheet[el]) {
if (prop != "children") {
props.push(prop + ":" + sheet[el][prop]);
}
}
to += el + "{" + props.join(";") + "}";
addNest(sheet[el]);
}
#Trevor - Your Idea with the descendants, is a good one :)
My code is a little longer than Trevors but personally more manageable. (39 lines his is 20)
I usually like making my own code because I usually understand how the code works.
Ha, I was a little confused looking at you guys code.

Related

How can I set the number of decimals on a popup using JavaScript?

I have a popup on a website that displays values from a geoJSON file. Right now, it looks as in the picture below:
This is the current code:
function popUp_cc_210303(f, l) {
var out = [];
if (f.properties) {
var url =
'<a href=/uas_tools/crop_analysis/index.php?crop=Wheat&year=2021&location=Amarillo&sublocation=Irrigation';
var parameters = '';
for (key in f.properties) {
out.push(key + ': ' + f.properties[key]);
parameters += '&' + key.replace(/\ /g, '_') + '=' + f.properties[key];
}
url +=
parameters.replace(/\ /g, '%20') + " target='_blank'>Growth analysis</a>";
out.push(url);
var url2 =
'<a href=/uas_tools/variety_analysis/index.php?crop=Wheat&year=2021&location=Amarillo&sublocation=Irrigation';
url2 += " target='_blank'>Variety analysis</a>";
out.push(url2);
l.bindPopup(out.join('<br />'));
}
}
I am trying to use out.push(key+": "+f.properties[key].toFixed(2)); but it does not work.
This is the geoJSON file structure
{ "type": "Feature", "properties": { "Row name": "row-1", "Col": "1", "plot_num": "?436", "plot_name": "?AOBS", "join_key": "?AOBS?436", "CC201014": 0.0, "CC201104": 0.0016344676538850001, "CC201120": 0.56401258728343395, "CC201217": 8.3524346613304221, "CC210113": 7.7746312091202094, "CC210224": 9.7393145428079926, "CC210303": 7.673018393542411, "CC210311": 14.576431943872961, "CC210323": 31.081778483525209, "CC210331": 30.067189249720045, "CC210408": 62.738628486108894, "CC210412": 63.94711538461538, "CC210418": 73.721694264987974, "CC210423": 70.039654826897262, "CC210430": 98.045130406889243, "CC210504": 91.969625530436502, "CC210510": 93.321666364934728, "CC210517": 85.521939491083955, "CC210525": 88.782478347768162, "CC210601": 95.859434682964093, "CC210607": 15.974798327739503, "CC210610": 0.0085470085470090006, "CC210614": 0.0, "CC210617": 0.0 }
The toFixed() method only works on floats, it appears you may have a string. You could parse it to a float first.
out.push(key + ': ' + parseFloat(f.properties[key]).toFixed(2));
One-liners are not your friend. JavaScript has its own ideas about priority. To force "order of operation", use parenthesis around any important group. Even then, code editors may format, prettify, or remove the parenthesis. Good luck finding the error then! It is best to isolate any complex operation in a named variable. A named variable is more descriptive and easier to read and reuse.
Seems like the code is calling toFixed on all values, even if the value is a string.
for (key in f.properties) {
//The first few prperties are strings, this is throwing an error as String.prototype.toFixed is not defined, so you can't call it.
out.push(key + ': ' + parseFloat(f.properties[key]).toFixed(2));
parameters += '&' + key.replace(/\ /g, '_') + '=' + f.properties[key];
}
Try this instead
for (var key in f.properties) {//include var to not add key to global scope
var val = f.properties[key]
if (typeof val === "number") {val = val.toFixed(2)}
out.push(key + ': ' + val);
parameters += '&' + key.replace(/\ /g, '_') + '=' + val;
}
Also, as noted in other answers, the values with floates might still be strings, if this is the case, you could try:
for (var key in f.properties) {//include var to not add key to global scope
var val = f.properties[key]
if (Number.isNaN(Number(val))) val = Number(val).toFixed(2)
out.push(key + ': ' + val);
parameters += '&' + key.replace(/\ /g, '_') + '=' + val;
}
You could also use the Array.reduce() method:
var out = Object.keys(f.properties)
.reduce((a, c) => (typeof f.properties[c] === `number` ?
a.push(`${c}: ${f.properties[c].toFixed(2)}`) :
a.push(`${c.replace(/\s/, `_`).toLowerCase()}: ${f.properties[c]}`), a), [])
const f = {
"type": "Feature",
"properties": {
"Row name": "row-1",
"Col": "1",
"plot_num": "?436",
"plot_name": "?AOBS",
"join_key": "?AOBS?436",
"CC201014": 0.0,
"CC201104": 0.0016344676538850001,
"CC201120": 0.56401258728343395,
"CC201217": 8.3524346613304221,
"CC210113": 7.7746312091202094,
"CC210224": 9.7393145428079926,
"CC210303": 7.673018393542411,
"CC210311": 14.576431943872961,
"CC210323": 31.081778483525209,
"CC210331": 30.067189249720045,
"CC210408": 62.738628486108894,
"CC210412": 63.94711538461538,
"CC210418": 73.721694264987974,
"CC210423": 70.039654826897262,
"CC210430": 98.045130406889243,
"CC210504": 91.969625530436502,
"CC210510": 93.321666364934728,
"CC210517": 85.521939491083955,
"CC210525": 88.782478347768162,
"CC210601": 95.859434682964093,
"CC210607": 15.974798327739503,
"CC210610": 0.0085470085470090006,
"CC210614": 0.0,
"CC210617": 0.0
}
};
const urls = [
`/uas_tools/crop_analysis/index.php`,
`/uas_tools/variety_analysis/index.php`
];
const div = document.createElement('div');
Object.keys(f.properties)
.reduce((a, c) => (typeof f.properties[c] === `number` ?
a.push(`${c}: ${f.properties[c].toFixed(2)}`) :
a.push(`${c.replace(/\s/, `_`).toLowerCase()}: ${f.properties[c]}`), a), [])
.map(el => {
const br = document.createElement('br');
const span = document.createElement('span');
span.innerText = el;
div.appendChild(span).appendChild(br);
});
const parameters = {
crop: `Wheat`,
year: 2021,
location: `Amarillo`,
sublocation: `Irrigation`
};
urls.map(pathName => {
const url = new URL(pathName, window.location.origin);
Object.keys(parameters)
.map(elem => url.searchParams.set(elem, parameters[elem]));
const br = document.createElement('br');
const a = document.createElement('a');
a.href = url.toString();
a.target = `_blank`;
a.innerText = url.pathname
.split(`/`)
.slice(-2, -1)[0]
.split(`_`)
.map(el => el.charAt(0).toUpperCase() + el.substr(1).toLowerCase())
.join(` `);
div.appendChild(a).appendChild(br);
});
document.querySelector(`body`).appendChild(div);
body {
font-family: sans-serif;
font-size: 12px;
}

How to get rid of many IFs and make a system to differ elements?

In this piece of code you can see a JSON request that fetches some data. I need some help to check certain opportunities of minimizing the code and getting iterations with FOR instead of many IFs. Also, it would be nice if you advise anything on the differentiation system (how to make elements differ from each other)
<script type="text/javascript">
function deleteRow0() {
$('p.row0').remove();
};
function deleteRow1() {
$('p.row1').remove();
};
function deleteRow2() {
$('p.row2').remove();
};
function deleteRow3() {
$('p.row3').remove();
};
function deleteRow4() {
$('p.row4').remove();
};
</script>
<script type="text/javascript">
function hello2() {
//GETTING JSON INFO
$.getJSON("https://rawgit.com/Varinetz/e6cbadec972e76a340c41a65fcc2a6b3/raw/90191826a3bac2ff0761040ed1d95c59f14eaf26/frontend_test_table.json", function(json) {
$('#table-cars').css("display", "grid");
for (let counter = 0; counter < json.length; counter++) {
$('#table-cars').append("<p class='row" + counter +" main-text'>" + json[counter].title + "<br/>" + "<span class='sub-text'>" + json[counter].description + "</span>" + "</p>"
+ "<p class='row" + counter +" main-text'>" + json[counter].year + "</p>"
+ "<p id='color" + [counter] + "' class='row" + counter +" main-text'>" + json[counter].color + "</p>"
+ "<p id='status" + [counter] + "' class='row" + counter +" main-text'>" + json[counter].status + "</p>"
+ "<p class='row" + counter +" main-text'>" + json[counter].price + " руб." + "</p>"
+ "<p class='row" + counter +" main-text'>" + "<button class='delete' onclick='deleteRow" + [counter] + "()'>Удалить</button>" + "</p>");
// COLOR TEXT REPLACEMENT
if ($('p#color0').text("red")){
$('p#color0').text("").append("<img src='red.png'>");
}
if ($('p#color1').text("white")) {
$('p#color1').text("").append("<img src='white.png'>");
}
if ($('p#color2').text("black")) {
$('p#color2').text("").append("<img src='black.png'>");
}
if ($('p#color3').text("green")) {
$('p#color3').text("").append("<img src='green.png'>");
}
if ($('p#color4').text("grey")) {
$('p#color4').text("").append("<img src='grey.png'>");
}
// STATUS TEXT REPLACEMENT
if ($('p#status0').text("pednding")) {
$('p#status0').text("").append("Ожидается");
}
if ($('p#status1').text("out_of_stock")) {
$('p#status1').text("").append("Нет в наличии");
}
if ($('p#status2').text("in_stock")) {
$('p#status2').text("").append("В наличии");
}
if ($('p#status3').text("out_of_stock")) {
$('p#status3').text("").append("Нет в наличии");
}
if ($('p#status4').text("in_stock")) {
$('p#status4').text("").append("В наличии");
}
}
});
}
</script>
I expect this to be something like:
1) Iteration: For each p.row(i) {
compare it to many color (json.color)};
2) Any suggestion on differentiation system (i.e. changes in the FOR section, so it gives something easier to work with, not just simple p.row(n)). Of course, if it is possible.
I'm not going to rewrite the entire script, but in principle it would be something like this:
for (i = 0; i < 5; i++) {
var colors = ["red", "white", "black", "green", "grey"];
if ($('p#color' + i).text() == colors[i]){
$('p#color' + i).text("").append("<img src='" + colors[i] + ".png'>");
}
}
#Evik Ghazarian has a quality solution for the Text Translation portion of your script. Since this is the accepted answer, he allowed me to copy his solution so that the answers would be together:
function getTranslate(input) {
var inputMap = {
"pednding": "Ожидается",
"out_of_stock": "Нет в наличии",
"in_stock": "В наличии"
}
var defaultCode = input;
return inputMap[input] || defaultCode;
}
for (let i = 0; i < 5 , i ++){
var text = $("p#status"+i).text();
$("p#status"+i).text("").append(getTranslate(text));
}
Dynamic Iteration Counters
#Barmar mentioned in the comments below that the for loops that set a max iteration via i < 5 should actually be rewritten dynamically. I'll leave it to the OP to decide the best way to do this, but a good example might be something like i < json.length as used in the OP's original for loop.
First of all your code won't work because you are setting the text rather than comparing it. Second, you don't need to compare: just set the img src to text. Like below:
REMEMBER THIS IS FOR COLOR TEXT REPLACEMENT PART OF YOUR QUESTION
for (let i = 0; i < 5 , i ++){
let color = $("p#color"+i).text() + ".png";
$("p#color"+i).text("").append("<img src=" + color + ">");
}
FOR TEXT TRANSLATION YOU CAN USE:
function getTranslate(input) {
var inputMap = {
"pednding": "Ожидается",
"out_of_stock": "Нет в наличии",
"in_stock": "В наличии"
}
var defaultCode = input;
return inputMap[input] || defaultCode;
}
for (let i = 0; i < 5 , i ++){
var text = $("p#status"+i).text();
$("p#status"+i).text("").append(getTranslate(text));
}

Looping over JavaScript object and adding string to end if not the last item

{
field_country: ["England", "Netherlands", "India", "Italy"],
field_continent: ["Europe"],
field_group: ["Building", "People", "Landscape"
}
I want to loop over each item and return the key and the array together with ending 'OR' for example:
field_country: "England" OR field_country: "Netherlands"
The last item should not end with 'OR' in the loop. I am not sure what the best process is for this using vanilla JS. So far my code is as follows:
Object.keys(facets).forEach(function(facetKey) {
if (facets[facetKey].length > 1) {
facetResults = facets[facetKey];
for (var i = 0; i < facetResults.length; i ++) {
if (i == 1) {
filter = "'" + facetKey + "'" + ":'" + facetResults[i] + " OR";
return filter;
} else {
filter = "'" + facetKey + "'" + ":'" + facetResults[i];
}
}
} else {
filter = "'" + facetKey + "'" + ": " + facets[facetKey] + "'";
return filter;
}
});
I would be very grateful for any assistance.
Thanks in advance.
You can do something like this with Object.entries and Array.reduce if you would like to get the final result in the form of an object:
const data = { field_country: ["England", "Netherlands", "India", "Italy"], field_continent: ["Europe"], field_group: ["Building", "People", "Landscape"] }
const result = Object.entries(data).reduce((r, [k, v]) => {
r[k] = v.join(' OR ')
return r
}, {})
console.log(result)
It is somewhat unclear what is the final format you need to result in but that should help you to get the idea. If ES6 is not an option you can convert this to:
const result = Object.entries(data).reduce(function(r, [k, v]) {
r[k] = v.join(' OR ')
return r
}, {})
So there are is no arrow function etc.
The idea is to get the arrays into the arrays of strings and use the Array.join to do the "replacement" for you via join(' OR ')
Here's the idea. In your code you are appending " or " at the end of your strings starting at index 0. I suggest you append it at the the beginning starting at index 1.
var somewords = ["ORANGE", "GREEN", "BLUE", "WHITE" ];
var retval = somewords[0];
for(var i = 1; i< somewords.length; i++)
{
retval += " or " + somewords[i];
}
console.log(retval);
//result is: ORANGE or GREEN or BLUE or WHITE
Your conditional expression if (i == 1) would only trigger on the second iteration of the loop since i will only equal 1 one time.
Try something like:
if (i < (facetResults.length - 1)) {
// only add OR if this isn't the last element of the array
filter = "'" + facetKey + "'" + ":'" + facetResults[i] + " OR";
return filter;
}
Here's your updated code:
Object.keys(facets).forEach(function(facetKey) {
if (facets[facetKey].length > 1) {
facetResults = facets[facetKey];
for (var i = 0; i < facetResults.length; i ++) {
if (i < (facetResults.length - 1)) {
filter = "'" + facetKey + "'" + ":'" + facetResults[i] + " OR";
return filter;
} else {
filter = "'" + facetKey + "'" + ":'" + facetResults[i];
}
}
} else {
filter = "'" + facetKey + "'" + ": " + facets[facetKey] + "'";
return filter;
}
});

Display element of array onclick

This code displays the content of JSON file by formatting every word into sentences and then into HTML. On mouseover, words become blue. On click they become red. The next thing I want to do is to display the translation of the words (already in the json array) onclick.
https://jsfiddle.net/ve64qvtm/
var json = [
[
["Peki", "Well"],
["nedir", "what"],
["bu", "it"],
...
]
];
var arr2 = [];
for (k = 0; k < json.length; k++) {
var arr = json[k];
arr2.push('<p>');
for (i = 0; i < arr.length; i++) {
if (arr[i][0].length == 1) {
arr2.push(arr[i][0]);
} else {
arr2.push(' <span class="notclicked word ' + i + '">' + arr[i][0] + '</span>');
}
}
arr2.push('</p>');
}
document.getElementById("text").innerHTML = arr2.join('');
var words = [...document.getElementsByClassName("word")];
words.forEach(function(word) {
word.onclick = function() {
if (word.className == "clicked") {
word.className = 'notclicked';
}
if (word.className == "onmouse") {
word.className = 'clicked';
}
}
word.onmouseover = function onMouse() {
if (word.className != "clicked") {
word.className = 'onmouse';
}
}
word.onmouseout = function onMouse() {
if (word.className != "clicked") {
word.className = 'notclicked';
}
}
});
I have no idea how to do this as the text to display is a variable.
How am I supposed to do this?
How about using Twitter Bootstraps tooltip. Add jQuery, bootstraps JS and CSS; once all this is added you would need to edit the line
arr2.push(' <span class="notclicked word ' + i + '">' + arr[i][0] + '</span>');
To something like
arr2.push(' <span class="notclicked word ' + i + '" data-toggle='tooltip' data-placement='top' title='YOUR_TRANSLATION_HERE'>' + arr[i][0] + '</span>');
EDIT 2 - Updated Link:
Here is a working example
Edit 3
I Would also add a bit of margin on top and bottom so that you don´t get unexpected behaviour from the tooltips, just because there is no space.

parsing JSON efficiently

so I'm parsing through a JSON object like so
if(val.function1!==""){
$("#contentFunction").text(val.function1);
}
if(val.function2!==""){
$("#contentFunction").text(val.function1 + "; " + val.function2);
}
if(val.function3!==""){
$("#contentFunction").text(val.function1 + "; " + val.function2
+ "; " + val.function3);
}
I'm wondiering if there is a better way of checking if my json object property is empty instead of having tons of conditions... this gets really messy if for instance I have up to val.function10
Thanks for your help
var strs = [];
for (var i = 0; i < 10; i++) {
var value = val["function" + i];
value && strs.push(value);
}
$("#contentFunction").text(strs.join("; "));
Something like this?
var content = "";
for (var prop in val) {
if (!val.hasOwnProperty(prop)) continue;
if (val[prop] !== "") {
content += "; " + val[prop];
}
}
Or in node.js (or modern browsers):
var c = Object.keys(val).filter(function (k) {
return val[k] !== "";
}).map(function (k) {
return val[k];
}).join("; ");
A tool like underscorejs will help you enumerate functions and properties.

Categories

Resources