Variable returned by Symfony controller always undefined - javascript

Ok, so I have a text field in which I type a string and I have a button next to it.
<div class="sidebar-search">
<div class="input-group custom-search-form">
<<label for="riot-summoner-input">Search a Summoner</label><br>
<input type="text" id="riot-summoner-input" class="form-control" placeholder="Type summoner name..." style="margin-bottom: 20px">
<button type="button" id="valid-summoner">Search</button>
</div>
</div>
By Clicking on this button, the following script gets executed
let res = {{ summoner.summonerLevel }}
$(document).ready(function() {
// Get value on button click and pass it back to controller
$("#valid-summoner").click(function () {
const summoner_input = $("#riot-summoner-input").val();
console.log(summoner_input)
let url = `/coach/?summonerName=${summoner_input}`
history.replaceState(summoner_input, 'Coach Index', url);
console.log(url)
function loadXMLDoc()
{
document.getElementById("display-summonerLevel").innerHTML = `Summoner Level: <h2>${res}</h2>`
}
loadXMLDoc();
});
});
Now as far as I can understand this will change my page url to include the value inserted in the text field and will send it back to my controller without refreshing the page, which it does.
Now in my Controller I'm using that value to do some logic with it
/**
* #Route("/", name="app_coach_index", methods={"GET"})
*/
public function index(CoachRepository $coachRepository, riotApi $callRiot, Request $request): ?Response
{
$value = $request->request->get('summoner_input');
if($value != null){
$this->debug_to_console($value . "Hi");
return $this->render('coach/index.html.twig', [
'coaches' => $coachRepository->findAll(), 'summoner'=> $this->showSummoner("$value")
]);}
else{
$this->debug_to_console($value);
return $this->render('coach/index.html.twig', [
'coaches' => $coachRepository->findAll()
]);
}
}
Now it's interesting to note that I'm doing this in the index function.
Here's the function I'm calling within the index function which is actually the one that gets the value from the script
/**
* #Route("/?summonerName={summoner_input}", name="show_summoner", methods={"GET"})
*/
public function showSummoner($summoner_input)
{
$call = new ApiClient(ApiClient::REGION_EUW, 'API-KEY-HERE');
return $call->getSummonerApi()->getSummonerBySummonerName($summoner_input)->getResult();
}
Now that I'm seeing this I can see that the issue is I'm getting the value in the showSummoner() function but trying to use it in the index function. Which is why I'm not getting a value when I print it to console and the variable is undefined.
Honestly I can't think of any logic I can do to overcome this issue.
EDIT!!!!!!!
Okay, so I know where the problem is arising, the issue is when I'm calling showSummoner($value) within index function. I'm using $value = $request->query->get('summoner_input');
I thought I was getting that value in the index function when in fact I'm getting it in the showSummoner() function. You can tell by the annotations
For index I don't have a parameter in its url, whereas in showSummoner() I have a parameter in the annotations as such.
/**
* #Route("/?summonerName={summoner_input}", name="show_summoner", methods={"GET"})
*/
This is indeed the fact because I'm using that url in the script as such
let url = `/coach/?summonerName=${summoner_input}`
The reason for this is I can't use the parameter in the index url because then I would have to provide the parameter in all the other places I'm using index in even when I don't have a parameter meaning I didn't search for anything.
I hope this gives more clarification

You're trying to get a value from $_GET global, not $_POST.
You can replace :
$value = $request->request->get('summoner_input');
by:
$value = $request->query->get('summoner_input');

You are trying to access the GET parameter using the wrong name ('summoner_input').
$value = $request->request->get('summoner_input');
When you are setting it as summonerName here:
let url = `/coach/?summonerName=${summoner_input}`
You will also want to pass a default value to check for, as the second parameter.
Try this:
$value = $request->request->get('summonerName', false);
if(false !== $value){
/* the parameter is in the url */
}

Related

Unable to read value from document

