I was looking for a PRISM like InteractionRequest to use modal dialogs with Kendo UI MVVM. I could not find one and ended up creating one. I'll share it here as it would be great if it could be improved on or even adopted in the framework. It's usage is as follows:
I created a custom binding called interactionRequest. You bind it to an InterationRequest object in your view model and you need to provide the id of a template that will be rendered in the window. An InteractionRequest has a raise method that you pass a view model to be bound to the window and returns a promise that will be resolved when a result is set on the view model. I'm using TypeScript for this.
The InteractionRequest itself is very simple:
The raise method expects that the raise event it triggers is handled by the interactionRequest custom binding that will set the promise on the InteractionRequest so that it may be returned by the raise method.
This is the custom binding code (which is still in JavaScript)
It will open the window when the InteractionRequest triggers its raise event. It will bind the view model passed to the raise method of the InteractionRequest to the template rendered in the window. When a result is set on this view model, the window is closed and the deferred is resolved. In my case I set the result to true when an update button is clicked and to false when a cancel button is clicked.
I specifically think the custom binding could be improved with regards to the specification of the template to be rendered in the window. Preferably I set a custom data-template option on the div, but this does not currently get passed into the custom binding. I will also need to figure out how to trigger validation on the dialog before I set the dialog result to true.
EDIT: added validation
<div style="display: none;" data-role="window" data-bind='interactionRequest: { path: editOpportunityInteractionRequest, template: editOpportunityTemplate }' data-title="Edit" data-modal="true" data-visible="false"></div>import App = module("./DataContext");import Interactivity = module("./InteractionRequest");import Models = module("./Models");import ViewModels = module("./EditOpportunityViewModel");export class OpportunitiesViewModel extends kendo.data.ObservableObject { editOpportunityInteractionRequest = new Interactivity.InteractionRequest(); dataContext: App.DataContext; constructor (dataContext: App.DataContext) { super(); this.set("dataContext", dataContext); } onCustomCreate(e: JQueryEventObject) { e.preventDefault(); var opportunity = new Models.Opportunity(); this.dataContext.opportunities.insert(0, opportunity); var viewModel = new ViewModels.EditOpportunityViewModel(opportunity, this.dataContext); this.editOpportunityInteractionRequest.raise(viewModel).done((viewModel: ViewModels.EditOpportunityViewModel) => { if (!viewModel.result) { this.dataContext.opportunities.remove(viewModel.opportunity); } }); } onCustomEdit(e: JQueryEventObject) { e.preventDefault(); var opportunity = <Models.IOpportunityModel>e.data; var viewModel = new ViewModels.EditOpportunityViewModel(opportunity, this.dataContext); this.editOpportunityInteractionRequest.raise(viewModel).done(viewModel => { if (!viewModel.result) { this.dataContext.cancelChanges(); } }); }}The InteractionRequest itself is very simple:
/// <reference path="..\lib\jquery.d.ts"/>/// <reference path="..\lib\kendo.web.d.ts"/>export class InteractionRequest extends kendo.data.ObservableObject { promise: JQueryPromise; raise(viewModel: { result: any; }) { this.trigger("raise", viewModel); return this.promise; }}This is the custom binding code (which is still in JavaScript)
kendo.data.binders.widget.interactionRequest = kendo.data.Binder.extend({ init: function (element, bindings, options) { kendo.data.Binder.fn.init.call(this, element, bindings, options); var that = this, binding = bindings.interactionRequest; that.template = kendo.template($("#" + binding.path.template).html()); that.resultHandler = function (e) { if (e.field === "result") { that.element.close(); } }; that.closeHandler = function () { that.viewModel.unbind("change", that.resultHandler); var widget = that.element, container = widget.element.find(".k-edit-form-container"); kendo.destroy(container); that.deferred.resolve(that.viewModel); }; that.element.bind("close", that.closeHandler); }, refresh: function (attribute) { var that = this, binding = that.bindings.interactionRequest, source = binding.source.get(binding.path.path); if (that.previousSource) { that.previousSource.unbind("change", that.raiseHandler); } that.raiseHandler = function (e) { that.deferred = new $.Deferred(); source.promise = that.deferred.promise(); if (e instanceof kendo.data.ObservableObject) { that.viewModel = e; } else { that.viewModel = kendo.observable(e); } that.viewModel.bind("change", that.resultHandler); var widget = that.element; widget.content('<div class="k-edit-form-container"></div'); var container = widget.element.find(".k-edit-form-container"); container.html(that.template(that.viewModel)); that.viewModel.validator = container.kendoValidator().data("kendoValidator"); kendo.bind(container, that.viewModel); widget.center().open(); }; source.bind("raise", that.raiseHandler); that.previousSource = source; }, destroy: function () { var that = this, binding = that.bindings.interactionRequest, source = binding.source.get(binding.path); that.element.unbind("close", that.closeHandler); if (that.raiseHandler) { source.unbind("raise", that.raiseHandler); } }});It will open the window when the InteractionRequest triggers its raise event. It will bind the view model passed to the raise method of the InteractionRequest to the template rendered in the window. When a result is set on this view model, the window is closed and the deferred is resolved. In my case I set the result to true when an update button is clicked and to false when a cancel button is clicked.
import App = module("./DataContext");import Models = module("./Models");export class EditOpportunityViewModel extends kendo.data.ObservableObject { result: bool; validator: kendo.ui.Validator; constructor (public opportunity: Models.IOpportunityModel, public dataContext: App.DataContext) { super(); } update(e: JQueryEventObject) { e.preventDefault(); if (!this.validator.validate()) { return; } this.dataContext.sync().done(() => { this.set("result", true); }); } cancel(e: JQueryEventObject) { e.preventDefault(); this.set("result", false); }}I specifically think the custom binding could be improved with regards to the specification of the template to be rendered in the window. Preferably I set a custom data-template option on the div, but this does not currently get passed into the custom binding. I will also need to figure out how to trigger validation on the dialog before I set the dialog result to true.
EDIT: added validation