How to keep model up to date with dynamic datasource? - asp.net-mvc

In the case that I am fetching Country and State data from an API, I can't seem to keep the link to the model after updating a kendo combobox's data source.
Let's say that there is a form that will be used internationally. So we want to provide a Country Combobox and a State Combobox and then filter the state by the selected country. If the user selects a country, then the state combobox updates. This is great! However, when I go to submit, the stateid field in the model is null.
I've tried manually changing the datasource, and can't really find any more information on this kind of issue. If anyone can point me in the right direction, that would be greatly appreciated.
Data model:
class ExampleModel {
[display(name='StateId')]
int? stateId;
[display(name='CountryId')]
int? countryId
}
razor:
<div id='country-wrapper'>
#(Html.Kendo().ComboBoxFor(m => m.CountryId)
.Filter("contains")
.Placeholder("-- Select Country --")
.DataTextField("Text")
.DataValueField("Value")
.BindTo(new SelectList(stateList, "CountryId", "CountryName", 1))
.Suggest(true)
.HtmlAttributes(new { #id = "CountryId", onchange = " CountryChangeEvent(this); ComboChangeEvent(this);"
}));
</div>
<div id='state-wrapper'>
#(Html.Kendo().ComboBoxFor(m => m.StateId)
.Filter("contains")
.Placeholder("-- Select State --")
.DataTextField("Text")
.DataValueField("Value")
.BindTo(new SelectList(stateList, "StateId", "StateName"))
.Suggest(true)
.HtmlAttributes(new { #id = "StateId", onchange = "ComboChangeEvent(this)"
}));
</div>
js:
function CountryChangeEvent(elem) {
var chosenId = $("#CountryId").data('kendoComboBox').value();
if (!chosenId) return;
$("#ProfileStateId").kendoComboBox({
dataTextField: "Text",
dataValueField: "Value",
dataSource: {
transport: {
read: {
// call this method in the controller and reset datasource
cache: false,
serverFiltering: true,
async: false,
dataType: "json",
url: '#Url.Action("GetStatesByCountryJson", "Home")?countryId=' + chosenId,
}
}
},
filter: "contains",
suggest: true
});
var combobox = $("#StateId").data('kendoComboBox');
combobox.select(function (dataItem) {
// clear the statecombobox
return dataItem.Value === '';
});
}
Expected
I would expect that the state list would change when the CountryChangeEvent function is called. Also, when you submit the form, the StateId value would be stored in the model.
Actual
It does in fact change the state combobox visually. You can see all of the states and filter. However, when you submit and debug, you can see that the StateId in the model is null.
Current workaround/hack
I have found that if I download all states(for all countries) in the beginning, then it works as intended.

I found another workaround/hack that works much better than downloading all states on load. I changed the control from a combo box to a drop down list and voila, it works now.
No idea why this didn't work, but I'll close it since my problem was fixed, and a combo box is really over kill for this kind of problem anyway.
If you come across this problem hoping for a solution look into cascading dropdownlist
Courtesy of CarstenFranke
Should work better for you.

Related

ASP.NET: How to get from Dropdownlist to Autocomplete

