Adding autocomplete using images to a search box in a MVC application - javascript

I’m wondering if anyone can help me.
I’m attempting to create a search box for my MVC application that autocompletes (makes suggestions on the basis of the user's input) with images instead of text.
The feature will check whether the user’s input is similar to a “Title” property from an Entity Framework database table called “Word” and then return it’s “Imagepath” property, which is a string path to the image.
This path should then be used in the View to return a list of relevant images auto completing the user's query. These images are then clickable and link to their respective pages.
Similar to the below but without the text and solely box images:
https://www.algolia.com/doc/assets/images/guides/search-ui/autocomplete-textarea-8-2565ba67.png
I am struggling with the code here as I am unfamiliar with the Ajax and Javascript that I understand is necessary to achieve this in real time.
My attempt is outlined below:
DATABASE MODEL:
The table is essentially this:
public class Word
{
public int Id { get; set; }
public string Title { get; set; }
public string Imagepath { get; set; }
}
CONTROLLER:
_context is the database.
Controller name is "WordsController".
[HttpPost]
public JsonResult AutoComplete(string Prefix)
{
var words= _context.Words.ToList();
var specifiedWords = (from w in words
where w.Title.StartsWith(Prefix) || w.Title.Contains(Prefix)
select new { w.Imagepath });
return Json(specifiedWords , JsonRequestBehavior.AllowGet);
}
VIEW:
Firstly here is my attempt at the Javascript. I am trying to return a list of "Words" from the "Words" Controller above and append their Imagepath property to an HTML element attempting to create a sort of list. The search box and css is below.
<script src="~/Scripts/jquery-3.2.1.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<link rel= "stylesheet" href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css" >
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
<script src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<script>
$(document).ready(function () {
$("#Title").autocomplete(
{
source: function (request, response) {
$.ajax({
url: "/Words/AutoComplete",
type: "POST",
dataType: "json",
data: { Prefix: request.term },
success: function (data) {
response($.map(data, function (item) {
return {
label: item.Imagepath,
value: item.Title
};
}));
}
});
},
open: (event) => {
$('.ui-autocomplete .ui-menu-item div').toArray().forEach((element) => {
let imagePath = element.innerHTML;
$(element).html('');
var inner_html = '<div class="list_item_container"><div class="image"><img src="' +
imagePath + '"></div>';
$(element).append(inner_html);
});
}
});
});
</script>
Searchbox:
#Html.EditorFor(model => model.Title, new { htmlAttributes = new { #class = "form-control" } })
CSS:
<style>
.list_item_container {
width: 300px;
height: 60px;
padding: 5px 0;
}
.image {
width: 60px;
height: 60px;
margin-right: 10px;
float: left;
}
Needless to say, my best attempts do not yet work.
The JavaScript has been loosely taken from the tutorial here (which only covers autocompletion with words:
http://www.jamiemaguire.net/index.php/2017/04/08/how-to-implement-an-autocomplete-control-with-asp-net-mvc-and-json/
Any pointers or links to useful resources would be massively appreciated. Thanks!

set open. open trigger after response has been received and content has been rendered.
{
source: function(request, response) {
$.ajax({
url: '#Url.Action("AutoComplete", "Words")',
type: "POST",
dataType: "json",
data: { Prefix: request.term },
success: function (data)
{
response($.map(data, function (item)
{
return {
label: item.Imagepath,
value: item.Title
}
}));
}
});
},
open: (event) => {
$('.ui-autocomplete .ui-menu-item div').toArray().forEach((element) => {
let imagePath = element.innerHTML;
$(element).html('');
var inner_html = '<div class="list_item_container"><div class="image"><img src="' +
imagePath + '"></div>';
$(element).append(inner_html);
});
}
}
in case if autocomplete function is not defined or can't be called the following link will be useful .autocomplete is not a function Error
i guess you forgot to return title as well:
var specifiedWords = (from w in words
where w.Title.StartsWith(Prefix) || w.Title.Contains(Prefix)
select new { w.Imagepath, w.Title });
return Json(specifiedWords, JsonRequestBehavior.AllowGet);

Related

Multiple forms in #foreach loop. How do I submit one asynchronously with javascript. C# core Razor

Shopping cart with many items how to remove any item asynchronously with JavaScript this is my work so far. Can anyone improve on this?
your help would be greatly appreciated. Have a great day
Ok so this works if you remove items from the top of the list but fails if you remove items from some other place.
The problem seems to be that the form names are all the same "remove" without any indexing.
Problem is I'm not sure how to proceed with this.
document.forms['remove'].onsubmit = () => {
let formData = new FormData(document.forms['remove']);
fetch('/sales/cart?handler=RemoveItem', {
method: 'post',
body: new URLSearchParams(formData)
})
.then(() => {
var url = "/sales/cart?handler=CartPartial";
console.log(url)
$.ajax({
url: url,
success: function (data) {
$("#exampleModal .modal-dialog").html(data);
$("#exampleModal").modal("show");
//alert('Posted using Fetch');
}
});
});
return false;
}
<pre>
#foreach (var item in Model.Items)
{
<form name="remove" method="post">
<h4 class="text-left text-body">#item.Price.ToString("c")
<button class="btn btn-sm" title="Trash"><i style="font-size:large"
class="text-warning icon-Trash"></i></button>
</h4>
<input type="hidden" asp-for="#Model.Id" name="cartId" />
<input type="hidden" asp-for="#item.Id" name="cartItemId" />
</form>
}
</pre>
Update
----------
New markup
I added an index to the id and included an onclick event.
<form method="post" id="#i" onclick="removeItem(this.id)">
<button class="btn btn-sm" title="Trash">Item One</button>
<input type="hidden" asp-for="#Model.Id" name="cartId" />
<input type="hidden" asp-for="#item.Id" name="cartItemId" />
</form>
and create a new function that captured the form id including it in a constant.
<script>
function removeItem(formId) {
const form = document.getElementById(formId);
form.onsubmit = () => {
let formData = new FormData(form);
fetch('/sales/cart?handler=RemoveItem', {
method: 'post',
body: new URLSearchParams(formData)
})
.then(() => {
var url = "/sales/cart?handler=CartPartial";
console.log(url)
$.ajax({
url: url,
success: function (data) {
$("#exampleModal .modal-dialog").html(data);
$("#exampleModal").modal("show");
//alert('Posted using Fetch');
}
});
});
return false;
}
}
</script>
If anybody can improve on this please post it here.
Thanks.
Updates code behind Cart.cshtml.cs
using System;
using System.Threading.Tasks;
using Malawby.Models;
using Malawby.Services.Interfaces;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Malawby.Pages.Sales
{
public class CartModel : PageModel
{
private readonly ICartRepository _cartRepository;
public CartModel(ICartRepository cartRepository)
{
_cartRepository = cartRepository ?? throw new
ArgumentNullException(nameof(cartRepository));
}
[BindProperty]
public Cart Cart { get; set; } = new Cart();
public const string SessionKeyName = "_Name";
public string SessionInfo_Name { get; private set; }
public void OnGetAsync()
{
}
public async Task<PartialViewResult> OnGetCartPartialAsync()
{
var userName = GetUserName();
if (userName != null)
{
Cart = await _cartRepository.GetCartByUserName(userName);
}
return Partial("_ToCart", model: Cart);
}
private string GetUserName()
{
return HttpContext.Session.GetString(SessionKeyName);
}
public async Task OnPostRemoveItemAsync(int cartId, int cartItemId)
{
await _cartRepository.RemoveItem(cartId, cartItemId);
}
}
}
Update 2
This is the modified code I used. This is the error in the console.
XML Parsing Error: no root element found Location: localhost:44331/sales/cart?handler=RemoveItem Line Number 1, Column 1
There is no error on the page just nothing happens on the click of the trash can.
<script type="text/javascript">
function removeItem(cartItemId, cardId) {
var removeUrl = "/sales/cart?handler=RemoveItem";
$.post(removeUrl,
{
cartItemId: cartItemId,
cardId: cardId
})
.done(function (data) {
alert(data); //usually return true or false if true
remove card
$('#card_' + cardId).remove();
});
}
</script>
I am not familiar with asp.net core, but I will show how I usually do it without focusing on syntax.
first on the view no need to add multiple form but should use card id as index and delete button sent selected index like this:
#foreach (var item in Model.Items)
{
<div id="card_#item.cardId">
<h4 class="text-left text-body">#item.Price.ToString("c")
<button class="btn btn-sm" onclick="removeItem('#item.cardId') title="Trash"><i style="font-size:large"
class="text-warning icon-Trash"></i></button>
</h4>
</div>
}
then the script function will call remove api and remove selected card with no need to re-render the page:
<script type="text/javascript">
function removeItem(cardId) {
var removeUrl = "your apiUrl";
$.post( "removeUrl", { cardId: cardId })
.done(function( data ) {
alert( data ); //usually return true or false if true remove card
$('#card_'+ cardId).remove();
});
}
</script>

How to get length of Kendo HierarchicalDataSource with remote data?

I'm working on a Javascript function that will set a kendo.data.HierarchicalDataSource object using a url that fetches the data. At the end of this function, if the data source actually has data, I want to set a treeview with the data (which is already working). If it doesn't have data, instead of setting the treeview, I want to make a label visible that tells the user that there is no data.
My problem: How to I determine that there is/isn't data in the HierarchicalDataSource? When I try call any function or get any property of getData, it returns undefined.
function loadTreeGroupData(applicationId) {
var treeview = $("#treeview-kendo").data("kendoTreeView");
var url = $('#urlHolders').data("gettreeUrl");
var appId = 0;
if (applicationId != undefined && applicationId != "")
appId = applicationId;
var getData = new kendo.data.HierarchicalDataSource({
change: function (e) {
for (var i = 0; i < e.items.length; i++) {
e.items[i].load();
}
},
transport: {
read: {
url: url, //"GetAlertsTree",
dataType: "json",
contentType: "application/json",
data: { applicationId: appId }
}
},
schema: {
model: {
id: "id",
hasChildren: "hasChildren",
children: "measureGroups"
}
}
});
if (/*the treeview has data*/) {
treeview.setDataSource(getData);
} else {
/*set a label that tells the user that there's no data*/
}
}
I would suggest you to do the following changes in your code:
Set the HierarchycalDataSource at the treeView initialization, instead of add it later;
Declare treeView's div and label as display:none or whatever the way you hide them;
Use DataSource's requestEnd event to show/hide the elements.
<!DOCTYPE html>
<html>
<head>
<base href="https://demos.telerik.com/kendo-ui/treeview/remote-data-binding">
<style>html { font-size: 14px; font-family: Arial, Helvetica, sans-serif; }</style>
<title></title>
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2021.2.616/styles/kendo.default-v2.min.css" />
<script src="https://kendo.cdn.telerik.com/2021.2.616/js/jquery.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2021.2.616/js/kendo.all.min.js"></script>
</head>
<body>
<div id="example">
<div class="demo-section k-content">
<div id="treeview" style="display: none"></div>
<div id="no-data-label" style="display: none">No data found</div>
</div>
<script>
var serviceRoot = "https://demos.telerik.com/kendo-ui/service";
homogeneous = new kendo.data.HierarchicalDataSource({
transport: {
read: {
url: serviceRoot + "/Employees",
dataType: "jsonp"
}
},
schema: {
model: {
id: "EmployeeId",
hasChildren: "HasEmployees"
}
},
requestEnd: (e) => {
if (e.response && e.response.length) {
$("#treeview").show();
$("#no-data-label").hide();
}
else {
$("#treeview").hide();
$("#no-data-label").show();
}
}
});
$("#treeview").kendoTreeView({
dataSource: homogeneous,
dataTextField: "FullName"
});
</script>
</div>
</body>
</html>
Dojo

Deleting items from dynamic list ASP Core MVC

I'm trying to remove or hide items from a list and I'm facing two problems, 1- the newly cannot be removed, 2- Tried to tag the deleted items as isDeleted = true using Javascript then later delete them in the controller following this answer https://stackoverflow.com/a/40572625/10773318 but it didn't work.
Here's my view models
public class CreateEditParentViewModel
{
public int Id { get; set; }
public IList<ChildViewModel> ChildrenLists { get; set; }
}
public class ChildViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public bool isDeleted { get; set; }
}
In the main view
<div id="editorRows">
#foreach (var item in Model.ChildrenLists)
{
<partial name="_RowPartial" model="item" />
}
</div>
<a id="addItem" asp-action="BlankRow" asp-controller="Home">Add Row...</a> <br />
<input type="submit" value="Finished" />
The javascript in the main view
#section scripts {
<script>
$("#addItem").click(function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) { $("#editorRows").append(html); }
});
return false;
});
$("a.deleteRow").click(function () {
$(this).parents("div.editorRow:first").remove(); //does not work with newly added
return false;
}); //what it should do: hide and set isDeleted = true if id is not null - remove if null
</script>
Finally the partial view
<div class="editorRow">
#using (Html.BeginCollectionItem("ChildrenLists"))
{
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.isDeleted)
<span>Name: </span> #Html.EditorFor(m => m.Name);
}
delete
1- the newly cannot be removed
You can manually bind click event handler for the new generated <a href="#" class="deleteRow"> element, like below.
success: function (html) {
$("#editorRows").append(html);
$("a.deleteRow").bind("click", function () {
//...
//code logic here
});
}
2- Tried to tag the deleted items as isDeleted = true using Javascript
To achieve the requirement, you can refer to the following code snippet.
<script>
$("#addItem").click(function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) {
$("#editorRows").append(html);
$("a.deleteRow").bind("click", function () {
del_row($(this));
});
}
});
return false;
});
$("a.deleteRow").click(function () {
del_row($(this));
return false;
});
function del_row(el) {
console.log("del");
console.log($(el).siblings("input[id$='__Id']").val());
var childitem_id = $(el).siblings("input[id$='__Id']").val();
if (childitem_id == 0 || childitem_id == "") {
$(el).parent("div.editorRow").remove();
} else {
$(el).siblings("input[id$='__isDeleted']").val("true");
$(el).parent("div.editorRow").hide();
}
return false;
}
</script>
Test Result

