JS or jQuery way to dynamically create DOM elements - javascript

Could anyone point me a more easy-to-read-and-expand way of doing this:
data = {/*very very long json*/};
var inHTML = "";
jQuery.each(data, function(key, value){
var articleUrl = 'url-to-somewhere';
var newItem = "<div class='item'><div class='item--poster'><img src='"+data[key].backdropUrl+"' alt='title'/></div><div class='item--content'><h2>"+data[key].title+"</h2><ul><li>"+data[key].productionYear+"</li><li>"+data[key].productionCountry+"</li></ul></div><div class='item--link'><p><a class='' href='"+articleUrl+"'>Lue lisää</a></p></div></div>";
inHTML += newItem;
});
jQuery("#container").html(inHTML);
What I'm looking for is something similar to ng-repeat of Angular.

I would bet on using placeholder template and .clone(). What exactly you need to do is, create a Master DOM like this:
<div id="master-dom" class="item">
<p><strong>Name</strong> <span class="Name"></span></p>
<p><strong>Age</strong> <span class="Age"></span></p>
</div>
Now give a CSS that would hide the Master DOM:
#master-dom {display: none;}
The next attempt would be, have a #content area:
<div id="content"></div>
And now comes the JavaScript part.
var data = [
{
"name": "Praveen",
"age": 27
},
{
"name": "Jon Skeet",
"age": 29
},
{
"name": "Kumar",
"age": 25
}
];
Having the above as the data structure, you can loop through and insert:
$.each(data, function (i, v) {
// We need the v.
$("#master-dom").clone()
.removeAttr("id")
.find(".Name").text(v.name).end()
.find(".Age").text(v.age).end()
.appendTo("#content");
});
See the final output here:
$(function() {
var data = [{
"name": "Praveen",
"age": 27
}, {
"name": "Jon Skeet",
"age": 29
}, {
"name": "Kumar",
"age": 25
}];
$.each(data, function(i, v) {
// We need the v.
$("#master-dom").clone()
.removeAttr("id")
.find(".Name").text(v.name).end()
.find(".Age").text(v.age).end()
.appendTo("#content");
});
});
* {
margin: 0;
padding: 0;
list-style: none;
}
#master-dom {
display: none;
}
.item p strong {
display: inline-block;
width: 75px;
}
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<div id="master-dom" class="item">
<p><strong>Name</strong> <span class="Name"></span></p>
<p><strong>Age</strong> <span class="Age"></span></p>
</div>
<div id="content"></div>
I really believe this would be the underlying logic behind Angular's ng-repeat. Also I would use this above logic if I were in your place.

You could use ES6 feature Template literals
Your string would look like this
var newItem =
`<div class='item'>
<div class='item--poster'>
<img src='${data[key].backdropUrl}' alt='title'/>
</div>
<div class='item--content'>
<h2>${data[key].title}</h2>
<ul>
<li>${data[key].productionYear}</li>
<li>${data[key].productionCountry}</li>
</ul>
</div>
<div class='item--link'>
<p>
<a class='' href='${articleUrl}'>Lue lisää</a>
</p>
</div>
</div>`;

Related

modify handlebars template with javascript/jquery

