MVC Calling a function from a partial view is not working - javascript

This is my first MVC application, and this must be something simple but I've been trying to make this work for so many hours now..
What I want to do
I want to display a table in a partial view and be able to delete items from the parent view. The simple version looks something like this (the actual application is not about fruits):
What I have now
Partial View (_FruitList.cshtml)
<div id="listOfFruits">
<table class="table">
<tr>
<th class="active">Description</th>
<th class="active">Amount</th>
</tr>
#foreach(var item in Model)
{
<tr>
<td>#item.Description</td>
<td>#item.Amount</td>
<td><button class=".." onclick="d(#item.FruitID)">Delete</button></td>
</tr>
}
</table>
</div>
Parent View (Home.cshtml)
#section scripts
{
<script type="text/javascript">
$(document).ready(function (){
function d(id){
var url = "/Fruit/DeleteFruit/";
$.post(url, {id: id})
.done(function(response){
$("#listOfFruits").html(response);
});
}
});
</script>
}
#{Html.RenderPartial("_FruitList", Model.FruitList);}
Controller (FruitController.cs)
[HttpPost]
public ActionResult DeleteFruit(int id)
{
//Delete the selected fruit
FruitViewMode item = new FruitViewMode();
return PartialView(item.FruitList);
}
My Question
I can view the table with the fruit data in the parent view, but clicking the Delete button does not call the d function in the parent view.
(Javascript and JQuery should be working in the partial view because I've tested alert and addClass and they work fine)
I'm very new to this so it's very likely that I'm missing some basic stuff but what am I missing?

d() isn't declared in the global page scope, so it isn't found. declare it in the root of the <script> tag (i.e., not within a document.ready) to have access to it from the onclick
<script type="text/javascript">
function d(id){
var url = "/Fruit/DeleteFruit/";
$.post(url, {id: id})
.done(function(response){
$("#listOfFruits").html(response);
});
}
</script>

I believe in the past I had used a HTML.ActionLink for the same goal of deleting something by Id:
HTML.ActionLink method

Related

Onclick event open a new page and load an table by doing an api call using javascript

I'm trying to display a table on a new page by calling an API and loading the data in the table. This page is loaded on click of a menuItem.
But the issue I'm facing is that the table is displaying, but not the data I'm intending to. I know that I'm able to fetch the data from the API since i can see that in the console log.
Here is the code:
In this first html file im clickling the menu and calling my next html page i want to load
and also im giving my id="covidLink" which im calling in my JS FILE.
pan.html
<div class="navbar">
<a class="covidText" id="covidLink" href="covidStatusUpdate.html">Covid-19</a>
</div>
In the below js file im making a call to the api and appending the data in tbody.
Fetchdata.js
$(document).ready(function () {
$("#covidLink").click(function () {
console.log("Link clicked...");
requestVirusData();
});
});
function requestVirusData() {
$.getJSON('https://api.covid19api.com/summary',
function(data){
var countries_list = data.Countries;
//console.log(countries_list);
$(countries_list).each(function(i, country_dtls){
$('#totalbody').append($("<tr>")
.append($("<td>").append(country_dtls.country))
.append($("<td>").append(country_dtls.TotalConfirmed))
.append($("<td>").append(country_dtls.TotalDeaths))
.append($("<td>").append(country_dtls.TotalRecovered)));
});
})
}
and lastly
statusUpdate.html
<table class="table table-striped table-bordered table-sm" cellspacing="0" width=80%>
<thead>
<tr>
<th>Country</th>
<th>TotalConfirmed</th>
<th>TotalDeaths</th>
<th>TotalRecovered</th>
</tr>
</thead>
<tbody id="totalbody">
</tbody>
</table>
What am I supposed to do ? I have to admit that I'm lost here.
I don't think you quite understand how AJAX works. You're handling a click on "covidLink". This does two things simultaneously.
it tells the browser to navigate away from the current page and go to statusUpdate.html instead.
it runs the requestVirusData() function. This gets the data from the API and returns it to the page.
But the problem is: the API call returns the data to the page where the script was called from - i.e. it returns it to pan.html. And you've just told the browser to move away from that page. Also, pan.html doesn't contain a table to put the returned data into.
The logical solution here is to link to fetchdata.js from statusUpdate.html instead, and tweak the code slightly so it runs when that page loads, rather than on the click of a button:
$(document).ready(function () {
console.log("page loaded...");
requestVirusData();
});
As suggested by ADyson i did changes in my code and now im able to display the table with data.
Here are my code changes:
statusUpdate.html
<tbody id="tbody">
<script>
var datatable;
fetch('https://api.covid19api.com/summary')
.then(function (response) {
return response.json();
})
.then(function (data) {
appendData(data);
})
.catch(function (err) {
console.log('error: ' + err);
});
function appendData(data) {
var countries_list = data.Countries;
var tbody = document.getElementById("tbody");
// clear the table for updating
$('table tbody').empty();
// hide the table for hidden initialize
$('table').hide();
// loop over every country
for (var i in countries_list) {
var country_dtls = countries_list[i];
// replace -1 with unknown
for (var o in country_dtls) {
if (country_dtls[o] == -1) country_dtls[o] = 'Unknown';
}
$('table tbody').append(`
<tr>
<td>${country_dtls.Country}</td>
<td>${country_dtls.TotalConfirmed}</td>
<td>${country_dtls.TotalDeaths}</td>
<td>${country_dtls.TotalRecovered}</td>
</tr>`);
}
}
// }
</script>
</tbody>
pan.html
<a class="covid" href="statusUpdate.html">Covid-19</a>
and now i do not need fetchdata.js obviously.
Hope this helps someone stuck like me :)