I'm a novice in MVC, Below is my code
I am unable to read the value of an ID and use that in an decision statement, I am getting "The name "Text" does not exist in current context", I need to work on the if statement based on the value I get from my document.getElementById
#{
var grid = new WebGrid(Model.Abc, canPage: true, canSort: true, rowsPerPage: 50);
}
#{
var gridColumnsNew = new List<WebGridColumn>();
gridColumnsNew.Add(grid.Column("Details", header: "Id"));
<text>
var obj = document.getElementById("NextAction").value;
</text>
if (#text.obj == "Start")
{
gridColumnsNew.Add(grid.Column("Temp"));
}
}
Try using
document.getElementsByName("NextAction").value;
I have seen in my case that Blazor changes Id to name.
Note: I am using DevexpressBlazor
Did you checked if you are able to see on the html generated that ID?
If yes, Did you have any JS error before?
Looks like the ID not was generated or the place where you are run the getElementById don't have visibility to your specific code.
You are mixing razor syntax and javascript. The line var obj = document.getElementById("NextAction").value; is javascript and should go inside <script> tag. You can't call javascript functions from razor code.
Solution:
Assuming you have a controller named GridController.cs and a view named Grid.cshtml. Inside your controller add a new HttpPost action:
[HttpPost]
public IActionResult NextAction(string nextAction)
{
ViewData["NextAction"] = nextAction;
return View("Grid");
}
Inside the view add a form that posts the nextAction value to the controller:
<form asp-action="NextAction" asp-controller="Grid">
<input type="hidden" value="Start" name="nextAction" />
<button type="submit">Start</button>
</form>
The controller added the NextAction value in the ViewData dictionary so now the view can access it:
#{
var gridColumnsNew = new List<WebGridColumn>();
gridColumnsNew.Add(grid.Column("Details", header: "Id"));
if (ViewData["NextAction"] == "Start")
{
gridColumnsNew.Add(grid.Column("Temp"));
}
}
You are getting that error because you are using #text.obj. In Razor, once you attached # before any identifier, it considers it a C# or VB variable.
Since we don't have your entire page, you may need to clarify where the source of the NextAction. It will be helpful. See a sample of something similar.
#if(item.Ward == "start")
{
gridColumnsNew.Add(grid.Column("Temp"));
}
The item is from the model I am iterating to form the grid.

Changing an Input value in Blazor by javascript doesn't change it's binded property value

