angular add data outside ng-repeat - django

I have an angular app that has the following ng-repeat:
<div ng-repeat="detail in mpttdetails">
<div ng-if="detail.category.id == {{node.id}}" class="list-group">
{%verbatim%}
<li class="list-group-item" >
<div class="row">
<div class=".col-lg-2 col-md-2 col-sm-2">
{{detail.student_academic_credit.course_name}}
</div>
<div class=".col-lg-4 col-md-4 col-sm-3">
{{detail.student_academic_credit.title}}
</div>
<div class=".col-lg-2 col-md-2 col-sm-2">
{{detail.student_academic_credit.credit}}
</div>
<div class=".col-lg-2 col-md-2 col-sm-2">
{{detail.student_academic_credit.final_grade}}
</div>
<div class=".col-lg-2 col-md-2 col-sm-3">
{{detail.student_academic_credit.term}}
</div>
</div>
</li>
{%endverbatim%}
</div>
</div>
My mpttservice here is a service that fetches data from a django app. Here i would like to have a bootstrap badge as:
<span class="badge badge-info">{{sum}}</span>
just above the ng-repeat such that it adds all the {{detail.student_academic_credit.credit}} from the list. How do i achieve this since all the details are inside the ng-repeat??

I suggest you use a couple of filters: Angular's filter for filtering details based on their categoryId and one custom filter for summing credits.
app.filter('sumCredits', function () {
return function (details) {
var sum = 0;
details.forEach(function (item) {
sum += item.student_academic_credit.credit;
});
return sum;
};
});
<span class="badge badge-info"
ng-bind="mpttdetails | filter:{catagory:{id:node.id}} | sumCredits">
</span>
<div ng-repeat="detail in mpttdetails | filter:{catagory:{id:node.id}}">
{%verbatim%}
<li class="list-group-item">
...
See, also, this short demo.

In the controller that has the service injected, loop through just like ng-repeat is doing and add to your $scope.sum.
app.controller('WhateverCtrl', function($scope, mptt) {
// Assuming it's an ajax call
mptt.getDetails().success(function(mpttdetails) {
$scope.mpttdetails = mpttdetails;
// Set sum on scope
var sum = 0;
angular.forEach(mpttdetails, function(detail) {
sum = sum + detail.student_academic_credit.credit;
});
$scope.sum = sum;
});
});

Related

Render View Only After all Data has been fetched from Service?

I have a table Component where when the user Selects a particular row he is Redirected to a different route to a team component .
I want to Load the View Of the New Route when all the data from the Service has been Fetched by the Component .
How to Prevent the View From rendering till the Time the whole data has been fetched from Service in the New components OnInit Method.
in angular 1 there was ngCloak how to do it in Angular 2 .
I have used ngIf but for a split second till the data loads the div pops up and then disappears
My table Component
onSubmit(team:any){
this.teamId = team._links.team.href.split('/').pop(-1);
this.competitionService.storeTeamCrest(team.crestURI);
this.router.navigate(['team', {id: this.teamId}]);
}
Service
storeTeamCrest(link:string){
this.teamCrest = link;
}
getPlayers(id:string){
let headers = new Headers();
headers.append('X-Auth-Token', 'XXX');
return this.http.get('https://api.football-data.org/v1/teams/'+id+'/players',{headers:headers}).map(response => response.json())
}
getFixtures(id:string){
let headers = new Headers();
headers.append('X-Auth-Token', '7c94f28bddf34648bd9a6f5c2e2da0f0');
return this.http.get('https://api.football-data.org/v1/teams/'+id+'/fixtures',{headers:headers}).map(response => response.json())
}
Team Component
ngOnInit(){
this.teamId = this.route.snapshot.params['id'];
this.teamCrest = this.competitionService.teamCrest;
this.getPlayers();
}
getPlayers(){
this.competitionService.getPlayers(this.teamId).subscribe(player => this.players = player.players);
}
Team Component Template
<div class="container">
<div class="row">
<div *ngFor="let player of players"#data>
<div class="col-md-4">
<div class="well box">
<img class="avatar" src="{{teamCrest}}">
<h4> {{player.name}} </h4>
<hr style="background-color:black;" />
<span class="label label-default"><strong>Position :</strong> {{player.position}} </span><br/>
<span class="label label-primary"><strong>Jersey Number :</strong> {{player.jerseyNumber}} </span><br/>
<span class="label label-success"><strong>Date Of birth :</strong> {{player.dateOfBirth}}</span><br/>
<span class="label label-info"><strong>Nationality :</strong> {{player.nationality}} </span><br/>
<span class="label label-warning"><strong>Contract Untill :</strong> {{player.contractUntil}}</span><br/>
<span class="label label-success"><strong>Market Value :</strong> {{player.marketValue}}</span><br/>
</div>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="!players?.length">
<h3>No Player Data found For the Selected Team</h3>
</div>
handle the (complete) => {} callback inside subscribe (Subscribe Docs) and set a boolean to true. Check against this boolean with your *ngIf.
So, inside your Team Component:
players:any[] = [];
loadCompleted:boolean = false;
ngOnInit(){
this.teamId = this.route.snapshot.params['id'];
this.teamCrest = this.competitionService.teamCrest;
this.getPlayers();
}
getPlayers(){
this.loadCompleted = false;
this.competitionService.getPlayers(this.teamId)
.subscribe(
player => this.players = player.players,
(error) => console.error(error),
() => this.loadCompleted = true)
);
}
then in your HTML template:
<div *ngIf="loadCompleted" class="container">
<div class="row">
<div *ngFor="let player of players" #data>
<div class="col-md-4">
<div class="well box">
<img class="avatar" src="{{teamCrest}}">
<h4> {{player.name}} </h4>
<hr style="background-color:black;" />
<span class="label label-default"><strong>Position :</strong> {{player.position}} </span><br/>
<span class="label label-primary"><strong>Jersey Number :</strong> {{player.jerseyNumber}} </span><br/>
<span class="label label-success"><strong>Date Of birth :</strong> {{player.dateOfBirth}}</span><br/>
<span class="label label-info"><strong>Nationality :</strong> {{player.nationality}} </span><br/>
<span class="label label-warning"><strong>Contract Untill :</strong> {{player.contractUntil}}</span><br/>
<span class="label label-success"><strong>Market Value :</strong> {{player.marketValue}}</span><br/>
</div>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="loadCompleted && !players?.length">
<h3>No Player Data found For the Selected Team</h3>
</div>
If i understood your question, this should do what you want.
Just a side note: in your backend service, IMHO, you shouldn't return null if there aren't players for the input teamId, but an empty List/Array/whatever.

