I am writing a simple app that loads movie info from an API. After this loading, I am attempting to use Angular to display the movies in a simple list view. I am correctly loading the movies, but it seems like the angular controller is created and sends the movie array to the view before the movie array is populated. I am unsure how to get around this.
var movieList = [];
var app = angular.module('top250', []);
// immediately make a call to the server to get data (array of movies from text file)
$.post('/', {}, function(data) {
init(data);
});
app.controller('MovieController', function() {
// should be setting this.movies to an array of 250 movies
this.movies = movieList;
});
function init(data) {
// cycle through array, use title to retrieve movie object, add to array to be sent to view
$.each(data, function(index, value) {
var title = value.split(' (')[0];
title = title.split(' ').join('+');
var url = 'http://www.omdbapi.com/?t=' + title + '&y=&plot=short&r=json';
$.getJSON(url, function(data) {
console.log('in get json', data);
var movieObj = data;
storeMovie(movieObj);
});
});
}
function storeMovie(movieObj) {
movieList.push(movieObj);
}
And my HTML (although I'm certain this isn't the problem:
<body ng-controller="MovieController as MovieDB">
<div class="row">
<div class="large-12 columns">
<h1>IMDB Top 250 List</h1>
</div>
</div>
<div class="row">
<div class="large-12 columns" id="movie-list">
<div class="list-group-item" ng-repeat="movie in MovieDB.movies">
<h3>{{movie.Title}} <em class="pull-right">{{movie.Plot}}</em></h3>
</div>
</div>
<script src="js/foundation.min.js"></script>
<script>
$(document).foundation();
</script>
</body>
First I transformed your ajax calls to an angular factory:
app.factory('MoviesService', function($http, $q) {
function getMovie(value) {
var title = value.split(' (')[0];
title = title.split(' ').join('+');
var url = 'http://www.omdbapi.com/?t=' + title + '&y=&plot=short&r=json';
return $http.get(url).then(function(res){ return res.data; });
}
return $http.post('/').then(function(res) {
return $q.all(res.data.map(getMovie));
});
});
Then I can consume it like so:
app.controller('MovieController', function(MoviesService) {
var self = this;
MoviesService.then(function(movies) {
self.movies = movies;
});
});
don't use jquery
use angular $http or $resource
using $http, you set scope var to the data inside promise, and life will be good
You need to wait for you init method to complete:
function init(data, complete) {
$.each(data, function(index, value) {
var title = value.split(' (')[0];
title = title.split(' ').join('+');
var url = 'http://www.omdbapi.com/?t=' + title + '&y=&plot=short&r=json';
$.getJSON(url, function(data) {
console.log('in get json', data);
var movieObj = data;
storeMovie(movieObj);
}).always(function(){ // count competed ajax calls,
// regardless if they succeed or fail
if(index === data.length -1)
complete(); // call callback when all calls are done
});
});
}
Now you can do this:
app.controller('MovieController', function() {
$.post('/', {}, function(data) {
init(data, function(){
this.movies = movieList;
});
});
});
Personally I would just keep the movieList inside of the init method and send it with the callback when you're done, but that's just a preference.
Related
This is a live link from a web API want to use:
http://gmgapi.azurewebsites.net/SystemParameters/Hotels/GetAll?langId=en
This is my first time to call data from API so maybe I missed some setting
In html page I put some code like this to test the connection with API or not
<div id="result"></div>
<script>
(function() {
var myAPI = "http://gmgapi.azurewebsites.net/SystemParameters/Hotels/GetAll?langId=en";
$.getJSON(myAPI, {
format: "json"
})
.done(function (data) {
$("#result").html(data);
alert("Load was performed.");
});
})();
</script>
the array contained hotel data like hotel name , description and images
i just need to call them using jquery
new edits
this code is working properly to get the data from Api
(function() {
var myAPI = "https://gmgapi.azurewebsites.net/SystemParameters/Hotels/GetAll?langId=en";
$.getJSON(myAPI, {
format: "json"
})
.done(function(data) {
doSomething(data);
console.log("Load was performed.");
});
})();
function doSomething(data) {
for (var i = 0; i < data.length; i++) {
var div = $("<div>");
var label = $("<label>").text(data[i].DisplayValue);
$(div).append(label);
$('#result').append(div);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="result"> </div>
I need to make a href and user select the name hotel and retrieve the other data related to this hotel only
something like hotel 1 any user click on ot to get the all hotel data
If I understand you correctly, you want the data receiving from the API to be used to show as result as html?
(function() {
var myAPI = "http://gmgapi.azurewebsites.net/SystemParameters/Hotels/GetAll?langId=en";
$.getJSON(myAPI, {
format: "json"
})
.done(function(data) {
doSomething(data);
alert("Load was performed.");
});
})();
function doSomething(data) {
for (var i = 0; i < data.length; i++) {
var div = $("<div>");
var label = $("<label>").text(data[i].DisplayValue);
$(div).append(label);
$('#result').append(div);
}
}
http://jsfiddle.net/trf432xm/
I have a simple controller like this:
public JsonResult GetPosts(int? id)
{
var varid = id;
var ret = (from post in db.Posts.ToList()
orderby post.PostedDate descending
select new
{
NeighbourhoodId = varid,
Message = post.Message,
PostedByName = post.ApplicationUser.UserName,
PostedDate = post.PostedDate.ToString(),
PostId = post.PostId,
});
return Json(ret, JsonRequestBehavior.AllowGet);
}
Here, i am able to get the dropdown selected value.I am assigning it to a variable varid and then assigning it to NeighbourhoodId.
But, when the view page is rendered nothing changes all the 4 Post are showing.but in reality it should display only 2 Post.
This is my view page code:
<ul id="msgHolder" data-bind="foreach: posts">
<li class="postHolder">
<p><a data-bind="text: PostedByName"></a>: <span data-bind=" html: Message"></span></p>
and my wallpost.js file in script folder where all knockout view model related code is here.It first loads all the Post from database correctly but data doesnot get filtered if i am trying to filter it with dropdown change.
function Post(data) {
var self = this;
data = data || {};
self.PostId = data.PostId;
self.NeighbourhoodId = data.NeighbourhoodId;
self.Message = ko.observable(data.Message || "");
self.PostedByName = data.PostedByName || "";
self.PostedDate = getTimeAgo(data.PostedDate);
self.error = ko.observable();
function viewModel() {
var self = this;
self.posts = ko.observableArray();
self.newMessage = ko.observable();
self.error = ko.observable();
self.loadPosts = function () {
// to load existing posts
$.ajax({
url: postApiUrl,
datatype: "json",
contentType: "application/json",
cache: false,
type: 'Get'
})
.done(function (data) {
var mappedPosts = $.map(data, function (item)
{ return new Post(item); });
self.posts(mappedPosts);
})
.fail(function () {
error('unable to load posts');
});
}
return self;
};
ko.applyBindings(new viewModel());
and my dropdown related code is here:
#Html.DropDownList("Locations", ViewBag.NeighbourhoodId as SelectList,"Select a Location")
<script type="text/javascript">
$(document).ready(function () {
$("#Locations").change(function () {
var locationSelected = $("#Locations").val();
var url = '#Url.Action("GetPosts", "Post")';
$.post(url, { id: locationSelected },
function (data) {
});
});
});
</script>
When i debug, i am getting correct id value in controller but there is problem in filtering out data. Is there a need for some change in knockout file.what to do here ??
Instead of mixing jquery event handlers and knockout bindings, i think it's better for you handle all with knockout.
Bind your select element (drop down) with the options binding and load the locations array on view model initialization; bind the value to a observable property, ex: 'CurrentLocation'
Subscribe to the change of the property 'CurrentLocation', ex:
myViewModel.CurrentLocation.subscribe(function(newValue) {
//request to GetPosts here
});
On .done() function of the GetPosts request update the observableArray with the items received by the server
Hope this helps!
UPDATE
The following is a very simple example, i changed my mind and used "event" binding to handle the change event instead of "options" binding but the concept it's the same.
#Html.DropDownList("Locations", new SelectList(Model.Locations, "Id", "Name"), new { data_bind = "event: { change: reloadPosts}" })
<ul data-bind="foreach: posts">
<li data-bind="text:CompleteText"></li>
</ul>
<script>
function Post(data) {
var self = this;
self.Id = ko.observable(data.Id);
self.LocationId = ko.observable(data.LocationId);
self.Text = ko.observable(data.Text);
self.CompleteText = ko.computed(function () {
return self.Id() + " " + self.Text();
});
}
function PageViewModel() {
var self = this;
self.posts = ko.observableArray();
self.reloadPosts = function () {
$.ajax({
type:"POST",
url: "GetPosts",
data: { locationId: $("#Locations").val() }
}).done(function (data) {
var mappedPosts = $.map(data, function (item)
{ return new Post(item); });
self.posts(mappedPosts);
});
}
}
var vm = new PageViewModel();
ko.applyBindings(vm);
</script>
The GetPosts method in the controller:
[HttpPost]
public JsonResult GetPosts(string locationId)
{
var selectedPosts = posts.Where(x => x.LocationId == locationId);
return Json(selectedPosts, JsonRequestBehavior.AllowGet);
}
The posts collection in the controller in the example above it's just an inmemory collection, probably you will read it from a DB or something like that.
The post class on the c# code:
class Post
{
public string Id { get; set; }
public string LocationId { get; set; }
public string Text { get; set; }
}
And finally the viewmodel used:
public class TestViewModel
{
public List<Location> Locations { get; set; }
}
I am having trouble updating my HTML UI.
When the document load and calls "getAllProducts()", the HTML UI displays all my items and with the right css class for 'styleStatusCss', and the right 'displayName', the problem is that when I try to update my observableArray with a newly updated product (product name or status has changed), the html UI does not update and remains the same
So here is a quick list of what is happening:
getUpdatedProducts() is called every 25 sec, and returns, any product
that has been updated
I check how many products my observable array has: appVM.itemList.length and it does have 100 (as expected!), I also check that the json product that has been sent back has some modified data, and indeed it has changed!
I then create the javascrip obj MyProduct using that product json object
Now I add my newly created javascript obj MyProduct to the observablearray: appVM.itemList.push(newUpdatedProduct);
And finally I check how many items my observablearray has, after doing the push, (and since I cannot see any changes on the HTML UI), and appVM.itemList.length now says 101 !!! How can that be? the HTML UI still displays the data as it was after the initial load
Please see below most of the code
HTML
<table >
<tbody data-bind="foreach: itemList">
<tr>
<td>
<div data-bind="css: styleStatusCss"></div>
</td>
<td>
<div data-bind="text: displayName"></div>
</td>
</tr>
</tbody></table>
And here is the javascript:
<script type="text/javascript">
var appVM;
var initializeItems = false;
$.ajaxSetup({
// Disable caching of AJAX responses
cache: false
});
$(document).ready(function () {
getAllProducts();
});
setInterval(function () {
if (initializeItems) {
getUpdatedProducts();
}
}, 25000);
function getAllProducts() {
var url = '#Url.Action("_AllProducts", "Home")';
$.ajax({
url: url,
type: 'GET',
dataType: 'JSON',
})
.success(function (result) {
initializeItems = true;
appVM = new AppViewModel();
var mappedProducts = ko.utils.arrayMap(result.ItemList, function (item) {
var con = new MyProduct(item);
return con;
});
appVM.itemList = mappedProducts;
ko.applyBindings(appVM);
})
.error(function (xhr, status, error) {
alert("FATAL ERROR");
})
}
function getUpdatedProducts() {
var url = '#Url.Action("_UpdateProducts", "Home")';
$.ajax({
url: url,
type: 'GET',
dataType: 'JSON',
})
.success(function (result) {
if (result.HasNewData) {
alert("we have some data");
alert("START COUNT: " + appVM.itemList.length); //this shows all my 100 products -> START COUNT: 100
alert("Number of new items: " + result.ItemList.length); // i only get one product back for easy debugging
for (index = 0; index < result.ItemList.length; ++index) {
var updatedProdJson = result.ItemList[index]; //get the json for the product
alert("New prod json: " + objToString(updatedProdJson)); //just for debugging print out in a readable format
var newUpdatedProduct = new MyProduct(updatedProdJson);//create our MyProduct object (which has all properties as observable)
appVM.itemList.push(newUpdatedProduct); //add our new MyProduct to the list
alert("FINAL COUNT: " + appVM.itemList.length); // --> FINAL COUNT: 101
}
}
})
.error(function (xhr, status, error) {
alert("Error: " + status);
})
}
function AppViewModel() {
var self = this; //so it references the viewModel object
self.itemList = ko.observableArray([]);
self.doAlert = function () {
alert("HERE");
}
}
function MyProduct(data) {
//alert("DATA: " + objToString(data));
this.Id = ko.observable( data.Id);
this.Name = ko.observable(data.Name);
this.status = ko.observable(data.Status);
this.displayName = ko.computed(function () {
var fullnmae = this.Id() + " " + this.Name();
return fullnmae;
}, this);
this.styleStatusCss = ko.computed(function () {
var pStatus = 'divstatusnone';
if (this.status() === 'H')
pStatus = 'divlowstatus';
if (this.status() === 'D')
pStatus = 'divhighstatus';
return pStatus;
},this);
}
function objToString (obj) {
var str = '';
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
str += p + '::' + obj[p] + '\n';
}
}
return str;
}
Hope somebody can tell me where i went wrong.
Many thanks,
in getAllProducts, you're assigning the results to itemList, losing your observable array:
appVM.itemList = mappedProducts;
you need to do this instead:
appVM.itemList(mappedProducts);
I have a list of events that gets build from a JSON call to my server. The list gets parsed through ng-repeat. I want to implement a like button for the event and if its liked, replace that with unlike.
<a ng-show='event.liked==null' ng-click="like(event.event_id)">Like</a>
<a ng-show='event.liked!=null' ng-click="unLike(event.event_id)">Unlike</a>
Everything works perfectly except I have to refresh the feed to show "Unlike". Is there a way I can update the specific list item at the index that was liked once clicked without the need to refresh.
Please let me know if you need more information. Thanks!
edit: adding like function & unlike function. All it does is send request to my server to like or unlike a specific event with the event_id and user token.
$scope.like = function (event_id) {
var url = www.server.com?type=unlike&event_id=...
$http.post(url).success(function (data) {
console.log('success like');
//I want it to update my index here
}).error(function (data) {
console.log('fail like');
});
};
$scope.unLike = function (event_id) {
var url = www.server.com?type=unlike&event_id=...
$http.post(url).success(function (data) {
console.log('success unlike');
//I want it to update my index here
}).error(function (data) {
console.log('fail unlike');
});
};
Instead of passing in the event_id, pass the object to the like and unLike functions and update the object in success handler.
HTML
<a ng-hide='event.liked' ng-click="like(event)">Like</a>
<a ng-show='event.liked' ng-click="unLike(event)">Unlike</a>
Controller
$scope.like = function(event) {
var url = 'www.server.com?type=unlike&event_id=' + event.event_id;
$http.post(url).success(function (data) {
event.liked = true;
console.log('success like');
}).error(function (data) {
console.log('fail like');
});
};
$scope.unLike = function(event) {
var url = 'www.server.com?type=unlike&event_id=' + event.event_id;
$http.post(url).success(function (data) {
event.liked = null;
console.log('success unlike');
}).error(function (data) {
console.log('fail unlike');
});
};
In MVC 4 application I want when click a link, to show some related Products list in lightbox. I have method returns products I need:
public ActionResult GetRelatedProducts(int id)
{
var realProducts = GetRelatedProducts(id);
List<object> productsObjectList = new List<object>();
foreach (var item in realProducts)
{
productsObjectList .Add(new
{
id = item.Id,
fullname = item.Name
});
}
return Json(productsObjectList , JsonRequestBehavior.AllowGet);
}
HTML is:
<a class="show" id="show">Show</a>
<div id="productBox" style="display: none;">
// Product list will get here
</div>
And script:
$('#show').click(function (e) {
url = '#Url.Action("GetRelatedProducts", "Product")';
var data = { id: '#Model.Id' };
$.post(url, data, function (result) {
$('#productBox').lightbox_me({
onLoad: function () {
//How to send returned product list to light box, to show them by foreach loop
}
});
e.preventDefault();
});
});
How can I send product list to productBox to show products?
You code:
$('#show').click(function (e) {
url = '#Url.Action("GetRelatedProducts", "Product")';
var data = { id: '#Model.Id' };
$.post(url, data, function (result) { // <- "result" will contain array
$('#productBox').lightbox_me({
onLoad: function () { ... }
});
e.preventDefault(); // <- this must prevent "a" tag, put it outside
});
});
You could use your list on client side like this:
$.post(url, data, function (result) {
var list = '<ul>';
for(var i = 0; i < result.length; i++)
{
list += '<li>' + result[i].fullname + '</li>';
}
list += '</ul>';
$('#productBox').html(list).lightbox_me();
});
OR as Vladimir Bozic wrote, just use PartialViewResult, from controller return PartialView, it is like normal view, but without layout, just html block and you can use it like:
$.post(url, data, function (result) {
$('#productBox').html(result).lightbox_me();
});