I'm building a website using app.net core 3.1 with blazor.
In one of my components I have :
<input #bind="Message" type="text" id="input-message"/>
Message is just a string property.
and I have javascript:
document.getElementById('input-message').value = 'some text';
The problem is after running the above js, <input> value changes but Message value doesn't, and of course if I type or paste something inside <input> , Message value changes too.
Apparently changing <input> value or any other changes in DOM by javascript doesn't change State, so blazor won't re-render the component. Even calling StateHasChanged(); manually in your razor page won't work.
To get this done, you just have to trigger the same DOM events that occur if the user modifies the <input> normally, just like below:
var myElement = document.getElementById('input-message');
myElement.value = 'some text';
var event = new Event('change');
myElement.dispatchEvent(event);
You shouldn't change the input value directly in javascript, what you should do is call a c# function that updates the value and then it will update the javascript.
Instead of doing
document.getElementById('input-message').value = 'some text';
You should do something like
DotNet.invokeMethodAsync('UpdateMessageValue', 'some text');
Where you have
public void UpdateMessageValue(string value){
Message = value;
}
And because you are using bind in the input, the value of document.getElementById('input-message').value will be changed, and the value in the c# will also be changed.
This answer isn't complete, I'm passing you the idea on how to do it and not the correct code to solve your case, but if you want more information on how to do it, you can take a look at Call .NET methods from JavaScript functions in ASP.NET Core Blazor.
If you don't have control over the third-party lib script that is modifying your input field you can always use the following solution. The concept is the following:
After rendering the component we call JS to start intercepting the all input fields value setters, then we get our callback in Blazor from JS. Blazor then dispatches to appropriate field.
Fields example:
<div class="input-group input-daterange" data-date-format="dd.mm.yyyy">
<input type="text"
id="InputDateFrom"
#bind="InputDateFrom"
class="form-control text-center" placeholder="От">
<span class="input-group-addon"><i class="fa fa-angle-right"></i></span>
<input type="text"
id="InputDateTo"
#bind="InputDateTo"
class="form-control text-center" placeholder="До">
</div>
JS:
function WatchInputFields(callbackChangedName, dotnetRef) {
var descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");
var originalSet = descriptor.set;
// define our own setter
descriptor.set = function (val) {
console.log("Value set", this, val);
originalSet.apply(this, arguments);
dotnetRef.invokeMethodAsync(callbackChangedName, this.id, this.value);
}
Object.defineProperty(HTMLInputElement.prototype, "value", descriptor); }
BLAZOR: Inside OnAfterRenderAsync call InitInputFieldsToTrackOnce on first rendering (or second one in case of server with prerendering):
private async Task InitInputFieldsToTrackOnce()
{
_objRef = DotNetObjectReference.Create(this);
await JS.InvokeVoidAsync(
"WatchInputFields",
"OnInputFieldChanged",
_objRef);
WatchInputField("InputDateFrom", (value) => { _InputDateFrom = value; Console.WriteLine($"Setting FROM to {value}"); });
WatchInputField("InputDateTo", (value) => { _InputDateTo = value; ; Console.WriteLine($"Setting TO to {value}"); });
}
void WatchInputField(string id, Action<string> onChanged)
{
InputFieldChanges.TryAdd(id, onChanged);
}
private ConcurrentDictionary<string, Action<string>> InputFieldChanges { get; } = new ConcurrentDictionary<string, Action<string>>();
private DotNetObjectReference<ShopDesk> _objRef;
[JSInvokable]
public async Task OnInputFieldChanged(string id, string value)
{
var ok = InputFieldChanges.TryGetValue(id, out Action<string> action);
if (ok)
action(value);
}
And dispose _objRef when disposing your component.

How do I populate a list field in a model from javascript?

