Group panels inside a panel using AngularJS and Bootstrap - angularjs

I'm unable to find an answer to this, or I'm not looking in the right place, but I'm trying to find a way where I can group a number of elements (panels) inside one large panel using both AngularJS and Bootstrap.
So for example, I have around 60 or so mini panels that represent customer contact forms. All of these forms sit inside one large panel with an ng-repeat. I am wanting to somehow group my contacts by different criteria (e.g By State code or by Company Type, etc.) from a dropdown control.
I would like all contacts to be shown in the one panel but segregated by a .page-header (a similar setup to the example below):
Has anyone seen any examples of this anywhere, or have any details on how this could be achieved?

You can make use of array group-by using Array#reduce, ng-repeat-start and ng-repeat-end:
HTML (I used bootstrap's panel here for illustrative purpose):
<body ng-app="MyApp">
<div ng-controller="MyCtrl">
<div class="panel panel-default">
<div class="panel-heading" ng-repeat-start="(group, values) in groupedData">
{{ group }}
</div>
<div class="panel-body" ng-repeat-end ng-repeat="value in values">
{{ value.name }}
</div>
</div>
</div>
</body>
JavaScript:
angular.module('MyApp', []).controller('MyCtrl', ['$scope', function($scope){
var rawData = [{
"name": "A1",
"group": "A" // For simplicity, my grouping criteria is called 'group'
},{
"name": "A2",
"group": "A"
},{
"name": "A3",
"group": "A"
},{
"name": "B1",
"group": "B"
},{
"name": "C1",
"group": "C"
},{
"name": "C2",
"group": "C"
}];
// Perform a Group-by on the rawData: {"A": [{...}], ...} using Array#reduce
$scope.groupedData = rawData.reduce(function(accumulator, value){
if(!accumulator[value.group]) accumulator[value.group] = [];
accumulator[value.group].push(value);
return accumulator;
}, {});
}]);
JSBin Example: http://jsbin.com/gigerilope/edit?html,js,output
Edit: Answering your extended question:
To trigger this grouping upon some button click, we first add a button and bind a ng-click handler:
<button class="btn btn-default" ng-click="performGrouping()">
Perform Grouping
</button>
On the controller, instead of assigning the grouped data immediately, we wrap it in the handling function that would be called on the ng-click:
$scope.groupedData = null;
$scope.performGrouping = function() {
$scope.groupedData = rawData.reduce(function(accumulator, value) {
if (!accumulator[value.group]) accumulator[value.group] = [];
accumulator[value.group].push(value);
return accumulator;
}, {});
};
Now we want to show a "No Grouping" before clicking on the button, so we add a <div> panel to tell the story:
<div class="panel panel-default">
<div class="panel-body">
No Grouping Yet
</div>
</div>
At this point, both panels could be seen - we need to show one at a time. Truth-checking $scope.groupedData would be a good idea:
Results Panel:
<div class="panel panel-default" ng-show="groupedData">
"No Grouping" panel:
<div class="panel panel-default" ng-hide="groupedData">
However, in the case where rawData is an empty array, groupedData would be an empty object {}, which resolves to true. We can do a keys-check to check how many keys groupedData has. To do this, we have to add a function to return the keys:
$scope.keys = function(obj) {
return obj && Object.keys(obj);
};
And then update the template HTML again:
Results Panel:
<div class="panel panel-default" ng-show="keys(groupedData).length">
"No Grouping" panel:
<div class="panel panel-default" ng-hide="keys(groupedData).length">
And we should be good for now.
JSFiddle to the solution: https://jsfiddle.net/kazenorin/Ler7p6fe/

Related

How to display div of related content from template when clicked in Angular

I have a question on how best to achieve a feature in Angular. I have tried a number of solutions from ng-click, ng-if, ng-show and combinations of those but no luck in my novice state.
Currently I ng-repeat through an array of json data as li.
What I want to achieve is when I click one of the looped li it opens and displays the 'info div' with the related info to the li clicked (preferably by use of template). If user clicks another li it simply switches out the data to display correctly again.
Any advice on how to proceed to this feature would be greatly appreciated. I have provided my existing code as well as a CodePen.
JS:
var phonecatApp = angular.module('myApp', [])
.controller('starWarsCtrl', function ($scope) {
$scope.data = [
{"name": "Obi-Wan Kenobi",
"index":88,
"cat": "Jedi"},
{"name": "Yoda",
"index":69,
"cat":"Jedi"},
{"name": "Lando",
"index":31},
{"name": "Han Solo",
"index":90},
{"name": "Darth Vader",
"index":98},
{"name": "Jar-Jar Binks",
"index":80},
{"name": "Mace Windu",
"index":45},
{"name": "Chewy",
"index":76}
];
})
HTML:
<div ng-app="myApp" ng-controller="starWarsCtrl">
<input type="text" id="query" ng-model="query"/>
<div class="bscroll">
<ul>
<li class="box" ng-repeat="i in data | filter:query | orderBy: orderList">
<h2>{{i.name}}</h2>
<p>{{i.index}}{{i.cat}}</p>
</li>
</ul>
</div>
<div class="info">The details would appear here</div>
</div>
Simplest solution is to add an ng-click to your <li> that calls a function to select the item:
<li class="box" ng-repeat="i in data | filter:query | orderBy: orderList" ng-click="select(i)">
<h2>{{i.name}}</h2>
<p>{{i.index}}{{i.cat}}</p>
</li>
and then your js:
$scope.select = function (item) {
$scope.selectedItem = item;
}
and your info div:
<div class="info" ng-if="selectedItem">
Name: <span ng-bind="selectedItem.name"></span><br>
Index: <span ng-bind="selectedItem.index"></span>
<div ng-if="selectedItem.cat">
Category: <span ng-bind="selectedItem.cat"></span>
</div>
</div>
This should at least be enough to get you started in the right direction.
Updated pen: http://codepen.io/anon/pen/WrqXJa

Trouble getting objects with ng-repeat

I am having a tough time with ng-repeat using Angular.
My JSON data looks like this
{
"shows": {
"stations": {
"balaton": [{
"name": "Minimal",
"artist": "Sven Tasnadi",
"duration" : "1H"
}, {
"name": "Forest of Love",
"artist": "Bas Ibelini",
"duration" : "2H"
}, {
"name": "Sound of Underground",
"artist": "Potential Badboy",
"duration" : "2H30"
}],
"djradio": [{
"name": "Strickly Electronica",
"artist": "Culptrate",
"duration" : "1H"
}, {
"name": "Sound of Underground",
"artist": "Potential Badboy",
"duration" : "2H"
}, {
"name": "Sound Time",
"artist": "Leona Graham",
"duration" : "2H30"
}]
}
}
}
In my controller, I set the object to a scope.
$http.get('json/show-schedule.json').success(function(res){
$scope.schedule = res.shows.stations;
});
In my HTML,
<div class="schedule-station" ng-repeat="item in schedule">
<div class="schedule-station-logo" style="background-image:url('img/stations/{{balaton}}.png')"></div>
<ul>
<li class="schedule-duration-{{item.duration}}">
<span class="schedule-time">11PM</span>
<span>{{item.name}}</span>
<i><span>{{item.artist}}</span></i>
</li>
</ul>
</div>
Basically, I have a DIV that needs to be created based on how many stations there are in the JSON. Then, I need to get the station name, which is 'Balaton', and 'DJRADIO', this should be placed in my background-image such as balaton.png.
Then the List Item should be repeated based on how many tracks there in that particular station.
I am having a tough time figuring out how to do this, and my attempts are obviously way off.
Try something like this:
<div class="schedule-station" ng-repeat="(station, tracks) in schedule">
<h2>{{station}}</h2>
<div class="schedule-station-logo" style="background-image:url('img/stations/{{station}}.png')"></div>
<ul>
<li ng-repeat="track in tracks" class="schedule-duration-{{item.duration}}">
<span class="schedule-time">11PM</span>
<span>{{track.name}}</span>
<i><span>{{track.artist}}</span></i>
<b>{{track.duration}}</b>
</li>
</ul>
</div>
Check the demo fiddle
So there are a few things to consider here:
1) You have an object containing the stations, where it's a key, val set of items, where the values of the station property is an array of the songs. If the stations were an array, then you could do something like stations.length, but because it's a set of object properties, we need to count them manually, hence the angular.forEach.
$scope.schedule = data.shows.stations;
$scope.stationCount = 0;
angular.forEach($scope.schedule, function(station){
$scope.stationCount++;
});
2) To place each of the songs, we have to nest another ng-repeat so that we can iterate over each of the songs in the array.
3) The ng-repeat on the outside has to be setup for an object, and not an array, as you have in your original html.
I made a plnkr showing how to do everything for you: http://plnkr.co/edit/ca8WxSEvn00rYVfwN82r?p=preview
See ngRepeat documentation
It is possible to get ngRepeat to iterate over the properties of an
object using the following syntax: <div ng-repeat="(key, value) in myObj"> ... </div>
So to you should be able to iterate your collection like this:
<div class="schedule-station" ng-repeat="(stationName, songs) in schedule">
<div class="schedule-station-logo" style="background-image:url('img/stations/{{stationName}}.ng')"></div>
<ul>
<li ng-repeat="item in songs" class="schedule-duration-{{item.duration}}">
<span class="schedule-time">11PM</span>
<span>{{item.name}}</span>
<i><span>{{item.artist}}</span></i>
</li>
</ul>
</div>

