I have a webpage that list a lot of elements (movies to be specific), the HTML structure of every item is in some way large and complicated (divs, images, links, CSS class, etc).
Firstly I load 100 elements and the user have the option of load the next 100 (this is made using infinite scroll): by now, I make a AJAX petition requesting the another 100 elements and it responds with a HTML text (with all of them loaded) and I just append it to the document.
But, now I don't want to respond with the HTML text, instead of that I want to respond with the 100 elements data in a JSON (I can do that), then, my question is: Which is the best way to add these elements to the document using Javascript?
I know that I can loop over the JSON array and construct every element, but as I said, it's a large HTML structure and I don't really want to create divs and then attach it to another div,set CSS classes, etc with Javascript, because it might get disordered,messy and very large...So, there's a way in javascript to achieve this maybe using something like templates? How can I do that? I just want to get a clean and better code.
The structure of every movie is like this (can I use it like a template?):
<div data-section="movies" data-movie_id="myid" id="movie-id" class="movie anotherclass">
<img src="myImageUrl">
<div class="aCSSclass">
<div class="aCSSclass">
<div class="aCSSclass"></div>
<div class="aCSSclass">
<div class="aCSSclass">
Movie title
</div>
<div class="details form-group">
<a class="aCSSclass" href="myHref">Details</a>
<button onclick="SomeFunction" class="aCSSclass">My button</button>
<div class="aCSSclass"><span class="icon star"></span><span class="aCSSclass"></span><span class="aCSSclass"></span><span class="aCSSclass"></span><span class="aCSSclass"></span></div>
</div>
</div>
</div>
</div>
</div>
The answer is to make a template and then copy the node using cloneNode(). Append all the cloned nodes to a documentFragment to save time on drawing and finally append it to the page.
An approach to this:
var movies = {"movie1" : { "title" : "Die Hard", "imageurl" : "example.com/image.jpg", "details" : "http://example.com", "func" : "functionname" },
"movie2" : { "title" : "Die Hard 2", "imageurl" : "example.com/image.jpg", "details" : "http://example.com", "func" : "functionname" },
"movie3" : { "title" : "Die Hard With 3", "imageurl" : "example.com/image.jpg", "details" : "http://example.com", "func" : "functionname" }
};
function functionname()
{
alert("NYI");
}
var keys = Object.keys(movies); //get the keys.
var docFrag = document.createDocumentFragment();
for (var i = 0; i < keys.length; i++)
{
var tempNode = document.querySelector("div[data-type='template']").cloneNode(true); //true for deep clone
tempNode.querySelector("div.title").textContent = movies[keys[i]].title;
tempNode.querySelector("img").src = movies[keys[i]].imageurl;
tempNode.querySelector("button").onclick = window[movies[keys[i]].func];
tempNode.querySelector("a").href = movies[keys[i]].details;
tempNode.style.display = "block";
docFrag.appendChild(tempNode);
}
document.body.appendChild(docFrag);
delete docFrag;
<!-- template -->
<div style="display: none" data-type="template" data-section="movies" data-movie_id="myid" id="movie-id" class="movie anotherclass">
<img src="myImageUrl">
<div class="aCSSclass">
<div class="aCSSclass">
<div class="aCSSclass"></div>
<div class="aCSSclass">
<div class="aCSSclass title">
Movie title
</div>
<div class="details form-group">
<a class="aCSSclass" href="myHref">Details</a>
<button onclick="SomeFunction" class="aCSSclass">My button</button>
<div class="aCSSclass"><span class="icon star"></span><span class="aCSSclass"></span><span class="aCSSclass"></span><span class="aCSSclass"></span><span class="aCSSclass"></span></div>
</div>
</div>
</div>
</div>
</div>
This is just an example, not based upon your actual JSON. However you can easily clone a template and then fill in the values.
Use
document.querySelector
document.querySelectorAll
document.createDocumentFragment
Element.cloneNode(bool)
Related
My end result is supposed to be a list of objects in html. Bootstrap behind this. I'd like for the list to be created dynamically so I don't have to manually create all the divs because I don't know how many there will be. Here's what I have.
I have an array similar to this:
activities =
[
{
"activityOwner": "Raymond Carlson",
"activityDesc": "Complete all the steps from Getting Started wizard"
},
{
"activityOwner": "Flopsy McDoogal",
"activityDesc": "Called interested in March fundraising Sponsorship"
},
{
"activityOwner": "Gary Busy",
"activityDesc": "Get approval for price quote"
}
]
This is the first part where I'm not sure what to do. I can assign the element ids individually for my html like this but what I'd like to do is count how many elements are in my array and create these for me. I won't know how many there are to make these manually. I'm sure there needs to be a loop but I couldn't figure it out.
document.getElementById('activityowner0').innerHTML = activities[0].activityOwner;
document.getElementById('activitydesc0').innerHTML = activities[0].activityDesc;
document.getElementById('activityowner1').innerHTML = activities[1].activityOwner;
document.getElementById('activitydesc1').innerHTML = activities[1].activityDesc;
document.getElementById('activityowner2').innerHTML = activities[2].activityOwner;
document.getElementById('activitydesc2').innerHTML = activities[2].activityDesc;
etc.
etc.
And then...once I have that part, I'd like to know how to create my html divs dynamically based on how many elements are in my array. Again, right now I don't know how many there are so I'm having to create a bunch of these and then have extras if I have too many.
<div class="container">
<div class="row"></div>
<div class="qa-message-list" id="wallmessages">
<br>
<div class="message-item" id="m0">
<div class="message-inner">
<div class="message-head clearfix">
<div class="user-detail">
<h5 class="handle">
<p id='activityowner0'></p>
</h5>
<div class="post-meta"></div>
</div>
</div>
<div class="qa-message-content">
<p id='activitydesc0'></p>
</div>
</div>
</div>
I know this is a big ask so just pointing me in the right direction would be very helpful. I hope my question was clear and I appreciate it.
One way for you to achieve this would be to loop through the objects in your activities array. From there you can use a HTML template to store the base HTML structure which you can clone and update with the values of each object before you append it to the DOM.
In addition, an important thing to note when generating repeated content in a loop: never use id attributes. You will either end up with duplicates, which is invalid as id need to be unique, or you'll end up with ugly code generating incremental/random id at runtime which is unnecessary. Use classes instead.
Here's a working example:
const activities = [{ "activityOwner": "Raymond Carlson", "activityDesc": "Complete all the steps from Getting Started wizard"}, {"activityOwner": "Flopsy McDoogal","activityDesc": "Called interested in March fundraising Sponsorship" }, { "activityOwner": "Gary Busy", "activityDesc": "Get approval for price quote" }]
const html = activities.map(obj => {
let item = document.querySelector('#template').innerHTML;
item = item.replace('{owner}', obj.activityOwner);
item = item.replace('{desc}', obj.activityDesc);
return item;
});
document.querySelector('#list').innerHTML = html.join('');
<div id="list"></div>
<template id="template">
<div class="container">
<div class="row"></div>
<div class="qa-message-list">
<div class="message-item">
<div class="message-inner">
<div class="message-head clearfix">
<div class="user-detail">
<h5 class="handle">
<p class="activityowner">{owner}</p>
</h5>
<div class="post-meta"></div>
</div>
</div>
<div class="qa-message-content">
<p class="activitydesc">{desc}</p>
</div>
</div>
</div>
</div>
</div>
</template>
I am trying to make the following bit of code easier to maintain. I am not a web developer so bear with me. I think the following approach is appropriate.
I would like to dynamically add content and attributes to an html file using either javascript or jQuery. The items could reside in a .csv or .json (or something else?) file.
Given content like this
<div class="filtr-container">
<div class="col-12 col-sm-6 col-md-4 card filtr-item" data-category="cat-1" data-date="2018-02-09">
<div class="card-inner-border box-shadow">
<a href="address-1.html">
<img class="card-img-top rounded-top" src="./images/image-1.jpg" alt="img-2-alt">
</a>
<div class="card-body">
<h5 class="card-title">Title-1</h5>
<p class="card-text card-desc">
This is a description for title-1 content.
</p>
<a href="address-1.html">
<button type="button" class="btn btn-sm btn-outline-secondary">View</button>
</a>
<p class="card-text">
<small class="text-muted">Last updated February 2, 2018</small>
</p>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-md-4 card filtr-item" data-category="cat-2, cat-3" data-date="2018-02-14">
<div class="card-inner-border box-shadow">
<a href="address-2.html">
<img class="card-img-top rounded-top" src="./images/image-2.jpg" alt="img-2-alt">
</a>
<div class="card-body">
<h5 class="card-title">Title-2</h5>
<p class="card-text card-desc">
Here is a long description for title-2 content.
</p>
<a href="address-2.html">
<button type="button" class="btn btn-sm btn-outline-secondary">View</button>
</a>
<p class="card-text">
<small class="text-muted">Last updated February 14, 2018</small>
</p>
</div>
</div>
</div>
<!-- MANY MORE CARDS / ITEMS ... -->
</div> <!-- End of filtr-container -->
I think we could abstract the details into something like this (.csv)
item-id,title,description,categories,address,image,image-alt,update
1,Title-1,This is a description for title-1 content.,cat-1,address-1.html,image-1.jpg,img-1-alt,2018-02-09
2,Title-2,Here is a long description for title-2 content.,"cat-2, cat-2",address-2.html,image-2.jpg,img-2-alt,2018-02-14
What's a nice approach of attack for using `javascript` or `jQuery` to add this content from the `.csv` or `.json` file?
A few concerns:
The headers of the .csv will not match verbatim (e.g. <p class="card-desc"> aligns with the .csv header of description)
There could be embedded comma-separated items (e.g. item 2 has categories cat-2, cat-3 so it gets quotes " in the .csv -- maybe .json would better (?) or perhaps its a non-issue)
If possible can we reuse the date item for both data-date= and the final piece of text <small class="text-muted"> which converts the date into Last updated month-name-long, dd, yyyy instead of yyyy-mm-dd.
Some attributes are partial references (e.g. the src for an image is just the final part of the path; stated as image-1.jpg in the .csv not ./images/image-jpg).
To hopefully help make this feel less complicated, here's a picture with the highlighted elements that could be "referenced" from the .csv file.
To me this feels like:
Read in the .csv file.
For each item in the .csv file, append objects to $(".filtr-container") with the shell layout...
But I'm lost when it comes to the particulars or if that's an appropriate approach.
You seem to be searching for template parsing. You can find many libraries that will ease this burden. In its simplest form, template parses carry out the steps in the following code. If you don't need the flexibility, power, features, etc. from a template parser library or full framework, you should consider not including the thousands of lines of code if all you want to accomplish is what is shown below.
Since you mentioned both JSON and CSV I've included the code to parse both. I'll leave the AJAX and date formatting magic to you. I don't think I populate the ID either, but this shows that more data than template attributes will work fine.
let template = document.getElementById('card-template').innerHTML;
let container = document.querySelector('.filtr-container');
// Do some ajax magic to get csv file
let csv = `item-id,title,description,categories,address,image,image-alt,update
1,Title-1,This is a description for title-1 content.,cat-1,address-1.html,https://via.placeholder.com/75,img-1-alt,2018-02-09
2,Title-2,Here is a long description for title-2 content.,cat-2 cat-2,address-2.html,https://via.placeholder.com/75,img-2-alt,2018-02-14`;
let csvLines = csv.split("\n");
let csvHeaders = csvLines.shift().split(',');
csvLines.forEach(line => {
let parsed = template;
let props = line.split(',');
props.forEach((prop, idx) => {
parsed = parsed.replace('{{' + csvHeaders[idx] + '}}', props[idx]);
});
container.innerHTML = container.innerHTML + parsed;
});
let json = `[{
"item-id": "1",
"title": "Title-1",
"description": "This is a description for title-1 content.",
"categories": "cat-1",
"address": "address-1.html",
"image": "https://via.placeholder.com/75",
"image-alt": "img-1-alt",
"update": "2018-02-09"
}, {
"item-id": "2",
"title": "Title-2",
"description": "Here is a long description for title-2 content.",
"categories": "cat-2 cat-2",
"address": "address-2.html",
"image": "https://via.placeholder.com/75",
"image-alt": "img-2-alt",
"update": "2018-02-14"
}]`;
let data = JSON.parse(json);
data.forEach(col => {
let jParsed = template;
for (prop in col) {
jParsed = jParsed.replace('{{' + prop + '}}', col[prop]);
}
container.innerHTML = container.innerHTML + jParsed;
});
<div class="filtr-container">
<script type="template" id="card-template">
<div class="col-12 col-sm-6 col-md-4 card filtr-item" data-category="{{categories}}" data-date="{{date}}">
<div class="card-inner-border box-shadow">
<a href="{{address}}">
<img class="card-img-top rounded-top" src="{{image}}" alt="{{image-alt}}">
</a>
<div class="card-body">
<h5 class="card-title">{{title}}</h5>
<p class="card-text card-desc">
{{description}}
</p>
<a href="{{address}}">
<button type="button" class="btn btn-sm btn-outline-secondary">View</button>
</a>
<p class="card-text">
<small class="text-muted">Last updated {{update}}</small>
</p>
</div>
</div>
</div>
</script>
This post might help you parse your CSV document. If your data lives in a JSON, you can use JSON.parse
Once you properly retrieved and parsed your data, it's a matter or rendering it to the DOM.
You can do it using the standard javascript library, JQuery or frameworks such as React or VueJS
a button corresponding to a prompt box,each box is different shells;Although implements the desired function, but my code is too complicated, and that there is no simple way. how can I do? This is my code
<--html button-->
button1
button2
...
button100
<--html pop box-->
<div class="note1" style="display:none;">
<img class="title-css" src="note1.png">
<p class="one">note1</p>
</div>
...
<div class="note100" style="display:none;">
<img class="title-css" src="note100.png">
<p class="one">note100</p>
</div>
<--angular js-->
$scope.showRulePop = function(index) {
for(var i=1;i<=8;i++) {
$('.note'+i).hide();
}
$('.note'+index).show();
};
Well first of all, don't use jQuery, unless your in the directive level of angular jQuery have nothing to do there.
First let's get rid of the links part using a simple ng-repeat :
<--html button-->
<div ng-repeat="button in buttons">
{{button.label[i]}}
</div>
// JS in the controller
$scope.buttons = [{
label:'button1'
},{label:'button2'}];
As you can see i declare in the javascript all your buttons and i just loop over it.
Now the "bombox" or whatever it is let's make it a simple template :
<div class="{{currentnote.class}}" ng-if="currentNote">
<img class="title-css" src="{{currentNote.img}}">
<p class="one">{{currentNote.content}}</p>
</div>
// and use ng-repeat for the eight first when there is no button selected
<!-- show 1 to 8 if note current note selected -->
<div ng-repeat="button in buttons1To8" ng-if="!currentNote">
<div class="{{button.note.class}}">
<img class="title-css" src="{{button.note.img}}">
<p class="one">{{button.note.content}}</p>
</div>
</div>
// JS
$scope.buttons = [{
label:'button1'
note:{class:'note1', img:'note1.png', content:'note1'//assuming no HTML or you' ll need something more
}},{label:'button2', note:{...}}, ...];
$scope.showRulePop = function(index){
$scope.currentNote = $scope.buttons[index].note;
}
$scope.buttons1To8 = $scope.buttons.slice(0, 8);//0 to 7 in fact
That's all, no need of jQuery.
I have this hidden block of code that i want to call based on the values that are received from server:
<div id="hiddenChart" style="display:none;">
<li style="height:auto;">
<div class="col-sm-4" id="chart_0_0">
<div class="panel panel-success" style="width:550px; height:auto;" id="accordion_0_0">
<div class="panel-heading">
<div class="btn-group" style="float:right;">
<i class="glyphicon glyphicon-minus" id="minimize_0_0"></i>
<i class="glyphicon glyphicon-remove" id="close_0_0"></i>
</div>
<h3 class="panel-title">title</h3>
</div>
<div class="panel-body" style="height:400px;">
<nvd3-multi-bar-chart data="Sec1Graf1Data" id="dataChart_0_0" height="400" showXAxis="true" reduceXTicks="true" showYAxis="true" showLegend="true" showControls="true" tooltips="true">
<svg></svg>
</nvd3-multi-bar-chart>
</div>
</div>
</div>
</li>
</div>
but i need to change some values: ids, tags, data variables.
I know how to show the code using "$('ul').append($('div').html());" but i have to change it before doing it.
How can i do it?
How do i define in which fields i have to insert the string i'me receiving?
Thk
UPDATE:
I was able put it to work, here is the fiddle with it fiddle.
When i inspect the element, the ids that i want to change, instead of #1, it returns chart_0_0.
Thank you all for your posts and help
You can get a reference to your div like this:
var $div = $('#hiddenChart');
To clone it,
var $clonedDiv = $div.clone();
Then, in the $cloneDiv object, you make the changes:
$clonedDiv.find(--selector of a node--).attr(atributeName, atributeValue); //change/add attribute
$clonedDiv.find(--selector of a node--).removeAttr(atributeName); //remove attribute
And so on. I won't explain how jQuery works, Lekhnath gave you a link.
Finally you insert the $clonedDiv with .appendTo() wherever you want. The original div remains untouched so you can clone it again and again.
Jquery Change text/html from a hidden div content :
Simple
maintain a copy of the hidden content
replace the content based on the element class/id selector with the server response
then paste the html into another div
replace the content of the hidden back to original (optional)
http://jsfiddle.net/austinnoronha/ZJ3Nt/
$(document).ready(function(){
var serverRes = {
title: "New Title In Header",
body: "New body text from server <\/br><nvd3-multi-bar-chart data=\"Sec1Graf1Data\" id=\"dataChart_0_0\" height=\"400\" showXAxis=\"true\" reduceXTicks=\"true\" showYAxis=\"true\" showLegend=\"true\" showControls=\"true\" tooltips=\"true\"><svg><\/svg><\/nvd3-multi-bar-chart>"
};
var tmpOldCont = $("#hiddenChart").html();
var counter = 1;
$(".serverres").click(function(){
$("#hiddenChart").find(".panel-body").html(counter + " = " + serverRes.body);
$("#hiddenChart").find(".panel-title").text(serverRes.title + " " + counter);
counter++;
$(".box-container").html($("#hiddenChart").html());
$("#hiddenChart").html(tmpOldCont);
});
});
for example,
<div ng-repeat="item in items">
<p>item.title</p>
...
...
...
<p>item.up</p>
</div>
As i know ,anchor scroll need to specify an id for the anchor, but how can i do the anchor scroll without id, The situation is there's another loop wrap this item repeat. and i can't specify an id like
<div ng-repeat="item in items">
<p id="anchor{{$index}}">item.title</p>
...
...
...
<p>item.up</p>
</div>
So if the code is like below, there will be multi id="anchor{{$index}}"
<div ng-repeat="parent in parents">
<div ng-repeat="item in items">
<p id="anchor{{$index}}">item.title</p>
........
</div>
</div>
And i don't want to use two indexs to specify the id, or the code will be too complex.
How can i solve it .....Thanks
Without knowing your exact scenario, I would say that you are going to have to artificially create some kind of unique anchorId on the elements you care about.
Again, I don't know what your controller or model look like, but you could use a technique similar to this.
angular.forEach(this.parents, function(parent, i){
//Add unique anchorId to parent
parent.anchorId = "anchor-" + i;
angular.forEach(parent.children, function(child, j){
//Add anchorId to child as well
child.anchorId = "anchor-" + i + "-" + j;
});
});
This will basically give you something you can bind to in the DOM and then trigger a scroll with a click event. The bindings would be similar to what you have above.
<div data-ng-repeat="parent in ctrl.parents">
<h1 id="{{parent.anchorId}}">{{parent.name}}</h1>
<div data-ng-repeat="child in parent.children">
<h2 id="{{child.anchorId}}" class="child">
{{child.name}}
<small>
<a href="" ng-click="ctrl.scrollTo(parent.anchorId)">
| to {{parent.name}}
</a>
</small>
</h2>
</div>
</div>
And finally in your controller you could create a scrollTo method that looked something like this.
Ctrl.prototype.scrollTo = function(anchorId){
this.$location.hash(anchorId);
this.$anchorScroll();
};
This will basically do what you want and allow you to scroll around to your hearts content.
I've wrapped all this up in a jsFiddle example so you can see the full working example.