How to set data from jQuery plugin to data base ASP .NET MVC?

I have a jQuery plugin "Slider", and this slider show current price item, and I want to add posibility to change prices data by jQuery slider and update it at data base.
There is a model:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public int Price { get; set; }
}
Controller, where I added method "SetPrice", for update and save data from ajax post method. But I do not know, it is correct way or not for getting data from javascript.
public class ItemsController : Controller
{
private ItemsContext db = new ItemsContext();
[HttpGet]
public ActionResult Index()
{
var items = db.Items.ToList();
return View(items);
}
[HttpGet]
public ActionResult Details(int? id)
{
if (id == null)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
var item = db.Items.Where(i => i.Id == id).FirstOrDefault();
return View(item);
}
public void SetPrice(Item item)
{
if (item == null)
throw new Exception("Some exception");
db.Items.Add(item);
db.SaveChanges();
}
}
}
View "Details" where I show current data item by slider, and want to add a logic for change data Price slider.
<script src="https://code.jquery.com/jquery-1.11.3.js"></script>
<link rel="stylesheet" href="http://cdn.jsdelivr.net/jquery.roundslider/1.0/roundslider.min.css">
<script src="http://cdn.jsdelivr.net/jquery.roundslider/1.0/roundslider.min.js"></script>
<script src="~/CustomScripts/SliderJs.js"></script>
<div id="slider"></div>
<script>
var myApp = myApp || {};
myApp.item = #Model.Price
</script>
<script src="~/CustomScripts/SliderJs.js"></script>
<h2>Details</h2>
<div>
<hr />
<dl class="dl-horizontal">
<dt>
#Html.DisplayNameFor(model => model.Name)
</dt>
<dd>
#Html.DisplayFor(model => model.Name)
</dd>
<dt>
#Html.DisplayNameFor(model => model.Price)
</dt>
<dd>
#Html.DisplayFor(model => model.Price)
</dd>
</dl>
</div>
<p>
#Html.ActionLink("Back to List", "Index")
</p>
This is how it looks at Details view.
enter image description here
And section scripts.
I changed from this:
(function ($, window, myApp) {
$(document).ready(function () {
$("#slider").roundSlider({
radius: 90,
width: 10,
handleSize: "+10",
sliderType: "range",
value: myApp.item
});
});
})(jQuery, window, myApp);
To this, added an ajax post method but something goes wrong, even slider don`t show current date.
(function ($, window, myApp) {
$(document).ready(function () {
$("#slider").roundSlider({
radius: 90,
width: 10,
handleSize: "+10",
sliderType: "range",
value: myApp.item,
});
$.ajax({
url: 'Items/SetPrice',
type: 'POST',
data: { value: value},
contentType: 'application/json',
dataType: "json",
})
});
});
})(jQuery, window, myApp);
I`m looking for any advices, how to change properly a method "SetPrice" and script. Thank you so much for your time.
Regarding this documentation I guess you should subscribe to the change event and post your value to the server (using ajax there). You should also use Price instead of value when sending data back to object. Otherwise it would not bind to your Item object in SetPrice action. Also think about how you will find the proper Item to update the price. You should probably add an id along with price.
So that your slider initialization should look somehow like this (note I have not tested that):
$("#slider").roundSlider({
radius: 90,
width: 10,
handleSize: "+10",
sliderType: "range",
value: myApp.item,
change: function(value)
{
$.ajax({
url: 'Items/SetPrice',
type: 'POST',
data: { Price: value},
contentType: 'application/json',
dataType: "json",
})
}
});