Nested ng-repeat in Angularjs displayed at the same level

I have a json which I'll be using to generate some html displaying tabs and buttons.
A simplified version of my json is this:
[{"id": "A","buttons":[{"id":"A1"},{"id":"A2"}]},
{"id": "B","buttons":[{"id":"B1"},{"id":"B2"}]}]
which I store in $scope.navigation as it is.
And, ideally I'd like to generate something like this:
<span id="tabs">
<div>A</div>
<div>B</div>
</span>
<span id="buttons">
<div>A1</div>
<div>A2</div>
<div>B1</div>
<div>B2</div>
</span>
What I tried is this
<span id="tabs">
<div ng-repeat="tab in navigation">{{tab.id}}</div>
</span>
<span id="buttons">
<span ng-repeat="tab in navigation">
<div ng-repeat="button in tab.buttons">{{button.id}}</div>
</span>
</span>
Of course this does not work as it creates an extra span element which separates the buttons depending to the tab they belong to.
Is there a way to do what I need?
Thanks!
What's the purpose of it? A < div> inside a < span> is considered bad practice and I'd advise you to change your CSS if appropriate. There may be a good reasoning for it though, but this question doesn't give enough details for an answer.
And coming to your above problem it can be achieved by creating another array consists of buttons like below.
$scope.navigation= [{ "id": "A", "buttons": [{ "id": "A1" }, { "id": "A2" }] },
{ "id": "B", "buttons": [{ "id": "B1" }, { "id": "B2" }] }];
$scope.buttons = [];
angular.forEach($scope.navigation, function (tab) {
angular.forEach(tab.buttons, function (button) {
$scope.buttons.push(button);
});
});
and finally use the ng-repeat like below.
<div> <div data-ng-repeat="button in buttons">{{button.id}}</div> </div>
You have to do a (key, value) repeat after the initial repeat in the span:
<span id="buttons" ng-repeat="i in buttons">
<div>{{i.id}}</div>
</span>
$scope.data = [{"id": "A","buttons":[{"id":"A1"},{"id":"A2"}]},
{"id": "B","buttons":[{"id":"B1"},{"id":"B2"}]}]
$scope.getButtons = function() {
$scope.buttons = []
for (var i = 0; i < $scope.data.length; i++) {
for(var x = 0; x < $scope.data[i].buttons.length; x++) {
$scope.buttons.push($scope.data[i].buttons[x])
}
}
console.log($scope.buttons)
}
$scope.getButtons();
I updated the plunker so that it flattens the buttons arrays all into one new array.
Plunker Demo

