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>
}
Related
I want to pass the value i got it from model to the java-script function
<script type="text/javascript">
var checkin = #Model.Parameters.Checkin.ToString("dd-MM-yyyy");
var checkout = #Model.Parameters.Checkout.ToString("dd-MM-yyyy");
</script>
this function that i want to pass model chick-in and chick-out value to it:
$('document').ready(function () {
$("#Arrival").val(checkin);
$("#Departure").val(checkout);
});
i tried many solution but it didn't work yet .
any advice, thanks
if the #Model.Parameters.Checkin and #Model.Parameters.Checkout not null then Try:
<script type="text/javascript">
$( document ).ready(function(){
var checkin = '#Model.Parameters.Checkin.ToString("dd-MM-yyyy")';
var checkout = '#Model.Parameters.Checkout.ToString("dd-MM-yyyy")';
$("#Arrival").val(checkin);
$("#Departure").val(checkout);
});
Just you miss '. and also change $('document').ready(function () { }) to $(document).ready(function () { }).
you must write all script into a .cshtml file. #Model.Parameters.Checkin.ToString("dd-MM-yyyy") never work into a .js file.
Because, In .cshtml, when the the page is render then it white to HTTP response stream as a string.
In MVC, you can use following code:
<script type="text/javascript">
var number = parseInt(#ViewBag.Number); //Accessing the number from the ViewBag
alert("Number is: " + number);
var model = #Html.Raw(#ViewBag.FooObj); //Accessing the Json Object from ViewBag
alert("Text is: " + model.Text);
</script>
I have a MainView "About.cshtml" it has a script tag in it and a partial view.
<script>
$(function () {}
</script>
<div>
#Html.Partial("~/Views/Maps/_MapDetailsList.cshtml", Model.saVM)
</div>
Inside "_MapDetailList.cshtml" partial view i am referencing another script ge.js
#Scripts.Render("~/Scripts/ge.js")
<table id="MapDetails">
.....
<tr><th>
<script>setGrowthArray(1, 1);</script>
</th></tr>
</table>
ge.js
var dictionaryGrowth = new Array();
function setGrowthArray(colIndex, mapDetailId) {
//making a sparse array
dictionaryGrowth[colIndex] = mapDetailId;
}
Now i want to send this dictionaryGrowth array to server side after the page/table is loaded
so i did the following in the About.cshtml script but didnot work..
<script>
$(function () {
$("#MapDetails").load(function () { alert("everything seems fine");});
}
</script>
Also please tell me what will be the script and DOM loading sequence in my case.
UPDATE
Probably the Current sequence is
Script on About.cshtml is executed
ge.js is executed
document.ready inside partial view is fired
javascript function (setGrowthArray) from inside DOM is called
Now i want to call my controller??
If i write window.onload = ... inside ge.js it is never fired
You can substitute using $.post() for .load(), pass result of setGrowthArray(1, 1) as data posted to server
<script>
$.post("/path/to/server", {growth:setGrowthArray(1, 1)}, function(data) {
console.log(data); // response from server
$("#MapDetails").html(data);
})
</script>
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.
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.
I'm just getting started with Knockout.js and i have a view(html) which is supposed to be populated by data from a rest api via jquery's $.getJSON method.
When i run the app, nothing shows but using firebug i can see that the 'GET' query returns a status code of 200 and the right data.
I'm at a fix as to why nothing shows in the view since the bindings in Knockout.js are supposed to be automatic.
Below is my code.
Thanks
<div id ='main'>
<!-- ko foreach: posts -->
<p>Hello</p><span data-bind="text: title"></span></p><p data-bind="text: content"></p>
<p data-bind="text: author"></p><p data-bind="text: date"></p>
<!-- /ko -->
</div>
</body>
<script type="text/javascript">
function Post(data){
this.title = ko.observable(data.title);
this.content = ko.observable(data.content);
this.author = ko.observable(data.author);
this.date = ko.observable(data.date)
}
function PostListViewModel(){
var self = this;
self.posts = ko.observableArray([]);
$.getJSON("/posts", function(getPost){
var mappedPost = $.map(getPost, function(item){
return new Post(item)
});
self.posts(mappedPost);
});
}
var postlistviewmodel = new PostListViewModel();
ko.applyBindings(postlistviewmodel);
</script>
This should be:
$.getJSON("/posts", function(getPost){
var mappedPosts = $.map(getPost, function(item){
return new Post(item)
});
self.posts(mappedPosts);
});
wouldn't do self.posts.push(mappedPosts[i]) at all. You should just pass mappedPosts through the ko binding in order to update the listeners.
If your just getting the latest posts and want to update your current list simply do:
var allPosts = self.posts().concat(mappedPosts);
self.posts(allPosts);
You don't need the model to have ko.observable if you're just displaying them. If you want to edit model as well, then leave as.
Also, I tend to do this for single or multiple view models:
ko.applyBindings({viewModel : new viewModel() };
This allows for having multiple named view models. Access scope using: $root.viewModel
This is what I did earlier: http://jsfiddle.net/jFb3X/
Check your code against this fiddle then.
Script tags also need to be above the closing body tags
<html>
<head>
</head>
<body>
<!-- all your html content -->
<script type="text/javascript">
var viewModel = function () {
}
ko.applyBindings({viewModel : new viewModel()});
</script>
</body>
</html>
Is it something as simple as waiting for the DOM to be ready?
Are you able to try the following:
$(function () {
ko.applyBindings(postlistviewmodel);
});
Source: I've done this a few times and been stumped for a bit trying to see what I did wrong. :-)
(As a style thing, I'd also move the /body to after the /script - probably not related to your issue though).
I suspect you get multiple posts from /posts. You only push a single item (array).
...
$.getJSON("/posts", function(getPost){
var mappedPosts = $.map(getPost, function(item){
return new Post(item)
});
for(var i = 0; i < mappedPosts.length; i++) {
self.posts.push(mappedPosts[i]);
}
});
...