Definition of Function SubmitSurvey have not been found but I defined it - javascript

It is literally fifth day I try to solve this.
I try to invoke a method by a button in Razor View, no redirections to other views, just invoke a simple method when button is clicked.
The script looks like:
<script>
function SubmitClick () {
var pid = $(this).data('personid');
var sid = $(this).data('surveyid');
var url = '#Url.Action("SubmitSurvey", "Person")';
$.post(url, { personid: pid, surveyid: sid }, function (data) {
alert('updated');
});
};
</script>
The button looks like:
<button class='mybutton' type='button' data-personid="#Model.Item1.Id" data-surveyid="#survey.Id" onclick="javascript:SubmitClick()">Click Me</button>
The PersonController method looks like:
public void SubmitSurvey(int personId, int surveyId) {
System.Diagnostics.Debug.WriteLine("UPDATING DATABASE");
}
The full view (this is PartialView):
<script>
function SubmitClick () {
var pid = $(this).data('personid');
var sid = $(this).data('surveyid');
var url = '#Url.Action("SubmitSurvey", "Person")';
$.post(url, { personid: pid, surveyid: sid }, function (data) {
alert('updated');
});
};
</script>
#using WebApplication2.Models
#model System.Tuple<Person, List<Survey>>
<hr />
<h1>Surveys</h1>
<input type="button" id="Coll" value="Collapse" onclick="javascript:CollapseDiv()" />
#*<p>
Number of Surveys: #Html.DisplayFor(x => Model.Item2.Count)
</p>*#
#{int i = 1;}
#foreach (var survey in Model.Item2) {
using (Html.BeginForm()) {
<h2>Survey #(i)</h2>
<p />
#Html.EditorFor(x => survey.Questions)
<button class='mybutton' type='button' data-personid="#Model.Item1.Id" data-surveyid="#survey.Id" onclick="javascript:SubmitClick()">Click Me</button>
}
i++;
<hr style="background-color:rgb(126, 126, 126);height: 5px" />
}
<hr />
The problem is that when I click the button:
I get runtime error saying that there is no definition of: "SubmitClick".

I don't see any obvious problems in your code, but given that you're handling this in a sub-optimal way, refactoring your code may solve the problem just by improving the setup.
First, don't embed your scripts directly in the view. I understand that you need to include a URL generated via one of the Razor helpers, but what I'm talking about here is using sections so that your scripts get included in a standard location in the document:
So, in your view:
#section Scripts
{
<script>
// your code here
</script>
}
And then in your layout:
<!-- global scripts like jQuery here -->
#RenderSection("Scripts", required: false)
</body>
This ensures that 1) all your JavaScript goes where it should, right before the closing body tag and 2) all your JavaScript gets run after the various global scripts that it will likely depend on (jQuery).
Second, it's usually a bad idea to define things in the global scope, such as you are doing with your SubmitClick function. If another script comes along and defines it's own SubmitClick function in the global scope, then yours gets hosed or vice versa. Instead, you want to use namespaces or closures.
Namespace
var MyNamespace = MyNamespace || {};
MyNamespace.SubmitClick = function () {
...
}
Closure
(function () {
// your code here
})();
Of course, if you use a closure like this, then you SubmitClick function truly won't exist, as it's no longer in the global scope, which brings me to...
Third, don't use the on* HTML attributes. It's far better to bind functionality to elements dynamically, for example:
(function () {
$('.mybutton').on('click', function () {
var pid = $(this).data('personid');
var sid = $(this).data('surveyid');
var url = '#Url.Action("SubmitSurvey", "Person")';
$.post(url, { personid: pid, surveyid: sid }, function (data) {
alert('updated');
});
});
})();
Now, you've got zero scope pollution and behavior is bound where behavior is defined, instead of tightly-coupling your HTML and JavaScript.

Related

Unable to execute scripts depending on Thymeleaf variables <script th:inline="javascript">

