AngularJS load ng-repeat into another ng-repeat with ajax call - javascript

I'm new to angular and would like some help in solving the following issue. This is the code I currently have, simply getting an array of results from the server using a post request and displaying them using the ng-repeat directive:
<div id="mainW" ng-controller="MediaController">
<div id="mediaBodyW" ng-repeat="media in medias">
<div class="mediaW" data-id="{{media.id}}">
<div class="mediaNum">{{media.num}}</div>
<div class="mediaN">{{media.name}}</div>
<div id="loadSubs" ng-click="loadSubMedias(media.id)">load sub medias</div>
<div id="subMediaW"></div>
</div>
</div>
This is my controller:
app.controller("MediaController",['$scope','$http','$httpParamSerializerJQLike',function($scope,$http,$httpParamSerializerJQLike){
$scope.medias = [];
try {
$http({
method: 'POST',
url: 'media.php',
data: $httpParamSerializerJQLike({"request":"getAllMedia"}),
headers: {'Content-Type':'application/x-www-form-urlencoded'}
}).then(function (ans) {
$scope.medias = ans.data;
}, function (error) {
console.error(JSON.stringify(error));
});
}
catch (ex) {
console.error("Error: " + ex.toString());
}
}]);
Now, what I would like to achieve, is: on clicking the div with id of "loadSubs", run another $http post query which will load an array of results into the "subMediaW". Of course the query and appending of html should be unique for each media element, and each time a data is loaded for a particular element all previous results should be cleared, all this while taking into account that the loaded data will be also manipulated in the future.
Can someone please help me understand how can I do this using AngularJS?

Try this,
$scope.prevMedia = null; //use this to store last clicked media object
$scope.loadSubMedias = function(media){
$http.post().... //make api call for subMedia here
.then(function(res){
media.subMediaW = res.data // attach a subMediaW array to the media object
media.showSubMedia = true; // set variable true to make submedia visible for current media object, this will be used with ng-if attribute in html
if($scope.prevMedia != null) $scope.prevMedia.showSubMedia = false; // if last selected media object is not null, hide its submedia
})
}
and html
<div id="mainW" ng-controller="MediaController">
<div id="mediaBodyW" ng-repeat="media in medias">
<div class="mediaW" data-id="{{media.id}}">
<div class="mediaNum">{{media.num}}</div>
<div class="mediaN">{{media.name}}</div>
<div id="loadSubs" ng-click="loadSubMedias(media)">load sub medias</div>
<div id="subMediaW" ng-repeat="submedia in media.subMediaW" ng-if="media.showSubMedia"></div>
</div>
</div>

Firstly you should have a function in your controller with the name loadSubMedias and instead of simply taking media.id you can send whole media object to it (later on we will add new data into this object as an another property).
$scope.loadSubMedias = function (media) {
$http({
method: 'POST',
url: 'media.php',
data: $httpParamSerializerJQLike({"mediaId":media.id}),
headers: {'Content-Type':'application/x-www-form-urlencoded'}
}).then(function (response) {
// now add subDatas into main data Object
media.subMedias = response.data;
});
}
and in your controller just use ng-repeat like this
<div id="mainW" ng-controller="MediaController">
<div id="mediaBodyW" ng-repeat="media in medias">
<div class="mediaW" data-id="{{media.id}}">
<div class="mediaNum">{{media.num}}</div>
<div class="mediaN">{{media.name}}</div>
<div id="loadSubs" ng-click="loadSubMedias(media)">load sub medias</div>
<div id="subMediaW" ng-repeat="subMedia in media.subMedias">
<pre>{{subMedia | json}}</pre>
</div>
</div>
</div>

Related

Cannot work out how to access property of self in ViewModel