I have a Kendo.MVC project. The view has a model with a field of type List<>. I want to populate the List from a Javascript function. I've tried several ways, but can't get it working. Can someone explain what I'm doing wrong?
So here is my model:
public class Dashboard
{
public List<Note> ListNotes { get; set; }
}
I use the ListNotes on the view like this:
foreach (Note note in Model.ListNotes)
{
#Html.Raw(note.NoteText)
}
This works if I populate Model.ListNotes in the controller when the view starts...
public ActionResult DashBoard(string xsr, string vst)
{
var notes = rep.GetNotesByCompanyID(user.ResID, 7, 7);
List<Koorsen.Models.Note> listNotes = new List<Koorsen.Models.Note>();
Dashboard employee = new Dashboard
{
ResID = intUser,
Type = intType,
FirstName = user.FirstName,
LastName = user.LastName,
ListNotes = listNotes
};
return View(employee);
}
... but I need to populate ListNotes in a Javascript after a user action.
Here is my javascript to make an ajax call to populate ListNotes:
function getReminders(e)
{
var userID = '#ViewBag.CurrUser';
$.ajax({
url: "/api/WoApi/GetReminders/" + userID,
dataType: "json",
type: "GET",
success: function (notes)
{
// Need to assign notes to Model.ListNotes here
}
});
}
Here's the method it calls with the ajax call. I've confirmed ListNotes does have the values I want; it is not empty.
public List<Koorsen.Models.Note> GetReminders(int id)
{
var notes = rep.GetNotesByCompanyID(id, 7, 7);
List<Koorsen.Models.Note> listNotes = new List<Koorsen.Models.Note>();
foreach (Koorsen.OpenAccess.Note note in notes)
{
Koorsen.Models.Note newNote = new Koorsen.Models.Note()
{
NoteID = note.NoteID,
CompanyID = note.CompanyID,
LocationID = note.LocationID,
NoteText = note.NoteText,
NoteType = note.NoteType,
InternalNote = note.InternalNote,
NoteDate = note.NoteDate,
Active = note.Active,
AddBy = note.AddBy,
AddDate = note.AddDate,
ModBy = note.ModBy,
ModDate = note.ModDate
};
listNotes.Add(newNote);
}
return listNotes;
}
If ListNotes was a string, I would have added a hidden field and populated it in Javascript. But that didn't work for ListNotes. I didn't get an error, but the text on the screen didn't change.
#Html.HiddenFor(x => x.ListNotes)
...
...
$("#ListNotes").val(notes);
I also tried
#Model.ListNotes = notes; // This threw an unterminated template literal error
document.getElementById('ListNotes').value = notes;
I've even tried refreshing the page after assigning the value:
window.location.reload();
and refreshing the panel bar the code is in
var panelBar = $("#IntroPanelBar").data("kendoPanelBar");
panelBar.reload();
Can someone explain how to get this to work?
I don't know if this will cloud the issue, but the reason I need to populate the model in javascript with an ajax call is because Model.ListNotes is being used in a Kendo Panel Bar control and I don't want Model.ListNotes to have a value until the user expands the panel bar.
Here's the code for the panel bar:
#{
#(Html.Kendo().PanelBar().Name("IntroPanelBar")
.Items(items =>
{
items
.Add()
.Text("View Important Notes and Messages")
.Expanded(false)
.Content(
#<text>
#RenderReminders()
</text>
);
}
)
.Events(e => e
.Expand("getReminders")
)
)
}
Here's the helper than renders the contents:
#helper RenderReminders()
{
if (Model.ListNotes.Count <= 0)
{
#Html.Raw("No Current Messages");
}
else
{
foreach (Note note in Model.ListNotes)
{
#Html.Raw(note.NoteText)
<br />
}
}
}
The panel bar and the helpers work fine if I populate Model.ListNotes in the controller and pass Model to the view. I just can't get it to populate in the javascript after the user expands the panel bar.
Perhaps this will do it for you. I will provide a small working example I believe you can easily extend to meet your needs. I would recommend writing the html by hand instead of using the helper methods such as #html.raw since #html.raw is just a tool to generate html in the end anyways. You can write html manually accomplish what the helper methods do anyway and I think it will be easier for you in this situation. If you write the html correctly it should bind to the model correctly (which means it won't be empty on your post request model) So if you modify that html using javascript correctly, it will bind to your model correctly as well.
Take a look at some of these examples to get a better idea of what I am talking about:
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
So to answer your question...
You could build a hidden container to hold your list values like this (make sure this container is inside the form):
<div id="ListValues" style="display:none">
</div>
Then put the results your ajax post into a javascript variable (not shown).
Then in javascript do something like this:
$('form').off('submit'); //i do this to prevent duplicate bindings depending on how this page may be rendered futuristically as a safety precaution.
$('form').on('submit', function (e) { //on submit, modify the form data to include the information you want inside of your ListNotes
var data = getAjaxResults(); //data represents your ajax results. You can acquire and format that how you'd like I will use the following as an example format for how you could save the results as JSON data: [{NoteID ="1",CompanyID ="2"}]
let listLength = data.length;
for (let i = 0; i < listLength; i++) {
$('#ListValues').append('<input type="text" name="ListNotes['+i+'].NoteID " value="' + data.NoteID +'" />')
$('#ListValues').append('<input type="text" name="ListNotes['+i+'].CompanyID " value="' + data.CompanyID +'" />')
//for your ajax results, do this for each field on the note object
}
})
That should do it! After you submit your form, it should automatically model bind to you ListNotes! You will be able to inpsect this in your debugger on your post controller action.

Pass Angular typeahead object result to another function in controller

