I'm using jQWidgets UI framework with Knockout.js and datajs library(for supporting OData) for building client side of my app. And OData Endpoint in ASP.NET Web API2 at the server side. I created ViewModel for jqWidgets Grid as in code below:
var vm = function() {
this.items = ko.observableArray();
var self = this;
//qet data from service
OData.read(url,
function success(data, response) {
//convert data to observable array
self.items(data.results);
},
function error(err) {
alert(err.message);
});
this.removeItem = function() {
// remove item
var element = "#jqxgrid";
var cell = $(element).jqxGrid('getselectedcell');
if (cell != null && cell != "") {
var selectedrowindex = cell.rowindex;
};
var item =$(element).jqxGrid('getrowdata', selectedrowindex);
OData.request({
requestUri: url + '(' + item.CompanyID + ')',
method: "DELETE",
},
function success(data, response) {
alert('DELETE successfull');
},
function error(err) {
alert(err.message);
});
};
As you see I can get and remove items.
My problem is how to save ALL changes and send JUST changed items to server. For add/update entities at server side I have to send POST/PUT request with appropriate json object (not collection of objects). So, for example, if I want to update all changed items, i have to do PUT request for each item.
Is it any way to detect which items in observableArray was added/changed and send each of those items to server??
knockout does not do this out of the box. What I have done in the past is set an isDirty flag in the object stored in the array (this assumes your object is populated with observables, if not this wont work, and there is no easy way to do it using regular js objects). The is dirty flag watches the observable properties for changes and when on is made it sets the flag to true. Then when saves are being made you just see if the record for isDirty() == true
var entryForm = function(){
var self = this;
self.firstName = ko.observable();
self.lastName = ko.observable();
self.email = ko.observable();
self.dirty = ko.observable(false);
self.dirtyCalculations = ko.computed(function(){
//read from any observable we want to watch as part of our "dirty" calculation
self.firstName();
self.lastName();
self.email();
//if any of the above changed, this method will be called, so this model is now "dirty"
self.dirty(true);
});
//see the next section for an explanation of these lines
self.resetDirtyFlag = function(){self.dirty(false);}
self.resetDirtyFlag();
}
I see in your code above that you plug your return object straight into the array without converting the properties to observable. I would suggest converting the properties and using a similar method above.
Related
I have a View which calls the function 'Versions' on click of a href, which normally returns a 'Version.cshtml'.
I modified that Controllerfunction:
If i click on that link and JavaScript is enabled, it starts an ajax call which returns a JSON-result. It modifies the DOM so the page won't reload. Now my problem is to differentiate when JavaScript is enabled or not - If enabled, it should return the JSON result; if disabled it should return the 'Version.cshtml'. When I inspect the element, we have already a class called 'nojs-noborder' when JS is disabled. But how can i get that value through the controller without JS so I can fill the bool 'isJavaScriptenabled'?
Controller function looks like this:
if (isJavaScriptEnabled)
{
var tocstr = ControllerContext.RenderView(PartialView("Versiontoc", model));
var middlestr = ControllerContext.RenderView(PartialView("Versionmiddle", model));
var result = new JsonResult
{
Data = new { tocstr, middlestr },
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
MaxJsonLength = MaxValue
};
return result;
}
return View("Version", model);
The class in the inspected element:
<div class="nojs-noborder" id="contentwrapper">
It's unclear from your answer whether you have done this but you need to update your view to make an AJAX request or follow the anchor tag depending on whether JavaScript is enabled or not.
Then, in your controller, you don't need to determine if JavaScript is enabled in the browser, but rather whether the request is an AJAX request. If it is, return JSON else return the view.
Most JavaScript libraries will append the X-Requested-With header with a value of XMLHttpRequest. You can check for the existence of this header.
I'm unsure what version of MVC you are using but you could use the built in Request.IsAjaxRequest() to determine this.
Your ActionMethod then becomes:
if (Request.IsAjaxRequest())
{
var tocstr = ControllerContext.RenderView(PartialView("Versiontoc", model));
var middlestr = ControllerContext.RenderView(PartialView("Versionmiddle", model));
var result = new JsonResult
{
Data = new { tocstr, middlestr },
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
MaxJsonLength = MaxValue
};
return result;
}
return View("Version", model);
If this method isn't available you could roll your own based on this answer
I'm attempting to implement an asynchronous computed observable as show here.
I can do it successfully for one ajax call. The challenge I have at the moment is how to perform various ajax calls in a loop building an array asynchronously and then returning the array to my computed observable array using jQuery promises.
Basically the HTML form works in the following way:
This a student course form.
For each row, users type the person number and on another column they'll type a list of course ids separated by commas. Eg 100, 200, 300.
The purpose of the computed observable is to store an array
containing course details for the courses entered in step 2.
The details are obtained by firing ajax calls for each course and storing HTTP response in the array.
I don't want users to wait for the result, thus the reason to implement an async computed observable.
My problem: I'm having problem returning the value of the final array to the observable. It's always undefined. The ajax calls work fine but perhaps I'm still not handling the promises correctly.
Here's the code for my class:
function asyncComputed(evaluator, owner) {
var result = ko.observable(), currentDeferred;
result.inProgress = ko.observable(false); // Track whether we're waiting for a result
ko.computed(function () {
// Abort any in-flight evaluation to ensure we only notify with the latest value
if (currentDeferred) { currentDeferred.reject(); }
var evaluatorResult = evaluator.call(owner);
// Cope with both asynchronous and synchronous values
if (evaluatorResult && (typeof evaluatorResult.done == "function")) { // Async
result.inProgress(true);
currentDeferred = $.Deferred().done(function (data) {
result.inProgress(false);
result(data);
});
evaluatorResult.done(currentDeferred.resolve);
} else // Sync
result(evaluatorResult);
});
return result;
}
function personDetails(id, personNumber, courseIds) {
var self = this;
self.id = ko.observable(id);
self.personNumber = ko.observable(personNumber);
self.courseIds = ko.observable(courseIds);
// Computed property to extract PIC details for additional PICs.
// This is computed observable which returns response asynchronously
self.courseDetails = asyncComputed(function () {
var courseIdsArray = self.courseIds().split(",");
var arr = [];
var arr_promises = [];
function getCourseDetails(courseId) {
var dfrd = $.Deferred();
var content = {};
content.searchString = courseId;
var url = 'MyURL';
return $.ajax(url, {
type: 'POST',
dataType: 'json',
data: requestData, // content of requestData is irrelevant. The ajax call works fine.
processdata: true,
cache: false,
async: true,
contentType: "application/json"
}).done(function (data) {
arr.push(new PicDetails(data.GenericIdentifierSearchResult[0]));
}).fail(function () {
alert("Could not retrieve PIC details");
}).then(function () {
dfrd.resolve();
});
}
if (courseIdsArray.length > 0) {
$.each(courseIdsArray, function (index, courseId) {
if (courseId.length > 0) {
arr_promises.push(getCourseDetails(courseId));
}
});
};
$.when.apply($, arr_promises).done(function () {
return arr;
})
}, this);
}
I think you dont really need a separate api/code for this.
You could just create observables for every input/value that changes on your site, and create a computed observable based on those.
e.g in rough pseudo code
self.id = ko.observable(id);
self.personNumber = ko.observable(personNumber);
self.courseIds = ko.observable(courseIds);
self.courseDetailsArray = ko.observableArray([]);
self.courseDetails = ko.computed(function() {
//computed the course details based on other observables
//whenever user types in more course ids, start loading them
$.get( yoururl, {self.courseIds and self.id}).success(data) {
when finished async loading, parse the data and push the new course details into final array
self.courseDetailsArray.push( your loaded and parsed data );
//since courseDetailsArray is observableArray, you can have further computed observables using and re-formatting it.
}
});
I have something a bit different from your approach, but you can build something like an asyncComputed out of it if you prefer:
make a simple observable that will hold the result
make a dictionary of promises that you'll basically keep in sync with the array of course ids
when the array of course ids change, add / remove from the dictionary of promises
wrap all your promises in a when (like you're doing) and set the result when they're all done
Basic idea:
var results = ko.observable([]);
var loadingPromises = {};
var watcher = ko.computed(function () {
var ids = ko.unwrap(listOfIds);
if (ids && ids.length) {
ids.forEach(function (id) {
if (!loadingPromises.hasOwnProperty(id)) {
loadingPromises[id] = $.get(url, {...id...});
}
});
var stillApplicablePromises = {};
var promises = []; // we could delete from loadingPromises but v8 optimizes delete poorly
Object.getOwnPropertyNames(loadingPromises).forEach(function (id) {
if (ids.indexOf(id) >= 0) {
stillApplicablePromises[id] = loadingPromises[id];
promises.push(loadingPromises[id]);
}
});
loadingPromises = stillApplicablePromises;
$.when.apply(this, promises).then(function () {
// process arguments here however you like, they're the responses to your promises
results(arguments);
});
} else {
loadingPromises = {};
results([]);
}
}, this);
This is the file (that may change) where you can see this "in real life": https://github.com/wikimedia/analytics-dashiki/blob/master/src/components/wikimetrics-visualizer/wikimetrics-visualizer.js
And the basic fiddle: http://jsfiddle.net/xtsekb20/1/
I am trying to set up a variant fetch method on my backbone model that will fetch the current model for a given user. This is available from the API on /api/mealplans/owner/{username}/current.
I have written the following model. I commented out the URL Root, as the prototype fetch call was simply using the urlRoot and I wanted to see if that was overriding the url parameter I passed in portions somehow.
var mealPlan = Backbone.Model.extend({
name: 'Meal Plan',
//urlRoot: '/api/mealplans',
defaults: {},
fetchCurrent: function (username, attributes, options) {
attributes = attributes || {};
options = options || {};
if (options.url === undefined) {
options.url = "/api/mealplans/owner/" + username + "/current";
}
return Backbone.Model.prototype.fetch.call(this, attributes, options);
},
validate: function (attributes) {
// To be done
return null;
}
});
I've seen this done, in some variations in other places, such as at backbone.js use different urls for model save and fetch - In that case the code is slightly different (I started with that and broke it down to make it easier for me to read.)
The options object has the url parameter in it fine when I pass it to fetch, but then it seems to ignore it!
I was assuming the same parameters to fetch as to save - This is not the case.
The method signature for fetch ONLY takes 'options' and not 'attributes', hence the url parameter wasn't found.
The model code should look a bit more like this..
var mealPlan = Ministry.Model.extend({
name: 'Meal Plan',
urlRoot: '/api/mealplans',
defaults: {
},
fetchCurrent: function (username, options) {
options = options || {};
if (options.url === undefined) {
options.url = this.urlRoot + "/owner/" + username + "/current";
}
return Backbone.Model.prototype.fetch.call(this, options);
},
validate: function (attributes) {
// To be done
return null;
}
});
I think it is better to override url() method, like below:
var mealPlan = Ministry.Model.extend({
name: 'Meal Plan',
urlRoot: '/api/mealplans',
//--> this gets called each time fetch() builds its url
url: function () {
//call the parent url()
var url=Backbone.Model.prototype.url.call(this);
//here you can transform the url the way you need
url += "?code=xxxx";
return url;
}
...
besides, in your example above I think there is a mistake and you should replace fetchCurrent by fetch
I really, really don't get it anymore. I'm trying to develop an interface using Knockout, but trying to 'toJSON' it gives me headaches.
The problem is as follows:
I have a FormViewModel. This form contains logic and meta data (observables) about the form itself.
I have FormSection, which only has 2 observables.
I have Deletion, also just 2 observables.
I wanna AJAX that to a PHP file receiving the JSON object, but I've got no clue how to proceed from here. I just want a number of observables, not all of them. I also wanna be able to load an initial state FROM an Ajax call (with JSON).
Here are the relevant parts of my code:
// This only has some observables
var FormSection = function(number)
{
var self = this;
this.title = ko.observable('Section ' + number);
this.selectedPageStyle = ko.observable();
};
// Only observable / computed
var Deletion = function(page)
{
var self = this;
// reference to a just deleted page, to make 'undeletion' possible
this.page = page;
this.pageTitle = ko.computed(function(){ return self.page.title() });
};
So far so good, but I've got my ViewModel with some observables, some functionality, etc. This is what I've got so far: (I removed the body of functions for readability)
var FormViewModel = function(data)
{
var self = this;
this.pages = ko.observableArray([]);
this.deletions = ko.observableArray([]);
this.name = ko.observable();
this.description = ko.observable();
this.availablePageStyles = ko.observableArray(['Header', 'Fields', 'Signatures']);
this.hasPages = ko.computed(function(){}, this);
this.numberOfPages = ko.computed(function(){}, this);
self.selectedPage = ko.observable( );
self.isPageSelected = function(page) {};
self.clearPage = function(data, event) {};
this.save = function() {
$.ajax("x", {
// WHAT TO DO?!
data: { data: ko.toJSON(this)},
type: "post",
//success: function(result) { alert(result) },
error : function(jqXHR, textStatus, errorThrown) { alert(textStatus + errorThrown)}
});
};
$.getJSON("x", function(allData) {
// How to populate back?!
});
this.addPage = function(data, event){}
this.removePage = function(page){}
this.unremovePage = function(deletion){}
};
I've got no idea how to proceed if I wanna save the relevant observables. For example: I don't need self.selectedPage. I use that for sorting only.
Do I use mapping here?
How to map the relevant observables?
How do I put that in JSON?
How do I 'map back' from the JSON I receive from the server?
To control what is converted to JSON when using ko.toJSON you can overload the function by doing
viewModel.prototype.toJSON = function(){
var copy = this;
delete copy.//whatever variables you don't want to return to server here
//any other variables you want to not return
return copy;
}
I put together a sample fiddle http://jsfiddle.net/Rynan/hA4Kz/.
For more information and samples see http://www.knockmeout.net/2011/04/controlling-how-object-is-converted-to.html
I'm trying to understand how a portion of backbone.js works. I have to fetch a collection of models once the app begins. I need to wait until fetch is complete to render each view.
I'm not 100% sure the best approach to take in this instance.
var AppRouter = Backbone.Router.extend({
routes: {
"": "home",
"customer/:id": "customer"
},
home: function () {
console.log("Home");
},
customer: function (id) {
if (this.custromers == null)
this.init();
var customer = this.customers.at(2); //This is undefined until fetch is complete. Log always says undefined.
console.log(customer);
},
init: function () {
console.log("init");
this.customers = new CustomerCollection();
this.customers.fetch({
success: function () {
console.log("success");
// I need to be able to render view on success.
}
});
console.log(this.customers);
}
});
The method I use is the jQuery complete callback like this:
var self = this;
this.model.fetch().done(function(){
self.render();
});
This was recommended in a Backbone bug report. Although the bug report recommends using complete, that callback method has since been deprecated in favor of done.
You can also do this with jquery 1.5+
$.when(something1.fetch(), something2.fetch()...all your fetches).then(function() {
initialize your views here
});
You can send your own options.success to the collections fetch method which runs only when the fetch is complete
EDIT (super late!)
From the backbone source (starting line 624 in 0.9.1)
fetch: function(options) {
options = options ? _.clone(options) : {};
if (options.parse === undefined) options.parse = true;
var collection = this;
var success = options.success;
options.success = function(resp, status, xhr) {
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
if (success) success(collection, resp);
};
Note the second to last line. If you've passed in a function in the options object as the success key it will call it after the collection has been parsed into models and added to the collection.
So, if you do:
this.collection.fetch({success: this.do_something});
(assuming the initialize method is binding this.do_something to this...), it will call that method AFTER the whole shebang, allowing you trigger actions to occur immediately following fetch/parse/attach
Another useful way might be to bootstrap in the data directly on page load. This if from the
FAQ:
Loading Bootstrapped Models
When your app first loads, it's common to have a set of initial models that you know you're going to need, in order to render the page. Instead of firing an extra AJAX request to fetch them, a nicer pattern is to have their data already bootstrapped into the page. You can then use reset to populate your collections with the initial data. At DocumentCloud, in the ERB template for the workspace, we do something along these lines:
<script>
var Accounts = new Backbone.Collection;
Accounts.reset(<%= #accounts.to_json %>);
var Projects = new Backbone.Collection;
Projects.reset(<%= #projects.to_json(:collaborators => true) %>);
</script>
Another option is to add the following inside of your collections initialize method:
this.listenTo(this.collection, 'change add remove update', this.render);
This will fire off the render method whenever the fetch is complete and/or the collection is updated programmatically.
You Can Use on and Off Methods
if you want to add trigger method like suppose if you want on success you want to call render method so please follow below example.
_this.companyList.on("reset", _this.render, _this);
_this.companyList.fetchCompanyList({firstIndex: 1, maxResult: 10}, _this.options);
in Model js please use like
fetchCompanyList: function(data, options) {
UIUtils.showWait();
var collection = this;
var condition = "firstIndex=" + data.firstIndex + "&maxResult=" + data.maxResult;
if (notBlank(options)) {
if (notBlank(options.status)) {
condition += "&status=" + options.status;
}
}
$.ajax({
url: "webservices/company/list?" + condition,
type: 'GET',
dataType: 'json',
success: function(objModel, response) {
UIUtils.hideWait();
collection.reset(objModel);
if (notBlank(options) && notBlank(options.triggerEvent)) {
_this.trigger(options.triggerEvent, _this);
}
}
});
}