I am very new to js and html - trying to make a basic front end for a C# web api.
I'm making a simple app for tracking bugs. I have a panel for the list of bugs, where I can click "Details" to see more info on each bug (I would post an image, but my reputation is too low). Then a new panel opens with with the details of the bug, including a button to close the bug, ie change set the status to "closed". It's with this button that I have the problem.
I have this in my Index.cshtml:
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Bugs</h2>
</div>
<div class="panel-body">
<ul class="list-unstyled" data-bind="foreach: bugs">
<li>
<strong><span data-bind="text: Title"></span></strong>: <span data-bind="text: Description"></span>
<small>Details</small>
</li>
</ul>
</div>
</div>
<div class="alert alert-danger" data-bind="visible: error"><p data-bind="text: error">
</p></div>
<!-- ko if:detail() -->
#* Bug Detail with Close Button *#
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Detail</h2>
</div>
<table class="table">
<tr><td>Title</td><td data-bind="text: detail().Title"></td></tr>
<tr><td>Description</td><td data-bind="text: detail().Description"></td></tr>
<tr><td>Status</td><td data-bind="text: detail().Status"></td></tr>
<tr><td>Created</td><td data-bind="text: detail().Created"></td></tr>
<tr><td>Owner</td><td data-bind="text: detail().Owner"></td></tr>
</table>
<div class="panel-body">
<form class="form-horizontal" data-bind="submit: closeBug(detail())">
<button type="submit" class="btn btn-default">Close bug</button>
</form>
</div>
</div>
</div>
<!-- /ko -->
Then this is the relevant stuff in app.js:
var ViewModel = function () {
var self = this;
self.bugs = ko.observableArray();
self.error = ko.observable();
self.detail = ko.observable();
self.getBugDetail = function (item) {
ajaxHelper(bugsUri + item.Id, 'GET').done(function (data) {
self.detail(data);
});
}
var bugsUri = '/api/bugs/';
function ajaxHelper(uri, method, data) {
self.error(''); // Clear error message
return $.ajax({
type: method,
url: uri,
dataType: 'json',
contentType: 'application/json',
data: data ? JSON.stringify(data) : null
}).fail(function (jqXHR, textStatus, errorThrown) {
self.error(errorThrown);
});
}
// get open bugs
function getAllBugs() {
ajaxHelper(bugsUri, 'GET').done(function (data) {
self.bugs(data);
});
}
// Fetch the initial data.
getAllBugs();
//close bug
self.closeBug = function (localDetail) {
var closedBug = {
OwnerId: self.localDetail.OwnerId,
Description: self.localDetail.Description,
Status: "closed",
Title: self.localDetail.Title,
Created: self.localDetail.Created
};
ajaxHelper(bugsUri + self.localDetail.Id, 'DELETE', self.localDetail.Id);
ajaxHelper(bugsUri, 'POST', closedBug).done(function (item) {
self.bugs.push(item);
});
}
};
To update the status of a bug, I want to take the Id of the bug currently open in the detail panel and create an identical bug except with Status set to "closed". The trouble is that there's always a problem access self.localDetail in the new variable closedBug. I've tried it here by parameterizing the closeBug method, but I've also tried accessing self.Detail, but it's done no good, so I'm here. My next step, if this fails, is to create a separate panel entirely with a form for bugId which closes the bug when you submit, but it would be better to be in the bug details window.
you're already passing localDetail as the param in the closeBug fn, so you don't need to refer to it by adding self. Try this (removed all references to self.):
//close bug
self.closeBug = function (localDetail) {
var closedBug = {
OwnerId: localDetail.OwnerId,
Description: localDetail.Description,
Status: "closed",
Title: localDetail.Title,
Created: localDetail.Created
};
ajaxHelper(bugsUri + localDetail.Id, 'DELETE', localDetail.Id);
ajaxHelper(bugsUri, 'POST', closedBug).done(function (item) {
self.bugs.push(item);
});
}
Your first issue is in your submit binding itself. It's being called as soon as it's rendered, not on submit. You want to pass the function object (optionally with bound arguments) instead of calling it in your html.
Explicit Bound Arguments
<form class="form-horizontal" data-bind="submit: closeBug.bind(null, detail)">
<button type="submit" class="btn btn-default">Close bug</button>
</form>
which will bind null as the this value of the function and pass the detail observable as the first argument. With this, your closeBug looks like
self.closeBug = function (localDetail) {
var closedBug = {
OwnerId: localDetail().OwnerId
}
}
Note, you want to unwrap it in the handler, not the html so you get the latest value and not the initial value.
Binding Context as this
Alternatively (and in more idiomatic knockout fashion) you can bind to the function object and it will be called with the binding context as this (the same as explicitly using closeBug.bind($data)).
<form class="form-horizontal" data-bind="submit: closeBug">
<button type="submit" class="btn btn-default">Close bug</button>
</form>
self.closeBug = function () {
var closedBug = {
OwnerId: this.localDetail().OwnerId
}
}
Aside: this may be helpful for better understanding this, self, and function.bind

Table not getting displayed on a button click in AngularJS