I have 2 simple scripts and 2 Thymeleaf variables, I want one script to be executed if thymeleaf variable is true, then again if other variable is true - related script to be executed and so on. But I get the following result - if one variable is true and script executed then second script won't be execudet even if second variable is true. So shortly, only one of scripts is executed, but need both (in sequence, not the same time). Here is the code:
<script th:inline="javascript">
var flag = [[${invalidInput}]]; //Thymleaf variable
window.onload = function() {
if(!flag)
return;
openForm();
};
</script>
<script th:inline="javascript">
var flag = [[${exists}]];
window.onload = function() {
if(!flag)
return;
openForm();
};
</script>
<!-- MODAL -->
<div class="form-popup" id="myForm">
<form id="registration" th:action="#{/registrate}" th:object="${newUser}" method="post" class="form-container">
<h1>Registration</h1>
<div class="alert" th:if="${exists}">
User already exists! Please try again.
</div>
<div class="invalidInput" th:if="${invalidInput}">
Username or password too short.
</div>
************************************************************
<script>
function openForm() {
document.getElementById("myForm").style.display = "block";
}
#PostMapping("/registrate")
public String login (#ModelAttribute(value = "newUser") User newUser, BindingResult bindingResult, Model model) {
userValidator.validate(newUser, bindingResult);
if(usrService.isUserPresent(newUser.getUsername())){
model.addAttribute("exists",true);
return "login";
}
else if(bindingResult.hasErrors()){
model.addAttribute("invalidInput",true);
return "login";
}
I don't think you can set the window.onload variable twice... one will simply overwrite the other. You can either combine your logic like this:
<script th:inline="javascript">
var invalidInput = [[${invalidInput}]]; //Thymleaf variable
var exists = [[${exists}]];
window.onload = function() {
if(invalidInput) {
openForm();
} else if (exists) {
openForm();
}
};
</script>
Or alternatively you could use something like jQuery, which has this functionality built in for you.
https://api.jquery.com/ready/
When multiple functions are added via successive calls to this method,
they run when the DOM is ready in the order in which they are added.

Trying to build HTML elements by code, functions included

This is my first post, hoping someone can help me:
I wish to build a web project, where all the HTML elements are stored in database and taken from it to build the web page.
i found a problem with the buttons, i cannot find a way to store the function for a button, i´m using Jquery to build the elements, for now the test element definitions are simulated in some arrays i left at the start of my Js file, the only way i can make the buttons to work is if the functions are hardcoded in the Js file, is there a way for me to bring the functions from database too? and having them in an array?
this is my project sample:
HTML
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<!--script src="functions.js"></script-->
<script src="system.js"></script>
<!--script src="elements.js"></script-->
</head>
<body>
<body onload="addElements()">
<div id="div1"></div>
</body>
</html>
JS File
/**
VARIABLE DEFINITIONS
THESE ARE SUPPOSED TO COME FROM A DATABASE
STILL UNKNOWN HOW TO BRING THE FUNCTIONS, AS STRING THEY ARE NOT ALLOWED. FOR NOW THERE ARE TEST FUNCTIONS.
**/
let buttonIds = ['btn1', 'btn2'];
let buttonText = ['Show Text', 'Show HTML'];
let buttonFunc = [alert1, alert2];
//let buttonFunc = ['alert("Hi");', 'alert("Hello");'];
let paragraphs = ['This is some <b>bold</b> text in a paragraph.', 'another <b>bold</b> test'];
//HELPER FUNCTIONS
// **** THESE ARE SUPPOSED TO COME FROM DATABASE, UNKNOWN HOW TO DO IT. ****
function alert1() {
alert("Hi");
}
function alert2(){
alert("Hello");
}
function addElements(){
for(var p=0; p<paragraphs.length; p++){ addParagraphs('#div1', paragraphs[p]); }
for(var i=0; i<buttonIds.length; i++) { createButton( '#div1', buttonIds[i] , buttonText[i]); }
}
// ANY ELEMENTS FUNCTION IS DEFINED HERE ONCE THE PAGE IS LOADED.
$(document).ready(function(){
for(var x=0;x<buttonIds.length; x++){ activateButton(buttonIds[x], buttonFunc[x]); }
});
//HELPER FUNCTIONS USED TO BUILD THE HTML ELEMENTS ON THE MAIN PAGE.
function addParagraphs(location, text){
$(location).append('<p id="test">'+text+'</p>');
}
function createButton(location, id, text){
var definition;
definition = "<button id="+id+">"+text+"</button>";
$(location).append(definition);
}
function activateButton(buttonId, functionName){
var composedId = "#"+buttonId;
$(composedId).click(functionName);
}
You can generate Javascript file serverside with all the funcions you need.
Supposing Node.js you can do something like this:
expressApp.get("some.js", (req, res) => {
getDataFromDatabase() // depends on your database
.then(data => {
let body = 'function your_fn () { alert("'+ JSON.stringify(data) +'")}';
res.send(body);
})
});
One approach is use an object to store the functions in javascript and use property names stored in db to associate which function to use for which element.
Without knowing more about your use case it is hard to really help design a proper system to use
Following is a very basic example
// functions stored in js file
const funcs = {
f1: function(e){ console.log('func one called , id = ', this.id)},
f2: function(e){ console.log('func 2 called , id = ', this.id)}
}
// data from database
const elems = [
{id: 1, className: 'one', func:'f1', text:'Item 1'},
{id: 1, className: 'two', func:'f2', text:'Item 2'}
]
elems.forEach(e => {
const $el= $('<div>', {id: e.id, class: e.className, text:e.text, click: funcs[e.func]})
$('body').append($el);
});
div {margin:1em;}
.one {color:red;}
.two {color:green;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<strong>Click on items</strong><br><br>

Undefined variable object using Underscore JS partials and for loop

I'm writing a twitter aggregator and I need some help on solving the error 'Uncaught ReferenceError: sqTweetData is not defined.' It looks like console is pointing me to my for loop. I have set up a partial that is compiled and loaded in #main-content using underscore js.
For Loop Code
<!-- Main Content -->
<main class="main">
<div class="container-flex" id="main-content"></div>
</main> <!-- End Main Content -->
<!-- Current Tweet Partials -->
<script id="active-tweet-partial" type="underscore/template">
<section class="tweetFlexItem">
<% for (var i = 0; i < sqTweetData.length; i++) { %>
<div class="activeTweet">
<div class="activeTweet__avatar"><img src="<%= sqTweetData[ i ].user.profile_image_url %>"></div>
<div class="activeTweet__wrapper">
<div class="activeTweet__name"> <%= sqTweetData[ i ].user.name %> </div>
<div class="activeTweet__message"><%= sqTweetData[ i ].text %></div>
</div>
</div>
<% } %>
</section>
</script>
home.js Compiling Code
var Home = (function() {
var sqTweetData = {
user: [{
profile_image_url : "assets/avatar.png",
name : "#johnsnow"
}],
text : "Someone once said that I know nothing..."
};
console.log("this is sqTweetData", sqTweetData);
// Partials
var tweetPartial = $('#active-tweet-partial').html();
tweetPartialCompiled = _.template( tweetPartial );
// DOM Handlers
// KICKSTART VIEW
function initHome() {
// load main content
$('#main-content').html(tweetPartialCompiled( sqTweetData ));
// bind events
}
return {
init: initHome
};
})();
The console.log on line 11 works just fine, so I'm assuming my variable object is set up correctly. There just seems to be a disconnect between the partial and the rest of the javascript.
Any thoughts?
This is a scoping issue. sqTweetData says it's undefined because it's exactly that. window["sqTweetData"] does not exist. When you declare a variable outside of a function it's inserted into the global namespace, in this case the browser window is the namespace.
Since you're declaring the variable inside of home using the var keyword, the variable will only be accessible within the Home function. So you'd have to add it as either a this.sqTweetdata and return it with the object, or add a separate getTweetData() that return the variable, or something along those lines.
Check out this answer that covers scoping very comprehensively:
https://stackoverflow.com/a/500459/3629438
Yours falls under:
Advanced: Closure
var a = 1;
var six = (function() {
var a = 6;
return function() {
// JavaScript "closure" means I have access to 'a' in here,
// because it is defined in the function in which I was defined.
alert(a);
};
})();
EDIT:
In your case you would do something along the lines of
var Home = (function() {
// ....... //
function getTweetData() {
return sqTweetData;
}
return {
init: initHome,
get: getTweetData
};
})();

Passing a parameter to my Knockout ViewModel

I have an MVC site that uses Knockout JS. Basically, the MVC handles routing to a few different pages, and each page has a viewmodel.
One of the pages requires a parameter to filter the data. The code for the MVC Controller for that page is as follows:
public ActionResult Transactions(int policyId)
{
ViewData["policyId"] = policyId;
return View();
}
The View for that page includes a hidden field.
<input type="hidden" name="hldPolicy" value="#ViewData["policyId"]">
Then after the html for the page,
#section scripts
{
#Scripts.Render("~/bundles/myBundle")
<script>
$(document).ready(function () {
var policyId = $('#hldPolicy').val();
var transactionViewModel = new TransactionViewModel(policyId);
ko.applyBindings(transactionViewModel);
});
</script>
}
The problem is this doesn't work because the hidden field is undefined when the script runs. That doesn't make sense to me as I thought that was what the $(document).ready was protecting against. What am I doing wrong here? And is there a better way to pass a parameter from the URL params into the viewmodel?
You can use it like this. Here you dont actually have to pass the parameter instead define a function which will be called on viewmodel initialization and get the data according to your requirements.
#section scripts
{
#Scripts.Render("~/bundles/myBundle")
<script type="text/javascript">
function TransactionViewModel(){
var self = this
self.SomeProperty = ko.observable()
self.LoadData = function(){
var policyId = $('#hldPolicy').val();
self.SomeProperty(policyId)
}
self.LoadData()
}
$(document).ready(function () {
ko.applyBindings(new TransactionViewModel());
});
</script>
}
When knockout model will be initialized it will call self.LoadData() automatically.
EDIT
I found you are missing id attribute at your input
<input type="hidden" id="hldPolicy" name="hldPolicy" value="#ViewData["policyId"]">
Now it should work properly.
EDIT:
You can also do it like this
#section scripts
{
#Scripts.Render("~/bundles/myBundle")
<script type="text/javascript">
function TransactionViewModel(policyId){
var self = this
self.SomeProperty = ko.observable()
self.LoadData = function(policyId){
self.SomeProperty(policyId)
}
self.LoadData(policyId);
}
$(document).ready(function () {
var policyId = $('#hldPolicy').val();
ko.applyBindings(new TransactionViewModel(policyId));
});
</script>
}

how to access the current controller name inside my Razor view jquery

I have the following script , that is being used inside multiple views:-
$("#ChoiceTag, #ChoiceName").each(function () {
$(this).change(function () {
if ($("#ChoiceName").prop("checked")) {
$.getJSON("#Url.Content("~/Firewall/LoadCSName")",
function (CSData) {
var select = $("#GeneralCSID");
select.empty();
select.append($('<option/>', {
value: "",
text: "Select Name..."
}));
$.each(CSData, function (index, itemData) {
select.append($('<option/>', {
value: itemData.Value,
text: itemData.Text
}));
select.val('#Model.CSIT360ID');
});
});
}
the script is exactly the same for all the views except for the controller name inside the following statement:-
$.getJSON("#Url.Content("~/Firewall/LoadCSName")",
so i am looking to move the above script and add it inside a separate .js file, and then reference this script , but i have the following two question:-
if i move the script to the script folder i need to dynamically reference the current controller name to build the URL, so is this possible
can i still reference the viewbag as i am currently doing ..
Thanks
If you move your Javascript into an external file you can't use your Razor syntax. Therefore, #Url.Content("~/Firewall/LoadCSName") will not resolve.
To overcome this add this to your view
<script type="text/javascript"> var AppPath = '#Url.Content("~/")'</script>
and reference it in your script like this
$.getJSON(AppPath + "Controller/Action")
Regarding the viewbag. Just put the viewbags value in a variable as shown above and your external file can reference it.
Hope this helps
Update
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<script type="text/javascript">
var AppPath = '#Url.Content("~/")';
var SomeValue = '#Model.CSIT360ID';
var ControllerName = "Firewall/LoadCSName";
</script>
<!--Move this to an external File-->
<script type="text/javascript">
$("#ChoiceTag, #ChoiceName").each(function () {
$(this).change(function() {
if ($("#ChoiceName").prop("checked")) {
$.getJSON(AppPath + ViewBagValue), function(CSData) {
var select = $("#GeneralCSID");
select.empty();
select.append($('<option/>', {
value: "",
text: "Select Name..."
}));
$.each(CSData, function(index, itemData) {
select.append($('<option/>', {
value: itemData.Value,
text: itemData.Text
}));
select.val(SomeValue);
});
//end each
});
}
});
</script>
</body>
</html>
Update 2
This is how you could reference the controller in the url.content
<script type="text/javascript">
var AppPath = '#Url.Content("~/" + HttpContext.Current.Request.RequestContext.RouteData.Values["controller"])'
</script>
you can get the controller name this way:
#{
string controllerName = HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString();
}
To access controller name from view use
#{
ViewContext.RouteData.Values["controller"].ToString();
}
To access controller instance one can use as follow
#{
(HomeController)ViewContext.Controller
}
One (slightly hacky) to make the current controller name accessible to JS would be to burn it into a global or namespaced variable assignment in the layout.
<script>
var app = window.app || {}
app.currentController = "#HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString().toLower()";
</script>
Alternatively, a common way I work is to add classnames of the current controller and action to the body tag, to assist in DOM based routing in any javascript.

Categories

Resources