In this scenario I'm using the ui-bootstrap typeahead to capture an object from an external api. Using the select callback I'm getting that object and have the results set in a separate function within my controller.
The issue is that I want to take those results and send them off to a separate api with a click function I already have set up. My question is how do i get the results of the type-ahead into the click function to post? The user flow is as follows.
<div>
<input type="text" placeholder="Find A Game"
typeahead-on-select="setGames($item)"
ng-model="asyncSelected"
typeahead="test.name for test in getGames($viewValue)"
typeahead-loading="loadingLocations" typeahead-min-length="3"
typeahead-wait-ms="500" typeahead-select-on-blur="true"
typeahead-no-results="noResults">
</div>
<div ng-show="noResults">
No Results Found
</div>
<button ng-disabled="!asyncSelected.length"
ng-click="addtodb(asyncSelected)">Add To Database</button>
As you can see the label is set to the items name and this works fine. When the user selects the name I then use typeahead-on-select="setGames($item)" to send off the entire object to its own funtion. From there I want to take the object and pass it to another function that you can see within the button tags ng-click. I currently have it passing the model, but what I really want is to pass the entire object within $item from the select event. So far my controller looks like this:
angular.module('2o2pNgApp')
.controller('GiantCtrl', function ($scope, $http, TermFactory, $window, SaveFactory) {
$scope.getGames = function(val) {
return $http.jsonp('http://www.example.com/api/search/?resources=game&api_key=s&format=jsonp&limit=5&json_callback=JSON_CALLBACK', {
params: {
query: val
}
}).then(function(response){
return response.data.results.map(function(item){
return item;
});
});
};
$scope.setGames = function (site) {
var newsite = site;
};
$scope.addtodb = function (asyncSelected, newsite) {
TermFactory.get({name: asyncSelected}, function(data){
var results = data.list;
if (results === undefined || results.length === 0) {
SaveFactory.save({vocabulary:'5', name:newsite.name, field_game_id:newsite.id}, function(data) {
$window.alert('All Set, we saved '+asyncSelected+' into our database for you!')
});
} else {
// do stuff
});
}
});
No matter what I do I cant seem to pass the entire $item object into this click function to post all the info i need.
Via New Dev in Comments:
$item is only available locally for typeahead-on-select... you can
either assign it to some model within your controller, or, in fact,
make the model of typeahead to be the item: typeahead="test as
test.name for test in getGames($viewValue)" – New Dev

Javascript push Object to cookies using JSON

Hi All on click button I need to add object to array and then write array to cookies.
From the start this array can be not empty so I parse cookie first.
function addToBasket(){
var basket = $.parseJSON($.cookie("basket"))
if (basket.length==0||!basket){
var basket=[];
basket.push(
{ 'number' : this.getAttribute('number'),
'type' : this.getAttribute('product') }
);
}
else{
basket.push(
{ 'number' : this.getAttribute('number'),
'type' : this.getAttribute('product') }
);
}
$.cookie("basket", JSON.stringify(basket));
}
And HTML
<button type="button" class="btn btn-success btn-lg" number="12" product="accs" onclick="addToBasket()">Add</button>
Unfortunately I'm getting Uncaught ReferenceError: addToBasket is not defined onclick.
Can't understand what am I doing wrong?
Thanks!
I simplified your code a good deal, heres a fiddle: http://jsfiddle.net/yJ6gp/
I wired the click event using jQuery and simplified some of your code (see comments). Note I changed your html a little so I could select the add basket button by class - change as desired.
$(function () {//doc ready
$.cookie.json = true; //Turn on automatic storage of JSON objects passed as the cookie value. Assumes JSON.stringify and JSON.parse:
$('.add-basket').click(function() {
var basket = $.cookie("basket") || []; //if not defined use an empty array
var $this = $(this);
basket.push({
'number': $this.attr('number'),
'type': $this.attr('product')
});
console.log(basket);
$.cookie("basket", basket);
});
});

Categories