Knockout.js Adding data by using push method not updating view - javascript

I'm getting data from WebApi on JSON format and then adding received data to MVC View by using .push() method of KnockoutJS. The JSON data I received on POST response is correct, so I believe it's something wrong on client side - instead of data I'm geting undefined and [Object].
Although after page refresh all data showing correctly.
Here my knockout code:
<script>
var viewModel = {
prepp: ko.observableArray(),
currentPage: ko.observable(-1)
};
$(function () {
getData(viewModel.currentPage() + 1);
ko.applyBindings(viewModel, document.getElementById("prepps"));
});
//This function used for paging, not concern to question directly
function getData(pageNumber) {
if (viewModel.currentPage() != pageNumber) {
$.ajax({
url: "/api/index",
type: "get",
contentType: "application/json",
data: { id: pageNumber }
}).done(function (data) {
if (data.length > 0) {
viewModel.currentPage(viewModel.currentPage() + 1);
for (var i = 0; i < data.length; i++) {
viewModel.prepp.push(data[i]);
}
}
});
};
//Here we call POST action of WebApi.
$(".send-feed").click(function () {
var guid = getguid();
var styles;
var req = { RequestName: $("#request").val(), RequestDescription: $("#request-review").val(), RequestOwner: $("#username").val(), RequestGuid: guid, RequestStyles: [] }
$("div.click").each(function () {
styles = { RequestGuid: guid, StyleId: $(this).text() };
req.RequestStyles.push(styles);
});
var model = JSON.stringify(req);
$.ajax({
url: "/api/index",
type: "POST",
contentType: "application/json, charset: utf-8",
data: model
}).done(function (data) {
viewModel.prepp.push(data);
});
});
}
});
</script>
And here is the MVC View markup:
div class="prepp-blocks-container" data-bind="foreach: prepp" id="prepps">
<div class="prepp-block">
<div class="star" data-bind="if: $data.IsStylistOffer == true">
<img src="../../img/star-yellow.png" alt="added by stylist">
</div>
<a data-bind="attr: {href: 'Request/' + $data.RequestGuid}"><h3 data-bind="text: $data.RequestName"></h3></a>
<span data-bind="foreach: {data: RequestStyles, as: 'style'}">
<div data-bind="text: style" class="taste-prepp"></div>
</span>
<p class="text-small-grey" data-bind="text: $data.PreppsNumber + ' prepps'"></p>
</div>
</div>

I believe your return type from controller should be adjusted so it will match the view model structure like
public model Get()
{
//build your list .
return model ;
}
Try to use ko.mapping.toJS() so knockout advantages are not lost .
Refer knockout doc's you can find more relevant info how we can better use it Here

Related

Return a view from an ajax call

