Extra div created by ng-repeat - angularjs

I am attempting to use angularJS to print out some data. Here is a relevant segment of the script:
$scope.incidentCats = [{
name: "FIRE",
incidents: [{
location: "location 1",
dateTime: "datetime 1",
currStatus: "currStatus 1"
},
{
location: "location 2",
dateTime: "datetime 2",
currStatus: "currStatus 2"
}]
},
{
name: "CODE BLUE",
incidents: [{
location: "location 3",
dateTime: "datetime 3",
currStatus: "currStatus 3"
}]
}];
As you can see in the data, incident type FIRE has records of two incidents, while incident type CODE BLUE has only one.
Here's the relevant code in the EJS file:
<div ng-repeat="category in incidentCats" class="incident">
<header class="category">
<strong>{{ category.name }}</strong>
</header>
<section>
<div ng-repeat="incident in category.incidents">
{{ incident.location }}<br>
{{ incident.dateTime }}<br>
{{ incident.currStatus }}<br>
</div>
</section>
</div>
Finally, here's my CSS for this particular section of the HTML (excluding any common CSS used for the whole project that is out of my hands):
.incident {
width: 100%;
height: 100%;
border: 1px solid #fff;
position: relative;
padding: 5px;
margin: 25px 0;
}
.incident section {
margin-top: 10px;
}
.incident section > div {
margin: 5px auto;
}
So far, the code works as expected except for one thing: when the div elements are created in the incident loop, I'm expecting two div to be created for the FIRE category and only one div for the CODE BLUE category. However, the code created two div for both categories.
That is not what I want. As a result of the second div in CODE BLUE containing <br> tags but no data because there is no second incident in the records for this category, there is now a section of empty space where this second div is location under the first div, which is fine because the first incident does exist and is correctly printed out. How can I rectify this?

Try using ng-repeat-start and ng-repeat-end
From Angular version 1.2 onwards, ng-repeat got two siblings directives named ng-repeat-start and ng-repeat-end. With these, we can explicitly specify the starting and ending for ng-repeat. So, instead of using the ng-repeat directive on one single DOM element, we can specify the ng-repeat-start and ng-repeat-end on any two DOM elements.
<div class="incident">
<header ng-repeat-start="category in incidentCats" class="category">
<strong>{{ category.name}}</strong>
</header>
<section ng-repeat-end>
<div ng-repeat="incident in category.incidents">
{{ incident.location }}<br>
{{ incident.dateTime }}<br>
{{ incident.currStatus }}<br>
</div>
</section>
</div>

I tried to replicate the behavior and I can see everything working well.
The ng-repeat is generating the HTML structure as it should. This might relate to the data you have posted. If the data posted is correct then HTML should work as expected. You can try sharing your exact code in order to debug the issue. I have used following array to reproduce the same:
$scope.incidentCats = [{
name: "FIRE",
incidents: [{
location: "location 1",
dateTime: "datetime 1",
currStatus: "currStatus 1"
},
{
location: "location 2",
dateTime: "datetime 2",
currStatus: "currStatus 2"
}]
},
{
name: "CODE BLUE",
incidents: [{
location: "location 3",
dateTime: "datetime 3",
currStatus: "currStatus 3"
}]
}];
Plnkr Link

Related

How to create a unique hyperlink for each element in an angularjs array