Using multiple radio buttons to filter ng-repeat boolean object properties

I have json data array of Carpet objects (see plunker)
designerApp.controller('CarpetCtrl', ['$scope', function ($scope) {
$scope.carpetList =
[
{
"Carpet": {
"name": "red carpet",
"standard": true,
"wide": false,
"extra_wide": true
},
},
{
"Carpet": {
"name": "blue carpet",
"standard": false,
"wide": true,
"extra_wide": true
},
},
{
"Carpet": {
"name": "green carpet",
"standard": true,
"wide": true,
"extra_wide": false
},
}
];
}]);
I want to use radio buttons to filter the list - buttons for standard, wide and extra_wide to filter the respective boolean values.
I have scoured the internet and cannot find anyone else doing this. Can angular handle this natively?
NB The reason each array element is keyed on Carpet is because I have cut down the data pumped out from CakePHP - there is other keyed data in each element.
Here's a working Plunker.
This is what I changed:
<div class="carpet_list" ng-controller="CarpetCtrl">
<label>Standard Width<input type="radio" name="width" ng-model="type" value='standard' ></label>
<label>Wide<input type="radio" name="width" ng-model="type" value='wide' ></label>
<label>Extra Wide<input type="radio" name="width" ng-model="type" value='extra_wide'></label>
<br>
<br>
{{type}}
<h2>Carpet List</h2>
<ul class="thumblist">
<li class="thumb" ng-repeat="item in carpetList">
<label ng-show="item.Carpet[type]">{{item.Carpet.name}}</label>
</li>
</ul>
</div>
As you can see the idea is to show the carpets with 'type' true.
Here's a way: plunker
The radios model is a single variable containing the type of carpet to display
$scope.filters = {
width: 'standard'
};
Then you display only the element on which this property is true.
<li class="thumb" ng-repeat="item in carpetList" ng-show="item.Carpet[filters.width]">
There's probably a "cleaner" way with filters, but apparently you can't filter a single item, you have to filter the whole array

