I have something of a weird situation going on. I'm trying to build a Telerik MVC grid in a custom HTML helper which implements some other custom functionality. (Amongst other things, it renders a form to the right of the grid when a row is selected. We're not using the in-box editing features of the grid due to UI standardization. The whole requirement is that, for simple list-of-values tables in a database, we'd like a minimal-code approach. One line in the HTML, a few lines of Javascript at most, and boom, done.)
Everything works -- except rebinding the data dynamically. The grid renders, its selection works, the form displays, the form saves at blur events. The grid hits the OnDataBinding event, but nothing happens after that. It never gets to the OnDataBound event, and it never hits the internal bindTo nor bindData methods on the grid object itself.
"Enough" of the code (a lot of it can't be revealed) is thus (HTML helper):
public static void ListOfValuesEditorFor<TModel, TModelCollection>(this HtmlHelper<TModelCollection> htmlHelper, string gridName, string refreshAction, string refreshController, string loadItemUrl, IEnumerable<TModel> model) where TModel : class where TModelCollection : IEnumerable<TModel>
{
var factory = HtmlHelperExtension.Telerik<TModelCollection>(htmlHelper);
var grid = factory.Grid(model);
grid = grid.Name(gridName).Pageable(pager => pager.Enabled(false)).Selectable(select => select.Enabled(true)).Filterable(filter => filter.Enabled(false)).Scrollable().Sortable(sort => sort.Enabled(false));
grid = grid.DataBinding(binding => binding.Ajax().Select(refreshAction, refreshController));
grid = grid.ClientEvents(events =>
{
events.OnDataBound("Telerik.ListOfValues.OnDataBound");
events.OnDataBinding("Telerik.ListOfValues.OnDataBinding");
events.OnRowSelect("Telerik.ListOfValues.SelectRow");
});
var textControls = new List<string>();
int idColumn = -1;
grid = grid.Columns(columns =>
{
int cellCount = 0;
foreach (var prop in typeof(TModel).GetProperties())
{
// Populates columns, creates text entry controls in the list,
// handles some other proprietary work.
// SNIP
}
});
// Container for the form
var formDivBuilder = new TagBuilder("div");
// Build out the form
// SNIP
// Render to the response
var response = HttpContext.Current.Response;
response.Write("<input type=\"hidden\" id=\"loadItemUrl\" value=\"" + loadItemUrl + "\" />");
response.Write("<input type=\"hidden\" id=\"idColumnIndex\" value=\"" + idColumn.ToString() + "\" />");
grid.Render();
response.Write(formDivBuilder.ToString(TagRenderMode.Normal));
}
That HTML helper is called thusly:
<% using (Html.BeginForm()) {
Html.ListOfValuesEditorFor("JobTitleGrid", "RefreshJobTitles", "Home", "/Home/LoadJobTitle", Model);
} %>
On the Javascript side of the world, all OnDataBound and OnDataBinding do is display messages indicating that they've been hit. In fact, they won't even make it to the production version of the code; they're in there for debugging purposes now.
OnSelect displays and populates the form. This is happening correctly.
The form itself updates the object any time a text field's onChange event fires. This portion is validated as functional. This is done via a $.ajax() call, which again, is validated to function.
The success callback from that $.ajax() call is thus:
function onSubmitComplete(responseData, callbackData) {
// Some irrelevant junk here
$('#JobTitleGrid').data('tGrid').ajaxRequest();
}
The call to ajaxRequest functions. At the server, my grid action functions, returning an IList of the IJobTitle objects. At the client, OnDataBinding fires, displaying its message. OnDataBound never fires, and the grid display does not update.
I know this is somewhat outside the bounds of the way Telerik controls are normally used, but the sheer amount of code necessary to use them encourages my team to try to create reusable entities (such as these custom HTML helpers) wherever possible. For the simpler controls (text boxes, calendars, etc), our custom helpers have always "just worked." The grid, though, is presenting this problem.
Any ideas on why we never get to binding the data? More importantly, how to fix that?
After coming up with the solution, I'd briefly considered deleting the question -- Telerik's grid is only minimally involved here. However, I know first-hand how hard it is to troubleshoot code when you're building on top of frameworks, which are built on top of frameworks, which are further built on top of frameworks. :) So hopefully this answer will help the next guy down the line.
The actual issue turned out to be a serialization exception from the DAL call in the grid action. This seemed odd to me, since I used the exact same call to populate both the pre-loaded view in the Index action and the response from the GridAction, but sure enough, if I debugged down in the Javascript deeply enough, I eventually found it. The exception wasn't being handled (pro-tip: implement an OnError handler for the grid), and thus client-side rebinding failed, as it had no data to bind.
Once I resolved the serialization issue, everything just magically worked, and we were down to about 20 lines of code to implement an entire generic entity data entry screen.
Related
Our users asked to have the Kendo Tree show up in the MVC web app in a panel on the left hand side of the page. They want that left hand panel to be present on every screen.
Currently I have a section of my _Layout.cshtml page that renders the Kendo Tree:
<div>
#{Html.RenderAction("GetTree", "Tree");}
</div>
Inside that Action I make a database call to get the contents of the tree and bind the model to the view as follows:
#model IEnumerable<TreeViewItemModel>
But obviously with this pattern every page I go to the _Layout gets called and the tree data is fetched from the database again. This is not very efficient.
What is a better way so that I only make a single database call?
I'm going to assume that the content of the tree is the same for every page and every user.
In that case, you can cache the retrieved database data so that you don't have to retrieve it on every page render. There are lots of ways to cache it: the simplest is probably to use ASP.NET's own caching, which is described in detail (with walkthroughs) here.
You probably will still have to render it on every page (unless you want to get into partial page caching, and I'm not sure how that works in MVC) but you definitely can avoid the repeated database hit.
ETA: You can create a wrapper or helper class that retrieves the necessary tree data by company. The relevant method would look a bit like this:
public IEnumerable<TreeViewItemModel> GetCachedTreeDataByCompany(int companyId)
{
var data = Cache["TreeData"] as IEnumerable<TreeViewItemModel>;
if(data == null)
{
data = GetTreeData(); // whatever you need to do to get the data
Cache.Insert("TreeData", data);
}
return data.Where(tvim => tvim.CompanyId == companyId).ToArray();
}
I'm really new to Kendo UI, and I'm having problems with CascadeFrom() not calling an action on my controller. Here's the bare bones of my problem:
// The parent dropdown
<select id="Testing">
<option value="0">Vehicle</option>
<option value="1">Driver</option>
<option value="2">Trailer</option>
</select>
// The dynamic dropdown
#(Html.Kendo().DropDownListFor(m => m.VDTId)
.DataValueField("Id")
.DataTextField("Item")
.DataSource(ds =>
{
ds.Read(c => c.Action("GetVDT", "CompanyVDTUnavailability")
.Data("getVDTSelection"))
.ServerFiltering(true);
})
.CascadeFrom("Testing"))
// Function to allow Kendo to pass a value to
// the type parameter of my GetVDT action.
function getVDTSelection() {
return {
type: parseInt($("#Testing").val())
};
}
The action is being called when the page first loads, and returns the correct data. The problem is, if I then make a selection from the Testing dropdown, the action is never invoked on the controller (I've verified this using a breakpoint on the action), meaning the dynamic dropdown never gets updated.
Looking through the official example, and other questions around SO, I can't see what I'm doing wrong. Could someone point me in the right direction, please?
Edit: I've tried Petur's solution below by changing the parent dropdown to the following:
#(Html.Kendo().DropDownListFor(m => m.Type)
.Name("Testing")
.DataValueField("Id")
.DataTextField("Text")
.BindTo(Model.UnavailabilityTypes))
This binds the parent dropdown correctly, but no longer invokes the controller action for the cascading dropdown even when the page first loads. Any suggestions?
Controller action signature as requested:
public JsonResult GetVDT(CompanyUnavailabilityType type)
Where CompanyUnavailabilityType is an enum.
Kendo DropDownList can cascade only from another Kendo DropDownList/ComboBox. Turn the first widget into kendo DropDownList and it should start working properly.
I think the problem is that getVDTSelection() is returning an int or string value not an Enum value. Change your method sig to an int if not, try a string and the method described in my comment
public JsonResult GetVDT(int type)
{
//AllowGet might be needed as well
return Json(jsonObjx,JsonRequestBehavior.AllowGet);
}
Edit:
You can also try to Manually force the ddl to cascade. Ditch CascadeFrom and do it manually.
function OnChangeOfParentDDL(e){
var parentValue = $("#ParentDDL").val();
$("#ChildDDL").val("").data("kendoDropDownList").text("");//clear it out
var child = $("#ChildDDL").data("kendoDropDownList");
child.dataSource.read({ type : parentValue });
}
Both Petur and C Sharper were on the right track with the problem.
I did need to build the dropdown using Html.Kendo.DropDownList() (I've just verified this after getting the solution to work.)
The method signature on the controller was a problem, but only because I'd had old testing methods left on there, leading to an ambiguous call.
The major difficulty for me was that nothing was being reported in the debugger, so diagnosing the problem was a pain. In the end, I used the Network tab of the Firefox web developer tools to ensure the Ajax request was indeed being sent, and it was. Inspecting that request showed it was leading to an ambiguous call on the controller.
Also, to clear up comments from C Sharper's answer:
The parseInt() call is not required.
The call will correctly map to an enum on the server-side, meaning the method signature I posted in my question is correct.
I have cascading dropdowns in my MVC partial view. DD1 drives the values in DD2. When I select DD1 I want DD2 to be populated with, based on a DB table, the correct values based on DD1's value.
My thought was to make DD2 a partial view and nest in my form. Then, with ajax, I can tell the partial view to refresh and pass it the value of DD1.
The problem is, when I submit the whole view (with both DD1 and DD2 and a bucnh of other stuff, how do I get the value that is in DD2?
I'm trying to solve this problem using MVC, rather than triggering a javascript function on change of DD1 to make a JSON call to get the options and then using javascript to modify DD2 to the correct values.
How should I do this?
How big are your value sets for each drop down?
I was attempting to do this same thing a few years ago. DD1 was United States and Canada, and DD2 was the associated States and Provinces. If your data set is relatively small, you're better off just putting all the select list markup for both cases in the page, and then swapping it out with javascript (jQuery). You'll be saving yourself the request round trip, regardless of whether you go ajax or a full page refresh.
If the data set is large and it doesn't make sense to put all values in the markup, and you want to use MVC views instead of modifying the DOM with an ajax call, then just refresh the entire page. If you want to go ajax, then just modify the DOM with jQuery; you don't need a partial view to accomplish this.
You are going to have to use javascript unless you want to do an entire page postback. For this type of thing, javascript/ajax is the way to go. I personally had a hard time when I switched to MVC having to accept that all this business logic was happening outside of the MVC model. But in the end, it's whatever makes the website work best (user doesn't see your code and know how pretty it is).
Anyway, partials won't work either unless you post the whole page since without using javascript, the partial is rendered as part of that page/form.
I would just add a onchange event to the first dropdown that triggers a json call to a method in the same controller...something like
...jquery...
$("#mydropdown").change(function() {
$.post("/Controller/DropdownChangedJSON", { firstdropdownvalue: $("#mydropdown").val() }, function(data) {
$("#seconddropdown").empty();
// loop through "data" to populate dropdown
}); //post
}); //mydropdown_change()
and in your controller:
public JsonResult DropdownChangedJSON(string firstdropdownvalue) {
//get results
List<datamodel> myarray = //something
return new JsonResult { Data = new { success = true, rows = myarray } };
}
hope this helps
I've got hard time resolving the following. My problem is quite simple : I would like to highlight in red the forms fields that triggered validation errors. The error messages are placed correctly in the FacesContext using a context.addMessage(...) line.
I'd like my system to be generic. All form fields having a message attached are automatically highlighted.
I've found on this site a link to this excellent article :
http://www.jroller.com/mert/entry/how_to_find_a_uicomponent
With it, I did implement a PhaseListener for the RENDER_RESPONSE phase, which do the following :
#Override
public void beforePhase(PhaseEvent event) {
// get context
FacesContext context = event.getFacesContext();
// iterate on all the clientIds which have messages
Iterator<String> clientIdsWithMessages = context.getClientIdsWithMessages();
while (clientIdsWithMessages.hasNext()) {
// get the clientId for the field component
String clientIdWithMessage = clientIdsWithMessages.next();
// split on ":"
String[] splitted = clientIdWithMessage.split(":");
UIComponent component = findComponentInRoot(splitted[splitted.length - 1]);
if (component != null) {
Map<String, Object> attributes = component.getAttributes();
if (attributes.containsKey("style")) {
attributes.remove("style");
}
attributes.put("style", "background-color: #FFE1E1;");
}
}
}
This perform perfectly well for almost all my usage.
Now, where it becomes a bit tricky, is that some of my forms have such code :
<ice:dataTable id="revisionDocuments" value="#{agendaBean.agenda.revisionsDocuments}" var="revision">
<ice:column>
<ice:inputText value="#{revision.sequenceAdresse}" id="revisionSequenceAdresse" />
</ice:column>
....
The generated form has several lines (one for each object of the revisionsDocuments list), and each element has a unique identifier (clientId) which looks like :
contentForm:revisionDocuments:0:revisionSequenceAdresse
With 0 changed for 1, 2, ... for each iteration.
Consequently, the code provided to search the UIComponent from ViewRoot does not work properly. All forms fields have the same "id". What surprise me more is : they have the same "clientId" in FacesContext too :
contentForm:revisionDocuments:revisionSequenceAdresse
I cannot distinguish, while going through the tree, if I do see the right form field or any of the others.
Does anyone have a hint to solve this ? Or another suggestion to implement the highlight of my fields ? I have to admit, I dont really like my code, I consider dirty to manipulate the viewRoot like I'm doing, but I could not figure out a better solution to have a generic highlight of my fields.
I'm running IceFaces 2.0.2 with JSF-Impl 2.1.1-b04 on JBOss AS 7.0.2.Final.
Thank you in advance for the answers.
Best regards,
Patrick
You should apply this in the client side instead. You've got a collection of client IDs with messages. One of the ways is to pass this information to JavaScript and let it do the job. You can find an example of such a PhaseListener in this article: Set focus and highlight in JSF.
Since JSF 2.0 there is however another way without the need for a PhaseListener. There's a new implicit EL variable, #{component} which refers to the UIComponent instance of the current component. In case of UIInput components, there's an isValid() method. This allows you to do something like:
<h:inputText styleClass="#{component.valid ? '' : 'error'}" />
with this in a CSS file:
.error {
background: #ffe1e1;
}
(yes, you can also do this in a style attribute, but mingling style with markup is a poor practice)
To abstract this away (so that you don't need to repeat it in every input), you can just create a composite component for this, something like <my:input>.
For completeness, here is the solution I finally found to highlight the fields that do have error messages with IceFaces 2.0.2 :
The basic idea is strictly the same than proposed by BalusC on http://balusc.blogspot.com/2007/12/set-focus-in-jsf.html
The piece of code I had to change with IceFaces is the small Javascript call :
<script>
setHighlight('${highlight}');
</script>
I could not find any IceFaces component which is re-rendered at each JS call. I found that placing the script into a panelGroup works most of the time. However, there was a case that did not work :
submitting the form with errors do trigger the JS.
then, re-submitting the form with errors on the same field than previous validation do NOT trigger the JS.
then, re-submitting the form with any error field having no more errors do trigger JS.
then, re-submitting the form with any non-errored field having an error do trigger JS.
For some reason, IceFaces do not render the panelGroup that contains the JS when the set of fields having errors is the same between two calls.
I tried to use the Javascript API with code like Ice.onAsynchronousReceive(), using Prototype library to attach an event to the AJAX completion of the commandButton, but had not much success with it. Some of my tests could run (with errors but did the job) and I could observe similar behavior.
Here is the trick I finally used (ugly but working) :
<ice:panelGroup>
<script type="text/javascript">
var useless = '#{testBean.time}';
setHighlight('${highlight}');
</script>
</ice:panelGroup>
The getTime() function simply return the current timestamp. The value is then always different and trigger the JS execution at any AJAX request.
Sadly, IceFaces do not have the RichFaces useful "oncomplete" attribute, which I do regret highly for this case.
Ugly solution, but funny and working.
I am new to ASP.NET MVC, particularly ajax operations. I have a form with a jquery dialog for adding items to a drop-down list. This posts to the controller action.
If nothing (ie void method) is returned from the Controller Action the page returns having updated the database, but obviously there no chnage to the form. What would be the best practice in updating the drop down list with the added id/value and selecting the item.
I think my options are:
1) Construct and return the html manually that makes up the new <select> tag
[this would be easy enough and work, but seems like I am missing something]
2) Use some kind of "helper" to construct the new html
[This seems to make sense]
3) Only return the id/value and add this to the list and select the item
[This seems like an overkill considering the item needs to be placed in the correct order etc]
4) Use some kind of Partial View
[Does this mean creating additional forms within ascx controls? not sure how this would effect submitting the main form its on? Also unless this is reusable by passing in parameters(not sure how thats done) maybe 2 is the option?]
UPDATE:
Having looked around a bit, it seems that generating html withing the controller is not a good idea. I have seen other posts that render partialviews to strings which I guess is what I need and separates concerns (since the html bits are in the ascx). Any comments on whether that is good practice.
look at the ContentResult you can specify the mime type of what you return (text/html)
You could alternatively make a control that take a IEnumerable of whatever you put in the selectlist, and build it using the view engine. That way you keep the formatting of the html (in this case a list of options) into a view, and not in your code.
<%# Control Language="C#"Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Article>>"%>
<%foreach (var article in Model){%>
<option><%:article.Title %></option>
<%} %>
I think I would go for that second one
From what I understood, the jQuery dialog contains a form that, when submitted, will post to an action which updates the database with some information. You want to get the newly added database information and update the same form that was used to trigger the database update.
If that is the case, then I think the best clean and logical option is to return JSON serialization of the items to be put in the drop down right after you update the database. Then, using jQuery, you would clear the drop down and append option tags into it.
You can also write a new, seperate action that returns the JSON serialization of the database objects you need. You would have jQuery call another post to this action as a callback to your first ajax post (the one used to update the database).
Here is a quick snippet
public ActionResult UpdateDatabase(string something)
{
/// update the database
IEnumerable<Items> items = getItemsFromDatabase(); // or w/e
var vals = items.Select(x=> new { value = x.ID, text = x.Name }); // something similar
return Json(vals);
}
Personally, I would write a separate function that returns JSON. This ensure separation of concerns, and gives me a function I can use in many different places.
Returning a JsonResult with all the items is the most versatile and least-bandwidth intensive solution as long as you are happy to iterate through the list in jQuery and update your drop-down list.
Using a partial view is nice for HTML that you can .load(...) directly into your select, but less versatile.
I would go with the JsonResult.
In your Controller:
public JsonResult UpdateItem(string sItem)
{
// 1. Insert new item into database if not exist...
// {update code here}
// 2. retrieve items from database:
IEnumerable<Item> Items = GetItems();
// 3. return enumerable list in JSON format:
return new JsonResult{ Data = new {Items = Items, Result = "OK" }};
}
On client-side:
Iterate through Items array and add the items to your list.