How to Lazy load mvc4 webgrid rows using knockout.js ? - asp.net-mvc-4

I have a web grid with large number of rows and I am not interested in paging the grid. I need to view only a particular number of rows initially. Then while scrolling I need to view rows one after the other. I have read it somewhere that it is possible with knockout.js. Do anyone have a sample code to share? Am working with MVC 4 Razor.

I implemented similar control. It was tree view with button "Load more".
A lot of items in observable array might slow down your app, especially when you are showing them, because it's a lot of DOM operations.
So, all my data pushes into ordinary array on page load. For data to be shown I have KO observable array. I am pushing more data into an observable array when I need to show it.
Here is basic example:
JavaScript:
$(function () {
$.get("URL TO GET DATA FROM", function (data) {
// data = [{name: "Andrei"}, {name: "James"}, {name: "Bill"}]
var page = new PageModel(data);
ko.applyBindings(page);
});
});
function PageModel(data) {
self = this;
//Show items from this array on the page
self.itemsToShow = ko.computed(function () {
return self.allItems.slice(0, self.numberToShow);
});
self.numberToShow = 10;
self.allItems = data;
self.showMore = function () {
self.numberToShow += 10;
}
}
HTML Template:
<div data-bind="foreach: itemsToShow">
<span data-bind="text: name"></span>
</div>
If you need to show more items, you can just call page.showMore();

Related

For each results in v-for loop how can I nest another v-for loop using a parameter from the results of the first loop