ASP.NET MVC Cascading DropDownLists Javascript Issues

After reviewing many tutorials and various approaches to Cascading DropDownLists, I decided to create a ViewModel for my View and then populate my DropDownLists based on this post:
MVC3 AJAX Cascading DropDownLists
The goal here is the most basic and covered in many tutorials, but I still can't get it quite right... to populate a City dropdown based on the value of a State dropdown.
EDIT:
Since posting this request for help, I discovered Firebug (yes, that's how new I am to doing any sort of programming), and I was able to determine that I am successfully calling my controller, and pulling the necessary data. I believe the problem is the second half of my JavaScript that returns the data to my View.
Here is my View:
<label>STATE HERE:</label>
#Html.DropDownListFor(x => x.States, Model.States, new { #class = "chzn-select", id = "stateID" })
<br /><br />
<label>CITY HERE:</label>
#Html.DropDownListFor(x => x.Cities, Enumerable.Empty<SelectListItem>(), new { id = "cityID" })
Here is the JavaScript within my View, and somehow I'm not handling my results correctly once I get them:
$(function () {
$("#stateID").change(function () {
var stateId = $(this).val();
// and send it as AJAX request to the newly created action
$.ajax({
url: '#Url.Action("GetCities")',
type: 'GET',
data: { Id: stateId },
cache: 'false',
success: function (result) {
var citySelect = $('#cityID');
$(citySelect).empty();
// when the AJAX succeeds refresh the ddl container with
$.each(result, function (result) {
$(citySelect)
.append($('<option/>', { value: this.simpleCityID })
.text(this.cityFull));
});
},
error: function (result) {
alert('An Error has occurred');
}
});
});
});
Here is my controller called by the JavaScript:
public JsonResult GetCities(int Id)
{
return Json(GetCitySelectList(Id), JsonRequestBehavior.AllowGet);
}
private SelectList GetCitySelectList(int Id)
{
var cities = simpleDB.simpleCity.Where(x => x.simpleStateId == Id).ToList();
SelectList result = new SelectList(cities, "simpleCityId", "cityFull");
return result;
}
Here are my results from Firbug, which tell me I'm building and getting the data without issue, just not populating my DropDownList correctly:
[{"Selected":false,"Text":"Carmel","Value":"IN001"},{"Selected":false,"Text":"Fishers","Value":"IN002"}]
If anyone has any suggestions as to why the JavaScript fails to populate the dropdrown, please comment, thanks!
I have done this several times with something like this:
Create a partial to popolate dropdown list. Name it DropDownList and put in Shared folder of Views
#model SelectList
#Html.DropDownList("wahtever", Model)
Your create view should be something like this (skipped irrelevant parts)
<script type="text/javascript">
$(function() {
$("#StateId").change(function() {
loadLevelTwo(this);
});
loadLevelTwo($("#StateId"));
});
function loadLevelTwo(selectList) {
var selectedId = $(selectList).val();
$.ajax({
url: "#Url.Action("GetCities")",
type: "GET",
data: {stateId: selectedId},
success: function (data) {
$("#CityId").html($(data).html());
},
error: function (result) {
alert("error occured");
}
});
}
</script>
#Html.DropDownList("StateId")
<select id="CityId" name="CityId"></select>
Carefully note the Empty Select item for CityId and the call of loadLevelTwo at document.ready
And your controller should be like:
public ActionResult Create()
{
ViewBag.StateId = new SelectList(GetAllCities(), "Id", "Name");
return View();
}
public ActionResult GetCities(int stateId) {
SelectList model = new SelectList(GetCitiesOfState(stateId), "Id", "Name");
return PartialView("DropDownList", model);
}
Thank you for your assistance,
It turns out that in my JavaScript below, I was attempting to directly reference the simpleCityID and cityFull fields associated with my data model:
$.each(result, function (result) {
$(citySelect)
.append($('<option/>', { value: this.simpleCityID })
.text(this.cityFull));
Instead, I needed to keep it generic and inline with JavaScript standards of referencing Value and Text:
$.each(modelData, function (index, itemData) {
select.append($('<option/>', {
value: itemData.Value,
text: itemData.Text

Categories