I have a products page that I want to show 3 items in each row and then if it has more, create a new row and show more. So 3 cols per row with unlimited rows. Below is the code that I have that contains my loop which I assume the code will need to go into.
$(data).find('products').each(function() {
itemName = $(this).find('itemName').text();
itemDesc = $(this).find('itemDesc').text();
itemID = $(this).find('id').text();
items +='<div class="row-fluid">\
<div class="span3">Col 1</div>\
<div class="span3">Col 2</div>\
<div class="span3">Col 3</div>\
</div>';
count++;
});
Here is where I need to do it but I am a little stuck on how to approach this. If the count is dividable by 3 I assume it will then need to create a new row.
Thanks for any help or input you can provide.
First of all, no need to handle a count variable on your own, the .each() function already supplies an index element (as an optional argument).
With the modulus operator you can get the remainder from dividing the index by 3. Then you can tell when do you need to print the opening of the row and the ending of it.
$(data).find('products').each(function(index) {
itemName = $(this).find('itemName').text();
itemDesc = $(this).find('itemDesc').text();
itemID = $(this).find('id').text();
if ((index % 3) == 0) items += '<div class="row-fluid">';
items += '<div class="span3">Col 1</div>';
if ((index % 3) == 2) items += '</div>';
});
if (items.substr(-12) != '</div></div>') items += '</div>';
Going left field, don't! Use CSS instead.
Style up your span3 class to have a with of 30ish % with a display of inline block. That way when you decide to display 2, 4 or 60 per row you only need to change the CSS. This also opens you up to change the number of items per row with CSS media queries for diferent viewports e.g. mobile.
Further more this way you don't need to worry about closing off the row when your items returned aren't divisible by 3
On a side note, if you decide to go the CSS route, consider using <ul> and <li> instead, as semanticaly you have a list.
http://jsfiddle.net/UKQef/1/
Update Fiddle updated to demonstrate use of li and the flexibility of this approach.
You should use modulus: http://msdn.microsoft.com/en-us/library/ie/9f59bza0(v=vs.94).aspx
It gives you a remainder back from dividing two numbers so you could probably do this with something like (inside your .each):
if(!($(this).index() % 2)){
// Add your row
}
$(this).index() returns the index of your .each() and the % 2 returns the remainder of that index divided by 2 so the first 3 times this runs it'd be like this:
0 / 2 = 0 = add a row
1 / 2 = .5 = don't add a row
2 / 2 = 1 = don't add a row
Hopefully this is what you meant.
Here's what I think is a cleaner approach:
// Map each product to a cell.
var cells = $(data).find('products').map(function() {
var itemName = $(this).find('itemName').text();
var itemDesc = $(this).find('itemDesc').text();
var itemID = $(this).find('id').text();
return $('<div></div>').addClass('span3').text(itemName+' '+itemDesc+' '+itemID);
});
// Collect the cells into rows.
var rows = [];
for (var i=0, j=cells.length; i<j; i+=3) {
rows.push(
$('<div></div>')
.addClass('row-fluid')
.append(cells.slice(i,i+3))
);
}
The best approach to your issue is using jquery template. you can fetch your data in Json format by ajax request and create rows dynamically by jquery template:
<script src="jquery.tmpl.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
var data = [
{ name: "Astor", product: "astor", stocklevel: "10", price: 2.99},
{ name: "Daffodil", product: "daffodil", stocklevel: "12", price: 1.99},
{ name: "Rose", product: "rose", stocklevel: "2", price: 4.99},
{ name: "Peony", product: "peony", stocklevel: "0", price: 1.50},
{ name: "Primula", product: "primula", stocklevel: "1", price: 3.12},
{ name: "Snowdrop", product: "snowdrop", stocklevel: "15", price: 0.99},
];
$('#flowerTmpl').tmpl(data).appendTo('#row1');
});
</script>
<script id="flowerTmpl" type="text/x-jquery-tmpl">
<div class="dcell">
<img src="${product}.png"/>
<label for="${product}">${name}:</label>
<input name="${product}" data-price="${price}" data-stock="${stocklevel}"
value="0" required />
</div>
</script>
Html:
<div id="row1" class="drow"></div>
var $products = $(data).find('products');
var num_of_rows = Math.ceil($products.length/3)
//Create your rows here each row should have an id = "row" + it's index
$products.each(function(i,val){
var row_index = Math.ceil(i/3)
$('#row' + row_index).append("<div>Col"+i%3+"</div>")
});
Something like that should work
Related
I have been struggling with this for days, I have a function that takes in an object and loops through it creating rows with display columns in it (bootstrap), so I am using jQuery in order to select an id with a value from the object itself and appending to it, but for some reason the id selector that contains the variable is not seen and does not append to the div with the id?
function add_to_page(product) {
//this is the main row that will contain the columns
var rows = $('<div class="row" ></div>');
for (var category in products) {
for (var i = 0; i < products[category].length; i++) {
rows.append($("<div id='" +products[category][i].id +"'></div>").html($('<img>').attr('src', products[category][i].img)));
//the below code is never executed it only creates the image and stops appending after that
$('#' + products[category][i].id).append($('<div class=name>').text(products[category][i].name));
$('#' + products[category][i].id).append($('<div class=category>').text(products[category][i].category));
$('#' + products[category][i].id).append($('<div class=price>').text('Price: ' + products[category][i].price + 'L.E'));
$('#' + products[category][i].id).addClass('col-md-3');
//the below code is to create a new row after filling each row with 4 display columns
if ( i % 3 == 0 && i != 0 ) {
$('#content').append(rows.clone(true));
rows = $('<div class="row"></div>');
}
}
}
}
here is the html i am trying to append to:
<div class="container full-width">
<div id="content"></div>
</div>
i am calling the function normally , no error in the console
add_to_page(products);
You cannot use a selector to find an element you have not yet added to the document (unless you use the second argument of $(selector, context)). But for your purposes, you can just use the fact that the append method can accept more than one argument.
With some other changes to make your code more jQuery-like, you get this:
function add_to_page(products) {
//this is the main row that will contain the columns
var rows = $('<div>').addClass("row");
for (var category in products) {
for (var item of products[category]) {
rows.append(
$("<div>").attr("id", item.id).addClass('col-md-3').append(
$('<img>').attr('src', item.img),
$('<div>').addClass('name').text(item.name),
$('<div>').addClass('category').text(item.category),
$('<div>').addClass('price').text('Price: ' + item.price + 'L.E')
)
);
//the below code is to create a new row after filling each row with 3 display columns
if ( rows.children().length >= 3 ) {
$('#content').append(rows);
rows = $('<div>').addClass("row");
}
}
}
// Flush any rows that were not yet added:
if ( rows.children().length ) {
$('#content').append(rows);
}
}
// Sample data
var products = {
fruit: [{
name: 'apple',
price: 2.20,
img: "https://www.colourbox.com/preview/7011130-apples-on-table-and-knife.jpg",
category: 'fruit'
}, {
name: 'kiwi',
price: 3.10,
img: "https://www.colourbox.com/preview/10157893-kiwi-fruit.jpg",
category: 'fruit'
}, {
name: 'banana',
price: 1.50,
img: "https://www.colourbox.com/preview/6294218-banana.jpg",
category: 'fruit'
}],
vegetables: [{
name: 'lettuce',
price: 0.90,
img: "https://www.colourbox.com/preview/2347661-fresh-salad-leaves-assortment-in-a-basket.jpg",
category: 'vegetables'
}, {
name: 'radish',
price: 1.60,
img: "https://www.colourbox.com/preview/3602479-red-radish.jpg",
category: 'vegetables'
}]
};
add_to_page(products);
.col-md-3 { display: inline-block; margin: 5px }
img { max-width: 100px }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container full-width">
<div id="content"></div>
</div>
Append all your dynamic html to an element stored in a variable then append that variable to the DOM. that selector wont work bc the element you are trying to access doesnt exists yet.
Do something like:
var el = $('<div' + id + '></div>')
el.append(your row constructed html)
Then append that to your rows at the end of the 2nd for loop.
EDIT
for(.... row iteration code) {
var el = $('<div' + id + '></div>');
el.append('your image goes here');
el.append('name element');
...
rows.append(el);
}
Use #trincot's superior answer, but note that you can also pass an entire HTML string into jQuery's .html(). E.G:
function add_to_page(products) {
var $content = $("#content"),
cols=3, //number of columns
$row,
i=0;
for (var category in products) {
for (var item of products[category]) {
if(i%cols == 0){//add a new row
$row = $("<div class='row'></div>");
$content.append($row);
}
//add the item to the row
$row.append($("<div class='col-md-"+cols+"' id='" +item.id +"'><img src='"+item.img+"' /><div class=name>"+item.name+"</div><div class=category>"+item.category+"</div><div class=price>Price: "+item.price+"L.E</div>"));
i++;
}
}
}
var products = {
"jam" : [
{"id":1,"name":"strawberry", "category":"jam", "price":123, "img":"pic.gif"},
{"id":2,"name":"rasberry", "category":"jam", "price":456, "img":"pic.gif"},
{"id":3,"name":"madberry", "category":"jam", "price":123, "img":"pic.gif"},
{"id":4,"name":"sadberry", "category":"jam", "price":456, "img":"pic.gif"}
],
"bees" : [
{"id":4,"name":"baz", "category":"bee", "price":1, "img":"pic.gif"},
{"id":5,"name":"buzz", "category":"bee", "price":2, "img":"pic.gif"}
]
};
//console.log(products);
add_to_page(products);
.row {display:block; border:1px solid blue;}
.row div[id] {display:inline-block; border:1px solid red; width:100px; margin:5px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container full-width">
<div id="content"></div>
</div>
You need to restructure the code.. are you able to find the element first
rows.append($("")
Instead use products.category [i].id then use jQuery wrap () to wrap the columns to row .
I'm comparing 2 integers in a loop which loops through an array which contains JSON objects. The JSON objects contain an ID and a Name. When I click on the close button of my window that window should disappear and the others should remain but what happens is that if I close the window with the highest ID all of them get close and if for example I close the fifth one the #1-5 gets closed but the higher IDs remain.
ctrl.items = [{
id: 1,
name: "Number one"
},
{
id: 2,
name: "Number two"
},
{
id: 3,
name: "Number three"
}];
ctrl.removeWindow = function(toBeRemovedId) {
console.log("Called: " + toBeRemovedId);
for (var i = ctrl.items.length - 1; i >= 0; i--) {
console.log(ctrl.items[i].id + " " + toBeRemovedId);
if(ctrl.items[i].id == toBeRemovedId) {
ctrl.items.splice(ctrl.items[i], 1);
}
};
}
<div class="window show" id="item_window" ng-repeat="item in itemWindowCtrl.items">
<hgroup>
<h1>{{ item.name}}</h1>
<button class="close" ><span class="fa fa-times fa-lg" ng-click="itemWindowCtrl.removeWindow(item.id)"></span></button>
</hgroup>
<section>
<p>{{ item.id + " " + item.name }}</p>
</section>
</div>
Thanks in advance for any clues you might be able to bring me.
The array splice function takes the index, rather than the item to remove, so you should just be doing:
ctrl.items.splice(i, 1);
The looping is a bit unnecessary though. Why don't you just pass through the item to be removed rather than looping through in descending id order to find matching, id? For example:
ng-click="itemWindowCtrl.removeWindow(item)"
Then in your controller / directive:
ctrl.items.splice(ctrl.items.indexOf(item), 1);
change the for loop to get the index and then just remove that index
var index = -1;
for (var i = 0; i < ctrl.items.length; i++) {
if(ctrl.items[i].id === toBeRemovedId) {
index = i;
}
};
ctrl.items.splice(index, 1);
In my application i have 2 array of object.layout array is for creating twitter bootstrap layout.this array is like below :
$scope.layout = [
{c:[{size:12}]},
{c:[{size:2},{size:3},{size:4},{size:3}]},
{c:[{size:3},{size:5},{size:4}]}
];
you can see how this array work in this jsbin.the other array is items array and this array is like below:
$scope.items =[
{row:1,column:0,names:['Jack','Daniel']},
{row:3,column:3,names:['Eli','Bill']},
{row:2,column:1,names:['Fred','David']}
];
and this is the repeater that i used :
<div ng-repeat="(ri,r) in layout" class="row">
<div ng-repeat="(ci,c) in r.c" class="col-md-{{c.size}} col-sm-{{c.size}} col-xs-{{c.size}} col-lg-{{c.size}} bi"> Row{{ri}}-Column{{ci}}
//Maybe other repeater come here
</div>
</div>
now i want when i want to display Jack , Daniel in row 1 column 0 and this 1 and 0 is r and c in repeater of first and second repeater.so when the repeater create row 2 column 1 also repeat on $scop.item and find the related names. but i don't know how to find items in $scope.item.and this is my jsbin
You can do something like this:
<div ng-repeat="(ri,r) in layout" class="row">
<div ng-repeat="(ci,c) in r.c" class="col-md-{{c.size}} col-sm-{{c.size}} col-xs-{{c.size}} col-lg-{{c.size}} bi">
{{getNames(ri, ci)}}
</div>
</div>
Where getNames is defined in controller:
$scope.getNames = function(r, c) {
var items = $scope.items;
for (var i = 0; i < items.length; i++) {
if (items[i].row == r && items[i].column == c) {
return items[i].names;
}
}
return '';
};
Demo: http://jsbin.com/sumuwigo/1/edit
transaction.executeSql('SELECT * FROM Table1 ORDER BY date DESC', [],
function(transaction, result) {
if (result != null && result.rows != null) {
for (var i = 0; i < result.rows.length; i++) {
var row = result.rows.item(i);
$('#records').append('<li date = "'+row['date']+'" data-rowid2="' + row['Id'] + '">' + 'test: ' + row['column1'] + '</li>').trigger('change');
}
}
},errorHandler);
transaction.executeSql('SELECT * FROM Table2 ORDER BY date DESC', [],
function(transaction, result) {
if (result != null && result.rows != null) {
for (var i = 0; i < result.rows.length; i++) {
var row = result.rows.item(i);
$('#records').append('<li date = "'+row['date']+'" data-rowid="' + row['Id'] + '">'+ 'Test2: ' + row['column1'] +'</li>').trigger('change');
}
$("#records").listview().listview("refresh");
}
},errorHandler);
},errorHandler,nullHandler);
This is some code which selects columns from 2 different tables. The tables have two different columns called date which stores the date input. Notice the date attribute assigned to the li elements. My problem comes here:
$(document).on("pageinit", "#mypage", function(){
$("#records").listview({
autodividers: true,
autodividersSelector: function (li) {
var out = li.attr("date");
return out;
}
}).listview("refresh");
});
Now since the dates are from two different tables, two autodividers are being created even if they share the same date. For example if table1 date column had 2014-01-02 and table2 date column had 2014-01-02, two separate dividers would be created for the same date and the list elements wouldn't be shown under one. Is there a solution to this problem?
NOTE: Both the select statements append results to the same listview, they are just from different tables
EDIT: I think I have found what the issue is, but I am not sure on how to solve it. Basically, when I append those list elements, Table1 stuff takes priority and always goes first. For example if I had the list like this:
Table 1 item
Table 2 item
Then if I added another Table 1 item it will do this:
Table 1 item
Table 1 item
Table 2 item
Which is displacing the table 2 item from its original position. Is there anyway to solve this?
I have a list of cars:
cars = [
{model: "BMW", count: 23, details: "<p>I like <b>X6</b> from Germany</p>"},
{model: "Hyundai", count: 30, details: "<p>I have <b>Verna</b> from Korea</p>"},
{model: "Toyota", count: 08, details: "<p>Small <b>Yaris</b> from Japan</p>"},
{model: "Fiat", count: 12, details: "<p>Latest <b>500c</b> from Italy</p>"}
]
What I'm trying to do is:
The list should always be displayed in a three column layout, also in the case of, for
instance, only four models in total.
List should be ordered alphabetically.
Clicking on each car, model should display additional information about the car (some html) as listed above.
The number of cars in the list can vary at any moment.
I'm trying to get my head around these requirements while I'm learning JS. I like to understand the thinking process about each step in order to learn correctly.
Here is an example that may solve your requirements:
1) As I mentioned in a comment, There is an html table with three columns.
2) Sorts cars by model name.
3) When the models are clicked, additional information is displayed about the car. (in this case, in an alert window)
4) You can add cars to the list with the form, and the table is then regenerated.
Run code here.
<html>
<head>
<title>Just for fun</title>
</head>
<body>
<h3>Cars and types:</h3>
<form name="myForm" onsubmit="return mysubmit()">
Model: <input type="text" name="model"><br>
Count: <input type="text" name="count"><br>
Details: <input type="text" name="details"><br>
<input type="submit" value="Submit">
</form>
<button onclick="generate_table()">Generate Table</button><br>
<table class="cars">
</table>
<div class="details" style="float:right;">
</div>
<script>
console.log('script started');
var cars = [
{model: "BMW", count: 23, details: "<p>I like <b>X6</b> from Germany</p>"},
{model: "Hyundai", count: 30, details: "<p>I have <b>Verna</b> from Korea</p>"},
{model: "Toyota", count: 08, details: "<p>Small <b>Yaris</b> from Japan</p>"},
{model: "Fiat", count: 12, details: "<p>Latest <b>500c</b> from Italy</p>"}
]
var types = ["model","count","details"];
function generate_table() {
cars.sort(compare);
var body = document.getElementsByTagName("body")[0];
// creates a <table> element and a <tbody> element
var tbl = document.getElementsByClassName("cars")[0];
tbl.innerHTML = '';
var tblBody = document.createElement("tbody");
console.log(cars.length);
for (var i = 0; i < 3; i++) {
var index = ((j*3)+i);
if (index < cars.length) {
// Create a <td> element and a text node, make the text
// node the contents of the <td>, and put the <td> at
// the end of the table row
var cell = document.createElement("td");
var num = index;
cell.innerHTML = '' + cars[index][types[0]] + ' (' + cars[index][types[1]] + ')';
row.appendChild(cell);
}
}
tbl.appendChild(tblBody);
body.appendChild(tbl);
tbl.setAttribute("border", "2");
}
// This function throws details into 'details' div
function showDetails(num) {
console.log('details showing',num);
div = document.getElementsByClassName('details')[0];
div.innerHTML = '';
var car = cars[num];
div.innerHTML = 'Model: ' + car.model + '</br>Count: ' + car.count +
'</br>Details:\t' + car.details;
}
function mysubmit() {
var model = document.forms["myForm"]["model"].value
var count = document.forms["myForm"]["count"].value
var details = document.forms["myForm"]["details"].value
cars.push({
model:model,
count:count,
details:details
});
generate_table();
return false;
}
function compare(a,b) {
if (a.model < b.model)
return -1;
if (a.model > b.model)
return 1;
return 0;
}
</script>
</body>
</html>
EDIT: Changed code to throw details into div instead of alert box - this way you can see the HTML being run.
EDIT2: Shows cars in 3 columns.
References:
Sorting Function
generate_table function [slightly modified]