Using durandal with jaydata and kendoui - kendo-ui

I created a new VS2012 project using the hottowel template, which in return uses durandal, knockout and breeze.
I want to use jaydata instead of breeze and for the ui layer I want to use the excellent asKendoDataSource() functionality to power a kendoui grid.
I followed all the instructions to make kendoui work well with durandal. This is fine.
I have a model in which I get the jaydata entities and run asKendoDataSource() on it. My view is an MVVM declared kendoui grid with its source property set to the viewmodel's property that contains a reference to the asKendoDataSource().
In the knockout world the viewModel property would be an empty entities = ko.observableArray() which would then would be initialized by using entities(values) when the data service returns.
I need to mimic this such that I have a viewModel property that is an empty kendo datasource which then is initialized by the asKendoDataSource() conversion when the data comes back from jaydata. This way the mvvm kendo grid is bound initially to the empty datasource and then receives its items when the asKendoDataSource() is called.
This is all because the model - viewModel binding in durandal is asynchronous and there needs to be a placeholder property in the viewModel from the very beginning, which then, after the viewModel's activate() method promise is resolved, gets updated with the bound data and in return powers the DOM that the viewModel is bound to.
I can't figure out how to mimic the knockoutjs practice of an empty observable array which is bound to the dom and then (the same exact reference) gets initialized and populates the dom. There seems to be no way to create an empty kendo datasource which then is re-initialized by the asKendoDataSource() method. Reassigning the viewModel property to the new data source doesn't work because the kendo grid is bound to the original reference.
This is my airport view:
<section>
<h2 class="page-title" data-bind="text: title"></h2>
<div id="airportGrid" data-kendo-role="grid" data-kendo-sortable="true" data-kendo-pageable="true" data-kendo-page-size="25" data-kendo-editable="true" data-kendo-columns='["Id", "Abbrev", "Name"]' data-kendo-bind="source: airports"></div>
</section>
This is my datacontext:
define([
'durandal/system',
'services/logger'],
function (system, logger) {
var getAirports = function (airportsObservable) {
$data.Entity.extend("Airport", {
Id: { type: "int", key: true, computed: true },
Abbrev: { type: String, required: true, maxLength: 200 },
Name: { type: String, required: true, maxLength: 512 }
});
$data.EntityContext.extend("JumpSeatDatabase", {
Airports: { type: $data.EntitySet, elementType: Airport }
});
var airportDB = new AirportDatabase('http://localhost:2663/odata');
var deferred = Q.defer();
airportDB.onReady(function () {
deferred.resolve(airportDB.Airports);
});
return deferred.promise;
}
var datacontext = {
getAirports: getAirports
};
return datacontext;
function log(msg, data, showToast) {
logger.log(msg, data, system.getModuleId(datacontext), showToast);
}
function logError(msg, error) {
logger.logError(msg, error, system.getModuleId(datacontext), true);
}
//#endregion
});
This is my airport viewmodel:
define(['services/datacontext', 'durandal/plugins/router'],
function (datacontext, router) {
var airports = null;// = kendo.data.ObservableArray([]);
var activate = function () {
var airportRes = datacontext.getAirports();
return airportRes.then(function (airp) {
vm.airports = airp.asKendoDataSource();
});
};
var deactivate = function() {
//airports([]);
};
var viewAttached = function (view) {
//using this type of reach in to the viewModel is considered bad practice in durandal
//$('#airportGrid').kendoGrid({
// dataSource: airports.asKendoDataSource({ pageSize: 10 }),
// filterable: true,
// sortable: true,
// pageable: true,
// height: 500,
// columns: ['Id', 'Abbrev', 'Name']
//});
//kendo.init($("#container"));
kendo.bind(view, vm);//this should eventually go away the recommended ViewModelBinder code in main.js is not firing for me
};
var vm = {
activate: activate,
deactivate: deactivate,
airports: airports,
title: 'Airports',
viewAttached: viewAttached
};
return vm;
});
One last issue that I am seeing:
It seems to me that an MVVM declared kendoui grid, bound to the view model through data-kendo-bind={source: airports)" where airports is a property of the viewmodel that was created through entities.asKendoDataSource() does not work. Somehow the grid does not show the data. Is there something extra that needs to be done?
Thanks

