I have a situation where I have a list of data to be displayed in individual panels, Using Bootstrap's grid system, I'd like to take advantage of a wide screen and display several panels horizontally, but on narrow screens have them stack. I'm currently laying things out on the server side with ejs like this, with columns being passed in as a query parameter, typically set to 2 or 3, so each colClass is either col-sm-6 or col-sm-4.
<% var colWidth = 12/columns; var colClass = "col-sm-" + colWidth; %>
<% for(var i=0; i<contestData.classData.length; i++) {%>
<% if ((classCount % columns) == 0) { %>
<div class="row">
<% } %>
<div class="<%= colClass %>">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title"> <%= contestData.classData[i].name %> </h3>
</div>
<div>...</div>
</div>
</div>
<% classCount++ %>
<% if ((classCount % columns) == 0) { %>
</div>
<% } %>
<% } %>
This works, but doing this level of layout on the server side offends me, I'd really rather do this with Angular but I can't figure out how to wrap the appropriate number of panels in a div with class=row while doing ng-repeat or even ng-repeat-start="classData in contestData.classData"
Thanks!
Here a simple solution with just HTML, 3 ROWS
<div class="row" >
<div class="col-md-4" ng-repeat-start="item in data">
I'M A ROW
</div>
<div class="clearfix" ng-if="($index+1)%3==0"></div>
<div ng-repeat-end=""></div>
</div>
If you start by chunking your data into smaller parts, based on the number of columns, it will be easy to use nested ng-repeats to create your layout:
$scope.getRows = function(array, columns) {
var rows = [];
//https://stackoverflow.com/questions/8495687/split-array-into-chunks
var i,j,temparray, chunk = columns;
for (i=0,j=array.length; i<j; i+=chunk) {
temparray = array.slice(i, i+chunk);
rows.push(temparray);
}
return rows;
};
$scope.rows = $scope.getRows($scope.contestData, $scope.columns);
Then your markup is simply:
<div ng-repeat="row in rows">
<div class="row">
<div ng-class="{'col-xs-4': columns == 3, 'col-xs-3': columns == 4}" ng-repeat="contest in row">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">{{contest}}</div>
</div>
</div>
</div>
</div>
</div>
Notice that ng-class is doing the work of deciding which type of class to add based on the number of columns. This example is handing 3 and 4, but you could extend it to handle others.
Here is a working demo: http://plnkr.co/edit/B3VAXlq9dkzO3hQkbkN3?p=preview
Update:
Plunker's full screen mode seems to interfere with the column width style, so I changed the link to display in preview mode.
Answering my own question here, similar to the answer from j.wittwer, I created a filter to chunk my data appropriately by row, etc.:
angular.module('myApp.filters').
filter('rowfilter', function () {
return function (data, columnCount) {
var rows = [];
var colCount = columnCount || 2;
var columns = [];
for (var i = 0; i< data.length; i++) {
columns.push(data[i]);
if (columns.length == colCount) {
rows.push(columns);
columns = [];
}
}
if (columns.length > 0) {
rows.push(columns);
}
return rows;
};
});
And then I use the filter (jade shown here):
.row(ng-repeat="row in contestData.classData | rowfilter")
.col-sm-6(ng-repeat="column in row")
Works very nicely, still wrapping my head around Angular!
I have this decision, seems to be working for 3 col
<div ng-repeat="r in data">
<div class="row" ng-if="$index%3==0">
<div class="col-md-4" ng-if="$index<data.length">
{{data[$index]}}
rrr
</div>
<div class="col-md-4" ng-if="$index+1<data.length">
{{data[$index+1]}}
rrr
</div>
<div class="col-md-4" ng-if="$index+2<data.length">
{{data[$index+2]}}
rrr
</div>
</div>
</div>
and data is
$scope.data = ['1','2','3','4','5','6','7'];
You can add something like this, first in your controller, do a function dad gets an integer "breakpoint" that is the number of columns you want to wrapped by a row, and the data you want inside each column like so:
function getRows(breakpoint,data) {
var len = data.length; var i = 0;
var rows = []; var temp = [];
for (; i < len; i++) {
if (i % breakpoint == 0 && i != 0) {
rows.push(temp);
temp = [];
}
temp.push(data[i]);
}
var len2 = rows.length * breakpoint;
if (len > len2) {
//var leftOvers = len - len2;
i = len2; temp = [];
for (; i < len; i++) {
temp.push(data[i]);
}
rows.push(temp);
}
return rows;
}
then whenever you recive the data yo simply do:
$scope.rows = getRows(3,data); // in case you want 3 cols.
then in your html:
<div class="row" ng-repeat="row in rows">
<div class="col-lg-4" ng-repeat="data in row">
{{data.whatever}}
</div>
</div>
</div>
and that`s it, it should work for u.
Related
I'm a beginner in JavaScript. I've got a problem.
I need to create some elements and I need to put my API's data is these elements. But I want to create 3 cards (bootstraps) in my first row and 2 in my second row.
But I think my loop isn't ok. Because all my data are on my fifth card.
That's my code HTML and JavaScript:
HTML :
</section>
<section class="container">
<div class="row">
<div id="colCam1" class="col-12 col-lg-4"></div>
<div id="colCam2" class="col-12 col-lg-4"></div>
<div id="colCam3" class="col-12 col-lg-4"></div>
</div>
<div class="row">
<div id="colCam4" class="col-12 col-lg-4"></div>
<div id="colCam5" class="col-12 col-lg-4"></div>
</div>
</section>
JS :
fetch("http://localhost:3000/api/cameras")
.then((response) =>
response.json().then ((data) => {
console.log(data);
for(i=0; i < data.length; i++) {
let indexCard = document.createElement("div");
let indexImg = document.createElement("img");
let indexBodyCard = document.createElement("div");
let indexProductTitle = document.createElement("h5");
let indexProductPrice = document.createElement("p");
colCam1.appendChild(indexCard);
colCam2.appendChild(indexCard);
colCam3.appendChild(indexCard);
colCam4.appendChild(indexCard);
colCam5.appendChild(indexCard);
indexCard.classList.add("card");
indexCard.appendChild(indexImg);
indexImg.classList.add("card-img-top");
indexCard.appendChild(indexBodyCard);
indexBodyCard.classList.add("card-body");
indexBodyCard.appendChild(indexProductTitle)
indexProductTitle.classList.add("card-title");
indexBodyCard.appendChild(indexProductPrice);
indexProductPrice.classList.add("card-text");
indexProductTitle.innerHTML = data[i].name;
indexProductPrice.innerHTML = parseInt(data[i].price) + " €";
indexImg.setAttribute("src", data[i].imageUrl);
}
})
);
That's the result on my inspector :
Result of my code
Thx for your help
If I understood correctly, you are only expecting to get 5 elements from your API and you want to put each of them in one column. If that's the case, you can put your column elements in an array and index them accordingly in your loop like so:
const cols = [colCam1, colCam2, colCam3, colCam4, colCam5]
for(i=0; i < data.length; i++) {
let indexCard = document.createElement("div");
let indexImg = document.createElement("img");
let indexBodyCard = document.createElement("div");
let indexProductTitle = document.createElement("h5");
let indexProductPrice = document.createElement("p");
cols[i].appendChild(indexCard);
indexCard.classList.add("card");
indexCard.appendChild(indexImg);
indexImg.classList.add("card-img-top");
indexCard.appendChild(indexBodyCard);
indexBodyCard.classList.add("card-body");
indexBodyCard.appendChild(indexProductTitle)
indexProductTitle.classList.add("card-title");
indexBodyCard.appendChild(indexProductPrice);
indexProductPrice.classList.add("card-text");
indexProductTitle.innerHTML = data[i].name;
indexProductPrice.innerHTML = parseInt(data[i].price) + " €";
indexImg.setAttribute("src", data[i].imageUrl);
}
This code is going to break if your API returns more than 5 elements. You could try something like cols[i % 5].appendChild(indexCard); or consider other layout strategies.
I have a list of table columns. I would like to display them in one row.
What am I trying is :
for (var i = 0; i < key.length; i++) {
writeToScreen3('<div class="col-sm">' + key[i] + '</div>'); //column name
}
function writeToScreen3(message) {
var pre = document.createElement("p"); //I realize I am creating another element <p> How to do it diffrently?
pre.innerHTML = message;
output.appendChild(pre);
}
What I need is this transferred to JavaScript :
<div class="container">
<div class="row">
<div class="col-sm">
One of three columns
</div>
<div class="col-sm">
One of three columns
</div>
<div class="col-sm">
One of three columns
</div>
</div>
</div>
What I also tried :
function test8() {
$("#output").html('<div class="container">< div class= "row" >'); //but it always closes these 2 divs here . I want it not to close it. output is a div field
}
You can do something like this:
function createContainer(columns) {
function createDivWithClass(cls) {
const div = document.createElement('div');
div.classList.add(cls);
return div;
}
const container = createDivWithClass('container');
const row = createDivWithClass('row');
container.appendChild(row);
for (let i = 0; i < columns.length; i++) {
const column = createDivWithClass('col-sm');
column.textContent = columns[i];
row.appendChild(column);
}
return container;
}
const container = createContainer([1, 2, 3, 4]);
document.getElementById('output').appendChild(container);
console.log(container.outerHTML);
<div id="output"></div>
Here, I've defined a function called createDivWithClass to make it easier to create a <div> and set a class name to it.
Then, I'm using this function to create a <div class="container">, create a <div class="row"> and add that to the container, and then go through the columns array and create <div class="col-sm"> and add those to the row element.
Just like you can append elements to the #output element in the DOM, you can also append elements to elements that you've created and are not yet in the DOM.
I'm attempting to extract the ID of checkbox when it is selected, but I can't seem to find a way that fits what I'm trying to do.
First I have the HTML / Angular for the check boxes. The check boxes are generated by three tiers. First there's a service level, then the day of the week and then the service itself (which are what the check boxes are). The service level makes an accordion, the days of the week are loaded into tabs and the check boxes themselves come in as normal.
<div class="delivery-rules">
<div class="panel-group" id="accordion">
<div class="panel panel-default" ng-repeat="level in settings.serviceLevels">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#{{level.LevelTmsCode}}">{{level.LevelName}}</a>
</h4>
</div>
<div id="{{level.LevelTmsCode}}" class="panel-collapse collapse in">
<div class="panel-body">
<ul class="nav nav-tabs">
<li id="{{day.Day}}-{{level.LevelTmsCode}}-tab" ng-repeat="day in settings.serviceDays">
<a id="{{day.Day}}-{{level.LevelTmsCode}}" href="#tabContent-{{day.Day}}-{{level.LevelTmsCode}}" ng-click="settings.changeTab(day, level, $event)">{{day.Day}}</a>
</li>
</ul>
<div class="tabContent" id="tabContent-{{day.Day}}-{{level.LevelTmsCode}}" ng-repeat="day in settings.serviceDays">
<h4>{{day.Day}}</h4>
<div class="time-check" ng-repeat="service in settings.services">
<input type="checkbox" value="None" ng-change="settings.showChecked(settings.rules, $event)" ng-model="settings.selected[$index]" class="time-check-input" id="{{level.LevelTmsCode}}-{{day.Day}}-{{service.TimeValidation}}" name="check"/>
<label for="{{level.LevelTmsCode}}-{{day.Day}}-{{service.TimeValidation}}" class="time-check-input"></label> <span>{{service.TimeValidation}}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
The arrays that build those check boxes, tabs and accordion are loaded with data from a standard http post request. Then once that is complete I place all the possible combinations of all three arrays into one big array and set their checked attribute to false.
// Get Service Levels to Build Delivery Rules Accordion
settings.getDeliveryServices = function() {
$http.get(resourceBase + "api/service/levels").success(function(data) {
settings.serviceLevels = data;
// Get Service Days
$http.get(resourceBase + "api/service/days").success(function(days) {
settings.serviceDays = days;
// Build the Accordion
setTimeout(() => settings.triggerClick(settings.serviceLevels), 500);
$http.get(resourceBase + "api/service/services").success(function (services) {
settings.services = services;
// Build a collection of all possible rules
for (var a = 0; a < settings.serviceLevels.length; a++) {
settings.rulesTmsCode.push(settings.serviceLevels[a].LevelTmsCode + "-");
}
for (var b = 0; b < settings.serviceDays.length; b++) {
settings.rulesDay.push(settings.serviceDays[b].Day + "-");
}
for (var c = 0; c < settings.services.length; c++) {
settings.rulesTime.push(settings.services[c].TimeValidation);
}
var allArrays = [settings.rulesTmsCode, settings.rulesDay, settings.rulesTime];
function allPossibleCases(arr) {
if (arr.length === 1) {
return arr[0];
} else {
var result = [];
var allCasesOfRest = allPossibleCases(arr.slice(1));
for (var i = 0; i < allCasesOfRest.length; i++) {
for (var j = 0; j < arr[0].length; j++) {
result.push(arr[0][j] + allCasesOfRest[i]);
}
}
return result;
}
}
var uncheckedRules = allPossibleCases(allArrays);
for (var i = 0; i < uncheckedRules.length; i++) {
settings.rules.push({
id: uncheckedRules[i],
checked: false
});
}
});
});
});
}
When each box is checked I'm trying to manipulate the combination array so that the selected combination is set to true.
// Check and Filter Rules to send
settings.showChecked = function (object, $event) {
for (var i = 0; i < settings.rules.length; i++) {
if (settings.rules.hasOwnProperty(i)) {
if (typeof settings.rules[i].id == settings.selected[i]) {
settings.showChecked(settings.rules[i], settings.selected[i]);
}
if (settings.rules[i].id === settings.selected[i]) {
settings.rules[i].checked = true;
}
}
}
console.clear();
console.log(settings.rules);
}
Currently, nothing is set to true as I can't seem to be able to get the ID from the checkbox to compare it with the string stored in the ID value of the combination array. So basically I need the ID of the checkbox that was selected and I need to pass that through to the ng-change event.
Try Some thing like this..
<input id={{emp.name}} type=checkbox value="{{emp.name}}" ng-change="settings.showChecked(settings.rules, $event)>
settings.showChecked=function(object,$event)
{
var el = event.target.id
}
the other way is you can pass id value in place event like below
<select id="hairColorComponent" ng-model="hairColor"
ng-options="option.name for option in hairColorData"
ng-change="updateUserData('hairColorComponent')">
$scope.updateUserData = function (id) {
var element = jQuery('#'+id);
};
I'm having trouble with this logic since react/jsx does not allow for non closing tags to be added to an array/child component. For example with bootstrap css I want to add a row for every 4 columns.
So the logic is as follows:
Add a opening row ex: <div className="row">, then loop inside this row and every loop append a column ex: <div className="column>{this.data}</div> when the loop reaches 4 check with if(i % 4 == 0) and add a closing </div> tag while adding new row tag <div className="row">;
The code below would work in another language but in react this is not doable since we push a closing tag and a opening tag (which is invalid jsx):
generateColumns(columns) {
let newColumns = [];
columns.forEach(function(column, idx) {
newColumns.push( <div className="column"> some data </div> );
if (idx % 4 == 0) {
// Here we end the row and start a new row, works in any other language.
newColumns.push( </div> <div className="row"> );
}
});
// This array now has the proper tags for opening a row every 4th item and closing it.
return newColumns;
},
render() {
return (
<div className="row">
{this.generateColumns(this.props.columns)}
</div>
)
}
The expected output would be:
<div class="row">
<div class="column">
Some data
</div>
<div class="column">
Some more data
</div>
<div class="column">
Other data
</div>
<div class="column">
Something else
</div>
</div>
<div class="row">
<div class="column">
Some data
</div>
<div class="column">
Some more data
</div>
<div class="column">
Other data
</div>
<div class="column">
Something else
</div>
</div>
//the above would be repeated and new rows would appear every 4 columns.
render() {
const rows = array_chunk(this.props.columns, 4)
return (
{
rows.map((row) => (
<div className="row">
{
row.map((col) => (
<div className="col">{ col }</div>
))
}
</div>
))
}
)
}
An example array_chunk (I recommend that you use lodash)
module.exports = function chunks(arr, size) {
if (!Array.isArray(arr)) {
throw new TypeError('Input should be Array');
}
if (typeof size !== 'number') {
throw new TypeError('Size should be a Number');
}
var result = [];
for (var i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, size + i));
}
return result;
};
I actually just used arrays and react handled fine the rendering.
render() {
let rows = [],
cols = []
let index = 0
const totalCols = 20;
for (index; index < totalCols; index++) {
cols.push(<div class="col" key={index}/>)
if ((index + 1) % 4 == 0) {
rows.push(
<div class="row" key={index}>
{cols}
</div>
)
cols = []
}
}
return (
<div class="container">
{rows}
</div>
)
}
I have some checkboxes inside blocks like this :
<div ng-show="searchIn.IS" class="filterContent">
<div ng-click="toggleFilter('IS')">
<span class="{{filters.IS}}FilterIcon"></span>
Information System :
</div>
<div ng-show="filters.IS">
<md-checkbox ng-repeat="IS in ISList" ng-click="setISSearch(IS)" ng-checked="isInISSearch(IS)">
{IS}}
</md-checkbox>
</div>
</div>
<br />
<div ng-show="searchIn.area" class="filterContent">
<div ng-click="toggleFilter('area')">
<span class="{{filters.area}}FilterIcon"></span>
Area :
</div>
<div ng-show="filters.area">
<md-checkbox ng-repeat="area in filterAreaList" ng-click="setAreaSearch(area)" ng-checked="isInAreaSearch(area)" ng-show="isInCurrentAreas(area)">
{{area}}
</md-checkbox>
</div>
</div>
So the thing is that filterAreaList is changed each time I check or uncheck a checkbox from the ISList block. But the view is not updated, all the area are still displayed.
I also tried ng-if instead of ng-show.
$scope.filterAreaList = [];
$scope.getAreaFilters = function () {
$scope.searchedIS = $scope.ISList.slice();
for (var i = $scope.searchedIS.length; i >= 0; i--) {
if ($scope.searched.IS.indexOf($scope.searchedIS[i]) === -1)
$scope.searchedIS.splice(i, 1);
}
for (var i = 0; i < $scope.areaList.length; i++) {
for (var j = 0; j < $scope.searchedIS.length; j++)
if ($scope.hasArea($scope.searchedIS[j], $scope.areaList[i]))
$scope.filterAreaList.push($scope.areaList[i]);
}
};
$scope.isInCurrentAreas = function (area) {
if ($scope.filterAreaList.indexOf(area) === -1)
return false;
return true;
};
Some other topics pointed to this : http://www.bennadel.com/blog/2443-rendering-dom-elements-with-ngrepeat-in-angularjs.htm
But I don't really see how to apply this to my case.
Well, I actually missed a few things :
In order to make it work, I added : $scope.filterAreaList = []; in my getAreaFilters() function, and I call getAreaFilters() in my setAreaSearch(area)
And that's it.