Expandable content in tables using AngularJS - javascript

I've been following the tutorials for AngularJS over on Egghead. Things are going pretty good, until I decided to try to combine some concepts.
My main.js is located here, due to the fact of the size of the file.
And here's my index.html:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8">
<title>Egghead Videos</title>
<link rel="stylesheet" href="foundation/css/foundation.min.css">
</head>
<body>
<div ng-app="myApp">
<div ng-controller="CardsCtrl">
<table>
<tr ng-repeat="set in cards.sets | orderBy:'releaseDate'">
<td>{{set.name}}</td>
<td>{{set.code}}</td>
<td>{{set.releaseDate}}</td>
</tr>
</table>
</div>
</div>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script>
<script type="text/javascript" src="scripts/main.js"></script>
</body>
</html>
So, as you can see, I have it set up right now to display the set, code, and releaseDate in a table using ng-repeat. What I'm trying to accomplish is every time that you click on a set name, it expands and shows all the cards in that set, showing the name and card number. I've tried wrapping the table in the 'zippy' attribute like the tutorial was doing, but that accomplished nothing. Any ideas or suggestions? Thank you.

So, I'll do this in the table format, but it is weird because it would be within the confines of the first td; you might want to consider a different layout. For visualization sake, I'll just make the set.name into a clickable link, but I believe it can be any element.
Please note that this is not tested; best practice that I've seen on here would be for the question to include either a plnkr.co or jsfiddle.net reference.
View:
<td>
<a href="" ng-click="showThisRow(set)">
{{set.name}}
</a>
<p ng-repeat="card in set.cards" ng-if="showRow">
{{card.number}} : {{card.name}}
</p>
</td>
Controller:
$scope.showThisRow = function (whichSet) {
if (whichSet.showRow == true) {
whichSet.showRow = false;
} else {
whichSet.showRow = true;
}
}
Notes: What this is doing is a nested ng-repeat call, which is shown only if the value of the showRow variable is true. showRow can be instantiated as a method of set (an instance of the first ng-repeat) via the ng-click call, even without being specified within the controller.
I don't know if Angular provides something similar to toggle in jQuery; it would be appreciated if someone who knows that could comment as well.

The trick with selection of individual items of ng-repeat, is the inclusion of an controller in the same element as the ng-repeat. The ng-repeat dedicated controller will contain all the new items generated by the iteration, ng-click calling a function in that controller will do the item selection.
view:
<div ng-controller="TableCtrl">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>Name</th>
<th>Company</th>
</tr>
</thead>
<tbody ng-repeat="i in invoices"
ng-controller="RowCtrl"
ng-click="toggleRow()"
ng-switch on="isSelected()">
<tr>
<td>{{i.name}}</td>
<td>{{i.company}}</td>
</tr>
<tr ng-switch-when="true">
<td>{{i.invoice}}</td>
<td>{{i.units}}</td>
</tr>
</tbody>
</table>
</div>
Controllers:
.value('invoices', [
{ name:'Jack', company:'Blue Widget Co', invoice:'$3,000,000', units: '555'},
{ name:'Jill', company:'Red widget Co', invoice:'$2,000,000', units: '777' }
])
.controller('TableCtrl', function ($scope, invoices) {
$scope.invoices = invoices;
})
.controller('RowCtrl', function ($scope) {
$scope.toggleRow = function () {
$scope.selected = !$scope.selected;
};
$scope.isSelected = function (i) {
return $scope.selected;
};
});
In the RowCtrl, take note of i from ng-repeat="i in invoices":
$scope.isSelected = function (i) {
return $scope.selected;
};
Heres a working expandable table demo

#cheekybastard This can be simplified further by using a directive.
Directive:
app.directive('cdToggleOnClick', function ()
{
return {
restrict: 'A',
scope:
{
cdToggleOnClick: '='
},
link: function(scope, element, attrs) {
element.bind('click', function () {
if (scope.cdToggleOnClick === true)
{
scope.cdToggleOnClick = false;
}
else
{
scope.cdToggleOnClick = true;
}
scope.$apply();
});
}
}
});
HTML:
<div ng-controller="Ctrl">
<table border=1>
<tr ng-repeat-start="i in invoices" cd-toggle-on-click="toggleRow">
<td>{{i.name}}</td>
<td>{{i.company}}</td>
</tr>
<tr ng-repeat-end ng-if="toggleRow">
<td>{{i.invoice}}</td>
<td>{{i.units}}</td>
</tr>
</table>
</div>
Controller:
app.controller('Ctrl', function ($scope) {
$scope.invoices = [
{ name:'Jack', company:'Blue Widget Co', invoice:'$3,000,000', units: '555'},
{ name:'phil', company:'green Widget Co', invoice:'$1,000,000', units: '545'},
{ name:'Jill', company:'Red widget Co', invoice:'$2,000,000', units: '777' }
];
});
Plunker Demo

Related