Google geocode suggest result is appearing behind the modal

I have a problem with google geocode suggest. I have a modal which show a form to complete an address. I have implemented geocode to make easier the address filling, but the suggest is appearing behind the modal, like this:
Some code over here:
JS which implements geocode:
GoogleGeocode = (function () {
var _api = {};
var _googleApiRetrievedOk = false;
var _autocompleteEvent;
var _addressInputId, _zipCodeInputId, _countryInputId, _countryComboboxId;
var _componentToRetrieve = {
street_number: 'short_name',
route: 'long_name',
locality: 'long_name',
administrative_area_level_1: 'short_name',
country: 'long_name',
postal_code: 'short_name'
};
function loadGoogleApis() {
// Importar fuentes
$('head').append($('<link rel="stylesheet" type="text/css" />').attr('href', 'https://fonts.googleapis.com/css?family=Roboto:300,400,500'));
// Importar api Places
$('head').append($('<script type="text/javascript" />').attr('src', 'https://maps.googleapis.com/maps/api/js?v=3.exp&signed_in=true&libraries=places&callback=GoogleGeocode.GoogleGeocodeInit'));
}
function googleGeocodeInit() {
_googleApiRetrievedOk = true;
}
$(document).ready(loadGoogleApis);
return _api;
})();
View which supports modal:
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-footer">
<div class="col-md-6 col-md-offset-1">
<h2>#T("StudentData")</h2>
</div>
<button type="button" class="btn btn-CTA" data-dismiss="modal">X</button>
</div>
<div class="modal-header">
#Html.Action("PrepareStudentDataModel", "Topic")
</div>
</div>
Part of view which calls google geocode:
<div class="form-group row">
<div class="col-md-12">
#Html.TextAreaFor(model => model.Address1, new { #class = "form-control", placeholder = #T("wke.checkout.StudentAddress.geocodeaddress") })
#Html.ValidationMessageFor(model => model.Address1)
<span class="col-md-6 col-xs-6 field-validation-valid" id="StudentAddress_address1MessageValidation" style="visibility: hidden;">
#T("wke.checkout.StudentAddress.messagevalidation")
</span>
</div>
</div>

Conditionally starting and ending a div using each helper