MVC - How to refresh HTML table after setting a value in <select> element

From what I understand, the Razor foreach statement used in a HTML table in a View iterates through the items found in an IEnumerable which is declared as a #model at the top of the View file, and that #model comes from a Controller returning the View with that IEnumerable as an argument for it's Model parameter.
I am not sure if my logic is good here - I am trying to replace that IEnumerable with another one returned from a Controller Action which takes a value as a parameter to selectively fill the IEnumerable based on that value. This value is passed as an {id} through jQuery after the value of an HTML select element is changed. After replacing the IEnumerable I could reload the table using jQuery and the #foreach iterating through the new IEnumerable would display the results needed based on the selected value in the HTML select element.
However, I am not sure how to do this. I have been trying various methods I could think of or find on StackOverflow as solutions to similar problems, no luck.
I am now stuck at this point without any idea where to go.
HTML:
#model IEnumerable<TrgovinaMVC.Models.racun>
...
<select class="selectpicker" id="selektor" data-style="btn-primary" onchange="TypeChanged(this)">
<option>Gotovinski</option>
<option>Virman</option>
</select>
...
<div id="tabela">
<table class="table table-hover">
...
<tbody>
#foreach (var item in Model)
{
<tr>
...
JavaScript:
function TypeChanged(element) {
var val = $(element).val();
$("#tabela").load("/racuns/Reload/" + val);
Controller:
public ActionResult Reload(string id)
{
List<racun> list = new List<racun>();
foreach (racun r in db.racuns)
{
if (r.tipracuna == id)
list.Add(r);
}
return View(list);
}
Please point me to the right direction. Thanks in advance!
There are two ways to fix your problem
To reload the entire page in TypeChanged() fucntion
To create a partial view and load the table alone from that - suggessted
Controller:
public ActionResult Reload()
{
return View();
}
public ActionResult _ReloadTable(string id)
{
List<racun> list = new List<racun>();
foreach (racun r in db.racuns)
{
if (r.tipracuna == id)
list.Add(r);
}
return PartialView(list);
}
In Reload.cshtml - keep the dropdown code alone
Reload.cshtml
<select class="selectpicker" id="selektor" data-style="btn-primary" onchange="TypeChanged(this)">
<option>Gotovinski</option>
<option>Virman</option>
</select>
<div id="tabela">
</div>
<script>
function TypeChanged(element) {
var val = $(element).val();
$("#tabela").load("/racuns/_ReloadTable/" + val);
</script>
_ReloadTable.cshtml
#model IEnumerable<TrgovinaMVC.Models.racun>
<table class="table table-hover">
...
<tbody>
#foreach (var item in Model)
{
<tr>
On dropdown change event, partial view will be loaded every time and table data will be updated based on value selected in dropdown

How to remove clicked row while sending parametr to a function? Knockout

I have some complication with service removing. I have function that removes service on the server but I have to reload page to update table. I found way how to remove row by click-binding but there is the issue beacuse I can only remove row or get ID for delete service from server NOT both. :/
This is example of code that removes service on the server but doesn't remove table row.
HTML:
<table id="serviceView" class="fixed_header" border: 1>
<thead>
<tr>
<th>Name</th>
<th>Adress</th>
<th>Notification</th>
</tr>
</thead>
<tbody data-bind="foreach: services">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: address"></td>
<td data-bind="text: serviceId"></td>
<td ><button data-bind="click: $parent.DeleteService.bind(this, serviceId)">Remove</button></td>
</tr>
</tbody>
</table>
JS:
self.services = ko.observableArray([]);
self.lastCheck = ko.observable();
$.getJSON("http://localhost:55972/api/status", function (data) {
self.services(data.services);
self.lastCheck = data.lastCheck;
}); //////This is loading data to the table from server
self.DeleteService = function (serviceId) {
$.ajax({
type: "GET",
url: "http://localhost:55972/api/services/remove/" + serviceId,
}).done(function () {
self.services.remove(serviceId)
})
};
This is example of code that removes table row
When I use click-binding like this:
<button data-bind="click: $parent.DeleteService">Remove</button>
And change delete function to this:
self.DeleteService = function (serviceId) {
self.services.remove(serviceId)
$.ajax({
type: "GET",
url: "http://localhost:55972/api/services/remove/" + serviceId,
}).done(function () {
// here I want to remove row but i doesnt goes here without service ID.
})
};
It removes row but instead serviceId I got [object, object] in the URL.
Can you help me with it ? I got idea to use jquery to just update the table but it's seems unnecessarily complicated for me when I can use knockout.
I know the solution is not that hard but I'am just unable to solve it..... -_-
I'am sorry for taking time with this bullshit but this is my first real project and I'am so desperate at this point beacuse I have lot of things to do and I'am stucked on this.
In your Js code, you can try this:
self.services = ko.observableArray([]);
self.lastCheck = ko.observable();
$.getJSON("http://localhost:55972/api/status", function (data) {
self.services(data.services);
self.lastCheck = data.lastCheck;
}); //////This is loading data to the table from server
var serviceIdRemoved;
self.DeleteService = function (serviceId) {
serviceIdRemoved = serviceId; // now you can do whatever you need more with this value
$.ajax({
type: "GET",
url: "http://localhost:55972/api/services/remove/" + serviceId,
}).done(function () {
self.services.remove(serviceId)
})
};
With this way of work you can user the content of the variable and donĀ“t loose it. Also if you get [Object, Object], you can:
console.log(serviceId) // to see the content in the console.
JSON.stringify(data) //to see the content in html
This source could help you to understand it better.
The [object, object] you are seeing is actually the data and event objects which are secretly added to the JS function parameters by Knockout. If you want to add your own parameter to the click binding then you should do it like this:
<button data-bind="click: function(data, event) { $parent.DeleteService(serviceId, data, event) }">Remove</button>
You can then define your JS function as follows:
self.DeleteService = function (serviceId, data, event) {
[code here...]
}
You can read up on the exact details of it in the excellent Knockout documentation here:
http://knockoutjs.com/documentation/click-binding.html
It's about half-way down under the heading that reads Note 2: Accessing the event object, or passing more parameters

Ajax POST not refreshing data in View

I'm having issues refreshing data that has been POSTed using Ajax. The POST is successfully being executed, but the data on the VIEW does not get refreshed with the new data. When I debug, the values from the Ajax POST are successfully being passed to my Search controller. When the controller returns the view model return View(model);, my VIEW is not refreshing the new data. How do I get the new data to show in my VIEW?
Ajax/jQuery
$(document).ready(function () {
$("#typeId, #scorecardId, #dateId").on('change', function () {
$.ajax({
url: "/StaffChange/Search",
type: "POST",
dataType: "json",
data: {
typeSelected: $('#typeId').val(),
scorecardSelected: $('#scorecardId').val(),
effectiveDateSelected: $('#dateId').val()
}
})
});
});
View
<table class="table table-condensed table-hover table-responsive table-striped">
<tr>
<th>
<a href="#Html.GetUrlAndRouteObject(Model.Sort, "change_date")">
Change Date
#Html.AddSortArrow(Model.Sort, "change_date")
</a>
</th>
<th>
#Html.EditorFor(model => model.effectiveDate, new { htmlAttributes = new { #class = "datepicker", #placeholder = "Effective Date", #id = "dateId" } })
</th>
<th>
#Html.DropDownListFor(model => model.type, new SelectList(Model.type), "-Type-", new { #id = "typeId" })
</th>
<th>
#Html.DropDownListFor(model => model.type, new SelectList(Model.scorecard), "-Scorecard-", new { #id = "scorecardId" })
</th>
</tr>
#foreach (var item in Model.get_staff_changelog_results)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Change_Date)
</td>
<td>
#Html.DisplayFor(modelItem => item.Effective_Date)
</td>
<td>
#Html.DisplayFor(modelItem => item.Type)
</td>
<td>
#Html.DisplayFor(modelItem => item.Scorecard)
</td>
</tr>
}
Controller
public ActionResult Search(string sort, string typeSelected, string scorecardSelected, DateTime? effectiveDateSelected)
{
//TO DO: Implement MVC way of permissions...
if (System.Web.HttpContext.Current.Session["ConnectelligenceAdmin"].ToString() != "true")
{
return View("AccessDenied");
}
//Get sort order for Change Date using BLL
var staffChangeSort = (String.IsNullOrEmpty(sort)) ? SortOptions.change_date_desc : (SortOptions)Enum.Parse(typeof(SortOptions), sort);
//Execute sort order for Change Date using BLL
var sortResults = _changeLogSort.SortStaffChangeLog(staffChangeSort,typeSelected, scorecardSelected, effectiveDateSelected);
//Get list of dropdown results which is used for filtering
var dropdownResults = _staffChangeFilter.StaffChangeFilter();
var model = new Hierarchy_AdjustmentViewModel { get_staff_changelog_results = sortResults.get_staff_changelog_results, Sort = staffChangeSort, type = dropdownResults.type, scorecard = dropdownResults.scorecard};
return View(model);
}
You need to use the response you receive from your ajax call. Currently your Search method is returning a view result. What you can do is, if the method is invoked from an xhr call ,you may return a partial view result which has only the markup for table rows (with the new set of data) and in your ajax call's done event, update the DOM (replace the existing table rows with this new markup).
So first create a partial view called _List.cshtml and have the code to render the table rows there
#model Hierarchy_AdjustmentViewModel
#if(Model.get_staff_changelog_results!=null)
{
foreach (var item in Model.get_staff_changelog_results)
{
<tr>
<td> #item.Change_Date </td>
<td> #item.Effective_Date </td>
<td> #item.Type </td>
<td> #item.Scorecard </td>
</tr>
}
}
You can use the same partial view in your main view as well to reduce duplicate code
Now update your action method to return this partial view when the request was made from ajax code
public ActionResult Search(string sort, string typeSelected)
{
// Your existing code to get data goes here
var model = new Hierarchy_AdjustmentViewModel();
mode.get_staff_changelog_results = sortResults.get_staff_changelog_result;
if (Request.IsAjaxRequest())
{
return PartialView("_List", model);
}
return View(model);
}
Now all you have to do is, use this response to update the table. Since you are returning view result (HTML markup), you do not need to specify dataType as json.
$("#typeId, #scorecardId, #dateId").on('change', function () {
var $tbl = $(this).closest("table");
$tbl.find("tbody").find("tr:gt(0)").remove();
$.ajax({
url: "#Url.Action("Search","StaffChange")",
type: "POST",
data: {
typeSelected: $('#typeId').val(),
scorecardSelected: $('#scorecardId').val(),
effectiveDateSelected: $('#dateId').val()
}
}).done(function(res) {
$tbl.find("tbody").append(res);
}).fail(function(x, a, e) {
alert(e);
});
});
Another option is returning the data as a JSON array, and the done handler has to parse it (loop through it) and create markup for each row and append to the table (after clearing existing rows).
Because you're not responding to the AJAX call in any way. Add a .done() callback handler to the AJAX call:
$.ajax({
/*...*/
}).done(function (response) {
// update the page in here
});
At that point the question becomes... What sort of updates do you plan to do? It looks like you're returning a view from the controller:
return View(model);
So the data you're getting back in the AJAX response is a bunch of raw HTML. If it's a subset of a full page (that is, no additional <head>, <body>, etc. tags) then you could replace an existing container element with the contents of the response:
$('#someContainer').html(response);
However, this tends to be a bit sloppy and can cause other problems. (For example, replacing DOM elements which have handlers attached to them or plugins initialized on them, requiring you to re-think how you approach some things.) Instead, for AJAX calls it's common to return JSON data:
return Json(model);
This returns just the data instead of all the HTML surrounding this. This is useful for a couple of reasons:
It's easier to manipulate if you only want to do small and specific things.
It uses less network bandwidth.
It's just plain silly to return a bunch of HTML that the page already has.
With that data you can then update the specific elements on the page. For example, maybe you want to update the value of an input:
$('#someInput').val(response.SomeProperty);
Or the text of a display element:
$('#someElement').text(response.AnotherProperty);
How you handle the response and what you need to do to your page to "update it" is up to you. The point is that the system doesn't automatically do this for you. You need to handle the AJAX response and write your logic accordingly.

Adding table rows from a Grails Template on Button Click

So, the _form.gsp template associated with my create.gsp creates an initial table from a template for the row as follows:
<table id="myTable">
<!-- define table headers here -->
<g:each var="i" in="${1..5}">
<g:render template="tableRow" model="['i': i]" />
</g:each>
</table>
What I'd like to do is add a button or a link underneath that table that let's you add five more rows, while keeping all the data you've entered in the form so far.
I can see how that's possible in "pure" javascript, but I'd basically have to repeat the _myTable.gsp HTML in my javascript file. I'd like to avoid that (DRY, etc.).
How can I do that?
Edit
So, I tried Gregg's solution (below). Here's what I came up with.
The Controller has an action:
def addMoreRows() {
println params
def i = params.rowNumber + 1
def j = i+5
println "i is set to " + i
render(template: "foapRow", bean:i, var:i, model: ['rowStart': i, 'rowEnd': j])
}
The create.gsp page calls the _form.gsp as normal, adding a rowStart and a rowEnd to the model.
create.gsp
<g:render template="form" model="['userId':userId, 'rowStart':1, 'rowEnd':5]"/>
*_form.gsp*, in turn, passes those parameters on to the row template, and creates a link to call the above controller action. It also has the javascript Gregg recommended:
<script type="text/javascript">
$("#addRowsLink").on("click", function(e) {
e.preventDefault();
$.get("/Controller/addMoreRows", function(html) {
$("#theTableInQuestion>tbody").append(html);
});
});
</script>
<table>
...
<g:render template="tableRow" model="['rowStart':1, 'rowEnd':5]"/>
</table>
<g:remoteLink id="addRowsLink" action="addMoreRows" update="theTableInQuestion" onSuccess="addRows(#theTableInQuestion, data, textStatus)" params="['rowNumber':data]">Add More Rows</g:remoteLink>
The *_tableRow.gsp* begins and ends with:
<g:each var="i" in="${rowStart..rowEnd}">
<tr>
...
</tr>
</g:each>
From a previous attempt, I have this function in my included javascript file:
function addRows(tableId, rowCode, status) {
$(tableId + ' tr:last').after(rowCode);
}
Right now, when I click the "Add More Rows" link, I still get taken to a new page, and it only has one row on it.
One possible solution. You're going to need to change your template so it does the looping:
GSP:
<table id="myTable">
<tbody>
<g:render template="tableRows" model="[loopCount:loopCount, moreData:moreData]" />
</tbody>
</table>
Template:
<g:each in="${loopCount}" var="idx">
<tr>
<td>.....</td>
......
</tr>
</g:each>
JavaScript:
$("#someButtonId").on("click", function(e) {
e.preventDefault();
$.get("/controller/someAction", function(html) {
$("#myTable>tbody").append(html);
});
});
Controller:
def someAction = {
// logic here
render template: "tableRows", model="[loopCount: 5, moreData:moreData]"
}
You could also submit all the data in your table to the server every time and refresh the entire page, adding logic to loop over some variable number of rows. But you would need to collect all that data on the server and make sure it gets put back in the request.
There's probably a dozen ways to do this so don't be surprised if you get that many answers. :o)

Categories