Looping through all divs inside a container div using jQuery - javascript

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"
});

Related

JavaScript: List Unique Attributes with Count

I am listing items for sale on a site, and I'd like to have a sidebar to filter the items. So for each attribute, in the filter section I want to list each unique value, and show a count of how many of that value there are.
As a very simple example, in the snippet below, there are four cars listed, with data-attributes for the number of doors. To let people filter by number of doors, I need to show each option in a sidebar and let them check one.
The problem for me is that for some of the attributes, I don't know what values there will be in advance. So I need some way to loop through each item, record each unique value for that attribute, and also count up the instances of each value, and display that in the "Filter By" sidebar.
This is what that sidebar would look like in the example:
Doors:
[] 2 (2)
[] 4 (1)
[] 5 (1)
It looks like I may need to use .each() and .length(), but I'm having trouble putting this together. I have no trouble filtering out the items once the checkboxes are checked, but my problem is just listing out all of the filter options.
<div class="items-list">
<p class="item" data-doors="2">Ford Mustang</p>
<p class="item" data-doors="5">Nissan Versa</p>
<p class="item" data-doors="4">Honda Civic</p>
<p class="item" data-doors="2">Audi A5</p>
</div>
This sets up an object with key/value pairs the key being the number of doors, the value being how many instances of them. Then it runs that object through an iteration which applies the key value pairs to a checkbox input.
const createCheckboxes = (dataName) => {
let refobj = {};
$('.items-list .item').each(function() {
let d = $(this).data(dataName);
refobj[d] = refobj[d] ? refobj[d] + 1 : 1;
});
Object.entries(refobj).forEach(set => $('.inputs[data-group="' + dataName + '"]').append(getCheckbox(set, dataName)));
}
const getCheckbox = (data, keyname) => {
const [key, value] = data;
return `
<label>
<input type='checkbox' name='${keyname}[]' class='${keyname}' value='${key}' onclick='testinput(this)' /> ${key} (${value})
</label>`
}
const testinput = (el) => {
console.log('input with class: ' + $(el).attr('class') + ' and value: ' + $(el).val() + ' and name: ' + $(el).attr('name') + ' clicked')
}
createCheckboxes('doors');
createCheckboxes('wheels');
label {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="items-list">
<p class="item" data-doors="2" data-wheels="4">Ford Mustang</p>
<p class="item" data-doors="5" data-wheels="4">Nissan Versa</p>
<p class="item" data-doors="4" data-wheels="4">Honda Civic</p>
<p class="item" data-doors="2" data-wheels="4">Audi A5</p>
<p class="item" data-doors="1" data-wheels="3">Messerschmitt KR200</p>
</div>
<div class='inputs' data-group="doors">How many doors?</div>
<div class='inputs' data-group="wheels">How many wheels?</div>
The final filterObj (in the below code) holds what you are looking for.
const list = document.getElementsByClassName('item');
//Converting HTMLNode list to Array.
const arrList = Array.from(list);
const filterObj = {};
arrList.map(el => {
filterObj[el.dataset.doors] = filterObj[el.dataset.doors]
? filterObj[el.dataset.doors] + 1
: 1;
});
console.log(filterObj);
<div class="items-list">
<p class="item" data-doors="2">Ford Mustang</p>
<p class="item" data-doors="5">Nissan Versa</p>
<p class="item" data-doors="4">Honda Civic</p>
<p class="item" data-doors="2">Audi A5</p>
</div>

Change text in div class with text from another

I'm trying to change the text in the div class "instock" with the words 'Made by' plus the "companyName" text
<div class="productDetails">
<div class="description">
<span class="companyName>The Company Name</span>
</div>
<div class="instock">Handmade to order</div>
<div>
I want instock to look like this:
<div class="instock">Made by The Company Name</div>
I've tried this
var companyName = document.getElementsByClassName('companyName');
var companyNameText = companyName.nodeValue;
var instock = document.getElementsByClassName('instock');
var instockAlt = instock.nodeValue;
instockAlt.textContent = 'Made by' + companyNameText;
Also as this might not always need to be done (when there is no company name for changing) I think I need to check if the span class is there first.
With JQuery, you can do the following
var companyName = $('.companyName').text();
if(companyName){
$('.instock').html('Made by ' + companyName);
}
With jQuery you could do:
$('.instock').html(function() {
if ( $(this).prev('.description').find('span').length ) return 'Made by ' + $(this).prev('.description').find('span').html()
})
jsFiddle example
You have a " missing in the <span>. The line:
var companyName = document.getElementsByClassName('companyName');
Returns a HTMLCollection. Either change it to:
var companyName = document.getElementsByClassName('companyName')[0];
var companyName = document.querySelector('.companyName');
If you are using jQuery, please use:
var companyName = $('.companyName');
var companyNameText = companyName.text();
var instock = $('.instock');
var instockAlt = instock.text();
instockAlt.text('Made by' + companyNameText);
The reason is, textContent is not available everywhere, while some browsers use innerText. jQuery takes care of browser incompatibilities like this.
Also, the final code is </div> not <div>.
If the whole code is a section block, then you need to use a contextual way:
$(function () {
$('.instock').each(function() {
if ( $(this).prev('.description').find('span').length )
$(this).text('Made by ' + $(this).prev('.description').find('span').text());
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="productDetails">
<div class="description">
<span class="companyName">The Company Name</span>
</div>
<div class="instock">Handmade to order</div>
</div>

Hide data, process to new div with each function

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/

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>

Adding get() to line causes "is not a function" error

This is the start of an inventory system I am working on. Basically it takes an array with items and quantities in a compressed form and outputs the items into an item div.
Running the below produces no error:
$('.item_amount').html(collection[itemName].amo);
Adding the get() method after the selector like so:
$('.item_amount').get(i).html(collection[itemName].amo);
produces "$(".item_amount").get(i).html is not a function".
This is what the line is altering:
<div class="item">
<img src="" class="item_image"/>
<div class="item_amount"></div>
</div>
The line that is causing the error is located in a for loop that loops through all the keys in an array. Then outputs the item quantity from the array in the item_amount div based on the index stored in the variable "i". The for loop also creates an object for each item in the array and puts in the a collection object.
Full code below:
<body>
<div class="item">
<img src="" class="item_image"/>
<div class="item_amount"></div>
</div>
<div class="item">
<img src="" class="item_image"/>
<div class="item_amount"></div>
</div>
<div class="item">
<img src="" class="item_image"/>
<div class="item_amount"></div>
</div>
<script type="text/javascript">
var collection = new Object();
function makeItem(itemName, id, amo) {
collection[itemName] = new item(id, amo);
}
function item(id, amo) {
this.id = id;
this.amo = amo;
}
var inventoryCom = "368.9,366.15,384.32"; //compressed inventory
var inventoryArr = inventoryCom.split(',');
for(var i=0; i < inventoryArr.length; i++) {
var itemName = 'item' + (i + 1); //unique name for each item
var itemArr = inventoryArr[i].split('.');
makeItem(itemName, itemArr[0], itemArr[1]);
$('.item_amount').get(i).html(collection[itemName].amo);
}
</script>
</body>
.get(i) returns DOM element, which doesn't have .html() method - that's what js engine wants to say to you.
You need to use .eq(i) instead. Like
$('.item_amount').eq(i).html(collection[itemName].amo);
or
$('.item_amount:eq(' + i + ')').html(collection[itemName].amo);
This line may be a problem
var itemName = 'item' + (i + 1); //
This may increment the array count out of the upper bound. check the itemName value.
Also try to add an alert for this
collection[itemName].amo

Categories