Using a v-for loop in Vue js. I am looping through the readingTasks data object which correctly produces two results from the data below.
readingTasks:Array[2]
0:Object
enabled:true
newunit:-1
task:"The part 3 guide"
unit:-1
unit_task_id:27
url:"#"
1:Object
enabled:true
newunit:-1
task:"The part 3 training units"
unit:-1
unit_task_id:28
url:"#"
The bit I am unsure about is how for each result, how do I run another Axios database call that shows if the reading Task is complete or not. For example for the first record, the complete status should be true (unit_task_id:27) and the second record should be false.
userTasks:Array[1]
0:Object
complete:true
enabled:true
newunit:-1
task:"The part 3 guide"
unit:-1
unit_task_id:27
unit_task_user_id:21
<ul>
<li v-for="task in readingTasks">
{{task.task}}
//trying to call a function that does an Axios call passing in parameters from readingTasks
{{getUserTaskByUnit(task.unit, task.unit_task_id)}}
<template v-for="usertask in userTasks">
{{usertask.complete}}
</template>
</li>
</ul>
//javascript if its useful
data: {
readingTasks: [],
userTasks: []
},
mounted() {
this.lastUnit();
},
methods: {
//functons
lastUnit: function() {
this.tasks();
},
tasks: function() {
var self = this;
var unit = this.unit;
axios.get("/WebService/units.asmx/GetTasks?unit=" + unit).then(function(response) {
self.readingTasks = response.data;
})
.catch(function(error) {
console.log(error);
})
.then(function() {
});
},
getUserTaskByUnit: function(unit, unitTaskId) {
var self = this;
axios.get("/WebService/units.asmx/GetUserTasks?unit=" + unit + "&unitTaskId=" + unitTaskId).then(function(response) {
self.userTasks = response.data;
})
.catch(function(error) {
console.log(error);
})
.then(function() {});
}
This code seems close to doing the correct thing, however {{usertask.complete}} flickers between true and false for both sets of results. Like it is stuck in a loop.
I would expect the first result to show True here and the second result to show False.
The part 3 guide - true
The part 3 training units - false
There are a few problems here.
The template has a dependency on userTasks, so every time userTasks changes it will cause the component to re-render, running the template again.
Every time the template runs it calls getUserTaskByUnit for both tasks. That will, asynchronously, update userTasks. When userTasks is updated it will trigger a re-render, which will call getUserTaskByUnit again, going round and round in an infinite loop.
Worse than just being an infinite loop, each time it renders it will trigger two requests, each of which will trigger another re-rendering. The number of requests will balloon exponentially.
When those requests do return you're then storing them in userTasks. But both responses are being stored in exactly the same place, so you'll only ever see the results of one request in the UI.
The first thing you'll need is a better data structure for storing the responses in getUserTaskByUnit. The simplest place to store them would be on the tasks in readingTask. That might look something like this:
// Note the whole task is now being passed to getUserTaskByUnit
getUserTaskByUnit: function(task) {
var self = this;
axios.get("/WebService/units.asmx/GetUserTasks?unit=" + task.unit + "&unitTaskId=" + task.unit_task_id).then(function(response) {
task.userTasks = response.data;
})
...
}
The call to getUserTaskByUnit needs moving out of the template. Moving it into the tasks method seems as good a place as any. There are also a few changes required to get it to work with the new version of getUserTaskByUnit:
tasks: function() {
var self = this;
var unit = this.unit;
axios.get("/WebService/units.asmx/GetTasks?unit=" + unit).then(function(response) {
var readingTasks = response.data;
// Pre-populate userTasks so it will be reactive
readingTasks.forEach(function(task) {
task.userTasks = [];
});
// This must come after userTasks is pre-populated
self.readingTasks = readingTasks;
readingTasks.forEach(function(task) {
// Passing task to getUserTaskByUnit, not unit and unit_task_id
self.getUserTaskByUnit(task);
});
})
...
Then within the template we'd need to loop over task.userTasks:
<ul>
<li v-for="task in readingTasks">
{{task.task}}
<template v-for="usertask in task.userTasks">
{{usertask.complete}}
</template>
</li>
</ul>
There are alternative data structures we could use depending on what other requirements you have. For example, you could retain a separate userTasks object to hold the userTasks but for that to work it would need to be a nested data structure rather than just an array. You'd need to key it by unit and then unitTaskId. The result in the template would be something like this:
<ul>
<li v-for="task in readingTasks">
{{task.task}}
<template v-for="usertask in userTasks[task.unit][task.unit_task_id]">
{{usertask.complete}}
</template>
</li>
</ul>
Much like with the earlier solution you would need to pre-populate the userTasks with empty values when readingTasks first loads to ensure the values are reactive and also to avoid the template blowing up at the undefined entries. Alternatively you could use $set and suitable v-if checks respectively.
This is all quite fiddly. It may be that you can simplify it a little based on your knowledge of the system. For example, it may be possible to form compound string keys for userTasks rather than using two levels of nesting. Or it might be that unit is a prop that can be considered constant and doesn't need including in that data structure.
Your userTasks is a view property and gets overwritten upon every call to getUserTaskByUnit (i.e. for each item in readingTasks). What you instead want is a nested structure. You should call getUserTaskByUnit in a loop as soon as readingTasks got loaded, i.e. after the line self.readingTasks = response.data;, and store the response as a property for every readingTask object.

vue-router view caching / not resetting data

Im working on a car sales website and using vue-router.
I have an index page with a list of all cars for sale, and then when clicked they link to the single view of that specific vehicle.
I have a large 'header' image on the single view page and have it inside a container with a fixed height so that when the page loads there is not jumping in page height.
When going to this single view, I do an API call to get the vehicle data and then wish to fade in the heading image.
To do this:
<div class="singleVehicle__mainImage">
<span :style="styles" :class="{'imageLoaded' : mainImageLoaded }" v-if="vehicle"></span>
</div>
export default {
data() {
return {
vehicle: null,
styles: {
backgroundImage: null
},
mainImageLoaded: null
}
},
created() {
this.getVehicle().then(() => {
this.mainImageBackground();
});
},
methods: {
mainImageBackground() {
var source = "IMAGE SOURCE URL";
this.styles.backgroundImage = "url("+source+")";
var $this = this;
var img = new Image();
img.onload = function() {
$this.mainImageLoaded = true;
}
img.src = source;
if (img.complete) img.onload();
}
}
}
By default, the span inside the image wrapper has a 0 opacity and then that is transitioned to 1 when the .imageLoaded class is added.
This works fine, but only the first time each vehicle loaded. The image waits till it loads and then fades in. Everyother time afterwards the image simply pops in when it loads almost like the imageLoaded class is not being reset / the data is not being reset when leaving the view.
When clearing browser cache, it works again but once for each vehicle view.
This is probably due to your v-if="vehicle". The vehicle call is possibly taking longer and so the span is not showing until after the class is added or some timing issue related to that.

Rendering results from an API after using a search function with Backbone.js

I am new to Backbone.js and I am trying to create an application that can check if you completed the videos games you control.
I am using an API to retrieve any information about videogames.
I want to be able to search for a game, for example "Zelda". It should then list every Zelda game.
I get stuck because I don't know how to get the search function to work properly with the API and I don't know how to render it properly. I have written a template for the games that should render.
I have no clue what to do know, or if I'm even on the right track. I am not asking for someone to code it completely, I am asking for a step in the right direction.
Let me know if you need more code.
library_view.js
var LibraryView = Backbone.View.extend({
el:$("#games"),
url: url = "http://www.giantbomb.com/api/search/?api_key=[KEY]",
events:{
"keypress input":"findGames"
},
findGames:function(e){
if(e.which == 13){
query = $(".searchfield").val()
field_list = "name,platforms"
resources = "game"
url = url +"&query="+ query +"field_list"+ field_list +"resources"+ resources
}
},
index.html
<input type="search" placeholder="Find a game" class="searchfield">
It looks like you are mashing together a View and a Model.
A view, for instance, shouldn't have URL inside it, it doesn't know what to do with it.
The correct path would be something roughly like so:
var SearchModel = Backbone.Model.extend();
var LibraryView = Backbone.View.extend({
el: $("#games"),
events:{
"keypress input":"findGames"
},
findGames: function(e){
// get query, field_list, resources
var searchModel = new SearchModel()
searchModel.fetch({
url: "http://www.giantbomb.com/api/search/?api_key=[KEY]"+"&query="+ query +"field_list"+ field_list +"resources"+ resources
});
// do something with searchModel
}
});
After the fetch, searchModel will hold the data Backbone Model style.
Let's say the returned value from the AJAX call is:
{
"answer": 42
}
Then:
searchModel.get("answer") // = 42
The SearchModel is just an abstraction here as you don't really need it (you can just ajax it). But I put it to help you understand what Model represents, it basically represents only data... It doesn't know what View is.

Backbone: Pattern for rendering and re-rendering subscriber views in a correct order

So basically I've been toying with this pattern of pubsub with subviews re-rendering when called by parent 'controller' view (sorry if that's confusing). If the subviews are based on a fetch of a collection or model (not shown below), sometimes they aren't rendered in the correct order I need them too, ie if SubView1 is a small nav for Subview2, I don't want it below SubView2.
I figure there has to be a pattern for such a common problem. Lemme know if this doesn't make sense and I will clarify. But the basic situation I'm dealing with is below.
markup:
<div id="main-container">
<div class="inner-container"></div>
</div>
js:
var ToggleNavView = Backbone.View.extend({
//let's say this template has two links
template: Handbars.compile(linkstmpl),
el: $('#main-container'),
events: {
"click a": "toggleViews"
}
initialize: function(){
_.bindAll(this, 'render', 'toggleViews');
// whoa, nice looking event aggregator http://bit.ly/p3nTe6
this.vent = _.extend({}, Backbone.Events);
this.render();
},
render: function(){
$(this.el).append(this.tmpl);
// suppose subviews below are declared as modules above with, say, requirejs
var sub1 = new SubView1({ vent: this.vent }),
sub2 = new SubView2({ vent: this.vent });
},
toggleViews: function(e){
e.preventDefault();
// get name of section or view you're toggling to
var section = $(e.currentTarget).data('section');
// publish events to subscribers
this.vent.trigger('toggleInboxViews', this, section);
},
});
var SubView1 = Backbone.View.Extend({
template: Handlebars.compile(tmpl1),
initialize: function(ops){
_.bindAll(this, 'render', 'removeOrRerender');
this.vent = ops.vent || null;
this.render()
},
render: function(){
$(this.el).append(this.template()).appendTo($(".inner-container"))
},
removeOrRerender: function(obj, section){
if( section == 'my-section'){
this.render();
} else if( section == 'other-section' ) {
$(this.el).fadeOut();
}
},
})
// another subview with same functionality etc...
// init view
new ToggleNavView();
If you need your sub-views to show up in specific places then the parent view should define that structure and the sub-views should be told where to render themselves. Then it won't matter what order things are draw in as the overall structure doesn't change.
For example, if you want one sub-view to appear at the top and the other below it, the main view should look like this:
<div id="main-view">
<div id="sub1"></div>
<div id="sub2"></div>
</div>
then the main view would render the sub-views with something like this:
var sub1 = new SubView1({ el: this.$el.find('#sub1'), vent: this.vent }),
var sub2 = new SubView2({ el: this.$el.find('#sub2'), vent: this.vent });
By specifying the el for the sub-views, their location on the page is no longer their problem and they won't shift positions if they're rendered in a different order. A happy side effect of this structure is that the sub-views are only concerned with themselves and are nicely self-contained; the parent view just needs to put the pieces in the right place by structuring its template properly and everything just works.
Here's a simple demo that might clarify the structure: http://jsfiddle.net/ambiguous/9u3S5/

Simple store connected list for dojo

Is there a simpler list type than DataGrid that can be connected to a store for Dojo?
I would like the data abstraction of the store, but I don't need the header and cell stucture. I would like to be more flexible in the representation of the datalines, where maybe each line calls an function to get laid out...
You ask a really good question. I actually have a blog post that is still in draft form called "The DataGrid should not be your first option".
I have done a couple thing using the store to display data from a store in a repeated form.
I have manually built an html table using dom-construct and for each.
var table = dojo.create('table', {}, parentNode);
var tbody = dojo.create('tbody', {}, table); // a version of IE needs this or it won't render the table
store.fetch({ // this is a dojo.data.ItemFileReadStore, but you cana dapt to the dojo.Store API
query: {},
onComplete: function(itms) {
dojo.forEach(itms, function(itm, idx) {
var tr = dojo.create('tr', {}, tbody);
// use idx to set odd/even css class
// create tds and the data that goes in them
});
}
});
I have also created a repeater, where I have an html template in a string form and use that to instantiate html for each row.
var htmlTemplate = '<div>${name}</div>'; // assumes name is in the data item
store.fetch({ // this is a dojo.data.ItemFileReadStore, but you cana dapt to the dojo.Store API
query: {},
onComplete: function(itms) {
dojo.forEach(itms, function(itm, idx) {
var expandedHtml = dojo.replace(htmlTemplate, itm);
// use dojo.place to put the html where you want it
});
}
});
You could also have a widget that you instantiate for each item.