stagger loading of ajax responses - json

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..

Related

Special JSON binding in WinJS ListView

I have problems binding this JSON to my list view.
http://pubapi.cryptsy.com/api.php?method=marketdatav2
No data is displayed.
Data.js
(function () {
"use strict";
var _list;
WinJS.xhr({ url: 'http://pubapi.cryptsy.com/api.php?method=marketdatav2' }).then(
function (response) {
var json = JSON.parse(response.responseText);
_list = new WinJS.Binding.List(json.return.markets);
},
function (error) {
//handle error
}
);
var publicMembers =
{
itemList: _list
};
WinJS.Namespace.define("DataExample", publicMembers);
})();
HTML:
<section aria-label="Main content" role="main">
<div id="listItemTemplate" data-win-control="WinJS.Binding.Template">
<div class="listItem">
<div class="listItemTemplate-Detail">
<h4 data-win-bind="innerText: label"></h4>
</div>
</div>
</div>
<div id="listView" data-win-control="WinJS.UI.ListView" data-win-options="{itemDataSource : DataExample.itemList, itemTemplate: select('#listItemTemplate'), layout: {type: WinJS.UI.GridLayout}}"></div>
</section>
I feel that the API is not that well formed.
Isnt this part a bit odd?
"markets":{"ADT/XPM":{...}...}
There are three things going on in your code here.
First, a ListView must be bound to a WinJS.Binding.List's dataSource property, not the List directly. So in your HTML you can use itemDataSource: DataExample.itemList.dataSource, or you can make your DataExample.itemList dereference the dataSource at that level.
Second, you're also running into the issue that the declarative binding of itemDataSource in data-win-options is happening well before DataExample.itemList is even populated. At the point that the ListView gets instantiated, _list and therefore itemList will be undefined. This causes a problem with trying to dereference .dataSource.
The way around this is to make sure that DataExample.itemList is initialized with at least an empty instance of WinJS.Binding.List on startup. So putting this and the first bit together, we have this:
var _list = new WinJS.Binding.List();
var publicMembers =
{
itemList: _list.dataSource
};
With this, you can later replace _list with a different List instance, and the ListView will refresh itself.
This brings us to the third issue, populating the List with your HTTP response data. The WinJS.Binding.List takes an array in its constructor, not an object. You're passing the parsed JSON object straight from the HTTP request, which won't work.
Now if you have a WinJS.Binding.List instance already in _list as before, then you can just walk the object and add items directly to the List as follows:
var jm = json.return.markets;
for (var i in jm) {
_list.push(jm[i]);
}
Alternately, you could populate a separate array and then create a new List from that. In this case, however, you'll need to assign that new List.dataSource to the ListView in code:
var jm = json.return.markets;
var markets = [];
for (var i in jm) {
markets.push(jm[i]);
}
_list = new WinJS.Binding.List(markets);
var listview = document.getElementById("listView").winControl;
listview.itemDataSource = _list.dataSource;
Both ways will work (I tested them). Although the first solution is simpler and shorter, you'll need to make sure to clear out the List if you make another HTTP request and repopulate from that. With the second solution you just create a new List with each request and hand that to the ListView, which might work better depending on your particular needs.
Note also that in the second solution you can remove the itemDataSource option from the HTML altogether, and also eliminate the DataExample namespace and its variables because you'll assign the data source in code each time. Then you can also keep _list entirely local to the HTTP request.
Hope that helps. If you want to know more about ListView intricacies, see Chapter 7 of my free ebook from MSPress, Programming Windows Store Apps with HTML, CSS, and JavaScript, Second Edition.

Angular load json file dynamically onclick

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');
};
});

Getting Current Data from KendoUI TreeView