I have 0 experience in Ajax, and now I'm trying to get an html table to return on an ajax call.
As a result, I get the error
jquery-3.6.0.min.js:2 POST http://test.loc/%7B%7B%20url('blog/articles')%20%7D%7D 404 (Not Found)
I understand that there are still a lot of mistakes, so don't judge too much, and help in any way you can :)
Route:
Route::post('blog/articles', 'App\Http\Controllers\BlogController#articles');
Ajax on calling page:
function getBlogLists(category_id) {
var category_id = category_id;
$.ajax({
url: "{{ url('blog/articles') }}",
type: 'POST',
data: { 'category_id': category_id },
datatype: 'html',
success: function(data) {
console.log('success');
console.log(data);
document.querySelectorAll('.blog-filter__item').forEach(el => {
el.addEventListener('click', () => {
document
.querySelector('.blog-filter__item.active')
.classList.remove('active');
el.classList.add('active');
var dataFilter = $(el).attr('data-filter');
if (dataFilter == 'all') {
$('.blog-list').show();
}
else {
$('.blog-list').hide()
$(dataFilter).show()
}
});
});
},
});
}
//on page load
getBlogLists("{{ $category->id }}");
Controller:
public function articles() {
$input = Request::all();
if(Request::isMethod('post') && Request::ajax()) {
if($input['category_id']) {
$articles = Article::select('select * from blog_categories where blog_category_id = ?', array($input['category_id']));
$returnHTML = view('blog.articles')->with('articles', $articles)->render();
return response()->json( array('success', 'html'=>$returnHTML) );
}
}
}
View:
#foreach($articles as $index => $article)
<div class="blog-list category_{{ $article->blog_category_id }}">
#if ($index % 2 === 1)
<div class="blog-article blog-article--right">
<h2 class="blog-article_title">{{ $article->title }}</h2>
</div>
#else
<div class="blog-article blog-article--left">
<h2 class="blog-article_title">{{ $article->title }}</h2>
</div>
#endif
</div>
#endforeach
You have this error because you have a problem in your URL.
function getBlogLists(category_id) {
var category_id = category_id;
$.ajax({
url: "{{ url('blog/articles') }}",
Here, the url is literally {{ url('blog/articles') }}, I mean, as a string.
You are sending the request to http://test.loc/{{ url('blog/articles') }}, which once encoded gives http://test.loc/%7B%7B%20url('blog/articles')%20%7D%7D.
That's why you are getting a 404 error (not found), obviously this url doesn't exist.
First, remove the url variabilization:
function getBlogLists(category_id) {
var category_id = category_id;
$.ajax({
url: "http://test.loc/blog/articles", //or whatever your url is
Then in your controller, you just have to return the HTML and it will be inside data in your javascript success callback.
Do a simple test first:
public function articles() {
return "<h1>HelloWorld</h1>";
}
If it works with this simple "hello world", it will work with the view you are rendering as well.

Data passing error from javascript to controller

I'm working on ASP.NET MVC project , I want to get location dropdown selected value and according to that i need to load values to City dropdown , i implemented following code and location dropdown onselect event call the javascript function and javascript function call the controller method but when executing controller method locationId is null ,and i debug javascript and locationID is there till the this line data: JSON.stringify({ locationId: +locationId }), after that javascript returns error.but controller method get hit but locationId null
code for dropddowns
<div class="row">
<div class="col-sm-4">
#Localizer["Locations"]
<select id="locationDropDown" class="selectpicker form-control" asp-for="selectedLocation" onchange="getCityList()">
<option value="0"><None></option>
#foreach (var item in Model.LocationList)
{
<option value="#item.Id">#item.Name</option>
}
</select>
</div>
</div>
<div class="row">
<div class="col-sm-4">
#Localizer["KioskGroup"]
<select id="cityDropDown" class="selectpicker form-control">
<option>---Select City---</option>
</select>
</div>
</div>
Javascript code
function getCityList()
{
debugger;
var locationId = $("#locationDropDown").val();
console.log(locationId)
$.ajax({
url: '/Kiosk/GetZoneListBYlocationID',
type: 'POST',
datatype: 'application/json',
contentType: 'application/json',
data: JSON.stringify({ locationId: +locationId }),
success: function (result) {
$("#cityDropDown").html("");
$("#cityDropDown").append
($('<option></option>').val(null).html("---Select City---"));
$.each($.parseJSON(result), function (i, zone)
{ $("#cityDropDown").append($('<option></option>').val(zone.Id).html(zone.Name)) })
},
error: function(){alert("Whooaaa! Something went wrong..")},
});
controller method
public ActionResult GetZoneListBYlocationID(string locationID)
{
List<Zone> lstZone = new List<Zone>();
long locationId = long.Parse(locationID);
var zones = _zoneRepository.GetZonesByLocationId(locationId);
return Json(JsonConvert.SerializeObject(zones));
}
Your current code is sending the json string {"locationId":101} in the request body because you specified the contentType as application/json. This is useful when you want to send an object with multiple properties and your action method parameter is a DTO/POCO class type. Model binder will be reading from the request body and map it to the parameter.
In your case, all you are sending is a single value. So do not send the JSON string. simply create a js object and use that as the data attribute value. Also remove the contentType: application/json as we are not sending a complex js object as json string.
Also application/json is not a valid option for the dataType property. You may use json. But jQuery is smart enough to guess the value needed here from the response headers coming back from server. So you may remove it.
function getCityList() {
var locationId = $("#locationDropDown").val();
$.ajax({
url: '/Kiosk/GetZoneListBYlocationID',
type: 'POST',
data: { locationID: locationId },
success: function (result) {
console.log(result);
// populate dropdown
},
error: function () { alert("Whooaaa! Something went wrong..") },
});
}
Now this data will be send in Form Data as locationID=101 with Content-Type header value as application/x-www-form-urlencoded and will be properly mapped to your action method parameter.
You should use the correct types. In your action method, you are using string as your parameter and later trying to convert it to long. Why not use long as the parameter type ? Also if zones variable is a list of Zone object, you can pass that directly to the Json method. No need to create a string version in between.
public ActionResult GetZoneListBYlocationID(long locationId)
{
var zones = _zoneRepository.GetZonesByLocationId(locationId);
return Json(zones);
}
Why you are stringify the data.Below one should work without stringify
data: { locationId: +locationId },
I was facing the same problem. and after that, I have tried below solution.
Hope it will help you.
ajax call is as follows:
$.ajax({
type: 'POST',
url: "/Account/GetCities",
dataType: 'json',
data: { id: $("#StateID").val() },
success: function (states) {
$.each(states, function (i, state) {
$("#CityID").append('<option value="' + state.Value + '">' + state.Text + '</option>');
});
},
error: function (ex) {
alert('Failed to retrieve cities.' + ex);
}
});
The controller code is as follows:
public List<CityModel> GetCities(int id)
{
//your code
}
You can do in your application like this:
function getCityList()
{
var locationId = $("#locationDropDown").val();
console.log(locationId)
$.ajax({
url: '/Kiosk/GetZoneListBYlocationID',
type: 'POST',
dataType: 'json',
data: { locationId: locationId },
success: function (result) {
$("#cityDropDown").html("");
$("#cityDropDown").append
($('<option></option>').val(null).html("---Select City---"));
$.each($.parseJSON(result), function (i, zone)
{ $("#cityDropDown").append($('<option></option>').val(zone.Id).html(zone.Name)) })
},
error: function(){alert("Whooaaa! Something went wrong..")},
});
}
And your controller will be same as you have done.

What's the optimal way of making these AJAX GET requests with jQuery & JSON API?

I'm outputting API data on separate pages coming from different end point urls, ie. https://api.server.com/first, https://api.server.com/second, etc.
The code is working, but it seems awfully redundant and I'm sure there's a better way of expressing this that's more optimal and faster:
var $rubys = $('#rubys');
$(function () {
$('#loading-rubys').show();
$.ajax({
type: 'GET',
url: 'https://api.server.com/first/',
success: function(rubys) {
$.each(rubys, function(i, ruby) {
$rubys.append('$'+parseFloat(ruby.price).toFixed(2)+' |
$'+parseFloat(ruby.attribute).toFixed(0));
});
},
complete: function(){
$('#loading-rubys').hide();
}
})
});
var $emeralds = $('#emeralds');
$(function () {
$('#loading-emeralds').show();
$.ajax({
type: 'GET',
url: 'https://api.server.com/second/',
success: function(emeralds) {
$.each(emeralds, function(i, emerald) {
$emeralds.append('$'+parseFloat(emerald.price).toFixed(2)+' |
$'+parseFloat(emerald.attribute).toFixed(0));
});
},
complete: function(){
$('#loading-emeralds').hide();
}
})
});
The following:
var $rubys = $('#rubys');
$('#loading-rubys').show();
are set for each post page using YAML front-matter (Jekyll) like so:
---
title: Post about Ruby
var-id: rubys
load-id: loading-rubys
---
and output them in HTML:
<div id="{{ page.var-id }}">
<div id="{{ page.load-id }}">
<img src="/assets/img/loading.svg"/>
</div>
</div>
Current workflow
So basically whenever I create a new post, I:
Set the var-id and load-id custom parameters for each post in the front-matter
Create a new function to include those and make a new GET request to the respective url, ie. https://api.server.com/third/, https://api.server.com/fourth/.
How would you write this better?
Something like this could help.
function getGems(gems,gemsURL) {
var $gems = $('#'+gems);
$('#loading-'+gems).show();
$.ajax({
type: 'GET',
url: gemsURL,
success: function(data) {
$.each(data, function(i, v) {
$gems.append('$'+parseFloat(v.price).toFixed(2)+' |
$'+parseFloat(v.attribute).toFixed(0));
});
},
complete: function(){
$('#loading-'+gems).hide();
}
});
}
$(function () {
getGems('rubys','https://api.server.com/first/');
getGems('emeralds','https://api.server.com/second/')
});

Retrieving event and htmlInput element from a foreach using javascript or jquery

I managed to retrieve a dynamic element ID from inside a foreach and send it to a controller this way:
#using (Html.BeginForm("DeleteConfirmed", "Gifts", FormMethod.Post, new { #class = "form-content", id = "formDiv" }))
{
foreach (var item in Model.productList)
{
<input type="button" value="Delete" onclick="DeleteButtonClicked(this)" data-assigned-id="#item.ID" />
}
}
and here's the relevant script, pointing to the controller's ActionResult method in charge for item deletion:
function DeleteButtonClicked(elem) {
var itemID = $(elem).data('assigned-id');
if (confirm('sure?')) {
window.location.href = "/Gifts/DeleteConfirmed/" + itemID;
}}
Now, this works just fine, as itemID is correctly retrieved.
As I would like to add a #Html.AntiForgeryToken() to the form, the idea is to change the MVC controller's Actionmethod into a JsonResult adding a little Ajax to the script, allowing me to pass both itemID and token.
Something like:
function DeleteButtonClicked(elem) {
event.preventDefault();
var form = $('#formDiv');
var token = $('input[name="__RequestVerificationToken"]', form).val();
var itemID = $(elem).data('assigned-id');
if (confirm('sure?')) {
$.ajax({
type: 'POST',
datatype: 'json',
url: '#Url.Action("DeleteConfirmed", "Gifts")',
data: {
__RequestVerificationToken: token,
id: itemID
},
cache: false,
success: function (data) { window.location.href = "/Gifts/UserProfile?userID=" + data; },
error: function (data) { window.location.href = '#Url.Action("InternalServerError", "Error")'; }
});
}
dynamic }Some
but I have no idea on how to add the 'event' to the element (this => elem) in <input type="button" value="Delete" onclick="DeleteButtonClicked(this)" data-assigned-id="#item.ID" /> that I am using to identify the item inside the foreach loop, in order to pass it to the script.
Above script obviously fails as there's no 'event' (provided this would end to be the only mistake, which I'm not sure at all).
Some help is needed. Thanks in advance for your time and consideration.
What you want to do is use jQuery to create an event handler:
$('input[type="button"]').on('click', function(event) {
event.preventDefault();
var form = $('#formDiv');
var token = $('input[name="__RequestVerificationToken"]', form).val();
var itemID = $(this).data('assigned-id');
if (confirm('sure?')) {
$.ajax({
type: 'POST',
datatype: 'json',
url: '#Url.Action("DeleteConfirmed", "Gifts")',
data: {
__RequestVerificationToken: token,
id: itemID
},
cache: false,
success: function (data) { window.location.href = "/Gifts/UserProfile?userID=" + data; },
error: function (data) { window.location.href = '#Url.Action("InternalServerError", "Error")'; }
});
}
});
Just make sure you render this script after your buttons are rendered. Preferably using the $(document).onReady technique.
Try the 'on' event handler attachment (http://api.jquery.com/on/). The outer function is shorthand for DOM ready.
$(function() {
$('.some-container').on('click', '.delete-btn', DeleteButtonClicked);
})

how to pass dynamically created options in multi select box to MVC controller

Please help. I'm using MVC, razor 3, jquery.
I dynamically create a multi select box when a dropdown selection changes. I bind the multiple selection to a List in my model. And it works, except it passes me the list of selected indice, instead of a list of the selected text. I want selected text, not index of the list. I set the value as text, but I have no luck.
if I manually create the list, everything works. How do I pass a list of selected options back to the controller?
I have this div in my view:
<div class="row-fluid" id="divAvailableAssemblies" hidden ="hidden">
<label class="span4">Available Assemblies:</label>
<select multiple="multiple" class="span8 ui-corner-all" id="Request_SelectingAssemblies" name="Request.SelectingAssemblies">
#*<option value="test">test</option>
<option value="test2">test2</option>*#
</select>
</div>
Here my jquery:
<script type="text/javascript">
$(function () {
$('#ddPartsToCreate').live('change',function () {
var selectedPart = this.value;
if (selectedPart < 6 || $("#txtOrderNumber").val()=="")
{
$("#divAvailableAssemblies").attr("hidden", "hidden");
return;
}
$("#divAvailableAssemblies").removeAttr("hidden");
$.ajax({
type: 'POST',
url: '#Url.Action("GetSelectingAssembliesFromOrder", "Home")',
data: JSON.stringify({ orderNumber: $("#txtOrderNumber").val() }),
dataType: 'json',
contentType: 'application/json; charset=utf-8',
cache: false,
async: false,
success: function (response) {
var returnedData = JSON.parse(response);
var selectingAssemblies = $("#Request_SelectingAssemblies");
selectingAssemblies.empty();
for (var assembly in returnedData)
{
//selectingAssemblies.append($('<option >', { value: assembly }).text(returnedData[assembly].Text)).hide().show();
//selectingAssemblies.append($('<option value=' + assembly + '>' + returnedData[assembly].Text + '</option>'));
//selectingAssemblies.append($('<option >', { value: assembly, text: returnedData[assembly].Text }));
//selectingAssemblies.append($('<option></option>').val(assembly).html(returnedData[assembly].Text));
//$("#Request_SelectingAssemblies").append($('<option>', { value: assembly }).text(returnedData[assembly].Text));
//$("#Request_SelectingAssemblies").append($('<option>', { value: assembly }).text(returnedData[assembly].Text));
//$('<option />', { value: assembly, text: returnedData[assembly].Text }).appendTo(selectingAssemblies);
selectingAssemblies.append($('<option></option>').val(assembly).html(returnedData[assembly].Text));
}
},
error: function (jqXHR, textStatus, errorThrown) {
alert(errorThrown);
}
});
});
});
in the backend, I generate JSON:
foreach (var assembly in pr.ShipParts)
{
sb.Append(String.Format(",{{\"Text\":\"{0}\", \"Value\":\"{1}\"}}", assembly.Mark.ToString(), assembly.Mark.ToString()));
availableAssemblies.Add(assembly.Mark.ToString());
}
I bind the multiple selection(Request_SelectingAssemblies) with this property in my model:
public List<String> SelectingAssemblies
{
get
{
return _SelectingAssemblies;
}
set
{
_SelectingAssemblies = value;
}
}
private List<String> _SelectingAssemblies = new List<string>();
When it gets to my action in the controller, SelectingAssemblies has index instead of the actual text. But I set the value of each option as text. If I set the option manually, they will show in source page and return the text. But since I dynamically create the options, they don't show in source page. I don't know how I can make MVC understand dynamic data.
In the picture, the list of CX001, RBX001, RBX002 is dynamically created. if I hit F12 in IE, I will see them created correctly in the DOM. If I choose CX001 and RBX002, SelectionAssembies will have 0 and 2.
Thanks
This is the latest and working code, thanks to #StephenMuecke:
<script type="text/javascript">
$(function () {
$('#ddPartsToCreate').live('change',function () {
var selectedPart = this.value;
if (selectedPart < 6 || $("#txtOrderNumber").val()=="")
{
$("#divAvailableAssemblies").attr("hidden", "hidden");
return;
}
$("#divAvailableAssemblies").removeAttr("hidden");
$.ajax({
type: 'POST',
url: '#Url.Action("GetSelectingAssembliesFromOrder", "Home")',
data: JSON.stringify({ orderNumber: $("#txtOrderNumber").val() }),
dataType: 'json',
contentType: 'application/json; charset=utf-8',
cache: false,
async: false,
success: function (response) {
var returnedData = JSON.parse(response);
var selectingAssemblies = $("#Request_SelectingAssemblies");
$.each(returnedData, function (index, item) {
selectingAssemblies.append($('<option></option>').val(item.Value).html(item.Text));
});
},
error: function (jqXHR, textStatus, errorThrown) {
alert(errorThrown);
}
});
});
});
public ActionResult GetSelectingAssembliesFromOrder(String orderNumber)
{
return Json(model.GetSelectingAssembliesFromOrder(orderNumber), JsonRequestBehavior.AllowGet);
}
public String GetSelectingAssembliesFromOrder(String orderNumber)
{
//...
StringBuilder sb = new StringBuilder();
sb.Append("[");
foreach (var assembly in pr.ShipParts)
{
string assemblyName = assembly.Mark.Trim();
sb.Append(String.Format(",{{\"Text\":\"{0}\", \"Value\":\"{1}\"}}", assemblyName, assemblyName));//JSON to build the list
//...
}
sb.Append("]");
sb.Remove(1, 1);//remove extra comma
_db.SaveChanges();
return sb.ToString();
}

Categories

Resources