Hide data, process to new div with each function - javascript

I have multiple same class divs that produce an array and a script that puts them into lists.
I would like to hide a JSON array object from briefly flashing (unprocessed) on the page before the script can process it into lists.
So I put them into a hidden div and the each function stops working.
The .hid divs actually contain:
<%=getCurrentAttribute('item','lists')%> that produce the JSON array.
<div class="hid" style="display:none;">[{"k":"Model","v":"AB"},{"k":"Color","v":"green"}]</div>
<div class="overview"></div>
<div class="hid" style="display:none;">[{"k":"Model","v":"AC"},{"k":"Color","v":"blue"}]</div>
<div class="overview"></div>
<div class="hid" style="display:none;">[{"k":"Model","v":"AD"},{"k":"Color","v":"red"}]</div>
<div class="overview"></div>
My script
jQuery('.hid').each(function () {
var $data = jQuery(this), spec,
specs = jQuery.parseJSON($data.html());
jQuery(".overview").html('<div class="bullet_spec"></div>');
jQuery.each(specs, function () {
jQuery(".overview").children('div').append('<div class="specs"><span class="label">' + this.k + ':</span><span class="value"> ' + this.v + '</span></div>');
});
{
}
});
http://jsfiddle.net/Qta2p/

Related

Infinite Scroll With Intersectionobserver & InnerHTML

I'm trying to make an infinite scroll (without jQuery) to show more results in a page. I'm using an IntersectionObserver to detect a div called #paginate and everytime it enters the screen, the #result div will be refreshed.
var result = document.querySelector('#result');
var paginate = document.querySelector('#paginate');
var observer = new IntersectionObserver(entries => {
if (entries.some(entry => entry.isIntersecting))
{
var pagination = 10;
fetch('/kernel/search.php?pagination='+pagination)
.then((response) => {
return response.text();
})
.then((html) => {
result.innerHTML = html;
});
}
});
observer.observe(paginate);
Here's the full code view with HTML:
<html>
<body>
<div class="row justify-content-sm-center justify-content-md-center justify-content-lg-center justify-content-xl-start no-gutters min-vw-100" id="result">
<div class="col-sm-11 col-md-11 col-lg-9-result col-xl-4-result order-0">
<div class="card mx-3 mt-3">
<div class="card-body">
<a class="text-decoration-none" href="?topic=result-1">
<h5 class="card-title">
Result 1
</h5>
</a>
<p class="card-text text-truncate">
Result 1 description.</p>
</div>
</div>
<div class="card mx-3 mt-3">
<div class="card-body">
<a class="text-decoration-none" href="?topic=result-2">
<h5 class="card-title">
Result 2
</h5>
</a>
<p class="card-text text-truncate">
Result 2 description.</p>
</div>
</div>
<div class="alert alert-light text-dark text-center border mx-3 my-3" id="paginate">
More results
</div>
</div>
</div>
<script>
var result = document.querySelector('#result');
var paginate = document.querySelector('#paginate');
var observer = new IntersectionObserver(entries => {
if (entries.some(entry => entry.isIntersecting))
{
var pagination = 10;
fetch('/kernel/search.php?pagination='+pagination)
.then((response) => {
return response.text();
})
.then((html) => {
result.innerHTML = html;
});
}
});
observer.observe(paginate);
</script>
</body>
</html>
It works, but it only works the first time and it doesn't refresh the #result div thereafter. I can see the fetch working in Web Browser > Inspect > Network tab, but there's no activity after the first refresh of the #result div meaning it doesn't detect the #paginate div anymore.
What's going on here? I assume it's because that I'm using an innerHTML and the observer somehow can't detect the #paginate div after the first refresh of the #result div. How can I solve this?
I did it with jQuery and .scroll function and used ajax like this, maybe my code can help you and adapt it to your needs.
$('#customersList').scroll(function () {
if ($(this).scrollTop() + $(this).innerHeight() >= $(this)[0].scrollHeight - 5000) {
// Do your stuff here.
getCustomers();
}
})
function getCustomers(){
let $customerList = $('#customersList');
let offset = ($customerList.attr('data-offset')) ?
$customerList.attr('data-offset') : 0;
if ($customerList.data('requestRunning')) {
return;
}
$customerList.data('requestRunning', true);
return $.ajax({
type: "GET",
data: {offset: offset},
url : routes.routes.customers.get
})
.done(function (data) {
let _htmlCustomersList = ($customerList.is(':empty')) ? '' : $customerList.html();
let response = data.data;
if (response) {
for (const i in response) {
let v = JSON.parse(response[i]);
_htmlCustomersList += '<div class="client-group edit" data-id="' + v['id'] + '"></div><hr>';
}
offset = parseInt(offset) + 200;
$customerList.attr('data-offset', offset).html(_htmlCustomersList);
}
})
.always(function () {
$customerList.data('requestRunning', false);
});
}
my getCustomer function runs before reaching the end of the page and loads 200 more items each time.
I hope this can help you a little bit
It seems you are removing #paginate after the first update, because it is in the #result.
Please, use indentation :)
The #result div is the main wrapper of the content
using innerHTML to update the contents of the div, will result in replacing the entire content inside of the div...
Beyond the fact that innerHTML is absolute, and erases any DOM objects (and their events) hence bad practice, it's not that good solution either, since you'd like to append rather then replace the data, upon scrolling
I would suggest to add a div above the paginate, which will hold the added data, something like:
...
<div id="result"></div>
<div class="alert alert-light text-dark text-center border mx-3 my-3" id="paginate">
More results
</div>
Then use some sort of appending, for the content added
so something like:
.then((html) => {
let res = new DOMParser().parseFromString(html, "text/xml");
document.getElementById("result").appendChild(res);
});
Hope that helps
I have removed innerHTML and replaced it with insertAdjacentHTML.
Because innerHTML seems to reset/forget #paginate after the first refresh of the #result, so the observer can't detect #paginate anymore to trigger the next #result refresh.
I could have used innerHTML with appendChild to do the same but appendChild adds a new div on each #result refresh, which I didn't really like.
As the solution, I'm inserting the refreshed html data before the beginning of #paginate without resetting/forgetting it's element id that's required for the observer to trigger the next #result refresh.
.then((html) => {
paginate.insertAdjacentHTML('beforebegin', html);
});