Hi I'm learning AngularJS and I have a question. I want to display a table on a button click. On clicking the button, JSON data gets fetched but I have to press the button twice for the data to be displayed.
This is the HTML page.
<html>
<body>
<div class="container">
<label for="tags" style="margin-top: 30px;margin-left: 15px;">GSTIN </label>
<input id="tags">
<button ng-click="searchfunction()">search</button>
</div>
<br/>
<hr>
<div class="container">
<div ng-show="tshow" ng-repeat="x in searchdata">
<table class="table table-bordered table-responsive">
<thead>
<tr>
<th>MON</th>
<th>SGST</th>
<th>CGST</th>
<th>IGST</th>
<th>CESS</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="i in x">
<td>{{i.mon}}</td>
<td>{{i.sgst}}</td>
<td>{{i.cgst}}</td>
<td>{{i.igst}}</td>
<td>{{i.cess}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>
This is the controller:
app.controller("searchcontroller", function ($scope,$http) {
$scope.tshow=false;
function make_base_auth(user, password) {
var tok = user + ':' + password;
var hash = btoa(tok);
return "Basic " + hash;
}
$scope.searchfunction=function() {
$scope.tshow=true;
var tf=document.getElementById("tags");
var value=tf.value;
var auth = make_base_auth("gstadmn112","Gstn#123");
var url6 = "http://164.100.148.67:8080/wsgstr3B/rest/payment/gstinsearch?gstin="+value+"&year=201718";
xml = new XMLHttpRequest();
// jQuery
$.ajax({
url : url6,
method : 'GET',
beforeSend : function(req) {
req.setRequestHeader('Authorization', auth);
},
success:function(response) {
console.log(response);
scope.searchdata=response;
},
failure:function() {
window.alert("wrong input data doesn't exist");
}
});
}
});
I need to click twice on the search button for the table to be displayed. I want the table to be hidden initially and once the search is successful the table should be displayed. The table is hidden initially and after clicking twice correct data gets displayed.
Maybe, you try to add $scope.tshow=true; in function success:
success:function(response) {
console.log(response);
$scope.tshow=true;
$scope.searchdata=response;
},
P.S. I advise to use $http instead of $ajax.
This problem is related to the digest loop of angularjs which keeps all changes sync between your view and controller.
When you invoke the searchfunction(), angularjs will know whats happening inside the method and sync the changes made with the view when its completed.
The problem is that your method uses $.ajax which has some async callback methods.
When these methods gets invoked angularjs have already left the party (digest loops is over) and don't know what these methods have done to your controller $scope.
The jQuery success callback will however set the $scope.searchdata=response; and this change gets notified the next time angularjs is in the party (the next time you click).
So basically you need to make sure angularjs is aware of the async methods which makes changes to your $scope.
To solve this I would inject angularjs own $http service (which takes care of async changes to the scope) and use that instead.
var req = {
method: 'GET',
url: url6,
headers: {
'Authorization': auth
}
}
$http(req).then(function(response){
console.log(response);
$scope.searchdata=response;
}, function(){
window.alert("wrong input data doesn't exist");
});
You can use this way.
$scope.searchfunction=function(){
$scope.tshow=true;
var tf=document.getElementById("tags");
var value=tf.value;
$http.get("http://164.100.148.67:8080/wsgstr3B/rest/payment/gstinsearch?gstin="+value+"&year=201718")
.success(function(result) {
$scope.searchdata=response;
$scope.tshow=false;
})
.error(function() {
window.alert("wrong input data doesn't exist");
});
}

cant display image from json object

I am trying to display data from my JSON objects, my object is as follows in 2 images
within data, I have c_name, max_slots and an array slots in that array I have base_image
I can not get the base image array to display in my HTML.
This is my current printout in HTML
here is my JavaScript
$scope.GetData = function () {
$http({
url: "http://www.ccuktech.co.uk/ccuploader/campaigns/getCampaign",
method: "POST",
date: {},
headers: {'Content-Type': 'application/json'}
}).then(function (data) {
// success
console.log('you have received the data ');
console.log(data);
$scope.responseData = data.data;
}, function (response) {
// failed
console.log('failed getting campaigns goo back to log in page.');
console.log(response);
});
};
$scope.GetData();
and HTML
<div ng-repeat="data in responseData">
<h2>name</h2>:
{{data.c_name}}
<h2>max slots</h2>:
{{data.max_slots}}
<h2>image</h2>:
{{data.base_image}}
</div>
You cant just display an image within the div tag, use <img> instead of it
<div ng-repeat="data in responseData">
<h2>name</h2>:
{{data.c_name}}
<h2>max slots</h2>:
{{data.max_slots}}
<h2>image</h2>:
<img ng-src="{{data.base_image}}"></img>
</div>
Since the base_image is inside an inner array, you have to loop that array inside your ng-repeat of responseData
<div ng-repeat="data in responseData">
<h2>name</h2>:
{{data.c_name}}
<h2>max slots</h2>:
{{data.max_slots}}
<h2>image</h2>:
<div ng-repeat="slot in slots">
<img src={slot.base_image} />
</div>
</div>
{{data.base_image}}
won't be accessible because it is inside nested slots array so in order to refernce base_image you have to iterate through slots as well. Try something like given here :
ng repeat nested array

Return content for it to be shown - Right now nothing comes out

When I have picked my json from my controller, so when I have to write it in index.cshtml side so but when I type "module." then there's nothing up around img, text or ID, etc.
by my ng-repeat will it count as I want appears as it should.
The problem here just in that it will not enter text and img whether it is empty or not. both my questions that appear on the page has no image but right now the picture is shown on the page.
It gives me such a helping hand to know what to write about it must be img or text or otherwise.
Opgaver.html
<input type="hidden" ng-init="Id='2'" ng-model="Id" />
<div ng-repeat="module in Newslist" style="clear:both; margin:7px 0; min-height:110px; margin:5px 0;">
<div class="col-md-8">
<p>{{module.text}}</p>
</div>
<div class="col-md-3" ng-show="module.Img != null">
<img class="img-responsive img-rounded mb-lg" src="img/projects/project.jpg">
</div>
</div>
Opgaver.js
var app = angular.module('Opgaver', []);
app.controller('OpgaverCheck', function ($scope, $http) {
//GET
$scope.$watch("Id", function() {
var url = "/opgaver/kategori/" + $scope.Id;
$http.get(url).success( function(response) {
$scope.Newslist = response;
});
});
});
Controller:
[HttpGet]
public JsonResult kategori(int Id)
{
var db = Helpers.HelperToTables.DbValue;
List<ListOpgave> list = db.LektionerOpgaves.Where(i => i.fk_LektionerId == Id).OrderByDescending(i => i.id).Select(x => new ListOpgave
{
Id = x.id,
Img = x.LektionerOpgaveImg.value,
Text = x.text,
}).ToList();
return Json(list, JsonRequestBehavior.AllowGet);
}
First, you have "module.text", but in your MVC Controller, you have "Text". They must be exactly the same including case. Try changing "module.text" to "module.Text". Also, make sure your Json is actually returning a list by putting breakpoints in both controllers.
I think you want $scope.Newslist = response.data;
The response object has five properties and the information you want is contained in one of them. So as you have it written, Newslist is equal to the entire response, including the status and headers, rather than just the response data.
See the documentation on $http.

Vue JS - Putting Json on data

I want to put my JSON data into Vue data, and a display, why can't I get to work?
compiled: function(){
var self = this;
console.log('teste');
$.ajax({
url: 'js/fake-ws.json',
complete: function (data) {
self.$data.musics = data;
console.log(self.$data.musics);
}
})
}
<div id="playlist" class="panel panel-default">
<div class="panel-body">
<ul>
<li v-repeat="musics.item" >
{{nome}}
</li>
<ul>
<div>
</div>
I can't get the code to work.. why?
I think the problem is that musics is not initially part of your Vue data, so when you set its value using self.$data.musics = data, Vue doesn't know it needs to watch it. Instead you need to use the $add method like this:
self.$set("musics", data);
From the VueJs Guide:
In ECMAScript 5 there is no way to detect when a new property is added to an Object, or when a property is deleted from an Object. To deal with that, observed objects will be augmented with two methods: $add(key, value) and $delete(key). These methods can be used to add / delete properties from observed objects while triggering the desired View updates.
this refers to the whole Vue object, so musics object is already accessible via this.musics. More info here in the VueJS API reference and here in the VueJS guide, and more on this here.
With that in mind the code should look something like this:
var playlist = new Vue({
el: '#playlist',
data:{
musics: '',
}
methods: {
compiled: function(){
var self = this;
console.log('test');
$.ajax({
url: 'js/fake-ws.json',
complete: function (data) {
self.musics = data
console.log(self.musics);
}
})
}
}
And the view would be something like this:
<div id="playlist" class="panel panel-default">
<div class="panel-body">
<ul>
<li v-repeat="musics">
{{nome}}
</li>
<ul>
</div>
</div>
Also look at the code of this example.
you can do that with vue-resource. Include vue-resource.js into your app or html file and:
{
// GET /someUrl
this.$http.get('/someUrl').then(response => {
// get body data
this.someData = response.body;
}, response => {
// error callback
});
}

Categories