Dependant ng-repeat in AngularJS

I have a following data structure coming from REST:
scope.taglist =
[ { name: "mylist", tags: ["tag1", "tag2", "tag3", ...]}, { name:
"mylist2", tags: ["tag2.1", "tag2.2", "tag2.3", ...]} ]
In order to present the names of the objects I have the following html:
<div>
<select ng-model="tagNameSelection">
<option ng-repeat="tagObj in taglist" value="{{tagObj}}">{{tagObj.name}}</option>
</select>
</div>
<div class="tagdetails">
<!-- present the list of tags from tagNameSelection -->
</div>
Now I am a little bit of a loss on how to present the tags list of
individual object. I am able to present the array in raw format (by
sticking {{tagNameSelection}} inside the tagdetails div) but when I
try to iterate through those with ng-repeat angular gives a error
message.
Oddly enough when I hard-code one of the tag lists to the scope in controller the ng-repeat works flawlessly.
Maybe you interesting something like this:
HTML
<div ng-controller="fessCntrl">
<div>
<select ng-model="tagNameSelection"
ng-options="tagObj as tagObj.name for tagObj in taglist"
ng-change="change(tagNameSelection)"></select>
</div>
<pre>{{tagNameSelection.tags|json}}</pre>
<div class="tagdetails">
<ul ng-repeat="tag in tagNameSelection.tags">
<li>{{tag}}</li>
</ul>
</div>
</div>
Controller
var fessmodule = angular.module('myModule', []);
fessmodule.controller('fessCntrl', function ($scope) {
$scope.change = function (value) {
};
$scope.taglist = [{
name: "mylist",
tags: ["tag1", "tag2", "tag3"]
}, {
name: "mylist2",
tags: ["tag2.1", "tag2.2", "tag2.3"]
}]
});
fessmodule.$inject = ['$scope'];
See Fiddle

Resources