Angular load json file dynamically onclick - json

I have a simple page that displays a list of items. Data is pulled from a JSON file (URL is provided by the server).
Two tabs allow to display (onclick) the "most recent" or the "popular" items (again data of each tab will is provided via JSON file). By default the "most recent" items should be displayed.
What is the best way to load the right JSON file onclick and display its content.
I was thinking of passing the URLs in the markup (but I doubt that's the best way to do it). What I had in mind:
HMTL
<ul>
<li jsrc="recentitems.json" urlgetter>Most recent</li>
<li jsrc="popularitems" urlgetter>Most popular</li>
</ul>
Plunker of my code:
http://plnkr.co/edit/glSz1qytmdZ9BQfGbmVo?p=preview
Any suggestions on how to approach it?
EDIT
I'm slightly changing my approach. I'm now making one HTTP request (hopefully also better performance wise?).
Basically I'd like to load all the items once then filter/sort them.
Expanding from your plunker: http://plnkr.co/edit/glSz1qytmdZ9BQfGbmVo?p=preview
I added a "date" & "views" attributes to the object item (see JSON).
Tabs
-- How could I filter/sort the items onclick? "Recent" would be sorted by date and "Popular" would be sorted by views.
Categories
-- I'm using ng-click to grab the category value although not sure how to update the filter dynamically (using the value passed onclick).
Thanks

One way is to do something like:-
First the HTML:-
<div ng-app="App">
<div ng-controller="tabsCtrl">
<ul>
<li ng-click="tab(1)">Recent items</li>
<li ng-click="tab(2)">Popular items</li>
</ul>
<ul>
<li ng-repeat='product in products'>{{product.Name}}</li>
</ul>
{{products || json}}
</div>
</div>
and the JS
var app = angular.module('App', []);
app.factory('products', function ($http, $q) {
return {
items: function (url) {
//create our deferred object.
var deferred = $q.defer();
//make the call.
$http.get(url).success(function (data) {
//when data is returned resolve the deferment.
deferred.resolve(data);
}).error(function () {
//or reject it if there's a problem.
deferred.reject();
});
//return the promise that work will be done.
return deferred.promise;
}
}
});
app.controller("tabsCtrl", function ($scope, products) {
$scope.products = products.items('/api/products');
$scope.tab = function (tabIndex) {
if (tabIndex == 1)
$scope.products = products.items('/api/products');
if (tabIndex == 2)
$scope.products = products.items('/api/popularproducts');
};
});

Related

How to populate a jQuery Mobile ListView with JSON data?

I'm developing a webapp here using HTML and jQuery Mobile (JQM), so I'm pretty new at this.
What I'm trying to do here is to populate a JQM listview with a list of names.
Each of this names will link to a new page with personal data being displayed (Full name, address, date of birth, etc).
Currently, because of my lack of knowledge, I manually create a new .html file for EACH individual person (e.g. johnDoe.html for a fictional character Mr. John Doe). I then physically link the list elements to this html file via the function.
Problem is now I have 100 over individuals to populate that list view. I think that there's an easier way to do this rather than manually creating 100+ html files for all these individual persons right?
I heard of this JSON thing that might do the trick, but coming from a background of ZERO computing knowledge, I don't really understand how it works. Will someone please shed some light on how can I do this?
Thanks a lot!
EDIT:
I'm using Dreamweaver CS5.5 to do the coding. For this webapp that I'm tasked to develop, I was given a "template" or sorts that uses JQM and Backbone.js. As such, somehow the "multi-page" structure for a single HTML file doesn't seem to work. From what I see in the template, every HTML file has a corresponding JS file that has code that looks like this:
define(['jquery',
'underscore',
'backbone',
'helper',
'views/phr/list',
'text!templates/vpr2.html'
],
function ($,
_,
Backbone,
Helper,
phrListView,
tmpVpr2) {
var view = Backbone.View.extend({
transition: 'fade',
reverse: true,
initialize: function () {
this.phrlistview = new phrListView();
},
render: function () {
$(this.el).append(_.template(tmpVpr2, {}));
console.log("Rendering subelements...");
Helper.assign(this, {
'#phrListView': this.phrlistview
});
return this.el;
}
});
return view;
});
For the HTML pages, they all begin with a <div data-role=header> tag, then a <div data-role=content>, before ending with a <div data-role=footer>, all with their respective content within the opening and closing tags.
For my listview in question, the JQM code for the listview will be within the <div data-role=content> part of the HTML file. How can I populate this listview with JSON data then?
(Apologies if I sound extremely noob at this, because I really am >.< Really appreciate the help!)
Solution
Yes. Its possible to have two pages and use one for displaying your data and one to show up the details of the clicked item. I had to pull in some old stuff, a demo I made when jQM was in version 1.1 and change it to modern times. Anyway, considering I have an array like this :
[
{
"id": 0,
"age": 31,
"name": "Avis Greene",
"gender": "female",
"company": "Handshake",
"email": "avisgreene#handshake.com",
"phone": "+1 (845) 575-2978",
"address": "518 Forrest Street, Washington, New York, 3579"
},
{
"id": 1,
"age": 31,
"name": "Dunn Haynes",
"gender": "male",
"company": "Signity",
"email": "dunnhaynes#signity.com",
"phone": "+1 (829) 454-3806",
"address": "293 Dean Street, Dante, Oregon, 5864"
}
]
I randomly generated stuff and made it upto 100 elements, just like how you seem to have. I have two pages.
<!--first page -->
<div data-role="page" id="info-page">
<div data-role="header" data-theme="b">
<h1> Information</h1>
</div>
<div data-role="content">
<ul data-role="listview" id="prof-list" data-divider-theme="a" data-inset="true">
<li data-role="list-divider" data-theme="b" role="heading">Names</li>
</ul>
</div>
</div>
<!--second page -->
<div data-role="page" id="details-page">
<div data-role="header" data-theme="b">Go back
<h1>Employee Details</h1>
</div>
<div data-role="content"></div>
</div>
The first page, #info-page is for showing data in a listview. The second page, #details-page is for the info of the clicked item. Thats all you need. Only two pages, not more than that. So every time a click happens, you do the following through JavaScript
Get the current value of data from the array. Like if you click on the 4th li in the list, get the 4th object from the array which has all the data.
Store it in the data variable of the second page, so that it can be retrieved later. Something like this:
$("#details-page").data("info", info[this.id]);
Then, redirect to second page using changePage, like this :
$.mobile.changePage("#details-page");
When the second page opens, use the pagebeforeshow event to get the data from the page (which you stored into this page when the tag in the previous page was clicked.
Use some HTML layout to populate the data. I used jQM's grids.
That's all folks!
Full code
Ive attached the JS used with the HTML. Its self explanatory. Read the inline comments in the code and you'll be able to understand more. Assume info is the array in picture.
//pageinit event for first page
//triggers only once
//write all your on-load functions and event handlers pertaining to page1
$(document).on("pageinit", "#info-page", function () {
//set up string for adding <li/>
var li = "";
//container for $li to be added
$.each(info, function (i, name) {
//add the <li> to "li" variable
//note the use of += in the variable
//meaning I'm adding to the existing data. not replacing it.
//store index value in array as id of the <a> tag
li += '<li>' + name.name + '</li>';
});
//append list to ul
$("#prof-list").append(li).promise().done(function () {
//wait for append to finish - thats why you use a promise()
//done() will run after append is done
//add the click event for the redirection to happen to #details-page
$(this).on("click", ".info-go", function (e) {
e.preventDefault();
//store the information in the next page's data
$("#details-page").data("info", info[this.id]);
//change the page # to second page.
//Now the URL in the address bar will read index.html#details-page
//where #details-page is the "id" of the second page
//we're gonna redirect to that now using changePage() method
$.mobile.changePage("#details-page");
});
//refresh list to enhance its styling.
$(this).listview("refresh");
});
});
//use pagebeforeshow
//DONT USE PAGEINIT!
//the reason is you want this to happen every single time
//pageinit will happen only once
$(document).on("pagebeforeshow", "#details-page", function () {
//get from data - you put this here when the "a" wa clicked in the previous page
var info = $(this).data("info");
//string to put HTML in
var info_view = "";
//use for..in to iterate through object
for (var key in info) {
//Im using grid layout here.
//use any kind of layout you want.
//key is the key of the property in the object
//if obj = {name: 'k'}
//key = name, value = k
info_view += '<div class="ui-grid-a"><div class="ui-block-a"><div class="ui-bar field" style="font-weight : bold; text-align: left;">' + key + '</div></div><div class="ui-block-b"><div class="ui-bar value" style="width : 75%">' + info[key] + '</div></div></div>';
}
//add this to html
$(this).find("[data-role=content]").html(info_view);
});
Demo
I've also made a demo where you can read more about this at jsfiddle.net.
Here's the link : http://jsfiddle.net/hungerpain/52Haa/
you can try something like this
Updated
Html Page
<div data-role="page" id="testpage">
<div data-role="content">
<ul data-role="listview" id="listitem" data-divider-theme="a" data-inset="true">
</ul>
</div>
</div>
javascript
$(document).on("pageinit", "#testpage", function(){
$.getJSON("example.json", function(data){
var output = '';
$.each(data, function(index, value){
output += '<li>' +data+ '</li>';
});
$('#listitem').html(output);
});
});

Move Ember object from one list to another with drag-and-drop

I'm trying to drag Ember objects from one list to another. If I drag an item to a new list, the item should be removed from its current list and moved to the new one.
Thanks to Drag&Drop with Ember.js and Ember.js - drag and drop list, I figured out how to copy an item to a different list. However, I am unable to determine from which list a dragged object originated. I have dozens of lists on the page, so I'd rather not do a O(n*k) search for the original object.
Currently, I'm using Ember views and the HTML 5 API. It seems like the Handelbars action helper should achieve my goal more easily. Ember's action supports the drop event, but I can't get it to fire: {{ action foo on="drop" }}. It probably has something to do with the nuanced event propagation defaults of the HTML 5 drag-and-drop implementation.
If you know how to solve this problem using actions instead of views, I'd much prefer that solution.
Here's how I'm currently transferring objects:
// this is heavily inspired by http://jsfiddle.net/ud3323/5uX9H/
// Draggable items
App.ItemView = Ember.View.extend({
templateName: 'item',
attributeBindings: 'draggable',
draggable: 'true',
dragStart: function(event) {
var dataTransfer = event.originalEvent.dataTransfer;
// The view's context is the item to transfer
var item = this.get('context');
// Use HTML 5 API to transfer object as JSON.
// There must be a more elegant way to do this.
dataTransfer.setData('application/json', JSON.stringify(item));
}
});
// Item list drop zone
App.ItemListView = Ember.View.extend({
templateName: 'itemList',
dragEnter: function(event) {
event.preventDefault();
return false;
},
dragOver: function(event) {
event.preventDefault();
return false;
},
drop: function(event) {
event.preventDefault();
// Extract the transferred data
var rawData = event.dataTransfer.getData('application/json');
// Create a new Ember object from the data
var item = App.Todo.create(JSON.parse(rawData));
this.get('controller').send('add', item);
return false;
}
});
Check out JS Bin for the complete code.
Thanks in advance for your help. Very much appreciated.
This is maybe not the full solution to your problem, but it satisfies the need to use the action helper instead of the itemView. Here is your modified jsbin http://jsbin.com/ibufeh/15/edit?html,javascript,live, the drop event fires and is catched at the ApplicationRoute level, from where you can then redirect your function call to the appropriate controller, have a look! it's not working correctly but it solves part of your problem - using an action helper. You need still to figure out from which list the item originated, but this will be easy I guess.
hope it helps

using multiple json request with backbone.js

I have recently started working with backbone.js and i am finally started to get my head around after many tutorials.
One thing i am stuck on is how to use the routing to allow a list to pull different rest request.
Say i have the following in my collection
var NewsCollection = Backbone.Collection.extend({
model : News,
url: 'http://api.example.com/index.php/news/all/format/json',
});
From my understanding correct me if i am wrong backbone stores all the data pulled from the above feed into my model that extends this collection, this will all work i will pull in the feed and then display it in the view
This is where i get confused within my routing i have the following.
var NewsRouter = Backbone.Router.extend({
routes: {
"": "defaultRoute",
"news/:country_code":"updatedRoute"
},
defaultRoute: function () {
console.log("defaultRoute");
var movies = new NewsCollection()
new NewsView({ collection: movies });
movies.fetch();
//setInterval(function(){movies.fetch({success: function(){}});}, 60000);
},
updatedRoute:function (country_code) {
//confused
this.movie = this.movies.get(country_code);
}
})
I need to run the updatedRoute function when that will display a list of news based on cat of country code see below.
http://api.example.com/index.php/news/country/gb/format/json
How do i update the whole feed when a list item is click so the browser url would be.
http://localhost:8888/backbonetut/#news/gb
my list item is.
<li><a href='#news/gb'>GB</a></li>
I can get that in the updateRoute function with
this.movie = this.movies.get(country_code);
Can someone please help
You can either override the fetch function on your collection or temporarily change the url of the collection in your router action.

mvc3 entity framework - convert comma separated list of strings into list<string> in viewmodel, allow users to remove items from list

I am struggling to figure out how to do this with MVC,
I have an entity framework object that has a comma separated list from the db, (can't change the fact that its a horrible csl in the db). I can easily display the list and let them edit it manually. This is rather error prone and would like to split them up and display a list of them in the view. Then allow the user to click a link / button and have them removed from the string and db and the page refreshed to reflect this.
My first thought was to use JQuery to do a ajax json post to do a delete for each item the click an #Html.ActionLink for. I could get it to do the async post back and it would delete the item and would send back a string representing the new string list which I could update the UL with. The second time they clicked a link it would give me a 404, the script I used is:
<script type="text/javascript">
$(document).ready(function () {
$('.viewSeasonsLink').click(function () {
var data =
{
item: $(this).parents('li').first().find('.flagName').text(),
deploymentId: #Model.Id
};
$.post(this.href, data, function (result) {
var list = $("#testme");
list.empty();
var items = result.split(",");
$(items).each(function(index) {
// /* var link = '"' + #Html.ActionLink("Remove", "RemoveItemFromList", "Deployment", null, new { #class = "viewSeasonsLink" }) + '"'; */
var link = '<a class="viewSeasonsLink" href="/SAMSite/Deployment/RemoveItemFromList">Remove</a>';
list.append('<li><span class="flagName">' + items[index] + '</span> - ' + link + ' </li>');
/* list.append('<li><span class="flagName">' + items[index] + '</span> - ' + '\'' + #Html.ActionLink("Remove", "RemoveItemFromList", "Deployment", null, new { #class = "viewSeasonsLink" }) + '\'</li>'); */
});
}, "json");
return false;
});
});
</script>
I could not get the action link to work with the jquery script, so tried hard coding it, still not success.
I then thought I would just try and do a simple actionlink back to a method to remove it and return the normal view, again this posts and will update the db, but will not refresh the webpage at all.
<ul id="testme2">
#foreach (string flag in ViewBag.FeatureFlags)
{
<li><span class="flagName">#flag</span> - #Html.ActionLink("Remove", "RemoveItemFromListTest", "Deployment", null, new { #class = "viewSeasonsLink" })</li>
}
</ul>
public ActionResult RemoveItemFromListTest(string item, int deploymentId)
{
Deployment deployment = db.Deployments.Single(d => d.Id == deploymentId);
ViewBag.CustomerId = new SelectList(db.Customers, "Id", "Name", deployment.CustomerId);
List<string> featureFlags = deployment.FeatureFlags.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
featureFlags.Remove(item);
deployment.FeatureFlags = ConvertBackToCommaList(featureFlags);
ViewBag.FeatureFlags = featureFlags;
//db.SaveChanges();
return View("Edit", deployment);
}
EDIT
released I was being a bit daft at one point:
The second test to get it to do a full post back and do the update was still getting caught by the jquery, (also was not passing in the values). I changed the line to this:
<li><span class="flagName">#flag</span> - #Html.ActionLink("Remove", "RemoveItemFromListTest", "Deployment", new { item = #flag, deploymentId = Model.Id }, null)</li>
which does work, but is a bit naff, it would mean any changes made to the form before the remove link clicked would be lost.
I think I see two issues. One is the initial .Post on the viewSeasonsList click event. You are posting back to the Action that loaded the page, not the Action that will handle the delete. I doesn't seem to me that they would be the same Action base on the approach you described.
var url = '/SAMSite/Deployment/RemoveItemFromList';
then
$.post(url, data, function (result) {
Second, in the Ajax response, when you are rebuilding the list, you are including an href attribute for the links. Why? you are not navigating with those links, you are initiating an Ajax request, which has already been set up.
var link = '<a class="viewSeasonsLink">Remove</a>';
ultimately I had one main problem with the jquery solution. When I added a new LI element it was not being hooked up to the ajax call as this was just happening at document.ready. I now replaced the simple .click with a delegate that will also hook up all elements that are added after the ready event, credit to this page for help with it:
$('#featureflaglist').delegate('.removeflaglink', 'click', RemoveFlagFromList);

stagger loading of ajax responses

I have a page that displays the status of all objects in a given set of database schemas. Schema objects are shown as a hierarchical tree (schema > object type > object name). There are a lot (thousands) of objects. The status is source-control related (is the object modified/deleted/added/unchanged in comparison to the source controlled version?).
I use a bit of ajax to load an icon representing the status of each object with the intention being that the status can be displayed whenever the asynchronous check completes. What actually happens, is that the loading icons freeze and after all statii requests have resolved, all icons display at the same time. This is unintended and gives the page load an undesirable and distinctly synchronous feel. I envisaged the loading icons changing individually to the correct status icon in some sort of staggered order.
How can I modify my javascript to give a better experience (icons transforming from loading gif to status png individually, rather than all at the same time)?
Here's some code:
$(window).load(function () {
#foreach (var schema in Model) {
foreach (var o in schema.Objects) {
#:$.getJSON('#Url.Action("ObjectStatus")', {service:'#schema.Service', schema:'#schema.Name', objectName:'#o.Name', objectType:'#o.Type'}, function (data) {
#:$('##string.Format("{0}-{1}", schema.Name, o.Name)').attr('src', '/Content/images/' + data + '.png');
#:});
}
}
});
...
<ul id="treeview">
#foreach (var schema in Model)
{
<li>#schema.Name<br />
<ul>
#foreach (var t in schema.Objects.Select(x=>x.Type).Distinct())
{
var objectType = t;
<li>#string.Format("{0}{1}", objectType, objectType.EndsWith("x") ? "es" : "s")<br />
<ul>
#foreach (var o in schema.Objects.Where(x => x.Type == objectType))
{
<li>
<img src="#Url.Content("~/Content/images/loading.gif")" alt="checking status..." id="#string.Format("{0}-{1}", schema.Name, o.Name)"/>
#Html.ActionLink(o.Name, "Diff", "Browse", new { service = schema.Service, schema = schema.Name, objectName = o.Name, objectType }, null)
</li>
}
</ul>
</li>
}
</ul>
</li>
}
</ul>
and here's what the finished tree looks like:
You're not going to get a true 'multi threaded' look and feel with javascript; it's simply not available.
What you can do is push all of your requests into an array, and then shift the first element off of the list for your next ajax request.
function getResponse()
{
if(myRequestArray.length == 0)
return;
var elementToRequest = myRequestArray.shift();
$.ajax({
url: '/some/url/',
data: { someData: elementToRequest.someData },
success: getResponse
});
}
Your callback will be the method invoked so you start the next request. This way, your UI can remain 'responsive' while the ajax requests complete. It's going to appear synchronous only so far as the ordering of the elements in the array get updated in order.
May not be best solution out there, but try this..
set
async : false
on your jquery call..