How to configure a confirmation window on Kendo UI Wizard? - c#

I'm using Kendo UI Wizard, with a form that's being fed a viewmodel. That's working as intended, and it's submitting back to the controller.
What I would like to setup is a confirmation as the second/final step of the wizard, but with the 1:1 (property:field) limitation I'm having some difficulty wrapping my mind around how to do this.
(If the first step is ~6 fields of data, I want the second/final step to be those same fields but readonly, giving the user a chance to review changes before hitting submit.)
#model MyApp.Web.Areas.Application.ViewModels.AddressViewModel
#(Html.Kendo().Wizard()
.Name("ChangeAddressWizard")
.Events(ev => ev.Done("onDone"))
.Tag("form")
.HtmlAttributes(new { #novalidate = "", action = Url.Action("ChangeAddress", "Home"), method = "post" })
.ContentPosition(WizardContentPosition.Right )
.Steps(s => {
s.Add<MyApp.Web.Areas.Application.ViewModels.AddressModel>()
.Title("Change Address")
.Form(f => f
.Validatable(v => {
v.ValidateOnBlur(true);
v.ValidationSummary(vs => vs.Enable(false));
})
.FormData(Model)
.Layout("grid")
.Events(e => e.Change("onChange"))
.Grid(g => g.Cols(2).Gutter(20))
.Items(items => {
items.AddGroup()
.Label("Address Info")
.Layout("grid")
.Items(i => {
i.Add().Field(p => p.City)
.Label(l => #Html.LabelFor(p => p.City));
i.Add().Field(p => p.State)
.Label(l => #Html.LabelFor(p => p.State));
i.Add().Field(p => p.Zip)
.Label(l => #Html.LabelFor(p => p.Zip));
i.Add().Field(p => p.Country)
.Label(l => #Html.LabelFor(p => p.Country));
});
})
)
.Buttons(b => {
b.Next();
});
s.Add<MyApp.Web.Areas.Application.ViewModels.AddressViewModel>()
.Title("Review Changes")
.Form(f => f
.FormData(Model)
.Layout("grid")
.Events(e => e.Change("onChange"))
.Grid(g => g.Cols(2).Gutter(20))
.Items(items =>
{
//Below fields would be readonly on this step
i.Add().Field(p => p.City)
.Label(l => #Html.LabelFor(p => p.City));
i.Add().Field(p => p.State)
.Label(l => #Html.LabelFor(p => p.State));
i.Add().Field(p => p.Zip)
.Label(l => #Html.LabelFor(p => p.Zip));
i.Add().Field(p => p.Country)
.Label(l => #Html.LabelFor(p => p.Country));
});
})
)
.Buttons(b => {
b.Previous();
b.Done();
});
}))
</div>
Initially I tried the above but the fields are blank on the second step. From my understanding this is due to each property mapped by name from the model to the input.
I see a couple options but they all seem kind of hacky...
Setup fields that aren't mapped to the model and update them via script and use those to display on the confirmation/review step
Write my own "step" that really just toggles all the fields to read only
Is there a better way than either of these options? In the example I provided It's trimmed down to 4 fields but some of my use cases are multiple steps of fields. I feel like there should be a simple and obvious way to show the entirety of a form as read only on the final step without breaking the 1 property : 1 field rule. (This could be a limitation with Kendo functionality)
I've been racking my brain on this one, any pointers would be appreciated.

You will have to add a final step and use JavaScript to populate the summary.
When creating a Form field
i.Add().Field(p => p.City)
.Label(l => #Html.LabelFor(p => p.City));
essentially an <input id="City"/> is generated. The first issue when adding a final step with the same fields is that you will end up with DOM elements with the same id. The second one is that the inputs will be blank (or completed with data passed via the Model during the component initialization), but will not reflect any changes the user has made.
You can add a Summary step:
s.Add().Title("Summary").Content("<ul id='content-container'></ul>")
.Buttons(b =>
{
b.Previous();
b.Done();
});
and an Activate event handler. Use the handler to get all the Forms in the Wizard. The Form API will allow you to get the current values for each field and you can list them in the summary step:
function onActivate(e){
if (e.step.options.index == 2) { //current final step
var formElements = e.sender.wrapper.find(".k-form");
//the wizard in the example contains two forms. Iterate them and list the values.
formElements.each(function(idx,elm){
var form = $(elm).getKendoForm();
var kvp = JSON.parse(JSON.stringify(form.editable.options.model))
for (var key in kvp){
$("#content-container").append(`<li><b>${key}</b> : ${kvp[key]} </li>`)
}
})
}
}
Here is a simple example where Kendo's Form integration demo is extended with a summary step. Sure, you'll have to fine tune it if you have DropDowns and/or other components where a value is submitted, but some descriptive text is being displayed, mask passwords, etc., but I hope you get the idea.

Related

Telerik Grid, Detail view - how to specify route values

I've just started using Telerik, to speed up some of my development, however I'm stuck trying to bring up a normal MVC Details page from a Grid Component.
All I want, is a select button to send the user to something like /Person/Details/5
The reason why I want a separate page (rather than a popup) is that some of the data relationships are complex, and it's better experience that these are displayed in it's own page, rather than a popup.
My model has a few objects, that's why you see the p.person.Title type setup below.
#(Html.Kendo().Grid(Model)
.Name("grid")
.Columns(columns =>
{
columns.Bound(p => p.person.Title).Width(100);
columns.Bound(p => p.person.Firstname);
columns.Bound(p => p.person.Firstname);
columns.Bound(p => p.person.Surname);
columns.Bound(p => p.person.ABN).Width(210);
columns.Bound(p => p.person.PracticeCode);
columns.Bound(p => p.currentform);
columns.Command(command => { command.Edit(); });
columns.Command(command => { command.Destroy(); });
columns.Command(command => { command.Select(); });
})
.Sortable()
.ToolBar(commands => commands.Create())
.Editable(editable => editable.Mode(GridEditMode.PopUp))
.DataSource(dataSource => dataSource
.Server()
.Model(model => model.Id(p => p.person.personkey))
.Create(create => create.Action("Create", "Person"))
.Read(read => read.Action("Details", "Person" ))
.Update(update => update.Action("Update", "Person"))
.Destroy(destroy => destroy.Action("Destroy", "Person"))
The grid itself is working fine, however, when someone selects the "Select" button, I want them to go to the /Person/Details/{id} page. Telerik is almost doing it right, it's sending the user to
/Person/Details?grid-mode=select&person.personkey=11 (I actually want /Person/Details/11 )
So, I noticed that the "Read" method has a few different signatures to enable route values, but I just can't seem to rig it up to get the right route values.
I've tried....
.Read(read => read.Action("Details", "Practitioner", p => p.practitioner.drkey ))
and combinations of, but I just can't seem to get the right route value in there.
How do I correctly specify the right route values ?
I ended by solving this with a Template.
Not exactly the outcome I was looking for, but close enough at this stage.
columns.Bound(p => p.person.pkey).Template(#<text>
#Html.ActionLink("Details" , "Details" , new { id = #item.person.pkey })
</text>);
The trick is that a traditional MVC razor action is placed inside a Telerik Template.
It also removed the need for the .Select and .Read rows.

ForeignKey displays a textbox instead of dropdownlist

Here is my issue :
I have two project with similar code to display a dropdownlist inside a kendogrid. In the first project all works fine, in the second project i got a textbox dipslaying instead of a dropdown. I've searched around telerik but nothing could resolve my issue. I think something is missing in my project to make this work fine. Or something is blocking it ! But can't find .. Hope you could help me !
Here is my grid :
#(Html.Kendo().Grid<DisplayUniteMultiple>()
.Name("GridDisplayUniteMultiple")
.ToolBar(t => t.Create().Text("Ajouter un couple Valeur/ Unite"))
.Columns(columns =>
{
columns.Bound(c => c.id).Hidden(true);
columns.Bound(c => c.Valeur);
columns.ForeignKey(c => c.Unite, unitesDeConversion, "Value", "Text").Title("Unité").Width(200);
})
.Editable(editable => editable.Mode(GridEditMode.InCell).Enabled(true))
.Events(e => e.Edit("onGridChange"))
.DataSource(datasource => datasource
.Ajax()
.Update(builder => builder.Url(""))
.PageSize(50)
.Batch(false)
.Model(model =>
{
model.Id(uniteMultiple => uniteMultiple.id);
model.Field(uniteMultiple => uniteMultiple.Valeur);
model.Field(uniteMultiple => uniteMultiple.Unite);
})
.ServerOperation(true)
))
You might missing to add the editor template in your project.
Create a new folder named EditorTemplates in ../Views/Shared/ directory, if there is not already. Then create new file named GridForeignKey.cshtml in that folder and copy below code into it, save and try again.
#model object
#(
Html.Kendo().DropDownListFor(m => m)
.BindTo((SelectList)ViewData[ViewData.TemplateInfo.GetFullHtmlFieldName("") + "_Data"])
)

Columns.Bound cannot resolve symbol 'Bound'

I have a web page that has some Kendo controls like dropdown and button working successfully, but adding a Grid is causing a problem.
#(Html.Kendo().Grid(Model).Name("grid").Columns(columns =>
{
columns.Bound(stuff => stuff.FirstColumn).Width(200);
columns.Bound(stuff => stuff.SecondColumn).Width(200);
columns.Bound(stuff => stuff.HiddenColumn).Hidden;
columns.Bound(stuff => stuff.ThirdVisibleColumn).Width(200);
})
.Pagable()
.Sortable()
)
At design time, the .Bound method is showing in red font with the tooltip Cannot resolve the symbol 'Bound', and the properties of Stuff (e.g. .FirstColumn) are also in red with similar messages.
Compilation Error
Description: An error occurred during the compilation of a resource required to service this request. Please review the following specific error details and modify your source code appropriately.
Compiler Error Message: CS1977: Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type
This problem first showed up in another project, so as a test I created a new Telerik prject and added just the minimum required to reproduce the problem in the hope that a newly created project would highlight where the original one had failed. No such luck.
Thanks in advance.
If you pass the model like this it needs to be of type IEnumerable (or similar).
This works for me:
#model IEnumerable<SomeModel>
#(Html.Kendo().Grid(Model)
.Name("grid")
.Columns(columns =>
{
columns.Bound(stuff => stuff.FirstColumn).Width(200);
columns.Bound(stuff => stuff.SecondColumn).Width(200);
columns.Bound(stuff => stuff.HiddenColumn).Hidden();
columns.Bound(stuff => stuff.ThirdVisibleColumn).Width(200);
})
.Pageable()
.Sortable()
)
Also you can pass model like:
#(Html.Kendo().Grid<SomeModel>().Name("grid")
.DataSource(src => src.Ajax().Read(read => read.Action("Action", "Controller"))
.Columns(col =>
{
col.Bound(e => e.Name);
col.Bound(e => e.Age);
col.Bound(e => e.ID);
})
.Selectable()
.Scrollable()
)

Saving to many rows to database

And Read, Create, Update, Destroy works fine when working with only one row at a time.
But, when i try to add two Customer's the EditingPopup_Create is called three times. One for the first row, and two for the second row... If i add more than two rows the same pattern follows. The EditingPopup_Create method is run as many times as the count of it's customer....
Current implementation is like this:
#(Html.Kendo().Grid<CustomerModel>()
.Name("grid")
.Columns(columns =>
{
columns.Bound(p => p.Name);
columns.Bound(p => p.ContactPerson);
columns.Bound(p => p.ContactPersonEmail);
columns.Bound(p => p.ContactPersonPhone);
columns.Bound(p => p.MainPhoneNumber);
columns.Command(command => { command.Edit(); command.Destroy(); }).Width(180);
})
.ToolBar(toolbar => toolbar.Create())
.Editable(editable => editable.Mode(GridEditMode.PopUp).TemplateName("CustomerPopUpTemplate"))
.Pageable()
.Sortable()
.Scrollable()
.HtmlAttributes(new {style = "height:500px;"})
.DataSource(dataSource => dataSource
.Ajax()
.PageSize(10)
//.Events(events => events.Error("error_handler"))
.Model(model => model.Id(p => p.Id))
.Read(read => read.Action("EditingPopup_Read", "CustomerManagement"))
.Create(update => update.Action("EditingPopup_Create", "CustomerManagement"))
.Update(update => update.Action("EditingPopup_Update", "CustomerManagement"))
.Destroy(destroy => destroy.Action("EditingPopup_Destroy", "CustomerManagement"))
)
)
This is probably a newbie mistake, but i cant seem to figure out why this would happen. In my opinion the wiring of the grid should only call the methods once per instance.
Explanation would be greatly appreciated :)
When you perform the save on create are you changing the id associated with the item created?
I suspect what you are doing is not changing the id from what is being provided and grid treats this as another create.
So when you are saving your item back to the database set the id to the database generated or how ever you are generating it and you should see it not doing the second call.
Showing the controller code will help as well to answer it.
Make sure you are returning the model back to the view so that it updates the id associated with it via the json.
Eg
Return json( new { model }.todatasourceresult(request, modelState))
Found a post that answered my question and filled out the question a bit more too...
Kendo UI Grid posting back already Inserted Rows again
A way of explaining it is that if the Id of an object is 0 the grid thinks it's a new row. If it's a new row it thinks it has to insert it. If the value of the Object/Row in the grid is not updated when the row is added, it will try to re-add it because it thinks it does not exist.
I hope this could help anyone out there... :)

Why kendo Grid is not generating any html output and no serverside/clientside errors?

I have this kendo grid in one of my views:
#model IEnumerable<IJRayka.Core.Utility.ViewModels.OrderDto>
#using IJRayka.Core.Web.Code
#{
ViewBag.Title = "Order List";
}
<h2>Order List</h2>
#{
Html.Kendo().Grid(Model).Name("grid").Columns(columns =>
{
columns.Bound(ord => ord.CustomerName);
columns.Bound(ord => ord.MarketerName);
columns.Bound(ord => ord.OrderDate);
columns.Template(ord => ord.CurrentStatus.GetDisplayName());
columns.Bound(ord => ord.PaymentMethod);
columns.Bound(ord => ord.Description);
columns.Command(cmd => cmd.Edit().Text("ویرایش"));
columns.Command(cmd => cmd.Select().Text("مشاهده"));
columns.Command(cmd => cmd.Destroy().Text("حذف"));
}).DataSource(ds => ds.Server()
.Model(m => m.Id(ord => ord.ID))
.Create(create => create.Action("Create", "OrderManagement"))
.Read(read => read.Action("Details", "OrderManagement"))
.Update(update => update.Action("Edit", "OrderManagement"))
.Destroy(destroy => destroy.Action("Delete", "OrderManagement"))
).Pageable().Sortable()
.ToolBar(toolbar => toolbar.Create().Text("ایجاد سفارش"))
.Selectable(selectable => selectable.Mode(GridSelectionMode.Single)));
}
When I debug, I see there is 4 items in the Model collection, but I don't know why without any errors, the grid won't show up at all. Not a single HTML tag is generated on client side, and there's no errors shown in the firebug console.
It was working great before, but all of a sudden, after some changes to my model I don't know what happened to it.
EDIT:
I just realized if I write this all lines, in one single line it works !
But I don't understand why?
When you are using the construct #{ .. } then you are in a code block in razor. And inside in a code block nothing gets written to the response by default.
You need to use # to output something to the output or you need to use some special methods like Render() on the Grid.
So the following invocations are all working:
Using Render on any Kendo Widget:
#{
Html.Kendo().Grid(Model).Name("grid").Columns(columns =>
{
//...
})
.Selectable(selectable => selectable.Mode(GridSelectionMode.Single)))
.Render(); //Render method provided by Kendo UI.
}
Using #:
#{
#(Html.Kendo().Grid(Model).Name("grid").Columns(columns =>
{
//...
})
.Selectable(selectable => selectable.Mode(GridSelectionMode.Single))))
}
Or if you anyway have this one statement in the code block don't use the code block at all just write:
#(Html.Kendo().Grid(Model).Name("grid").Columns(columns =>
{
//...
})
.Selectable(selectable => selectable.Mode(GridSelectionMode.Single))))
Note: if youe have a multiline statement after the # you need to wrap the whole statement with an extra set of parens (())

Categories

Resources