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