So i have an array of random names and i want each name in the array to have its own hyperlink. When displayed it should basically be a list of names that each link to different pages, how do i go about giving each name in the array a different hyperlink? I attached a snippet of the html and js.
$scope.artists=[
{
name: "Noelle",
rank: "Gold",
worth: 752000,
},
{
name: "John",
rank: "Silver",
worth: 700000,
},
{
name: "Shem",
rank: "Green",
worth: 5687952,
},
<ul>
<li ng-repeat="artist in artists | orderBy: order | filter: search">
<h3>{{artist.name}} - {{artist.worth | currency:'£'}}</h3>
<div class="remove" ng-click="removeArtist(artist)">x</div>
<span class="rank" style="background:{{artist.rank}}">{{artist.rank}}</span>
</li>
</ul>
You would include the href as part of the artist object - and have a standard URL that differs only in the name of the artist.
In the following I am interpolating the artist into the href of the a. Note that I am coding this as a link property with the artists full name in case there are two artists with the same first name.
Note that hovering over the link text will show you the link href - if you click it - nothing will show since there isnt a corresponding page to navigate to.
var app = angular.module('myApp', []);
app.controller('myController', function ($scope) {
$scope.artists=[
{
name: "Noelle",
rank: "Gold",
worth: 752000,
link: "noelle-smith"
},
{
name: "John",
rank: "Silver",
worth: 700000,
link: "john-jones"
},
{
name: "Shem",
rank: "Green",
worth: 5687952,
link: "shem-willis"
}]
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myController">
<ul>
<li ng-repeat="artist in artists | orderBy: order | filter: search">
<h3>{{artist.name}} - {{artist.worth | currency:'£'}}</h3>
<span class="rank" style="background:{{artist.rank}}">{{artist.rank}}</span><br/>
View Artist
</li>
</ul>
</div>

v-for render or dont render <div> on {x} iteration

I did not find solution to my question anywhere and I can't figure it out. I have divs like this
<div class="columns">
<div class="column">
{looping content here}
</div>
</div
Data is something like this:
{
title: 'blabla'
body: 'blabla'
msg: 'blabla"
}
For responsive purposes I need 3 columns max side by side and then start another columns container that will stack columns underneath. So 3 column divs inside columns container and then create another columns div with 3 column divs inside and go until the array is empty.
I have tried computed count property but don't know how to iterate it inside of v-for. Also tried v-if but it didnt work as planned :(
Is it even possible in v-for? I dont know what approach to take to be honest.
Why not just insert each column inside a single columns container and then use CSS to wrap to a new row every 3 columns. The added benefit of this is that you can adjust the number of columns that appear in each row with media queries.
Try running the snippet below in full screen and the resize the browser to less than 576px wide to see the responsive columns.
new Vue({
el: '#app',
data () {
return {
columns: [
{
title: 'blabla',
body: 'blabla',
msg: 'blabla'
},
{
title: 'blabla',
body: 'blabla',
msg: 'blabla'
},
{
title: 'blabla',
body: 'blabla',
msg: 'blabla'
},
{
title: 'blabla',
body: 'blabla',
msg: 'blabla'
},
{
title: 'blabla',
body: 'blabla',
msg: 'blabla'
}
]
}
}
})
.columns {
display: flex;
flex-wrap: wrap;
}
.column {
box-sizing: border-box;
padding: 1em;
width: 33.3%;
}
/* on devices smaller than 576px, stack columns */
#media (max-width: 576px) {
.column {
width: 100%;
}
}
<script src="https://unpkg.com/vue#2.6.8/dist/vue.min.js"></script>
<div id="app">
<div class="columns">
<div v-for="(column, index) in columns" class="column" :key="index">
<h2>{{ column.title }}</h2>
<p>{{ column.body }}</p>
<strong>{{ column.msg }}</strong>
</div>
</div>
</div>
It's possible to nest v-for loops if the data is correctly formatted.
For example, an array (for the first v-for) of objects (for the second loop):
new Vue({
el: "#app",
data() {
return {
items: [
{
title: 'Title 1',
body: 'body 1',
msg: 'message 1'
},
{
title: 'Title 2',
body: 'body 2',
msg: 'message 2'
}
]
}
}
})
.columns {
align-items: center;
display: flex;
height: 40px;
justify-content: space-around;
width: 50%;
}
<script src="https://unpkg.com/vue#2.6.8/dist/vue.min.js"></script>
<div id="app">
<div class="columns" v-for="item in items">
<div class="column" v-for="(value, key) in item">
<div>{{ value }}</div>
</div>
</div>
</div>

Displaying nested JSON data in AngularJS

I am building a cross platform app using Onsen UI, Monaca and AngularJS.
I have a screen where the user can select from various switches using Onsen UIs built in switches (Switch in List Item). Toggling a switch means that vehicle check needs to be performed, else it is assumed that all checks have passed.
I can display the Check Descriptions (checkitemdesc) as per the JSON below on the list item switches, but when I toggle any of the switches I want to be able to display their related "answers": [{...}] via a modal.
So toggling the "Engine oil level" switch, the user sees a modal with the related checks that can be performed on the "Engine oil level" e.g. Low, top up etc.
JSON example of the data
[{
"fleetcheckitemid": "1",
"checkitemdesc": "Engine oil level",
"answers": [{
"fleetcheckid": "1",
"checkvaluedesc": "Ok"
}, {
"fleetcheckid": "2",
"checkvaluedesc": "Low"
}, {
"fleetcheckid": "3",
"checkvaluedesc": "Top-Up Required"
}]
}, {
"fleetcheckitemid": "2",
"checkitemdesc": "Water level",
"answers": [{
"fleetcheckid": "1",
"checkvaluedesc": "Ok"
}, {
"fleetcheckid": "2",
"checkvaluedesc": "Low"
}, {
"fleetcheckid": "3",
"checkvaluedesc": "Top-Up Required"
}]
}]
My checksController.js used for getting JSON from $http API call which returns a JSON object.
$http.get("http://myfakedomain/api/getfleetchecks.php?fleetid=109").success(function(data)
{
$scope.checkItemDescriptions = data;
});
And my checks.html for displaying switches based on "checkitemdesc" in JSON.
<ul class="list">
<li class="list__item" ng-repeat="checkItemDescription in checkItemDescriptions">
{{checkItemDescription.checkitemdesc}}
<label class="switch switch--list-item">
<input type="checkbox"
class="switch__input"
checked >
<div class="switch__toggle"></div>
</label>
</li>
</ul>
Selecting any of the switches should fire the modal and populate it with the relevant "answers": [{...}] values
modal
<ons-modal var="modal">
<div class="alert-dialog-mask"></div>
<div class="alert-dialog alert-dialog--android">
<div class="alert-dialog-title alert-dialog-title--android">
<div style="text-align: center">Further Details</div>
</div>
<div class="alert-dialog-content alert-dialog-content--android">
<div style="text-align: center; padding-top: 10px; padding-bottom: 15px; padding-left: 10px; padding-right: 10px;">
<p>
Please give further details for<br>
<!-- Display the selected checkitemdesc here - NOT WORKING -->
<strong>{{checkItemDescription[i].checkvaluedesc[i]}}</strong>
</p>
</div>
<!-- Display sub-options for main sections - NOT WORKING-->
<div style="text-align: left; padding-top: 10px; padding-bottom: 15px; padding-left: 10px; padding-right: 10px;">
<!-- Display the selected subitems here - NOT WORKING -->
<label class="checkbox" ng-repeat="checkItemDescription in checkItemDescriptions[i].answers[i].checkvaluedesc">
<input type="checkbox">
<div class="checkbox__checkmark"></div>
<!-- Display the selected subitems here - NOT WORKING -->
{{checkItemDescription[i].answers[i].checkvaluedesc}}
</label>
</div>
</div>
</div>
</ons-modal>
I am able to display the main checks, but how do I do individual checks on each switch and then set the modal values based on that switch?
See this plunker: http://plnkr.co/edit/g952bdedUGuBhC5ez5Im?p=preview
What you do is:
Attach a selected: true/false to the checkitem level as well as the answers level.
Pass the selected row to the modal controller.
Use ng-repeat, using $filter to display the items.
The open modal function:
$scope.openModal = function(items) {
var selectedItems = [];
//get only the selected items
for(var i = 0; i < items.length; i++) {
if(items[i].selected === true) selectedItems.push(items[i]);
}
var modalInstance = $uibModal.open({
templateUrl: 'modalTemplate.html',
controller: MyModalCtrl,
backdrop: 'static',
keyboard: false,
resolve: { //pass selected items to the modal controller
fleetCheckItems: function() {return selectedItems;}
}
});
modalInstance.result.then(function (selectedItem) {
$scope.selected = selectedItem; //user clicked okay
}, function () {
//user click cancel, figure out something to do with the promise
});
}

ng-model value in multidimentional array

I am working in an application where i attach dynamic form attributes with parent element. Sample data will look like this
$scope.uploadedFiles = [{
name: sample.jpg,
attr: [{
id: "caption",
display: "Title",
value: "",
placeHolder: "Enter title",
type: "textbox",
},
{
id: "description",
display: "Description",
placeHolder: "Enter description",
type: "textarea",
rows: 8,
value: "",
}]
},
{
name: sample2.jpg,
attr: [{
id: "caption",
display: "Title",
value: "",
placeHolder: "Enter title",
type: "textbox",
},
{
id: "description",
display: "Description",
placeHolder: "Enter description",
type: "textarea",
rows: 8,
value: "ttttt",
}]
}]
In template i am using code like this
<div class="col-md-4" ng-repeat="item in uploadedFiles">
<div style="padding:10px 4px;">
<img class="{{photocss}}" style="width:100%; height:auto;" src="{{item.name}}" />
<div ng-repeat="config in item.attr">
<input ng-if="config.type=='textbox'" type="text" ng-blur="validate(config)" class="form-control" ng-model="config.value" placeholder="{{config.placeHolder}}"></input>
<textarea ng-if="config.type=='textarea'" rows="config.rows" class="form-control" ng-model="config.value" placeholder="{{config.placeHolder}}"></textarea>
</div>
</div>
When i run application and upload some files. list of uploaded photos appear along with dynamic attributes defined in array e.g Title and Description.
When i type something in Title, all titles updated, it shows ng-model bind with all title attributes even i defined attributes for each item (photo) in separate array.
Issue shown in attached screen:
In screen you will look, when i type test title, its populated with other element titles too. Can someone help my where i made mistake.
Also created plnkr http://plnkr.co/edit/wruINbUYdarcxFYffJ0o?p=preview for issue that i am facing.
I want to put title with each element without conflicting with another element title.
I've made a plnkr here that works with you model structure: http://plnkr.co/edit/svR5Vz?p=preview and there no seem to be this effect. So probably error is in other part of your code.
Also using ng-switch could be a better option in this case, then ng-if:
<div ng-repeat="config in item.attr" ng-switch="config.type">

How to manage certain carousel in <ons-list>

I have almost the same question as in Onsen UI Carousel Swipe list item question. But I need to manage a certain carousel (ccall functions a la setActiveCarouselItemIndex()) in ons-list-item tapped on instead of removing list member.
I wrote the code below:
<ons-page ng-controller="TaskListCtrl">
<ons-toolbar>
<div class="center">Home</div>
</ons-toolbar>
<ons-list>
<ons-list-item modifier="chevron" ng-controller="TaskItemCtrl" ng-repeat="task in tasks">
<ons-row>
<ons-col width="60px">
{{ task.id }}
</ons-col>
<ons-col>
<ons-carousel var="taskCarousel" auto-scroll style="width: 100%; height: 70px" ng-click="nextView()">
<ons-carousel-item>
View #1 {{ task.descr }}
</ons-carousel-item>
<ons-carousel-item>
View #2 {{ task.details }}
</ons-carousel-item>
</ons-carousel>
</ons-col>
</ons-row>
</ons-list-item>
</ons-list>
</ons-page>
The code is:
var app = angular.module('app', ['onsen']);
app.controller('AppController', function($scope) {
});
app.controller('TaskListCtrl', function($scope) {
$scope.tasks = [
{
id: "1",
descr: "Task Description #1",
details: "Details of Task #1"
},
{
id: "2",
descr: "Task Description #2",
details: "Details of Task #1"
},
{
id: "3",
descr: "Task Description #3",
details: "Details of Task #1"
},
{
id: "4",
descr: "Task Description #4",
details: "Details of Task #1"
}
];
});
app.controller('TaskItemCtrl', ['$scope', function($scope) {
$scope.nextView = function() {
var currentIndex = $scope.taskCarousel.getActiveCarouselItemIndex();
$scope.taskCarousel.setActiveCarouselItemIndex((currentIndex + 1) % 2); alert($scope.task.descr + "\n" + $scope.taskCarousel.toString());
};
}]);
A tapping any item in list manages only a carousel at the last ons-list-item.
Thank you in advance...
In your code you are naming every carousel item as var="taskCarousel". The problem is that every new carousel item with ng-repeat is "overwriting" the previous var="taskCarousel", so in the end it can only access to the last one. A possible solution would be to name your items dynamically with, for example, your tasks ids:
<ons-carousel var="{{task.id}}" auto-scroll style="width: 100%; height: 70px" ng-click="nextView(task.id)">
<ons-carousel-item>
View #1 {{ task.descr }}
</ons-carousel-item>
<ons-carousel-item>
View #2 {{ task.details }}
</ons-carousel-item>
</ons-carousel>
Notice that now we also send this id when an item is clicked with ng-click="nextView(task.id)". Therefore, in the controller we will need to use bracket notation instead of dot notation to access the element since the parameter we are sending is just a string:
app.controller('TaskItemCtrl', ['$scope', function($scope) {
$scope.nextView = function(taskID) {
var currentIndex = $scope[taskID].getActiveCarouselItemIndex();
$scope[taskID].setActiveCarouselItemIndex((currentIndex + 1) % 2);
};
}]);
You can try it in Codepen here.
Hope it helps :)

Resources