I'm using a kendo UI tree with a remote data source from a JSON file.
I have a button on the tree page which gets the current data of the tree,sends it through a POST to a server and the server saves the current data to the JSON file so as the next time I reload the page,the changes I made will be kept.That's what I want to happen.
So I know the current data of the tree is in:
$("#treeview").data("kendoTreeView").dataSource.data()
Which means the data changes real time in there for example when someone drag and drops a node of the tree.
My problem starts when this data doesn't seem to change when I drag and drop nodes inside the tree,and only changes when I drag and drop a node on the root level of the tree and even then it doesn't do it correcly as the node should be moved in there as well but instead the node gets copied,leaving the past node in the tree as well...
For Example I have this tree:
If I make a drag and drop change like this:
And I send the data,save it and reload the change isn't made at all!
PS:Even when I view the current data after the change before sending it,I see that there is no change on the data at all even though I did a change visualy with a drag and drop.So it doesn't have to do with the sending,saving and the server.
On the other hand,if I make a change like this:
I can see in the current data that the moved node is added in the end of the data indeed but it is not deleted from it's initial position within the data!So if i send the current data to the server,save it and then refresh I get the result:
The code for viewing and sending the data is:
function sendData() {
var req = createRequest();
var putUrl = "rest/hello/treeData";
req.open("post", putUrl, true);
req.setRequestHeader("Content-type","application/json");
var dsdata = $("#treeview").data("kendoTreeView").dataSource.data();
alert(JSON.stringify(dsdata));
req.send(JSON.stringify(dsdata));
req.onreadystatechange = function() {
if (req.readyState != 4) {
return;
}
if (req.status != 200) {
alert("Error: " + req.status);
return;
}
alert("Sent Data Status: " + req.responseText);
}
}
Is this a Bug or am I doing something wrong?Has anyone been able to see the current data changing correctly on every drag and drop?
First and most important you have to use the latest version of KendoUI (Kendo UI Beta v2012.3.1024) still in beta but is where they have solved many problems.
Then, when you create the kendoTreeView you have to say something like:
tree = $("#treeview").kendoTreeView({
dataSource :kendo.observableHierarchy(data),
dragAndDrop:true
}).data("kendoTreeView");
Here the important is not using directly data array but wrapping it with kendo.observableHierarchy.
Then you will have the data updated with the result of drag & drops.
For me in addition to OnaBai answer I had to use the sync function on the save method. I am using Type Script.
this.treeData = new kendo.data.HierarchicalDataSource({
data: kendo.observableHierarchy([]),//Thanks OnaBai
schema: {
model: {
id: "MenuItemId",
children: "MenuItemChildren",
hasChildren: (e) => {
//this removes arrow next to items that do not have children.
return e.MenuItemChildren && e.MenuItemChildren.length > 0;
}
}
}
});
public save() {
this.treeData.sync().done(() => {
console.log("sync data");
var myType = this.treeData.view();
this.$http.post("/api/TreeViewPicker", myType)
.then((response) => {
});
});
}

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);

problems displaying my array via JSON object

Hi I am having a problems displaying my array via JSON object. I passed two variables to PHP which returns an array. I then wish to loop through the array and append the result to a div
The PHP works fine as I have tested this before adding the JQuery. When I use google chrome to inspect the console, I dump out data which displays as [] not an object, is this correct?
the contents of the array do not have a key, only a collection of list items with an image path for example
<li><img src="'.$image_path.'"</li>
I encode the array back to the listener,
echo json_encode($result);
code for JQuery
$.post('Look_Controller.php', {look: look, account: account}, function(data) {
$.each(data, function(i, item) {
$('#carousel-ul').empty();
content = item;
$(content).appendTo('#carousel-ul');
});
}, "json");
How do I append each individual result to the div (#carousel-ul)?,
I have also tried
content = item.this;
content = (this).item;
content = $(this).item;
I am not sure if it because the console.log(data) displays [] instead of object?
Hope someone can advise!
Thanks
What happen when you try this ?
$.post('Look_Controller.php', {look: look, account: account}, function(data) {
$('#carousel-ul').empty();
console.log(data);
$.each(data, function(i, item) {
console.log(item);
content = item;
// If "carousel-ul" is a <ul> tag uncomment the next line
// content = $('<li/>').html(content);
$(content).appendTo('#carousel-ul');
});
}, "json");
[] is an empty array. If that's what's being shown you have no data.
So it's probably not coming back from the server. Check the Network tab in the Chrome debugger and see what your response looks like.