I have a handlebars template such as this
<script type="text/html" id="some-id">
<div class="some-class-1" {{#if some_condition}}style="color:blue;"{{/if}}>
</div>
<div class="some-class-2">
<div class="some-nested-class-1">
</div>
</div>
<div class="some-class-3">
</div>
</script>
I want to modify this template for example add another div after div.some-nested-class-1
I used the following code, to parse the template to dom
jQuery(function ($) {
var t = $("#some-id");
var d = $("<div>").html(t.html());
console.log(d.html());
});
but it changes {{#if}} as follows :
<div class="some-class-1" {{#if="" some_condition}}style="color:blue;" {{="" if}}="">
</div>
<div class="some-class-2">
<div class="some-nested-class-1">
</div>
</div>
<div class="some-class-3">
</div>
Using Chrome and jsfiddle here https://jsfiddle.net/wv7hzx5a/
How can I achieve this without affecting the template?
Many thanks
PS: the template is being compiled in a separate script which I do not have access to change. I want to modify the template so that when the other script compiles it, the new elements will be included.
I don"t understand what you're trying to to and what you mean by parse the template to DOM. If you want to process the template and replace the handlebar code to have html, then use handlebar to do it and not jQuery. Only handlebar can interpret and translate the handlebar instructions to have the result. Then once that you have your result you can display it with jQuery if you want. Here is one example :
var context = {
"mydata": [
{
"character-id": "Luke1",
"firstname": "Luke",
"name": "Skywalker",
"age": "20",
"rebel": true
},
{
"character-id": "Leia1",
"firstname": "Leia",
"name": "Organa",
"age": "20",
"rebel": true
},
{
"character-id": "Vader1",
"firstname": "Darth",
"name": "Vader",
"age": "45",
"rebel": false
}
]
}
// Here you process the template and put it in the DOM
var template = $('#handlebars-demo').html();
var templateScript = Handlebars.compile(template);
var html = templateScript(context);
$("#placeholder").append(html);
// Once you have inserted your code in the DOM you can now use jQuery to modify it
// For example I put a star after Leia's section
$("#Leia1").html($("#Leia1").html()+"*")
</script>
.some-class-1 {
padding: 10;
min-width: 10px;
min-height: 20px;
border: solid blue 1px;
}
.some-class-2 {
padding: 10;
min-width: 10px;
min-height: 20px;
border: solid black 1px;
}
.some-class-3 {
padding: 10;
min-width: 10px;
min-height: 20px;
border: solid green 1px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.min.js"></script>
<script type="text/html" id="handlebars-demo">
{{#each mydata}}
<div id="{{character-id}}">
<div class="some-class-1" {{#if rebel}}style="color:blue;"{{/if}}>
{{firstname}}
</div>
<div class="some-class-2" {{#if rebel}}style="color:blue;"{{/if}}>
{{name}}
</div>
<div class="some-class-3">
<div class="some-nested-class-1">
{{age}}
</div>
</div>
</div>
<br/>
{{/each}}
</script>
<div id="placeholder"></div>

trying to get values form a json object in a nested array

So the JSON I need to use is structured like this:
[
{
"result":"OK",
"message":"Display",
"value":200,
"rows":29
} ,
[
{
"personID":1,
"img_path":"/1234/",
"img":"00001.jpg"
},
{
"personID":2,
"img_path":"/1234/",
"img":"00002.jpg"
},
]
]
How do it get JUST this part?:
personID: 1
img_path: /1234/
img: 00001.jpg
Here's what I'm doing currently, which is outputs the full JSON... (exactly as it's shown in the first codeblock I added... the one that was showing how the JSON is structured).
var fullURL = the_URL_where_Im_getting_the_json
function readTextFile(file, callback)
{
var rawFile = new XMLHttpRequest();
rawFile.overrideMimeType("application/json");
rawFile.open("GET", file, true);
rawFile.onreadystatechange = function() {
if (rawFile.readyState === 4 && rawFile.status == "200")
{
callback(rawFile.responseText);
}
}
rawFile.send(null);
}
readTextFile(fullURL, function(text){
var data = JSON.parse(text);
console.log(data);
}
);
I appreciate you time and help. Thanks.
Trying to access this via indexes doesn't seem too robust. If you know the shape is going to be consistent with this type of output, you can deconstruct the data into info and results and then iterate over them. If you know what identifier you are looking for, for example, you can use find.
I have extended this example a bit to try to show how using functions like map and find can work over larger sets of data, as whatever project you are working on gets bigger. Front-end frameworks like React do a lot of this stuff for you.
const data = [{
"result": "OK",
"message": "Display",
"value": 200,
"rows": 29
},
[{
"personID": 1,
"img_path": "/1234/",
"img": "00001.jpg"
},
{
"personID": 2,
"img_path": "/1234/",
"img": "00002.jpg"
},
]
]
const [info, results] = data;
document.getElementById('information').innerHTML = Object.entries(info).map(([key, value]) => `
<div>
<span class="key">${key.toUpperCase()}:</span>
<span class="value">${value}</span>
</div>`).join('');
document.getElementById('results').innerHTML = results.map(result => {
return `<div>ID: ${result.personID}, path: ${result.img_path}</div>`
}).join('');
document.getElementById('find').addEventListener('keyup', function() {
document.getElementById('target').innerHTML = (results.find(result => result.personID == this.value) || {
img: 'Not Found'
}).img
})
.cards {
display: flex;
}
.card {
box-shadow: 1px 1px 10px;
padding: 16px;
width: 25%;
margin: 6px;
}
.card-title {
font-size: 2em;
border-bottom: 1px solid black;
padding: 6px 6px 6px 0px;
}
.card-content {
display: flex;
flex-direction: column;
align-items: space-between;
}
.card-content>div {
margin: 6px;
display: flex;
justify-content: space-between;
}
input {
width: 50px;
}
<div class="cards">
<div class="card">
<div class="card-title">
Information
</div>
<div id="information" class="card-content"></div>
</div>
<div class="card">
<div class="card-title">
All People
</div>
<div id="results" class="card-content"></div>
</div>
<div class="card">
<div class="card-title">
Find IMG
</div>
Person ID:
<input id="find" />
<div id="target" class="card-content" />
</div>
</div>
I really feel the data in your file should be modified for consistency.
For now you can do this:
//loop through this if you want data of all objects in the 2nd item i.e data[1][0...n]
var objectData = data[1][0]
var personID = objectData.personID
var img = objectData.img
var img_path = objectData.img_path

Generating a json object from given data

I'm having difficulty generating a json object in the below format from the data below. Fiddle
[ { Invoice:
{
headers: { date: "15-01-2020", buyer: "McDonalds", order: "145632"},
items: { name: "Big Mac", quantity: "5", rate: "20.00"},
items: { name: "Small Mac", quantity: "10", rate: "10.00"}
}
}
, { Invoice: { // Other invoices go here, I've used just one for this example} }
]
<div class="invoice">
<div class="header">
<div contenteditable data="date">15-Jan-2020</div>
<div contenteditable data="buyer">McDonalds</div>
<div contenteditable data="order">145632</div>
</div>
<div class="item">
<div contenteditable data="name">Big Mac</div>
<div contenteditable data="quantity">5</div>
<div contenteditable data="rate">20.00</div>
</div>
<div class="item">
<div contenteditable data="name">Small Mac</div>
<div contenteditable data="quantity">10</div>
<div contenteditable data="rate">10.00</div>
</div>
</div>
<button>Loop</button>
jQuery
var button = $("button")
button.on("click",function() {
jsonObj =[];
$('.invoice>.header>div, .invoice>.item>div').each(function(index,item) {
console.log($(this).parent().attr('class'));
console.log($(this).attr('data'),$(this).text());
q = {}
q ['header'] = $(this).parent().attr('class');
q [$(this).attr('data')] = $(this).text();
jsonObj.push(q);
});
console.log(jsonObj);
console.log(JSON.stringify(jsonObj));
});
I current end up with an object like this where the keys are repeated everywhere. How can I get this right?
[ { "header": "header", "date": "15-Jan-2020"}
, { "header": "header", "buyer": "McDonalds"}
, { "header": "header", "order": "145632"}
, { "header": "item", "name": "Big Mac"}
, { "header": "item", "quantity": "5"}
, { "header": "item", "rate": "20.00"}
, { "header": "item", "name": "Small Mac"}
, { "header": "item", "quantity": "10"}
, { "header": "item", "rate": "10.00"}
]
In your example, you have an object with two same keys:
"items":{
"name":"Big Mac",
"quantity":"5",
"rate":"20.00"
}
"items":{
"name":"Small Mac",
"quantity":"10",
"rate":"10.00"
}
This won't work, because you can only have one, so you need to change the value of items key to an array of objects:
"items":[
{
"name":"Big Mac",
"quantity":"5",
"rate":"20.00"
},
{
"name":"Small Mac",
"quantity":"10",
"rate":"10.00"
}
]
The iteration code may look like this:
const jsonObj = [];
$('.invoice').each((index, item) => {
const invoice = {
header: {},
items: []
};
$(item).find('.header > div').each((index, item) => {
const key = $(item).attr('data');
invoice.header[key] = $(item).text();
});
$(item).find('.item').each((index, item) => {
const itemObj = {};
$(item).find('div').each((index, item) => {
const key = $(item).attr('data');
itemObj[key] = $(item).text();
});
invoice.items.push(itemObj);
});
jsonObj.push({
Invoice: invoice
});
});
The main difference from your version is that it iterates through the dom step by step. First, through each invoice, then through each header of the invoice and each item. This way it's easy to build the desired structure.
Here's the jsfiddle link: https://jsfiddle.net/tara5/tanb174h/
Pure JS code for this:
Remarks : I have changed:
<div ... data="..."> ... </div>
to
<div ... data-ref="..."> ... </div>
to be conform with HTML directives (see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-* )
const jsonObj = []
, inVoices = document.querySelector('.invoice')
, subDivOf = (parent,query) => [...parent.querySelectorAll(query)]
, dataElms = (ac,el)=>{ac[el.dataset.ref]=el.textContent;return ac }
;
Loop.onclick=_=>
{
jsonObj.push( { Invoice: getInVoicesValues() } )
console.clear()
console.log( jsonObj )
}
function getInVoicesValues()
{
const headers = subDivOf(inVoices,'.header>div').reduce(dataElms,{})
, items = subDivOf(inVoices,'.item').reduce((accI,itm)=>
{
accI.push( subDivOf(itm, 'div').reduce(dataElms,{}))
return accI
},[])
;
return { headers, items }
}
.as-console-wrapper {
max-height: 100% !important;
width: 70% !important;
top: 0; left: 30% !important;
}
div.invoice>div:before {
display: block;
content: attr(class) ' :';
}
div[contenteditable] {
font: 12px Arial, Helvetica, sans-serif;
margin: .3em;
width: 12em;
border : 1px solid grey;
padding:.2em 1em;
margin-left:6em;
}
div[contenteditable]::before {
display: inline-block;
content: attr(data-ref);
font-weight : bold;
width: 4.9em;
margin-left:-5.3em;
}
<div class="invoice">
<div class="header">
<div contenteditable data-ref="date">15-Jan-2020</div>
<div contenteditable data-ref="buyer">McDonalds</div>
<div contenteditable data-ref="order">145632</div>
</div>
<div class="item">
<div contenteditable data-ref="name">Big Mac</div>
<div contenteditable data-ref="quantity">5</div>
<div contenteditable data-ref="rate">20.00</div>
</div>
<div class="item">
<div contenteditable data-ref="name">Small Mac</div>
<div contenteditable data-ref="quantity">10</div>
<div contenteditable data-ref="rate">10.00</div>
</div>
</div>
<button id="Loop">Loop</button>
.........................run snippets full screen for better view
second method
const jsonObj = [];
const inVoicesElms = document.querySelectorAll('.invoice div');
Loop.onclick=_=>
{
jsonObj.push( { Invoice: getInVoicesValues() } )
console.clear()
console.log( jsonObj )
}
function getInVoicesValues()
{
let rep = { headers:{}, items:[] }
, cur = null
;
inVoicesElms.forEach(el =>
{
if (el.matches('.header'))
{
cur = rep.headers
}
else if (el.matches('.item'))
{
cur = {}
rep.items.push(cur)
}
else // (el.matches('[contenteditable]'))
{
cur[el.getAttribute('data')] = el.textContent
}
})
return rep
}
.as-console-wrapper { max-height: 100% !important; width: 70% !important;
top: 0; left: 30% !important;
}
<div class="invoice">
<div class="header">
<div contenteditable data="date">15-Jan-2020</div>
<div contenteditable data="buyer">McDonalds</div>
<div contenteditable data="order">145632</div>
</div>
<div class="item">
<div contenteditable data="name">Big Mac</div>
<div contenteditable data="quantity">5</div>
<div contenteditable data="rate">20.00</div>
</div>
<div class="item">
<div contenteditable data="name">Small Mac</div>
<div contenteditable data="quantity">10</div>
<div contenteditable data="rate">10.00</div>
</div>
</div>
<button id="Loop">Loop</button>

Vue js: How to use a computed property to modify string and apply dynamically as CSS class

I am following a tutorial and I want to add a new feature where the surname of each candidate is added as a class. I got this to work inline but then I wanted to clean it up and rather call it as a function.
Working inline
mayor.name.replace(/ /g,'').replace('Mr.','').toLowerCase()
The function textClass removes spaces and "Mr." from the string. I've tried adding this as a computed property but I don't know how to call it on mayor.name
CSS
.black{ color: black;}
.brown{ color: brown;}
.pink{ color: pink;}
.red{ color: red;}
HTML
<div class="container">
<div id="mayor-vote">
<h2>Mayor Vote</h2>
<ul class="list-group" style="width: 400px;">
<li v-for="candidate in candidates" class="list-group-item clearfix">
<div class="pull-left">
<strong style="display: inline-block; width: 100px;">{{ candidate.name }}:</strong> {{ candidate.votes }}
</div>
<button class="btn btn-sm btn-primary pull-right" #click="candidate.votes++">Vote</button>
</li>
</ul>
<h2>Our Mayor is <span class="the-winner" :class="mayor.name.textClass">{{ mayor.name }}</span></h2>
<button #click="clear" class="btn btn-default">Reset Votes</button>
</div>
</div>
</body>
JS
new Vue({
el: '#mayor-vote',
data: {
candidates: [
{ name: "Mr. Black", votes: 140 },
{ name: "Mr. Red", votes: 135 },
{ name: "Mr. Pink", votes: 145 },
{ name: "Mr. Brown", votes: 140 }
]
},
computed: {
mayor: function(){
var candidateSorted = this.candidates.sort(function(a,b){
return b.votes - a.votes;
});
return candidateSorted[0];
},
textClass: function() {
return this.replace(/ /g,'').replace('Mr.','').toLowerCase();
}
},
methods: {
clear: function() {
this.candidates = this.candidates.map( function(candidate){
candidate.votes = 0;
return candidate;
})
}
}
});
There are few mistakes in your code, one is dynamic class binding in vue takes a hash object, not an string. So you have to return an hash like this : { active: true } from the computed property.
Second thing is computed property in vue always modify another vue propery or values returned from an mehtod, to correct these you need to make following changes:
You have to use this.mayor.name in computed property to calculate dynamic class like this:
computed: {
mayor: function(){
var candidateSorted = this.candidates.sort(function(a,b){
return b.votes - a.votes;
});
return candidateSorted[0];
},
textClass: function() {
var tmp = {}
tmp[this.mayor.name.replace(/ /g,'').replace('Mr.','').toLowerCase()] = true
return tmp
}
},
and apply like this in HTML:
<h2>Our Mayor is <span class="the-winner" :class="textClass">{{ mayor.name }}</span></h2>

getting specific json object array entered in textbox

I have html file , and JSON file . In html file , I have created textbox with button to enable the user to enter a course name . In JSON I have an array of course objects contains some information regarding the course .
I want to use Ajax with jQuery when the user enter a course and hit the button it should loop the array in json to check whether this value is found or not . If it is found then show the details of this course .
This is my trying .
<!DOCTYPE html>
<!--
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->
<html>
<head>
<title>Finding Course Details</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
$('#btn').click(function() {
var result = $('#div1');
result.html('');
var data1 = $('#text1').val();
$.ajax({
url: 'course.json',
method: 'get',
data: data1,
dataType: 'json',
success: function(response) {
result.html('level: ' + response.course[0].level + '<br/>' + 'Theoretical hours :' + response.course[0].Theoretical + '<br/>' + 'Practical hours :' + response.course[0].Practical);
}
});
});
});
</script>
<style>
div.container {
width: 100%;
border: 1px solid gray;
}
header,
footer {
padding: 1em;
color: black;
background-color: white;
clear: left;
text-align: center;
}
nav {
float: left;
max-width: 160px;
margin: 0;
padding: 1em;
}
a:hover {
color: blue;
background-color: transparent;
text-decoration: underline
}
nav ul {
list-style-type: none;
padding: 0;
}
nav ul a {
text-decoration: none;
}
article {
margin-left: 170px;
border-left: 1px solid gray;
padding: 1em;
overflow: hidden;
}
</style>
</head>
<body>
<div class="container">
<header>
<img src="9.jpg" width="300" height="200" alt="">
</header>
<nav>
<ul>
<li>Main
</li>
<li>Courses
</li>
</ul>
</nav>
<article>
<h1>Course Details</h1>
<form>
Enter Course Name :
<br/>
<input type="text" id="text1">
<input type="button" id="btn" value="Get Course Details">
<br/>
</form>
<div id="div1"></div>
</article>
<footer>B</footer>
</div>
</body>
</html>
This is json code :
{
"course" : [
{
"name":"Computer Programming",
"level" : "level 1",
"Theoretical" : "4",
"Practical" : "2"
},
{
"name":"Web Technologies",
"level" : "level 2",
"Theoretical" : "2",
"Practical" : "2"
},
{
"name":"Project",
"level" : "level 4",
"Theoretical" : "2",
"Practical" : "2"
}
]
}
get the specific json by user entered data using $.grep
var data = {
"items": [{
"id": 1,
"category": "cat1"
}, {
"id": 2,
"category": "cat2"
}, {
"id": 3,
"category": "cat1"
}]
};
var returnedData = $.grep(data.items, function (element, index) {
return element.id == 1;
});
alert(returnedData[0].id + " " + returnedData[0].category);
Vanilla JavaScript solution:
var searchItem = "Web Technologies"
var jsonCourses = {
"course" : [
{
"name":"Computer Programming",
"level" : "level 1",
"Theoretical" : "4",
"Practical" : "2"
},
{
"name":"Web Technologies",
"level" : "level 2",
"Theoretical" : "2",
"Practical" : "2"
},
{
"name":"Project",
"level" : "level 4",
"Theoretical" : "2",
"Practical" : "2"
}
]
};
var details = jsonCourses.course.filter(function(i){
if(i.name === searchItem)return i
})
alert("Course:"+details[0].name+"|Level:"+details[0].level)

Categories