Looping through all divs inside a container div using jQuery

I'm trying to do a sort of invoicing system, and the html looks like this:
<invoice>
<headers>
<div date contenteditable>15-Jan-2020</div>
<div buyer contenteditable>McDonalds</div>
<div order contenteditable>145632</div>
</headers>
<item>
<div name contenteditable>Big Mac</div>
<div quantity contenteditable>5</div>
<div rate contenteditable>20.00</div>
</item>
<item>
<div name contenteditable>Small Mac</div>
<div quantity contenteditable>10</div>
<div rate contenteditable>10.00</div>
</item>
</invoice>
<button>Loop</button>
I need to loop through each <invoice> and get details from <headers> and <item>, so the end results look like this.
date : 15-Jan-2020 buyer : McDonalds order:145632
item : Big Mac quantity : 5 rate : 20.00
item : Small Mac quantity : 10 rate : 10.00
I plan on sending this data as json to a PHP script for processing.
The problem is, <headers>,<items> wont be the only containers in each invoice. There could be <address>,<transporter> etc. but they'll all be inside each <invoice>.
With that being the case, how can I loop through each container and get it's data?
Here's the jQuery I was attempting:
var button = $("button")
button.on("click", function() {
$('invoice').each(function() {
alert('It works');
});
});
Fiddle here
You can loop through div and use data-attribute for name label as below
$('invoice>headers>div, invoice>item>div').each(function(index,item) {
console.log($(this).attr('data-name'), $(this).text());
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<invoice>
<headers>
<div date contenteditable data-name="date">15-Jan-2020</div>
<div buyer contenteditable data-name="buyer">McDonalds</div>
<div order contenteditable data-name="order">145632</div>
</headers>
<item>
<div name contenteditable data-name="name">Big Mac</div>
<div quantity contenteditable data-name="quantity">5</div>
<div rate contenteditable data-name="rate">20.00</div>
</item>
<item>
<div name contenteditable data-name="name">Small Mac</div>
<div quantity contenteditable data-name="quantity">10</div>
<div rate contenteditable data-name="rate">10.00</div>
</item>
</invoice>
$('headers > div, item > div').each(function(item) {
console.log('item');
});
It seems your HTML isn't valid HTML. The spec doesn't define elements like <invoice>, <headers> and <item>. Besides that, attributes on elements almost always resemble key-value pairs, meaning you should declare your name, buyer, order, quantity and rate attributes as values of existing attributes. The contenteditable attribute is a boolean attribute which is OK to be left as it currently is.
Here is a fixed and working example:
var button = $('#read-invoice');
// readLine :: [String] -> (HTMLElement -> String)
function readLine(fields) {
return function (el) {
return fields.reduce(function (txt, field) {
var data = $('.' + field, el).text();
return txt === ''
? field + ': ' + data
: txt + '; ' + field + ': ' + data
}, '');
}
}
// readBlock :: { (HTMLElement -> String) } -> (HTMLElement -> String)
function readBlock(readers) {
return function (el) {
var rtype = el.className;
if (typeof readers[rtype] === 'function') {
return readers[rtype](el);
}
return '';
}
}
// autoRead :: HTMLElement -> String
var autoRead = readBlock({
headers: readLine(['date', 'buyer', 'order']),
item: readLine(['name', 'quantity', 'rate'])
// ... address, etc.
});
button.on('click', function () {
var result = $('.invoice').
children().
toArray().
reduce(function (txt, el) {
var line = autoRead(el);
return line === ''
? txt
: txt + line + '\n';
}, '');
console.log(result);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="invoice">
<div class="headers">
<div class="date" contenteditable>15-Jan-2020</div>
<div class="buyer" contenteditable>McDonalds</div>
<div class="order" contenteditable>145632</div>
</div>
<div class="item">
<div class="name" contenteditable>Big Mac</div>
<div class="quantity" contenteditable>5</div>
<div class="rate" contenteditable>20.00</div>
</div>
<div class="item">
<div class="name" contenteditable>Small Mac</div>
<div class="quantity" contenteditable>10</div>
<div class="rate" contenteditable>10.00</div>
</div>
</div>
<button id="read-invoice">Loop</button>
JS explanation
The function readLine takes an Array of Strings, where each String resembles the class name of one of the inner <div> elements. It returns a function that's waiting for a "block" element (like <div class="headers">) and reads the contents of it's contained <div>'s into a single String. Let's call the returned function a reader.
The readBlock function takes an Object of reader functions and returns a function taking a "block" element. The returned function determines which type of "block" it received and calls the matching reader function with the element as argument. If no reader matches the block type, it returns the empty String.
In the end, autoRead becomes a single function taking in a whole "block" element and returning all of it's contents as a line of text.
The button click handler looks up the <div class="invoice"> element, traverses it's DOM tree down to it's child elements (our "block" elements) and passes each "block" to autoRead, building up a result String. The final result is logged to the console.
Extending
To add new types of "block"s, simply define a new reader for it and add it to the Object passed to readBlock. For example, to add an <div class="address"> reader that reads "name", "street", "zip" and "city" infos:
var autoRead = readBlock({
headers: readLine(['date', 'buyer', 'order']),
item: readLine(['name', 'quantity', 'rate']),
address: readLine(['name', 'street', 'zip', 'city']) // <<< new
});
Extending the fields a certain reader reads is also simple, just add the name of the field to read:
var autoRead = readBlock({
headers: readLine(['date', 'buyer', 'order']),
item: readLine(['name', 'quantity', 'rate', 'currency']) // <<< added "currency"
});

div sort not working when div generated dynamically

I'm trying to sort a list of divs with the properties shown by particular attributes (gender, level, name etc) using the following script:
html:
<div id="sortThis" class="col-xs-12 alert-container">
<div id="1" class="container-element sortable box box-blue" data-gender="1" data-level="4" data-name="AAA"> <h3>AAA</h3><div class="panel-body">AAA is resp</div>
</div>
<div id="2" class="container-element sortable box box-pink" data-gender="2" data-level="3" data-name="DDD"><h3>DDD</h3><div class="panel-body">DDD is a s</div>
</div>
<div id="3" class="container-element sortable box box-blue" data-gender="1" data-level="2" data-name="FFF"><h3>FFF</h3><div class="panel-body">FFF has mad</div>
</div>
<div id="4" class="container-element sortable box box-pink" data-gender="2" data-level="4" data-name="CCC"><h3>CCC</h3><div class="panel-body">CCC has ma</div>
</div>
<div id="5" class="container-element sortable box box-pink" data-gender="2" data-level="2" data-name=EEE><h3>EEE</h3><div class="panel-body">EEE is a f</div>
</div>
<div id="6" class="container-element sortable box box-blue" data-gender="1" data-level="3" data-name="BBB"><h3>BBB</h3><div class="panel-body">BBB is an ou</div>
</div>
</div>
<button id="sLevel" class="LbtnSort">Sort by Level</button><br/>
<button id="sGender" class="GbtnSort">Sort by Gender</button><br/>
js:
var LdivList = $(".box");
LdivList.sort(function(a, b){
return $(a).data("level")-$(b).data("level")
});
var GdivList = $(".box");
GdivList.sort(function(a, b){
return $(a).data("gender")-$(b).data("gender")
});
/* sort on button click */
$("button.LbtnSort").click(function() {
$("#sortThis").html(LdivList);
});
/* sort on button click */
$("button.GbtnSort").click(function() {
$("#sortThis").html(GdivList);
});
when the .sortable divs are static, the sort works fine, as this jfiddle shows, however if the contents of the #sortable div (i.e. .sortable divs) are dynamically generated (in this case as the result of a form submit), when the sort button is pressed, the entire contents of the #sortable div disappears, and I can't seem to get it to work.
Any help or suggestions would be appreciated.
edit: The code for dynamic generation of the list is as follows - effectively it's an AXAX form submit that pulls data from a sorted list of items and outputs them.
$('#formStep2').submit(function(event) {
// get the form data
var studentArray = [];
$(".listbox li").each(function() {
studentArray.push({
'name': ($(this).text()),
'gender': ($(this).closest('ol').attr('id')).substr(0, 1),
'level': ($(this).closest('ol').attr('id')).substr(2, 3),
'topic': ($('input[name=topic]').val())
})
});
var studentString = JSON.stringify(studentArray);
console.log(studentString);
var formData = {
'students': studentString,
};
// process the form
$.ajax({
type: 'POST', // define the type of HTTP verb we want to use (POST for our form)
url: 'process_step2.php', // the url where we want to POST
data: formData, // our data object
dataType: 'json', // what type of data do we expect back from the server
encode: true
})
// using the done promise callback
.done(function(data) {
if (!data.success) {
// error handling to go here.....
} else {
$('.alert-container').empty();
var obj = JSON.parse(data.message);
//sort the array alphabetically by name (default status)
var test = obj.sort(function(a,b){
var lccomp = a.name.toLowerCase().localeCompare(b.name.toLowerCase());
return lccomp ? lccomp : a.name > b.name ? 1 : a.name < b.name ? -1 : 0;
});
console.log(test);
var i=0;
test.forEach(function(st) {
console.log(st['name']);
var gen = (st['gender'] == 1) ? "blue" : (st['gender'] == 2) ? "pink" : NULL;
$('.alert-container').append('<div id="' + (i+1) + '" class="container-element sortable box box-' + gen + '" data-gender="' + st['gender'] + '" data-level="' + st['level'] + '" data-name="' + st['name'] + '"><h3>' + st['name'] + '</h3><div class="panel-body"><div class="col-xs-9"><i class="fa fa-quote-left fa-3x fa-pull-left fa-' + gen + '" aria-hidden=:true"></i>' + st['comment'] + '</div></div></div>');
i++;
});
// jump to the next tab
var $active = $('.wizard .nav-tabs li.active');
$active.next().removeClass('disabled');
nextTab($active);
}
})
// using the fail promise callback
.fail(function(data) {
// show any errors
// best to remove for production
console.log(data);
});
// stop the form from submitting the normal way and refreshing the page
event.preventDefault();
});
You are defining LdivList and GdivList inline with your code so they are defined on DOM ready. You have to wrap the definition of those inside a function and call it on click:
$(document).ready(function(){
$("button.LbtnSort").click(function() {
$("#sortThis").html(GenerateLdivList);
});
/* sort on button click */
$("button.GbtnSort").click(function() {
$("#sortThis").html(GenerateGdivList());
});
});
function GenerateLdivList(){
var LdivList = $(".box");
LdivList.sort(function(a, b){
return $(a).data("level")-$(b).data("level")
});
}
function GenerateGdivList(){
var GdivList = $(".box");
GdivList.sort(function(a, b){
return $(a).data("gender")-$(b).data("gender")
});
}
As #theduke said, the lists are probably empty at the time you sort them. Here's a simple change that will read and sort the lists when you click the buttons instead. (Not tested.)
var LdivList = function () {
return $(".box").sort(function(a, b){
return $(a).data("level")-$(b).data("level")
});
};
var GdivList = function () {
return $(".box").sort(function(a, b){
return $(a).data("gender")-$(b).data("gender")
});
};
/* sort on button click */
$("button.LbtnSort").click(function() {
$("#sortThis").html(LdivList());
});
/* sort on button click */
$("button.GbtnSort").click(function() {
$("#sortThis").html(GdivList());
});

Loop in jquery based on razor loop

I have a for each loop in my razor view (MVC4)
#foreach (var fe in ViewBag.FeatureProducts)
{
<div class="product-details" style="display:none;">#Html.Raw(fe.Details)</div>
<div class="product-qty-dt"> </div>
}
in the each loop i have to extract the #html row content and display in to the div 'product-qty-dt'.
For that I write the following jquery,
$(document).ready(function () {
var data = $('.product-details p').map(function () {
return $(this).text();
}).get();
//product availability
var availability = data[2].split(",");
$.each(availability, function (i) {
$('.product-qty-dt').append( availability[i])
});
});
But this was only consider the last foreach raw. how can i call this jquery in each loop call.
For example, In first loop,
<div class="product-details" style="display:none;"><p>Lorem, Ispum</p><p>doler emit,fghjh</p><p>9gm</p></div>
<div class="product-qty-dt"> 9gm </div>
second loop
<div class="product-details" style="display:none;"><p>Lorem, Ispum</p><p>doler emit,fghjh</p><p>5gm</p></div>
<div class="product-qty-dt"> 5gm </div>
Thirdloop
<div class="product-details" style="display:none;"><p>Lorem, Ispum</p><p>doler emit,fghjh</p><p>3gm</p></div>
<div class="product-qty-dt"> 3gm </div>
The following will add the text of the last <p> in a <div class="product-details"> element into the corresponding <div class="product-qty-dt"> element
$('.product-details').each(function () {
var t = $(this).children('p').last().text();
$(this).next('.product-qty-dt').text(t);
})
This should do the trick. Also the data array is still used just incase you would like to access other data portions stored in product-details.
$(".product-details").each(function()
{
var data = $(this).find('p').map(function () { return $(this).text() }).get();
$(this).next('.product-qty-dt').text(data[2]);
})

How to create dynamic content within syntaxhighlighter

I want to display a property name based on user input and display this inside of SyntaxHighlighter. Another post says this is supposed to be easy.
JS
$('#inputText').keyup(function () {
var outputValue = $('#codeTemplate').html();//Take the output of codeTemplate
$('#codeContent').html(outputValue);//Stick the contents of code template into codeContent
var finalOutputValue = $('#codeContent').html();//Take the content of codeContent and insert it into the sample label
$('.popover #sample').html(finalOutputValue);
SyntaxHighlighter.highlight();
});
SyntaxHighlighter.all();
Markup
<div style="display: none;">
<label class="propertyName"></label>
<label id="codeTemplate">
<label class="propertyName"></label>
//Not using Dynamic object and default Section (appSettings):
var actual = new Configuration().Get("Chained.Property.key");
//more code
</label>
<pre id="codeContent" class="brush: csharp;">
</pre>
</div>
<div id="popover-content" style="display: none">
<label id="sample">
</label>
</div>
This outputs plain text. As if SyntaxHighlighter never ran. I suspect that the issue has to do with the fact that <pre> doesn't exist after the page is rendered. However, updating
SyntaxHighlighter.config.tagName = "label";
along with pre to label did not work either.
There were many problems that had to be overcome to get this to function. I feel this is best explained with code:
JS
<script>
$(function () {
$('#Key').popover({
html: true,
trigger: 'focus',
position: 'top',
content: function () {
loadCodeData(true);
console.log('content updated');
var popover = $('#popover-content');
return popover.html();//inserts the data into .popover-content (a new div with matching class name for the id)
}
});
$('#Key').keyup(function () {
loadCodeData();
});
function loadCodeData(loadOriginal) {
var userData = $('#Key').val();
var codeTemplate = $('#codeTemplate').html();
var tokenizedValue = codeTemplate.toString().replace('$$propertyNameToken', userData);
$('#codeContent').html(tokenizedValue);
$('#codeContent').attr('class', 'brush: csharp');//!IMPORTANT: re-append the class so SyntaxHighlighter will process the div again
SyntaxHighlighter.highlight();
var syntaxHighlightedResult = $('#codeContent').html();//Take the content of codeContent and insert it into the div
var popover;
if(loadOriginal)
popover = $('#popover-content');//popover.content performs the update of the generated class for us so well we need to do is update the popover itself
else {
popover = $('.popover-content');//otherwise we have to update the dynamically generated popup ourselves.
}
popover.html(syntaxHighlightedResult);
}
SyntaxHighlighter.config.tagName = 'div';//override the default pre because pre gets converted to another tag on the client.
SyntaxHighlighter.all();
});
</script>
Markup
<div style="display: none;">
<label id="codeTemplate">
//Not using Dynamic object and default Section (appSettings):
var actual = new Configuration().Get("$$propertyNameToken");
//Using a type argument:
int actual = new Configuration().Get<int>("asdf");
//And then specifying a Section:
var actual = new Configuration("SectionName").Get("test");
//Using the Dynamic Object and default Section:
var actual = new Configuration().NumberOfRetries();
//Using a type argument:
int actual = new Configuration().NumberOfRetries<int>();
//And then specifying a Section:
var actual = new Configuration("SectionName").NumberOfRetries();
</label>
<div id="codeContent" class="brush: csharp;">
</div>
</div>
<div id="popover-content" style="display: none">
</div>

Categories