Show an image (or icon) when a boolean value is TRUE with AngularJS?

So I have a list which have a column with boolean values in case some item have an attached file, when it does have an attachment it will show a "clip" icon. I want to do the same with an AngularJS table:
This is my code, HTML:
Notice there's a custom filter ("booleanFilter") in the {{link.Attachments}}
<body ng-app="myApp">
<div ng-controller="myController">
<table>
<thead>
<tr>
<th>Attachments</th>
<th>Name</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="link in links">
<td>{{link.Attachments | booleanFilter}}</td>
<td>{{link.Name}}</td>
<td>{{link.Date | date:"MM/dd/yyyy"}}</td>
</tr>
</tbody>
</table>
</div>
</body>
Here's my SCRIPT of my filter:
I want to show an Attachment Image when there is an attachment (when it's true) and show nothing when it's false:
var myApp = angular
.module('myApp', [])
.filter('booleanFilter', function() {
return function(booleanFilter) {
switch(booleanFilter) {
case true:
return "clip.png";
case false:
return "";
}
}
})
The bad thing with this code is that it shows "clip.png" as a string instead of showing the icon picture, also I tried putting a Material Icon's code:
<i class="material-icons">attach_file</i>
But it won't work... so any ideas or is there something I've been doing wrong?
If your flag is Attachments which can have true or false value then you don't even need filter:
<tbody>
<tr ng-repeat="link in links">
<td><i ng-if="link.Attachments" class="material-icons">attach_file</i></td>
<td>{{link.Name}}</td>
<td>{{link.Date | date:"MM/dd/yyyy"}}</td>
</tr>
</tbody>
Try to load local image with:
app.controller('myCtrl', function($scope, $sce) {
$scope.attachImg = $sce.trustAsResourceUrl('path/clip.png');
});
Then use ngSrc directive:
<img ng-src="{{attachImg}}">
You could make is simpler using ng-if
<tr ng-repeat="link in links">
<td>{{link.Attachments | booleanFilter}}</td>
<i ng-if="(link.Attachments)>0" class="material-icons">attach_file</i>
<td>{{link.Name}}</td>
<td>{{link.Date | date:"MM/dd/yyyy"}}</td>
So if "link.Attachment > 0" or in your case can be == true/false then show the icon, applying css class "material-icons" or your custom class

How to break data-binding after first rendering in Angular?

I've got a page in my website in which I want to show a checkbox. I only want to show the checkbox if the model is initially false. So I wrote this (this was my initial code, but it was a simplified version of what I have myself. I updated the code in the snippet at the end of this question to show the problem):
<div ng-if="!the_field">
<input ng-model="the_field" type="checkbox">
</div>
The problem is that if I click the checkbox, it disappears. That of course makes sense, but I have no idea how to solve this.
So what I basically want is to show the checkbox if the model was false upon rendering the HTML. But after that I want to somehow break the databinding so that the checkbox remains on the page even if the model changes to true.
Does anybody know how I can achieve this? All tips are welcome!
[EDIT]
I would prefer doing this from within the template, so that I don't get a double list of these fields (because I've got about 50 of them). Any ideas?
[EDIT 2]
Turns out that it did work with the example above, which was a simplified version of my own code. In my own code however, I'm not using simple a field, but an item in a dict. I updated the code above and made a snippet below to show the problem:
var MainController = function($scope){
$scope.the_field = {};
$scope.the_field.item = false;
};
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="" ng-controller="MainController">
parent: {{the_field.item}}
<div ng-if="!the_field.item">
child: {{the_field.item}}<br>
<input ng-model="the_field.item" type="checkbox">
</div>
</div>
You can clone the source object. Like this:
angular.module('app', []).
controller('ctrl', function($scope) {
$scope.the_field = false;
$scope.the_field_clone = angular.copy($scope.the_field);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
{{the_field}}
<div ng-if="!the_field_clone">
<input ng-model="$parent.the_field" type="checkbox">
</div>
</div>
http://jsbin.com/ditoka/edit?html,js
Update - option 2 - Directive
angular.module('app', []).
controller('ctrl', function($scope) {
$scope.the_field = false;
}).
directive('customIf', function() {
return {
scope: {
customIf: '='
},
link: function(scope, element, attrs) {
if (!scope.customIf) {
element.remove();
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
{{the_field}}
<div custom-if="!the_field">
<input ng-model="the_field" type="checkbox">
</div>
</div>
It works with the code of your question, try it out ;)
(see What are Scopes?)
var MainController = function($scope){
$scope.the_field = false;
};
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="" ng-controller="MainController">
parent: {{the_field}}
<div ng-if="!the_field">
child: {{the_field}}<br>
<input ng-model="the_field" type="checkbox">
</div>
</div>
The answer to your updated question:
You can use another property in your model, edited when the first click occurs...
var MainController = function($scope){
$scope.model = {init: true, the_field: false};
};
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="" ng-controller="MainController">
parent: {{model.the_field}}
<div ng-if="!model.the_field || !model.init">
<input ng-model="model.the_field" type="checkbox" ng-click="model.init=false;">
</div>
</div>

Cannot read property 'insertBefore' of null at forEach.after

I have a very simple html page where I am loading two different html files using ng-include :-
<body ng-controller="testCtrl">
<div class="panel panel-primary">
<h3 class="panel-heading">Test</h3>
<ng-include src="'View/Details.html'" ng-show="details" />
<ng-include src="'View/Summary.html'" ng-show="summary" />
</div>
</body>
Initially only the details variable is true thus it is loading Details view which looks like this:-
<div class="panel-body">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in items">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
</tr>
</tbody>
</table>
</div>
and here is the controller for reference:-
.controller("testCtrl", function ($scope) {
$scope.loadItems = function () {
$scope.items= [
{ id: 1, name: "dummy" },
{ id: 2, name: "dummy1" },
{ id: 3, name: "dummy2" }
];
}
$scope.loadItems();
}
When I load the page it is displaying the data properly but no events are getting triggered (all button click handlers are attached via ng-click & associated with behaviors defined in controller) so I went ahead and checked the console, it is displaying the following error:-
TypeError: Cannot read property 'insertBefore' of null
at forEach.after (myUrl)
at Object.JQLite.(anonymous function) [as after]
I am pretty new to Angular. I saw few threads and tried to manipulate my mark-up code but no luck.
You cant <ng-include/> -- should be <ng-include></ng-include>. Also ng-if is usually better than ng-show, since it reduces amount of result html.
Rest works: plnkr.co/edit/EmUYuHltWaXnjEC6juid?p=preview.

Angular.js ng-repeat across multiple elements

This question has been partly addressed here: Angular.js ng-repeat across multiple tr's
However that is just a work-around really, it doesn't actually address the core issue, which is: how can one use ng-repeat across multiple elements without a wrapper?
For example, jquery.accordion requires you to repeat an h3 and div element, how could one do this with ng-repeat?
We now have a proper support for this, please see:
AngularJs Commmit
with this change you can now do:
<table>
<tr ng-repeat-start="item in list">
<td>I get repeated</td>
</tr>
<tr ng-repeat-end>
<td>I also get repeated</td>
</tr>
</table>
To answer Andre's question above on more than 2 levels of ng-repeat in a table, you can use multiple ng-repeat-start to accomplish this.
<tr ng-repeat-start="items in list">
<td>{{items.title}}</td>
</tr>
<tr ng-repeat-start="item in items">
<td>{{item.subtitle}}</td>
</tr>
<tr ng-repeat-end ng-repeat="value in item.values">
<td>{{value.col1}}</td>
<td>{{value.col2}}</td>
</tr>
<tr ng-repeat-end></tr>
Here is a plunker example
UPDATE: This answer is outdated. Please see #IgorMinar answer and use standard ng-repeat-start and ng-repeat-end directives.
There are two options:
First option is to create directive that will render several tags and replace source tag (jsfiddle)
<div multi ></div>
angular.module('components').directive('multi', function ($compile) {
return {
restrict: 'A',
scope : {
first : '=',
last : '=',
},
terminal:true,
link: function (scope, element, attrs) {
var tmpl = '', arr = [0,1,2,3]
// this is instead of your repeater
for (var i in arr) {
tmpl +='<div>another div</div>'
}
var newElement = angular.element(tmpl);
$compile(newElement)(scope);
element.replaceWith(newElement);
}
})
Second option is to use updated source code of angular that enables comment style ngRepeat directive (plnkr)
<body ng-controller="MainCtrl">
<div ng-init="arr=[0,1,2]" ></div>
<!-- directive: ng-repeat i in arr -->
<div>{{i}}</div>
<div>{{ 'foo' }}</div>
<!-- /ng-repeat -->
{{ arr }}
<div ng-click="arr.push(arr.length)">add</div>
</body>

ICanHaz Not detecting template

I've checked the other posts on this topic, but none of them match my problem. Here is the code for my template:
<script id ="postingcell" type="text/html">
<li class="postinglistcell">
<div class = "postinfowrapper">
<table class="centermargins">
<tr>
<td>
<div class="posttitle">{{Title}}</div>
</td>
</tr>
</table>
</div>
</li>
</script>
And here is the code where I call the ICH:
$(document).ready(function() {
var p = ich["postingcell"](thisobj);
});
I can get an error telling me that ich["postingcell"] is not defined, but it has been in the above script tag. Does anyone know what im doing wrong here?
ICanHaz also uses jQuery for setting up. One possible reason is your code runs before the ich.grabTemplates() call.
if (ich.$) {
ich.$(function () {
ich.grabTemplates();
});
}
You may try calling ich.grabTemplates() in your code:
$(document).ready(function() {
ich.grabTemplates();
var p = ich["postingcell"](thisobj);
});

Categories