I have a dropdownlist and want to turn it into autocomplete using jquery.
The dropdown looks like this and works:
#Html.DropDownListFor(m => m.CategoryID, new SelectList(Model.Categories, "ID", "Name", Model.CategoryID), "Categories", new { #class = "form-control" })
I also added an autocomplete field using jquery that works. But so far I can only populate it with dummy data:
$(function () {
var availableTags = [
"ActionScript",
"AppleScript",
"Asp"
];
$("#tags").autocomplete({
source: availableTags
});
});
How can I populate my dropdown field with the data that is available in the dropdown?
Thank you in advance!
You need to set the source as an action method which returns data you want to show as autocomplete option in JSON format.
$(function(){
$("#tags" ).autocomplete({
source: "#Url.Action("SearchCategories","Home")",
minLength: 1,
select: function (event, ui) {
//If you want to do something on the select event, you may do it here
//$("#tags").html(ui.item.label);
}
});
})
Make sure you have an action method called SearchCategories in your HomeController which returns the data you want.
public ActionResult SearchCategories(string term)
{
var db= new MyDbContext();
var results = db.Categories.Where(s => s.Name.StartsWith(term))
.Select(x=>new { id =x.Id,
label =x.Name }).ToList();
return Json(results,JsonRequestBehavior.AllowGet);
}
This should enable the autocomplete on an input with id tags,assuming you have jQuery ui library(and it's dependencies) loaded properly in your page and you do not have any other script errors in your page. I have used Url.Action helper method to generate the correct relative url to the action method. It will work fine if your js code is inside a razor view. But if your code is inside an external js file, you should follow the approach described in this post.

how to catch PageSizeChanged event of telerik MVC Grid (NOT KENDO GRID)

I am working on asp.net MVC 4 application in which i am using telerik MVC Grid (NOT KENDO GRID).
http://www.telerik.com/help/aspnet-mvc/telerik-ui-components-grid-client-api-and-events.html
in my page (cshtml) i am having the grid using the common partial page.
in the page, i am also having other input elements ( which acts as filter criteria for grid data).User selects the values from input elements and click on submit button and data in grid is filled accordingly (using post requst in MVC code).
I am using paging in grid and user may select the page size.
Grid set up
Html.Telerik().Grid(Model)
.Name("Employees")
.Columns(cols =>
{
cols.Bound(e => e.FirstName).Title("First Name");
cols.Bound(e => e.LastName).Title("Last Name");
cols.Bound(e => e.SSN).Title("SSN");
cols.Bound(e => e.HireDate).Format("{0:MM/dd/yyyy}").Title("Hire Date");
cols.Bound(e => e.GroupName).Title("Department GROUP");
})
.Groupable()
.Sortable(x => x.OrderBy(z=>z.GetType()))
.Pageable(p => p.PageSize(5).Style(GridPagerStyles.PageSizeDropDown | GridPagerStyles.NextPreviousAndNumeric).PageTo((Model.Any()&&!string.IsNullOrWhiteSpace(Model.First().PageNumber)?Convert.ToInt32(Model.First().PageNumber):1)))
.Filterable()
.Render();
the POST method in employee controller returns the filtered data by accepting the model values and querying the database.
now when changes the page size of the grid,request is sent to GET method of the controller instead of post.
How to catch PageSizeChanged like event of grid in JavaScript, so that i can manually post the form wherever user change page size?
EDIT : I have written following code to get click event whenever user clicks on number in page size dropdown
$(document).on('click', 'ul.t-reset > li.t-item', function (e) {
//how to avoid default functionality of server call?
//tried following
//event.unbind();
//event.preventDefault();
//alert("Was preventDefault() called: " + event.isDefaultPrevented());
//event.stopImmediatePropagation()
//return false;
});
MVC Grids just get turned into Kendo Grid javascript, so all the API methods/events and so on can be used with the MVC Grid.
Check out the kendo grid page change event and just use that.
http://docs.telerik.com/kendo-ui/api/javascript/ui/pager#events-change
var grid = $("#Employees").data('kendoGrid');
Now you have the grid, you can do whatever you wish to it. Subscribe to the page event change like the example from Kendo API documentation;
<div id="pager"></div>
<script>
function pager_change() {
console.log("pager change event");
}
var dataSource = new kendo.data.DataSource({
data: [
{ productName: "Tea", category: "Beverages" },
{ productName: "Coffee", category: "Beverages" },
{ productName: "Ham", category: "Food" },
{ productName: "Bread", category: "Food" }
],
pageSize: 2
});
dataSource.read();
var pager = $("#pager").kendoPager({
dataSource: dataSource
}).data("kendoPager");
pager.bind("change", pager_change);
</script>
I like to set up the grid with MVC and if I need to get into its workings and do a lot of client side magic I'll just grab it as a Kendo grid.

jQuery Autocomplete wrong item in text box on select

When a user begins typing in the DRMCompanyName input text box, and autocomplete feature fires that displays both the company name and the company id. When the use clicks on a selection, the company name and id are to be placed into the DRMCompanyName text box and the id in the DRMCompanyId text box just below.
When the json results are returned from the controller, the code in the autocomplete ajax success function populates the drop down list by setting the label to be equal to the value (company name) plus the key (company id) being returned. Likewise the value is set to just the key (company id).
When the user selects a particular item, the label is supposed to go in the DRMCompanyName text box and the value in the DRMCompanyId. However, what winds up happening is the value gets placed in both.
I've scoured my code over and over and cannot find out why the label does not get placed in the DRMCompanyName field.
jQuery
$(function () {
$('#DRMCompanyName').autocomplete({
source: function (request, response) {
$.ajax({
url: '#Url.Action("compSearchByName", "AgentTransmission")',
type: 'GET',
dataType: 'json',
data: request,
success: function (data) {
response($.map(data, function (value, key) {
return {
label: value + " " + key,
value: key
};
}));
},
});
},
minLength: 2,
select: function (event, ui) {
console.log(ui);
$('#DRMCompanyName').val(ui.item.label);
$('#DRMCompanyName').text(ui.item.label);
if ($('#DRMCompanyId').text() == '') {
$('#DRMCompanyId').val(ui.item.value);
$('#DRMCompanyId').text(ui.item.value);
}
}
});
});
Here is a sample screen shot of the ui item from the select function above (the company name is blacked out for privacy). When I click on this particular item in the autocomplete drop down, 200014 gets placed in both the DRMCompanyName and DRMCompanyId fields.
Razor Markup
<div class="M-editor-field">
#Html.TextBoxFor(model => model.DRMCompanyName)
#Html.ValidationMessageFor(model => model.DRMCompanyName)
</div>
<div class="M-editor-label">
#Html.LabelFor(model => model.DRMCompanyId)
</div>
<div class="M-editor-field">
#Html.TextBoxFor(model => model.DRMCompanyId, new { maxlength = 10, title = "Start typing company name to activate DRM Company Name lookup. When DRM Company is found, select to fill in DRM Company ID and DRM Company Name fields." })
#Html.ValidationMessageFor(model => model.DRMCompanyId)
</div>
EDIT
After following the suggestion in the answer below, I modified the select function like so:
select: function (event, ui) {
console.log(tempResults[ui.item.value]);
$('#DRMCompanyName').val(tempResults[ui.item.value]);
$('#DRMCompanyName').text(tempResults[ui.item.value]);
if ($('#DRMCompanyId').text() == '') {
$('#DRMCompanyId').val(ui.item.value);
$('#DRMCompanyId').text(ui.item.value);
}
}
Based on the console.log readout, this accesses the correct value when the user clicks on the autocomplete item. However, it still places the value in both text boxes. What I can't understand, when I select Inspect Element, is that the correct value for DRMCompanyName actually is placed in the HTML, however it does not appear on the screen, only the id or value (as opposed to label).
You are setting label: value + " " + key which will of course add the id in the label.
When you set $('#DRMCompanyName').val(ui.item.label);, it's going to set what you concatenated in the $.map to the value.
One way to do this is to store a temporary result set from the data in the source ajax call to access later. With this temp set, you can now pull any object or key/value from it for use later.
When getting results, store a temporary list of the results.
var tempResults = [];
...
source: function (request, response) {
$.ajax({
url: '#Url.Action("compSearchByName", "AgentTransmission")',
type: 'GET',
dataType: 'json',
data: request,
success: function (data) {
tempResults = data;
response($.map(data, function (value, key) {
return {
label: value + " " + key,
value: key
};
}));
},
});
}
Then, on the select, you can now access the stored data and set values:
select: function (event, ui) {
event.preventDefault();
var name = tempResults[ui.item.value].value;
var id = tempResults[ui.item.value].key;
$('#DRMCompanyName').val(name);
$('#DRMCompanyName').text(name);
if ($('#DRMCompanyId').text() == '') {
$('#DRMCompanyId').val(id);
$('#DRMCompanyId').text(id);
}
}
EDIT
Forgetting one minor thing! Add this to the beginning of the select: function!
event.preventDefault();
By default, when selecting, the autocomplete will use the ui.item.value to populate the element that it's wired up with. Using event.preventDefault() will prevent the already wired up event handler to be called used within the autocomplete.
event.preventDefault() documentation.

What is the sequence or flow to implement this view in MVC4

I'm still learning about MVC4.
Imagine the following scenario: I've got three dropDownLists and one big div for the content. I don't know how to deal the loading on demand.
The flow is simple, when page is loaded, display the data in the first dropdownlist. When this one has a value, the second one should load the information on demand (using the selected value from ddl1) and so on, until change the value on the ddl3 and displays the data.
Until here I have detected two partial views. I'm not sure if I should create 5 because each ddl must be in one partial view.
Another thing, what would you recomend to maintain the SelectList, should I have to use ViewBag or maintain in a viewModel the collections foreach ddl?
I just one to know if you can clarrify this scenario. I mean, give an idea about how can I start doing this? In fact, I forgot to mention this doubt but I don't if I have to use AJAX.
I got stuck , few weeks back in same kind of stuff:-
Let your MVC calls be like :-
private void LoadDropdown1()
{
var _data;//Your logic to get the data
ViewData["Dropdown1"] = new SelectList(_data, "Dropdown1Id", "Name");
}
private void LoadDropdown2(int dropdownParameterId)
{
var _data = "";//Use your ID to get the data
ViewData["Dropdown2"] = new SelectList(_data, "Dropdown2Id", "Name");
}
Your .cshtml be :-
#using (Html.BeginForm())
{
<div>
<table>
<tr>
<td><b>Select a District:</b></td>
<td>#Html.DropDownListFor(m => m.Dropdown1Id, ViewData["Dropdown1"] as IEnumerable<SelectListItem>, "Select One", new {#id="Dropdown1Id"})</td>
</tr>
<tr>
<td><b>Select:</b></td>
<td>#Html.DropDownListFor(m => m.Dropdown2Id, ViewData["Dropdown2"] as IEnumerable<SelectListItem>, "Select One")</td>
</tr>
</table>
</div>
}
Now AJAX call is best to load data to your dropdown:-
$(function () {
$('select#Dropdown1').change(function () {
var id = $(this).val();
$.ajax({
url: 'Bla Bla',
type: 'POST',
data: JSON.stringify({ id: id }),
dataType: 'json',
contentType: 'application/json',
success: function (data) {
$.each(data, function (key, data) {
$('select#Dropdown1').append('<option value="0">Select One</option>');
// loop through the LoadDropdown1 and fill the dropdown
$.each(data, function (index, item) {
$('select#Dropdown1').append(
'<option value="' + item.Id + '">'
+ item.Name +
'</option>');
});
});
}
});
});
});
What i am trying to say is.. Load your 1st dropdown the way you prefer. Then On change event of 1st dropdown, you can fire an ajax call to fetch data for 2nd dropdown.... Similarly..
Reference:- on select change event - Html.DropDownListFor

Cascading Combobox with default values on telerik mvc

I have a search page and want to add some filters to it, my setup is quite simple, I have a CombobBox for Countries and a ComboBox for States.
I want to show ALL the states if no country is selected(actually, the first item of the countries combobox is "All") here is my code:
#(Html.Telerik().ComboBoxFor(m => m.Country)
.Name("cbxCountry")
.BindTo(this.Model.CountryList)
.SelectedIndex(0).CascadeTo("cbxStates"))
#(Html.Telerik().ComboBoxFor(m=>m.State)
.Name("cbxStates")
.DataBinding(binding => binding.Ajax()
.Select("AjaxLoadStates","States")))
Note that even if the .SelectedIndex is set to 1, 3, 1231231 the second combobox keeps disabled until I select a value. Is there any way to make this work?
You can do this with the client API in javascript:
<script type="text/javascript">
function SelectFirstCountry() {
var cbxCountry = $("#cbxCountry").data('tComboBox')
var cbxStates = $("#cbxStates").data('tComboBox')
cbxCountry.select(1);
cbxStates.enable();
}
$(document).ready(function () {
#{
Html.Telerik().ScriptRegistrar().OnDocumentReady("SelectFirstCountry()");
}
});
</script>

Resources