My best guess is that this is a timing issue, where the Grid is binding to vm.airports while it is still null, then vm.airports = airp.asKendoDataSource(); is called after it is already bound? Perhaps try something like:
return airportRes.then(function (airp) {
vm.airports.data(airp.asKendoDataSource().data());
});

Related

Kendo listview - clicking elements that have been moved between listviews yields a different sender object structure

I'm seeing an issue where if I add new options to my listbox, the event.sender no longer has the same object structure when I click those newly moved options in their new listview.
I use an ajax event to bind data to the Kendo listview (this is in a method that gets triggered on document ready):
var myListBoxId = 'myListBoxHtmlId';
$.post('myUrl/myController/controllerMethod', dataToPost)
.done(function (response, status, jqxhr) {
$('#' + myListBoxId').kendoListBox({
dataSource: response.myListProperty,
connectWith: theOtherListBoxId,
dropSources: [theOtherListBoxId],
toolbar: {
position: "right",
tools: ["transferAllFrom", "transferAllTo",
"transferFrom", "transferTo"]
},
change: function (event) {
myJavascriptMethod(event);
},
dataTextField: 'propertyNameInObjectInMyPropertyList',
dataValueField: 'anotherPropertyNameInObjectInMyPropertyList'
});
You can see that it binds the 'myJavascriptMethod(event)' as the change event handler.
Here is how I'm accessing the event data in myJavascriptMethod(event):
myJavascriptMethod(event){
var selectedText = event.sender._target[0].innerHTML;
}
The problem is that if I modify the options (I'm using the 'transferFrom' and 'transferTo' to transfer options between two kendo listviews), the event.sender._target is null. I'm having difficulty figuring out what I should key onto that would work in all cases.
In addition to the example code above, I found this, which has more docs on listviews for .net-core:
https://github.com/telerik/kendo-ui-core/blob/master/docs/api/javascript/ui/listbox.md
When changing the return object in the C# List I was binding the datasource to in the response to the AJAX method, I also noticed that it doesn't really matter what type it is, as long as the property names match the listview's dataTextField and dataValueField.
The solution to properly getting the selected item from the listview that would work with both the originally bound options and options that have been moved between listviews was this (no changes required to the listview as shown in the question):
//reformatted this as Javascript
//for typescript it was of this format:
//static myTypeScriptEvent(event){
function myJavascriptEvent(event){
//This line was the fix / is key to the question:
var dataItem = event.sender.dataItem(event.sender.select());
}
Here's a minimum example of the AJAX method that binds the listview. Include a call to this method in the document.ready function (thingId is the id of some object that will have sub objects in a list which will then be bound to the listview). In my case, I'm using typescript, you may have to convert some of it to basic javascript as your needs require (it's pretty close, but it may need some slight changes - as indicated by the '$' characters, you'll also need to include jquery for this):
function bindListView( id: thingId ) {
var dataToPost = {
id: id
};
$.post('myUrl/myController/controllerMethod', dataToPost)
.done(function (response, status, jqxhr) {
$('#' + myListBoxId').kendoListBox({
dataSource: response.myList,
connectWith: theOtherListBoxId,
dropSources: [theOtherListBoxId],
toolbar: {
position: "right",
tools: ["transferAllFrom", "transferAllTo",
"transferFrom", "transferTo"]
},
change: function (event) {
myJavascriptMethod(event);
},
dataTextField: 'Id',
dataValueField: 'Name'
}); //end of listView bind
}); //end of $.post().done() function
} //end of bindListView() function
And finally, here's what your controller method should be like for the above:
I'll include a pseudo class and fill it with data. The return object is the "response" variable, and whatever your list names are is accessed like this in the datasource: response.listname. Finally, whatever the object types are in those lists, the property names on those objects just have to match the dataTextField and dataValueField of the listview.
//This will be the type of object I'm putting into the list that's
//going into the object I'm returning
public MyClass {
public int Id {get; set;} //so we change the listview's dataValueField to Id
public string Name {get; set;} //we change the listview's dataTextField to Name
}
//And this will be the overall type of object that will hold the list, will get
//returned, becoming the "response" in the AJAX .done:
public class MyReturnObject {
public List MyList {get; set;} //so we change the ListView's datasource to response.MyList
//It may become myList, so you may need to look at the response object in the debugger.
}
[HttpPost]
public JsonResult MyControllerMethod(int id)
{
//Create the return object, give it a new list, add things to the list
//so the ListView can be bound:
var myReturnObject = new MyReturnObject();
myReturnObject.Mylist = new List();
var object1 = new MyClass { Id = 1, Name = "Hello World" };
var object2 = new MyClass { Id = 2, Name = "Hello again" };
myReturnObject.MyList.Add(object1);
myReturnObject.MyList.Add(object2);
//Convert the return object and everything in it to Json and return it to
//The AJAX method:
return Json(myReturnObject);
}

Update state of like button in Backbone View

In the following code, I have a view which extends from another view (but does not inherit any functionality, only renders the template) and a model which I want to implement now. My view is for a like button, which I need to retrieve the state of the like button from the server each time the page is loaded. I am not sure how to do this using the model. Do I need to have an Ajax call in the model retrieving the state from the server or does that call fall into the view?
This is my code:
var likeButton = Backbone.Model.extend ({
initialize: function () {
this.isLiked = /* need something here! Ajax call to get state of button from server? */
}
});
var LikeButtonView = BaseButtonView.extend({ // extends form a previews view which simply extends from backbone and render's the template
template: _.template($('#like-button').html()),
sPaper: null,
sPolyFill: null,
sPolyEmpty: null,
isLiked: false,
events: {
"click .icon": "like",
},
model: new likeButton (),
initialize: function (options) {
BaseButtonView.prototype.initialize.apply(this, [options]); // inherit from BaseButtonView
this.likeButn = $("button.icon", this.$el);
this.svgNode = this.likeButn.find("svg").get(0); // find the svg in the likeButn and get its first object
this.sPaper = Snap(this.svgNode); // pass in the svg object into Snap.js
this.sPolyFill = this.sPaper.select('.symbol-solid');
this.sPolyEmpty = this.sPaper.select('.symbol-empty');
if (this.model.isLiked) {
this.likeButn.addClass("liked");
} else if (!this.model.isLiked) {
this.likeButn.addClass("unliked");
}
},
like: function() {
this._update();
},
_update: function () {
if ( !this.isLiked ) { // if isLiked is false, remove class, add class and set isLiked to true, then animate svg to liked position
this._like();
} else if ( this.isLiked ) { // is isLiked is false, remove class, add class, set isLiked to false, then animate svg to unliked position
this._unlike();
}
},
_like: function() {
this.likeButn.removeClass("unliked");
this.likeButn.addClass("liked");
this.isLiked = true;
this.sPolyFill.animate({ transform: 't9,0' }, 300, mina.easeinout);
this.sPolyEmpty.animate({ transform: 't-9,0' }, 300, mina.easeinout);
},
_unlike: function() {
this.likeButn.removeClass("liked");
this.likeButn.addClass("unliked");
this.isLiked = false;
this.sPolyFill.animate({ transform: 't0,0'}, 300, mina.easeinout);
this.sPolyEmpty.animate({ transform: 't0,0' }, 300, mina.easeinout);
}
});
There are three ways to implement the 'like' button's knowledge of the current state of the page: A hidden field delivered from the HTML, an Ajax call to the server, or generating your javascript server-side with the state of the like model already active.
Let's start with the basics. Your code is a bit of a mess. A model contains the state of your application, and a view is nothing more than a way of showing that state, receiving a message when the state changes to update the show, and sending messages to the model to change the state. The model and the view communicate via Backbone.Events, and the view and the DOM communicate via jQuery.Events. You have to learn to keep those two separate in your mind.
Here, I've turned your "like" model into an actual model, so that the Backbone.Event hub can see the changes you make.
var likeButton = Backbone.Model.extend ({
defaults: {
'liked': false
}
});
Now in your view, the initial render will draw the state in gets from the model. When a DOM event (described in the 'events' object) happens, your job is to translate that into a state change on the model, so my "toggleLike" only changes the model, not the view. However, when the model changes (explicitly, when the "liked" field of the model changes), the view will then update itself automatically.
That's what makes Backbone so cool. It's the way views automatically reflect the reality of your models. You only have to get the model right, and the view works. You coordinate the way the view reflects the model in your initialization code, where it's small and easy to reason about what events from the model you care about.
var LikeButtonView = BaseButtonView.extend({
template: _.template($('#like-button').html()),
events: {
"click .icon": "toggleLike",
},
initialize: function (options) {
BaseButtonView.prototype.initialize.call(this, options); // inherit from BaseButtonView
// A shortcut that does the same thing.
this.likeButn = this.$("button.icon");
this.model.on('change:liked', this._updateView, this);
},
render: function() {
BaseButtonView.prototype.render.call(this);
// Don't mess with the HTML until after it's rendered.
this.likeButn.addClass(this.model.isLiked ? "liked", "unliked");
},
toggleLike: function() {
this.model.set('liked', !this.model.get('liked'));
},
_updateView: function () {
if (this.model.get('liked')) {
this._showLikedState();
} else {
this._showUnlikedState();
}
}
});
How the like model gets initialized is, as I said above, up to you. You can set a URL on the model's options and in your page's startup code tell it to "fetch", in which case it'll get the state from some REST endpoint on your server. Or you can set it to a default of 'false'. Or you can set it in hidden HTML (a hidden div or something) and then use your page startup code to find it:
new LikeButtonView({model: new LikeButton({}, {url: "/where/page/state/is"}));
or
new LikeButtonView({model: new LikeButton({liked: $('#hiddendiv').data('liked')}, {}));
If you're going to save the liked state, I'd recommend the URL. Then you have someplace to save your data.

kendo ui form update cancel button

I am trying to a simple kendo ui form with 'Save' and 'Cancel' buttons. I am using the Kendo.Observable to bind the data to the form.
The functionality I am trying to achieve is, if the 'Save' button is clicked, the form data will be saved. Else, if 'Cancel' is clicked the form will come back to read-only mode with the previous data that was present. To do this, I am first saving the model data in a 'originalvalue' property on click of Update button. If 'Cancel' is clicked, the 'fields' model data is restored to the 'originalvalue'. But the issue is that the , 'originalvalue' does not contain the original value. It gets updated when the user is editing during 'Save'.
The question is - how do I retain the original model data so that it can be refreshed on cancel?
Please find below the code. Appreciate your help, thanks.
<script type="text/javascript">
var viewModel = kendo.observable ({
updated: false,
originalvalue: {},
update: function(e) {
var original = this.get("fields");
this.set("originalvalue", original);
this.set("updated", true);
},
save: function(e) {
e.preventDefault();
if (validator.validate()) {
// make an ajax call to save this data
this.set("updated", false);
}
},
cancel: function(e) {
var original = this.get("originalvalue");
validator.destroy();
this.set("fields", original);
this.set("updated", false);
},
fields: {}
});
viewModel.set("fields", formArray);
kendo.bind($("#outerForm"), viewModel);
// prepare the validator
var validator = $("#outerForm").kendoValidator().data("kendoValidator");
I had to make the exact thing on a form I am currently developing. I am using a DataSource object for the data so I had to use cancelChange().
The thing I did there:
1. I made a Datasource with a schema:
... schema: {
model: {id: "id"}}
...
2. I got the object I was editing with the mapped id:
clientDataSource.cancelChanges(clientDataSource.get(this.get("contactID")));
where the ContactID is created in a setData function where I have passed the ID:
this.set("contactID", contactID);
As I may have notices and understood, you have another problem here where you arent using a DataSource but rather data for fields?
The problem here is that your originalValue is inside the Observable object and it is referenced to the variable original and thus it has observable properties. You should have the variable originalValue defined outside the observable object:
var originalValue;
var viewModel = kendo.observable ({ ...
And you should send the formArray also to that variable so you will have the defaults load before even the observable object is loaded such as:
originalValue = formArray;
viewModel.set("fields", formArray);
So when you need to cancel it you should have:
cancel: function(e) {
var original = originalValue;
validator.destroy();
this.set("fields", original);
this.set("updated", false);
},
I havent tested it but it should provide you some guidance on how to solve that problem.

How to use marionette collectionView. I am using https://github.com/marionettejs/backbone.marionette as boilerplate

I want to know how to use marionette collectionView. I am using https://github.com/marionettejs/backbone.marionette as boilerplate.
Currently I am unable to see anything rendered by collection view.
Backbone Collection
define(["jquery","backbone","models/Store"],
function($, Backbone, Store) {
// Creates a new Backbone Collection class object
var StoreCollection = Backbone.Collection.extend({
// Tells the Backbone Collection that all of it's models will be of type Model (listed up top as a dependency)
model: Store
});
storeCollectionObj = new StoreCollection();
storeCollectionObj.add({"title":"wherwr werwe"});
return storeCollectionObj;
});
Model
define(["jquery", "backbone"],
function($, Backbone) {
// Creates a new Backbone Model class object
var Store = Backbone.Model.extend({
// Model Constructor
initialize: function() {
},
// Default values for all of the Model attributes
defaults: {
},
// Get's called automatically by Backbone when the set and/or save methods are called (Add your own logic)
validate: function(attrs) {
}
});
// Returns the Model class
return Store;
}
);
itemView
define( ['underscore', 'jquery', 'handlebars', 'text!templates/merchant/store.html'],
function(_, $, Handlebars, template) {
//ItemView provides some default rendering logic
return Backbone.Marionette.ItemView.extend( {
//Template HTML string
template: Handlebars.compile(template),
id: "store",
attributes: function () {
return {class :"storeView"}
},
// View Event Handlers
events: {
}
});
});
CollectionView
define( ['underscore', 'jquery', 'handlebars' , 'views/merchant/StoreView','text!templates/merchant/storeCollection.html' , 'collections/StoreCollection'],
function(_, $, Handlebars, SroreView, template, StoreCollection) {
SroreViewObj = new SroreView();
//storeCollectionObj = new StoreCollection();
//alert(StoreCollection);
//StoreCollection.add({"title":"sdfsadfasd"});
//ItemView provides some default rendering logic
StoreCollectionView = Backbone.Marionette.CollectionView.extend({
id : "StoreListing",
template: Handlebars.compile(template),
itemView: SroreViewObj,
collection : StoreCollection,
render: function(){
}
});
return StoreCollectionView;
});
I found my mistake. I was passing itemView Object in my collection view.
line itemView: SroreViewObj should be itemView: SroreView
fixed just by replacing "SroreViewObj" with "SroreView" (itemView definition)
SroreViewObj = new SroreView();
//storeCollectionObj = new StoreCollection();
//alert(StoreCollection);
//StoreCollection.add({"title":"sdfsadfasd"});
//ItemView provides some default rendering logic
StoreCollectionView = Backbone.Marionette.CollectionView.extend({
id : "StoreListing",
template: Handlebars.compile(template),
itemView: SroreViewObj, // This should not be an object. So fixed just by replacing "SroreViewObj" with "SroreView" (itemView definition)**
collection : StoreCollection,
render: function(){
}
});

Backbone click event fires events for all collection rather than model

Can't figure out what's wrong. When I click on a model title, it fetches all models in collection at once rather than fetch one model. If I move this event from logView to logsView it works properly but doesn't have access to model, well I can find this model using index or ant other model's ID but don't think this is a nice way.
var Log = Backbone.Model.extend({});
window.LogsList = Backbone.Collection.extend({
model:Log,
url:function (tag) {
this.url = '/logs/' + tag;
return this;
}
});
window.colList = new LogsList();
window.logView = Backbone.View.extend({
el:$('.accordion'),
template:_.template($('#log').html()),
initialize:function () {
this.model.bind('add', this.render, this);
},
events:{
"click .accordion-toggle" :"getLogBody"
},
render:function () {
return this.template(this.model.toJSON());
},
getLogBody:function () {
this.model.fetch();
}
});
window.LogsView = Backbone.View.extend({
el:$("#content"),
initialize:function (options) {
colList.bind('reset', this.addAll, this);
colList.url(options.data).fetch();
},
addOne:function (model) {
var view = new logView({model:model});
$("#accordion").append(view.render());
},
addAll:function () {
colList.each(this.addOne);
}
});
window.listView = new LogsView({data:"Visa_Cl"});
The problem is caused by your el in the LogView: el:$('.accordion')
Backbone's view events are scope to the view's el. In this case, you've specified the view's el as ALL HTML elements with a class of "accordion". Therefore, when you click on any of your HTML elements with this class, the code runs for all of them, which is why you are seeing this behavior.
This article will show you a few options for doing what you want, correctly:
Backbone.js: Getting The Model For A Clicked Element
I would also recommend reading this one, to better understand the use of el in Backbone, and a few of the tricks and traps of it:
Backbone.js: Object Literals, Views Events, jQuery, and el

Resources