This is a simple MVC4 application with single drop down list. When I click the Save button an event for SaveWorkRequestDetails should fire, seen below as the [HttpPost], but it doesn't for some reason. I am seeing a 500 error stating that myMachine:12345/WorkRequest/SaveWorkRequestDetails cannot be found when I hit the Save button. See bottom of post for picture of Console window in Chrome.
Controller
public ActionResult Index()
{
_model = new WorkRequestViewModel()
{
WorkSections = GetWorkSections()
};
return View(_model);
}
[HttpPost]
public JsonResult SaveWorkRequestDetails(WorkRequestViewModel viewModel)
{
// TODO: Save logic goes here
return Json(new {});
}
This is the WorkRequest.js file which should fire the Save event. This does trigger said event IF I DON'T include _model in the return of the View from the ActionResult Index() above, however, the alert seen in the WorkRequest.js file in beforeSend has 'null' when I do that. Clearly, this isn't the intended behavior because I do want those values and the Save button to trigger the [HttpPost].
var WorkRequest = {
PrepareKo: function () {
ko.bindingHandlers.date = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
element.onchange = function () {
var observable = valueAccessor();
observable(new Date(element.value));
}
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var observable = valueAccessor();
var valueUnwrapped = ko.utils.unwrapObservable(observable);
if ((typeof valueUnwrapped == 'string' || valueUnwrapped instanceof String) &&
valueUnwrapped.indexOf('/Date') === 0) {
var parsedDate = WorkRequest.ParseJsonDate(valueUnwrapped);
element.value = parsedDate.getMonth() + 1 + "/" +
parsedDate.getDate() + "/" + parsedDate.getFullYear();
observable(parsedDate);
}
}
};
},
ParseJsonDate: function (jsonDate) {
return new Date(parseInt(jsonDate.substr(6)));
},
BindUIwithViewModel: function (viewModel) {
ko.applyBindings(viewModel);
},
EvaluateJqueryUI: function () {
$('.dateInput').datepicker();
},
RegisterUIEventHandlers: function () {
$('#Save').click(function (e) {
// Check whether the form is valid. Note: Remove this check, if you are not using HTML5
if (document.forms[0].checkValidity()) {
e.preventDefault();
$.ajax({
type: "POST",
url: WorkRequest.SaveUrl,
data: ko.toJSON(WorkRequest.ViewModel),
contentType: 'application/json',
async: true,
beforeSend: function () {
// Display loading image
alert(ko.toJSON(WorkRequest.ViewModel));
},
success: function (result) {
// Handle the response here.
},
complete: function () {
// Hide loading image.
},
error: function (jqXHR, textStatus, errorThrown) {
// Handle error.
}
});
}
});
},
};
$(document).ready(function () {
WorkRequest.PrepareKo();
WorkRequest.BindUIwithViewModel(WorkRequest.ViewModel);
WorkRequest.EvaluateJqueryUI();
WorkRequest.RegisterUIEventHandlers();
});
This is my Index.cshtml
#using Microsoft.Ajax.Utilities
#model WorkRequest.ViewModel.WorkRequestViewModel
#using WorkRequest.Helper
#section styles{
#Styles.Render("~/Content/themes/base/css")
<link href="~/Content/WorkRequest.css" rel="stylesheet" />
}
#section scripts{
#Scripts.Render("~/bundles/jqueryui")
<script src="~/Scripts/knockout-2.2.0.js"></script>
<script src="~/Scripts/knockout.mapping-latest.js"></script>
<script src="~/Scripts/Application/WorkRequest.js"></script>
<script type="text/javascript">
WorkRequest.SaveUrl = '#Url.Action("SaveWorkRequestDetails", "WorkRequest")';
WorkRequest.ViewModel = ko.mapping.fromJS(#Html.Raw(Json.Encode(Model)));
</script>
}
<form>
<div class="mainWrapper">
<table>
<tr>
<td>Work Sections:
</td>
<td>
#Html.DropDownList("WorkSections", Model.WorkSections, new {data_bind="value: WorkRequest.ViewModel.WorkSections"})
</td>
</tr>
</table>
</div>
<br />
<input id="Save" type="submit" value="Save" />
</form>
Now this entire sample is based on a demo I found here: http://www.codeproject.com/Articles/657981/ASP-NET-MVC-4-with-Knockout-Js and I was able to get it working just fine until I tried to populate my own drop down list. I am able to get the data for my drop down list and populate my SelectList without issues and see it on my form.
My View Model
public class WorkRequestViewModel
{
public SelectList WorkSections { get; set; }
}
I have confirmed this SelectList is populated and I see the items in my drop down list but the demo application uses two custom classes to make the controls in the Index.cshtml page observable so that knockout can do its thing...
HtmlExtensions.cs
public static class HtmlExtensions
{
/// <summary>
/// To create an observable HTML Control.
/// </summary>
/// <typeparam name="TModel">The model object</typeparam>
/// <typeparam name="TProperty">The property name</typeparam>
/// <param name="htmlHelper">The <see cref="HtmlHelper<T>"/></param>
/// <param name="expression">The property expression</param>
/// <param name="controlType">The <see cref="ControlTypeConstants"/></param>
/// <param name="htmlAttributes">The html attributes</param>
/// <returns>Returns computed HTML string.</returns>
public static IHtmlString ObservableControlFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel,
TProperty>> expression, string controlType =
ControlTypeConstants.TextBox, object htmlAttributes = null)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
string jsObjectName = null;
string generalWidth = null;
// This will be useful, if the same extension has
// to share with multiple pages (i.e. each with different view models).
switch (metaData.ContainerType.Name)
{
case "WorkRequestViewModel":
// Where WorkRequest is the Javascript object name (namespace in theory).
jsObjectName = "WorkRequest.ViewModel.";
generalWidth = "width: 380px";
break;
default:
throw new Exception(string.Format("The container type {0} is not supported yet.",
metaData.ContainerType.Name));
}
var propertyObject = jsObjectName + metaData.PropertyName;
TagBuilder controlBuilder = null;
// Various control type creation.
switch (controlType)
{
case ControlTypeConstants.TextBox:
controlBuilder = new TagBuilder("input");
controlBuilder.Attributes.Add("type", "text");
controlBuilder.Attributes.Add("style", generalWidth);
break;
case ControlTypeConstants.Html5NumberInput:
controlBuilder = new TagBuilder("input");
controlBuilder.Attributes.Add("type", "number");
controlBuilder.Attributes.Add("style", generalWidth);
break;
case ControlTypeConstants.Html5UrlInput:
controlBuilder = new TagBuilder("input");
controlBuilder.Attributes.Add("type", "url");
controlBuilder.Attributes.Add("style", generalWidth);
break;
case ControlTypeConstants.TextArea:
controlBuilder = new TagBuilder("textarea");
controlBuilder.Attributes.Add("rows", "5");
break;
case ControlTypeConstants.DropDownList:
controlBuilder = new TagBuilder("div");
controlBuilder.Attributes.Add("class", "dropDownList");
break;
case ControlTypeConstants.JqueryUIDateInput:
controlBuilder = new TagBuilder("input");
controlBuilder.Attributes.Add("type", "text");
controlBuilder.Attributes.Add("style", generalWidth);
controlBuilder.Attributes.Add("class", "dateInput");
controlBuilder.Attributes.Add("data-bind", "date: " + propertyObject);
// date is the customized knockout binding handler. Check PrepareKo method of Person.
break;
default:
throw new Exception(string.Format("The control type {0} is not supported yet.", controlType));
}
controlBuilder.Attributes.Add("id", metaData.PropertyName);
controlBuilder.Attributes.Add("name", metaData.PropertyName);
// Check data-bind already exists, add if not.
if (!controlBuilder.Attributes.ContainsKey("data-bind"))
{
controlBuilder.Attributes.Add("data-bind", "value: " + propertyObject);
}
// Merge provided custom html attributes. This overrides the previously defined attributes, if any.
if (htmlAttributes != null)
{
controlBuilder.MergeAttributes(
HtmlExtensions.AnonymousObjectToHtmlAttributes(htmlAttributes), true);
}
return MvcHtmlString.Create(controlBuilder.ToString());
}
/// <summary>
/// To convert '_' into '-'.
/// </summary>
/// <param name="htmlAttributes">The html attributes.</param>
/// <returns>Returns converted <see cref="RouteValueDictionary"/>.</returns>
private static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes)
{
RouteValueDictionary result = new RouteValueDictionary();
if (htmlAttributes != null)
{
foreach (System.ComponentModel.PropertyDescriptor property in
System.ComponentModel.TypeDescriptor.GetProperties(htmlAttributes))
{
result.Add(property.Name.Replace('_', '-'), property.GetValue(htmlAttributes));
}
}
return result;
}
}
ViewModelConstants.cs
public static class ControlTypeConstants
{
public const string TextBox = "TextBox";
public const string TextArea = "TextArea";
public const string CheckBox = "CheckBox";
public const string DropDownList = "DropDownList";
public const string Html5NumberInput = "Html5NumberInput";
public const string Html5UrlInput = "Html5UrlInput";
public const string Html5DateInput = "Html5DateInput";
public const string JqueryUIDateInput = "JqueryUIDateInput";
}
So the root of my issue is that once the UI is successfully populated with a drop down list containing values, I select one and hit the save button and I see the alert display what is in the model but the Save button is never triggered.
In the example provided from CodeProject a control has this custom ObservableControlFor after #Html, see above class HtmlExtensions. This is what it looks like: #Html.ObservableControlFor(model => model.Name, ControlTypeConstants.TextBox) so I tried to do the same in my example but I don't know how to turn #Html.DropDownList("myList") into #Html.ObservableControlFor... I tried the following #Html.ObservableControlFor(model => model.WorkSections, ControlTypeConstants.DropDownList) but the control is never rendered to the UI and I get the same 500 error as before.
I have tried ViewData in my #Html control but the function is expecting something like model => model.WorkSections
HTML Markup from View Source
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title></title>
<link href="/Content/site.css" rel="stylesheet"/>
<script src="/Scripts/modernizr-2.6.2.js"></script>
<link href="/Content/themes/base/jquery.ui.core.css" rel="stylesheet"/>
<link href="/Content/themes/base/jquery.ui.resizable.css" rel="stylesheet"/>
<link href="/Content/themes/base/jquery.ui.selectable.css" rel="stylesheet"/>
<link href="/Content/themes/base/jquery.ui.accordion.css" rel="stylesheet"/>
<link href="/Content/themes/base/jquery.ui.autocomplete.css" rel="stylesheet"/>
<link href="/Content/themes/base/jquery.ui.button.css" rel="stylesheet"/>
<link href="/Content/themes/base/jquery.ui.dialog.css" rel="stylesheet"/>
<link href="/Content/themes/base/jquery.ui.slider.css" rel="stylesheet"/>
<link href="/Content/themes/base/jquery.ui.tabs.css" rel="stylesheet"/>
<link href="/Content/themes/base/jquery.ui.datepicker.css" rel="stylesheet"/>
<link href="/Content/themes/base/jquery.ui.progressbar.css" rel="stylesheet"/>
<link href="/Content/themes/base/jquery.ui.theme.css" rel="stylesheet"/>
<link href="/Content/WorkRequest.css" rel="stylesheet" />
</head>
<body>
<form>
<div class="mainWrapper">
<table>
<tr>
<td>Work Sections:
</td>
<td>
<div class="dropDownList" data-bind="value: WorkRequest.ViewModel.WorkSections" id="WorkSections" name="WorkSections"></div>
</td>
</tr>
</table>
</div>
<br />
<input id="Save" type="submit" value="Save" />
</form>
<script src="/Scripts/jquery-1.8.2.js"></script>
<script src="/Scripts/jquery-ui-1.8.24.js"></script>
<script src="/Scripts/knockout-2.2.0.js"></script>
<script src="/Scripts/knockout.mapping-latest.js"></script>
<script src="/Scripts/Application/WorkRequest.js"></script>
<script type="text/javascript">
WorkRequest.SaveUrl = '/WorkRequest/SaveWorkRequestDetails';
WorkRequest.ViewModel = ko.mapping.fromJS({"WorkSectionId":0,"WorkSections":[{"Selected":false,"Text":"308:IPACS","Value":"308"},{"Selected":false,"Text":"312:IPACS","Value":"312"},{"Selected":false,"Text":"301:IPACS","Value":"301"},{"Selected":false,"Text":"316:IPACS","Value":"316"},{"Selected":false,"Text":"307:IPACS","Value":"307"},{"Selected":false,"Text":"318:IPACS","Value":"318"},{"Selected":false,"Text":"313:IPACS","Value":"313"},{"Selected":false,"Text":"319:IPACS","Value":"319"},{"Selected":false,"Text":"315:IPACS","Value":"315"},{"Selected":false,"Text":"310:IPACS","Value":"310"},{"Selected":false,"Text":"300:IPACS","Value":"300"},{"Selected":false,"Text":"302:IPACS","Value":"302"},{"Selected":false,"Text":"304:IPACS","Value":"304"},{"Selected":false,"Text":"306:IPACS","Value":"306"},{"Selected":false,"Text":"309:IPACS","Value":"309"},{"Selected":false,"Text":"305:STORM","Value":"305"},{"Selected":false,"Text":"311:IPACS","Value":"311"},{"Selected":false,"Text":"317:IPACS","Value":"317"},{"Selected":false,"Text":"303:IPACS","Value":"303"},{"Selected":false,"Text":"314:IPACS","Value":"314"}]});
</script>
</body>
</html>
I think your issue may lie in these two lines of code:
data: ko.toJSON(WorkRequest.ViewModel),
contentType: 'application/json',
You should remove the contentType line and change:
data: $.param(WorkRequest.ViewModel)
The issue issue is you are trying to pass the server JSON and it by default accepts application/x-www-form-urlencoded. contentType dictates the format passed back from the server. dataType is what you are looking for however by default .Net MVC wants the default.
If this doesnt work it would be helpful to see the stack trace from the exception that is causing your 500 error, although I would expect the issue is that your model is not binding since your data is in the wrong format.
Related
How to populate dropdownlist using WCF Rest Service:
First, this is my code to get service:
service.js
(function (app) {
app.service("GetCategoryService", function ($http) {
this.GetCategory = function () {
return $http.get("http://localhost:51458/ServiceRequest.svc/GetCategory/");
};
});
})(angular.module('entry'));
Entry.Ctrl
(function (app) {
'use strict';
app.controller('entryCtrl', entryCtrl);
entryCtrl.$inject = ['$scope'];
function entryCtrl($scope) {
$scope.pageClass = 'page-entry';
//To Get Category
$scope.Category = function () {
var promiseGet = GetCategoryService.GetCategory();
promiseGet.then(function (pl) { $scope.GetCategory = pl.data },
function (errorPl) {
console.log('Some Error in Getting Records.', errorPl);
});
}
}
})(angular.module('entry'));
This is entry.html
<div class="dropdown">
<select ng-model="Category" ng-options="item.ITEM_TEXT for item in Category"></select>
</div>
WCF Output JSON like:
[{"ITEM_TEXT":"INTERNAL APP"},{"ITEM_TEXT":"INTERNAL IT"}]
I don't know how to passing this to html, what I'm doing like this is not working. please help. thank
Make sure you have set ng-model different from the array name, also inject the service into your controller,
entryCtrl.$inject = ['$scope', 'GetCategoryService'];
DEMO
var app = angular.module('todoApp', []);
app.controller("dobController", ["$scope",
function($scope) {
$scope.Category = [{"ITEM_TEXT":"INTERNAL APP"},{"ITEM_TEXT":"INTERNAL IT"}];
}
]);
<!DOCTYPE html>
<html ng-app="todoApp">
<head>
<title>To Do List</title>
<link href="skeleton.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<script src="MainViewController.js"></script>
</head>
<body ng-controller="dobController">
<select ng-model="selected" ng-init="selected = Category[0]" ng-options="item.ITEM_TEXT for item in Category"></select>
</body>
</html>
I'm trying to write CRUD operations using ajax. Here some code:
These are my View classes:
//PhotoSummary
#model PhotoAlbum.WEB.Models.PhotoViewModel
<div class="well">
<h3>
<strong>#Model.Name</strong>
<span class="pull-right label label-primary">#Model.AverageRaiting.ToString("# stars")</span>
</h3>
<span class="lead">#Model.Description</span>
#Html.DialogFormLink("Update", Url.Action("UpdatePhoto", new {photoId = #Model.PhotoId}), "Update Photo", #Model.PhotoId.ToString(), Url.Action("Photo"))
</div>
//Main View
#model PhotoAlbum.WEB.Models.PhotoListViewModel
#{
ViewBag.Title = "My Photos";
}
#foreach (var p in #Model.Photos)
{
<div id=#p.PhotoId>
#Html.Action("Photo", new {photo = p})
</div>
}
The sript:
$('.dialogLink').on('click', function () {
var element = $(this);
var dialogTitle = element.attr('data-dialog-title');
var updateTargetId = '#' + element.attr('data-update-target-id');
var updateUrl = element.attr('data-update-url');
var dialogId = 'uniqueName-' + Math.floor(Math.random() * 1000)
var dialogDiv = "<div id='" + dialogId + "'></div>";
$(dialogDiv).load(this.href, function () {
$(this).dialog({
modal: true,
resizable: false,
title: dialogTitle,
close: function () { $(this).empty(); },
buttons: {
"Save": function () {
// Manually submit the form
var form = $('form', this);
$(form).submit();
},
"Cancel": function () { $(this).dialog('close'); }
}
});
$.validator.unobtrusive.parse(this);
wireUpForm(this, updateTargetId, updateUrl);
});
return false;
});});
function wireUpForm(dialog, updateTargetId, updateUrl) {
$('form', dialog).submit(function () {
if (!$(this).valid())
return false;
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function (result) {
if (result.success) {
$(dialog).dialog('close');
$(updateTargetId).load(updateUrl);
} else {
$(dialog).html(result);
$.validator.unobtrusive.parse(dialog);
wireUpForm(dialog, updateTargetId, updateUrl);
}
}
});
return false;
});
}
And here my Tag builder:
public static MvcHtmlString DialogFormLink(this HtmlHelper htmlHelper, string linkText, string dialogContentUrl,
string dialogTitle, string updateTargetId, string updateUrl)
{
TagBuilder builder = new TagBuilder("a");
builder.SetInnerText(linkText);
builder.Attributes.Add("href", dialogContentUrl);
builder.Attributes.Add("data-dialog-title", dialogTitle);
builder.Attributes.Add("data-update-target-id", updateTargetId);
builder.Attributes.Add("data-update-url", updateUrl);
builder.AddCssClass("dialogLink");
return new MvcHtmlString(builder.ToString());
}
So, I have major problem if the dialog was called twice without the calling page being refreshed:
it just redirects me to the action page.
The question is how to update #Html.Action without reloading the page?
Could anyone help me?
Your #foreach loop in the main view is generating a partial view for each Photo which in turn is creating a link with class="dialogLink".
Your script handles the click event of these links and replaces it with a new link with class="dialogLink". But the new link does not have a .click() handler so clicking on the new (replacement) link does not activate your script.
Instead you need to use event delegation to handle events for dynamically generated content using the .on() method (refer also here for more information on event delegation). Note also that your current use of $('.dialogLink').on('click', function () { is the equivalent of $('.dialogLink').click(function () { and is not using event delegation. It attaches a handler to elements that exist in the DOM at the time the page is loaded, not to elements that might be added in the future.
Change your html to
<div id="photos">
#foreach (var p in #Model.Photos)
{
<div class="photo">#Html.Action("Photo", new { photo = p })</div>
}
</div>
and then modify the script to
$('#photos').on('click', '.dialogLink', function() {
....
});
Side note: There is no real need to add an id=#p.PhotoId to the containing div element and you could use <div class="photo"> as per above, and then reference it by using var updateTargetId = $(this).closest('.photo'); and delete the builder.Attributes.Add("data-update-target-id", updateTargetId); line of code from your DialogFormLink() method
I have followed the tutorial located here:
http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api
However, the main difference is I am using a 3rd party library to get a list of objects which I would like to return to the client.
Here is my index.html file:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Get Routes</title>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js"></script>
</head>
<body>
<div>
<h2>All Routes</h2>
<ul id="routes" />
</div>
<script>
var uri = '../api/MH_Route_Selection';
$(document).ready(function () {
// Send an AJAX request
$.getJSON(uri)
.done(function (data) {
// On success, 'data' contains a list of products.
$.each(data, function (key, item) {
// Add a list item for the product.
$('<li>', { text: formatItem(item) }).appendTo($('#routes'));
});
});
});
</script>
</body>
</html>
My controller file:
namespace LMVeKanban.Controllers
{
public class MH_Route_SelectionController : ApiController
{
//GET ROUTES METHOD FOR URI: /api/MH_Route_Selection
[HttpGet]
public IHttpActionResult GetRouteList()
{
var routeManager = new LmvRouteManager(1);
List<Route> routes = routeManager.GetRoutes();
return Ok(Json(routes));
}
}
Everything works fine, the controller gets called and the return value routes, looks like: http://imageshack.com/a/img661/5648/ZQ59Rt.jpg
After the controller passes the list back, I get a 500 error from the server with no clue where it went wrong.
EDIT: Watching the Ok(routes) value shows that it has successfully converted the object:
EDIT 2: I have tried the following which also returns a 500 error in the same manner as the previous attempt
public HttpResponseMessage GetRouteList()
{
var routeManager = new LmvRouteManager(1);
List<Route> routes = routeManager.GetRoutes();
return Request.CreateResponse(HttpStatusCode.OK, routes);
}
Does anyone know how, or have any links on how to filter the data returned in a textbox that is based on the value of the selected item in a dropdownlist.
i.e If a user selects hotel from a list, then when they start typing in the textbox only the address of the companies whos category matches hotel will appear using autocomplete.
I have added my server side code below, but I'm getting the following error.
public JsonResult FilterDirectorySearch(string searchText, string context)
{
var wq = DirectoryList(searchText,context).Select(a =>
new { Location = a.strLocationSearch });
return Json(wq.Distinct(), JsonRequestBehavior.AllowGet);
}
private List<DisplayDirectoryDataForSearchBox> DirectoryList(string searchString, string context)
{
var wq = _IGTDD.DisplayDirectoryData()
.Where(a => a.strLocationSearch.Contains(searchString, StringComparison.OrdinalIgnoreCase) && a.strCategory = context).ToList();
return wq.ToList();
}
Errors
Error 1 Instance argument: cannot convert from 'string' to 'System.Linq.IQueryable'
Error 2 'string' does not contain a definition for 'Contains' and the best extension method overload 'System.Linq.Queryable.Contains(System.Linq.IQueryable, TSource, System.Collections.Generic.IEqualityComparer)' has some invalid arguments
Error 3 Argument 3: cannot convert from 'System.StringComparison' to 'System.Collections.Generic.IEqualityComparer'
Forgot to add this error for the code below
return (from x in wq where x.strLocationSearch.StartsWith(searchString, StringComparison.OrdinalIgnoreCase) && x.strCategory = context select x.strLocationSearch).AsEnumerable();
Error message: Error 1 Operator '&&' cannot be applied to operands of type 'bool' and 'string'
Any Autocomplete javascript tooling generally allows you to configure the source as a function, being either a javascript function and local data, or a function to an AJAX server source.
Within either the local javascript function or the remove AJAX responder, you can simply add the extra context data, by pulling the value from the required dropdown box.
Handling would then be either in the local javascript code or in the remote AJAX responder to deal with the extra information.
Without any context of what autocomplete coding you are using, it's difficult to illustrate my answer with code.
However, if you're using jQuery, Autocomplete UI and an AJAX function for the source:
$("#autocompleteTextBox").autocomplete({
source: function(request, response) {
var autocompleteContext = $("#dropdownBox").val();
$.ajax({
url: "http://source.com/searchJSON",
dataType: "jsonp",
data: {
query: request.term,
context: autocompleteContext
},
success: function(data) {
...
Note the two lines:
Where autocompleteContext variable is set, presumably from the dropbox you speak of
Where autocompleteContext is fed through to the searchJSON action in the data parameter
On the server-side (handler of searcjJSON) [C#/MVC psuedocode]:
public List<string> searchJSON(string query, string context)
{
return (from x in db.SearchTable where x.Name.Contains(query) && x.context == context select x.Name).ToList()
}
If you're merely using a local array source in javascript, change to a function source, similar to the AJAX source...
var fullArray = ["Apple", "Bat", "Cat", "Dog"];
$("#autocompleteTextBox").autocomplete({
source: function(request, response) {
var autocompleteContext = $("#dropdownBox").val();
response = //Code to filter fullArray with request.term and autocompleteContext here
}
This is fully functional client only example:
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<script src="http://code.jquery.com/jquery-1.8.3.js" type="text/javascript"></script>
<script src="http://code.jquery.com/ui/1.9.2/jquery-ui.js" type="text/javascript"></script>
<link rel="stylesheet" href="http://jqueryui.com/resources/demos/style.css" />
<script type="text/javascript">
if (!Array.prototype.filter) {
Array.prototype.filter = function (fun /*, thisp */) {
"use strict";
if (this == null)
throw new TypeError();
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun != "function")
throw new TypeError();
var res = [];
var thisp = arguments[1];
for (var i = 0; i < len; i++) {
if (i in t) {
var val = t[i]; // in case fun mutates this
if (fun.call(thisp, val, i, t))
res.push(val);
}
}
return res;
};
}
$(function () {
var availableAddresses = [
"New York Hotel",
"London Restaurant",
"London Hotel",
"Paris Restaurant",
"Berlin Restaurant",
"Moscow Restaurant"
];
$("#businesses").keyup(function () {
$("#businesses").autocomplete({
source: availableAddresses.filter(function (element, index, array) {
return (element.toLowerCase()
.indexOf($("#filter").val().toLowerCase()) !== -1);
})
});
});
});
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<select id="filter">
<option>hotel</option>
<option>restaurant</option>
</select>
<input type="text" id="businesses"/>
</div>
</form>
</body>
</html>
I have implemented an autocomplete in my app for zip codes. I am debugging in Firebug and I see in my console that the action is performing and I get a list of zip codes in the list of results, but the actual list is not displaying when I debug.
Here's the action in my Customers controller:
//the autocomplete request sends a parameter 'term' that contains the filter
public ActionResult FindZipCode(string term)
{
string[] zipCodes = customerRepository.FindFilteredZipCodes(term);
//return raw text, one result on each line
return Content(string.Join("\n", zipCodes));
}
Here's the markup (abbreviated)
<% using (Html.BeginForm("Create", "Customers")) {%>
<input type="text" value="" name="ZipCodeID" id="ZipCodeID" />
<% } %>
and here's the order I load my scripts:
<script type="text/javascript" src="/Scripts/jquery-1.4.2.js"></script>
<script type="text/javascript" src="/Scripts/jquery.ui.core.js"></script>
<script type="text/javascript" src="/Scripts/jquery.ui.widget.js"></script>
<script type="text/javascript" src="/Scripts/jquery.ui.position.js"></script>
<script type="text/javascript" src="/Scripts/jquery.ui.autocomplete.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#ZipCodeID").autocomplete({ source: '<%= Url.Action("FindZipCode", "Customers") %>'});
});
</script>
Anything obvious that I'm missing? Like I say the script is grabbing the list of zip codes, they just won't display on my page when I test.
EDIT: I added an image that shows what I see in firebug - it appears that I get my zip codes back, but just won't display the dropdown.
I also updated my text box so that it's inside of the ui-widget div like so:
<div class="ui-widget">
<input type="text" name="ZipCodeID" id="ZipCodeID" />
</div>
and this is the script that I'm using:
<script type="text/javascript">
$(document).ready(function() {
$("#ZipCodeID").autocomplete('<%= Url.Action("FindZipCode", "Customers") %>');
});
</script>
I was able to get the autocomplete suggestions working using the following code:
Controller:
public JsonResult FindZipCode(string term)
{
VetClinicDataContext db = new VetClinicDataContext();
var zipCodes = from c in db.ZipCodes
where c.ZipCodeNum.ToString().StartsWith(term)
select new { value = c.ZipCodeID, label = c.ZipCodeNum};
return this.Json(zipCodes, JsonRequestBehavior.AllowGet);
}
Markup:
<script type="text/javascript">
$(document).ready(function() {
$("#ZipCodeID").autocomplete({
source: '<%= Url.Action("FindZipCode", "Customers") %>',
});
});
</script>
<div class="ui-widget"><input type="text" name="ZipCodeID" id="ZipCodeID" /></div>
I had huge problems with autocomplete few months ago when first setting it up. For instance, the simple default wireup like you do it never worked for me. I had to specify everything and also attach the result function to it.
This works 100% but it might not be suitable for you. But I hope it helps. Put both in document.ready() function.
$("#products").autocomplete('<%:Url.Action("GetProducts", "Product") %>', {
dataType: 'json',
parse: function (data) {
var rows = new Array(data.length), j;
for (j = 0; j < data.length; j++) {
rows[j] = { data: data[j], value: data[j].Title, result: data[j].Title };
}
return rows;
},
formatItem: function (row, y, n) {
return row.PrettyId + ' - ' + row.Title + ' (' + row.Price + ' €)';
},
width: 820,
minChars: 0,
max: 0,
delay: 50,
cacheLength: 10,
selectFirst: true,
selectOnly: true,
mustMatch: true,
resultsClass: "autocompleteResults"
});
$("#products").result(function (event, data, formatted) {
if (data) {
var item = $("#item_" + data.PrettyId),
edititem = $("#edititem_" + data.PrettyId),
currentQuantity;
// etc...
}
});
Try returning JSON from your controller action:
public ActionResult FindZipCode(string term)
{
string[] zipCodes = customerRepository.FindFilteredZipCodes(term);
return Json(new { suggestions = zipCodes }, JsonRequestBehavior.AllowGet);
}
Also don't forget to include the default CSS or you might not see the suggestions div appear.