I am using bootstrap and need to conditionally start a <div class="row"> depending on the index of the item; in this case, each row has two items.
{{#each items as |item index|}}
{{#if item.isStartRow}}
<div class="row">
{{/if}}
...my code here
{{#if item.isEndRow}}
</div>
{{/if}}
{{/each}}
The trouble is that the HTMLBars validates the div has a starting and ending tag, therefore the above will not work. Can anyone suggest a workaround?
The end result would look something like this if there were 6 items in the array:
<div class="row">
<div class="col-md-6">Item 1...</div>
<div class="col-md-6">Item 2...</div>
</div>
<div class="row">
<div class="col-md-6">Item 3...</div>
<div class="col-md-6">Item 4...</div>
</div>
<div class="row">
<div class="col-md-6">Item 5...</div>
<div class="col-md-6">Item 6...</div>
</div>
It's turned out to be a different scenario once you've updated the question, and this is how I'd approach this
<script type="text/x-handlebars" data-template-name="components/item-row">
{{#each chunks as |chunk|}}
<div class="row">
{{#each chunk as |item|}}
<div class="col-md-6">{{item}}</div>
{{/each}}
</div>
{{/each}}
</script>
JS Component class
App.ItemRowComponent = Ember.Component.extend({
chunks : function(){
var items = this.get('rows.[]').slice(0); // make a copy
var count = Math.ceil(items.get('length')/2);
var chunks = [];
for(var i = 0 ; i < count ; i++){
chunks.push(items.splice(0,2));
}
return chunks;
}.property('rows.[]')
});
DEMO
You are free to customize child element if you make it as another component. It's an added flexibility.

Using Sammy.js and Knockout.js with Templates Having Multiple Parameters

I have a website using Sammy and Knockout. I am trying to route multiple views to different URLs with Sammy but I'm having some difficulty. I need to pass the data parameter as well as the templateName parameter. How would I be able to pass the data parameter? I've included relevant code below.
Javascript
var View = function (title, templateName, data) {
var self = this;
this.title = title;
this.templateName = templateName;
this.data = data;
};
var viewModel = {
views: ko.observableArray([
new View("Home", "home", homeView),
new View("Announcements", "announcements", announcementView),
new View("Calendar", "calendar", monthView),
new View("Contact Us", "contactus", contactView),
new View("Events", "events", eventsView)
]),
selectedView: ko.observable()
};
$.sammy(function () {
this.get('#:templateName', function () {
viewModel.selectedView(this.params);
});
}).run('#home');
vbhtml
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
</button>
<a class="navbar-brand" href="#">Events</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav" data-bind="foreach: views">
<li data-bind="css: {active: $root.selectedView == $data"><a data-bind= "text: title, click: $root.selectedView " ></a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>Login</li>
</ul>
</div>
</div>
</nav>
<div data-bind="with: selectedView">
<div data-bind="template: { name: templateName, data: data }"></div>
</div>
<script id="home" type="text/html">...</script>
<script id="announcements" type="text/html">...</script>
<script id="calendar" type="text/html">...</script>
<script id="contactus" type="text/html">...</script>
Use the context parameter passed into the get's callback function
this.get('#:templateName', function(context) {
var params = context.params;
});
You should then be able to append querystring params and read them from the get function.
.run('#home?param1=1&param2=2')

knockout.js - modify DOM in current item in list (expand list item subsection) using templates

In this example I want to use knockout.js to allow the "Expand" link to be clicked and have its text changed to "Collapse". I also want to set the make the jobDetails section visible. This is a very general question of how to get knockout.js to specifically modify the DOM of the "current" item in a list using a click handler.
<script type="text/html" id="job-template">
<div class="jobContainer">
<label data-bind="text: JobTitle"></label>
<label data-bind="text: CompanyName"></label>
<div class="jobDetails">
<label data-bind="text: City"></label>
<label data-bind="text: State"></label>
</di>
<div>
<a class="expand" href="#" data-bind="click: ???">Expand</a>
</div>
</div>
</script>
This is very straight forward with KO. Here's a simple way to do it. FYI I had to fix your markup slightly.
<div>
<div class="jobContainer">
<label data-bind="text: JobTitle"></label>
<label data-bind="text: CompanyName"></label>
<div class="jobDetails" data-bind="visible: expanded">
<label data-bind="text: City"></label>
<label data-bind="text: State"></label>
</div>
<div>
<a class="expand" href="#" data-bind="click: toggle, text: linkLabel">Expand</a>
</div>
var viewModel = function() {
this.JobTitle = ko.observable("some job");
this.CompanyName = ko.observable("some company");
this.City = ko.observable("some city");
this.State = ko.observable("some state");
this.someValue = ko.observable().extend({ defaultIfNull: "some default" });
this.expanded = ko.observable(false);
this.linkLabel = ko.computed(function () {
return this.expanded() ? "collapse" : "expand";
}, this);
this.toggle = function () {
this.expanded(!this.expanded());
};
};
http://jsfiddle.net/madcapnmckay/XAzW6/
Hope this helps.