It's a fairly simple problem, I am using AngularJS v1.7.2 with C# MVC.
I got my standard setup with Layout pages and Views.
I load my AngularJs controllers/services from external script files so there's nothing on the Views.
My problem is that I want to assign a value from ViewBag to a controller variable, but I obviously can't reference ViewBag in a script as it needs to be done on the cshtml page.
I have tried doing it inside ng-init like so
<div ng-init="City = #ViewBag.City"></div>
Or
<div style="visibility: hidden;">{{CityId = '1'}}</div>
I tried variations with {{City = #ViewBag.City}}, '#ViewBag.City' and couple of others I saw on StackOverflow to no avail.
I load my scripts on the view using:
#section Page_Scripts {
#Scripts.Render("~/angular/ngListing")
}
That obviously is loaded in Layout. My controller works fine so that's not the issue.
My controller is making an ajax call upon initialization, at that point I need the $scope.City to be populated with the right value, however it's always set at 0.
Here's what my controller + service (combined for sake of SO) looks like:
_mainApp.controller("ListingCtrl", function ($scope, $http) {
$scope.City = 0;
$scope.Attractions = [];
$scope.Offset = 0;
$scope.Pages = new Array(10);
var MakeRequest = function (offset) {
$http.post("/City/GetStuff?City=" + $scope.City + "&Offset=" + offset).then(function (resp) {
$scope.Attractions = resp.data;
});
}
MakeRequest($scope.Offset);
$scope.PageUp = function () {
$scope.Offset++;
MakeRequest($scope.Offset);
}
$scope.PageDown = function () {
$scope.Offset--;
MakeRequest($scope.Offset);
}
$scope.GoTo = function (offset) {
$scope.Offset = offset;
MakeRequest(offset);
}
});
Any solution that is not hacky-ish would be appreciated. It can include directives or a way to assign a value to $scope.City but inline, or at least get my ViewBag.City value passed to the controller somehow.
Use a function in the ng-init directive:
<div ng-init="initCity(#ViewBag.City)"></div>
Define the function in the controller:
$scope.initCity = function(city) {
$scope.city = city;
MakeRequest($scope.offset, $scope.city);
};
function MakeRequest(offset,city) {
var url = "/City/GetStuff";
var params = {city:city, offset:offset};
var config = {params: params};
$http.get(url,config).then(function (resp) {
$scope.Attractions = resp.data;
});
}
This way the controller will wait for the ng-init directive.
Related
I'm setting Razor variables with data from my controller like this:
#{
var filterData = ((ShipmentController)this.ViewContext.Controller).GetNoCmrs(new List<int>() { 1 });
}
I want to change this data based on the selected option the user selects (within a select).
<script>
var x;
function loadShipmentsByCondition() {
x = document.getElementById("conditionSelect").value;
if (x === "CMR not received") {
#filterData = #newData;
} else if(x === "Not invoiced") {
console.log("INVOICE");
#filterData = #otherData;
}
}
</script>
Is this possible? Or if not, how can I do this efficiently?
I'm using MVC ASP.NET and vue js as front-end.
Thanks
Unfortunately, you cannot get JavaScript to change a value of your backend C# code because JavaScript, once rendered on the frontend, does not know anything about your C# code on the server.
However, you can assign a C# value to a JavaScript variable before it leaves the server. What this is doing is just injecting the value of #filterData into the html page in the script and then is assigned to the real JavaScript variable in the actual JS environment during runtime:
<script>
var x;
var filterData = #filterData;
var otherData = #otherData;
var newData = #newData;
function loadShipmentsByCondition() {
x = document.getElementById("conditionSelect").value;
if (x === "CMR not received") {
filterData = newData;
} else if(x === "Not invoiced") {
console.log("INVOICE");
filterData = otherData;
}
}
</script>
So you are essentially converting those C# variables into JavaScript variables, but if you really need to change the C# variables then you would have to do a postback, or AJAX call to the server.
I have (topbar + sidebar + bottombar) as Vuejs components that are part of page layout within every View is rendered.
In top bar vuejs file i have a method that is loading "some" data via API request to one of my controller:
beforeMount() {
this.loggedUserDeputies.userDeputies = [];
console.log("calling LoadLoggedUserDeputies");
$.get('api/Timesheets/LoadLoggedUserDeputies')
.then(response => response as Promise<LoggedUserDeputies>)
.then(data => {
this.loggedUserDeputies = data;
});
},
API controller method:
[Route("api/[controller]/[action]")]
public object LoadLoggedUserDeputies()
{
if (ActualUserContract.LoggedUserDeputies == null)
{
return null;
}
var result = ActualUserContract.LoggedUserDeputies
.ToList()
.Select(x => new
{
userContractId = x.UserContract.Id,
userContractFullName = x.UserFullName,
userContractPersonalNumber = x.UserContract.PersonalNumber
});
return new { userDeputies = result };
}
but i have an issue, that the url for fetch the data is always modified based on current view i am in.
(so instead of always call: https://localhost:44380/api/Timesheets/LoadLoggedUserDeputies
it is transformed to: https://localhost:44380/Activities/api/Timesheets/LoadLoggedUserDeputiesor https://localhost:44380/Reports/api/Timesheets/LoadLoggedUserDeputies
). Even when its called by the same layout component (which was new to me, so apologies for my ignorance).
Is there any way i can always call the same url: https://example.com/api/Timesheets/LoadLoggedUserDeputies from every view ?
You are using a relative path to load the file:
$.get('api/Timesheets/LoadLoggedUserDeputies')
You need a slash on the front of it to make it an absolute path
$.get('/api/Timesheets/LoadLoggedUserDeputies')
that will load from the current server. If you want to specify the server, too:
$.get('https://example.com/api/Timesheets/LoadLoggedUserDeputies')
Is it possible to set a javascript variable from a c# controller? We have a situation where we override our master page with a dumb downed version that doesn't require login for users. However, our javascript timeout timer still runs. I would like to in the controller method that overrides the master, to override the timeout to something huge.
public dumbDownController()
{
ViewData["MasterPageOverride"] = "~/Views/Shared/_NoLogin.cshtml";
//Somehow reset that timer below from 20 to like 9999. To simulate no timeout.
return View("Cities", model);
}
Then our javascript file has.
tb.sessionTimer = (function () {
var SESSION_TIMEOUT = 20;
var currentTimeoutCounter = SESSION_TIMEOUT * 60;
...
lots more irrelevant to question
...
}
Large app, so looking to barely change the javascript. Would like to handle it from the controller.
Short Answer:
<script>
var xyz = #ViewData["MasterPageOverride"];
</script>
Longer Answer:
You can't directly assign JavaScript variables in the Controller, but in the View you can output JavaScript which can do the same.
You need to pass the variable to the View somehow. For this example I'll use the ViewData object dictionary. You can set an element in the Controller:
public ActionResult SomeAction()
{
ViewData["aNumber"] = 24;
}
Then in the View it is possible to use it as:
<script>
var xyz = #ViewData["aNumber"];
</script>
Which will be sent to the client browser as:
<script>
var xyz = 24;
</script>
Strings need a bit more attention, since you need to enclose them between '' or "":
<script>
var xyz = "#ViewData["aString"]";
</script>
And as #Graham mentioned in the comments, if the string happens to be valid JSON (or any object literal, but it is very easy to create JSON), and you want to use it as a JavaScript object, you can:
<script>
var xyz = #ViewData["aJsonString"];
</script>
Just make sure the output is valid JavaScript.
On a footnote, be careful with special HTML characters, like < as they are automatically HTML-encoded to prevent XSS attacks, and since you are outputting JavaScript not HTML, this can mess things up. To prevent this, you can use Html.Raw() like:
<script>
var xyz = #Html.Raw(ViewData["aJsonString"]);
</script>
If tb is within the scope of your controller, consider just overriding the sessionTimer function:
public dumbDownController(){
...
tb.sessionTimer = function(){return false;}
}
You have two options (as I understand):
Create the variable from viewbag, data, tempdata, like so:
var SESSION_TIMEOUT = #ViewData["MasterPageOverride"];
var SESSION_TIMEOUT = #ViewBag["MasterPageOverride"];
var SESSION_TIMEOUT = #TempData["MasterPageOverride"];
Or, do it via jQuery AJAX:
$.ajax({
url: '/YourController/YourAction',
type: 'post',
data: { id: id },
dataType: 'json',
success: function (result) {
// setVar
}
});
Is it possible to access a Model property in an external Javascript file?
e.g. In "somescript.js" file
var currency = '#Model.Currency';
alert(currency);
On my View
<script src="../../Scripts/somescript.js" type="text/javascript">
This doesn't appear to work, however if I put the javascript directly into the view inside script tags then it does work? This means having to put the code in the page all the time instead of loading the external script file like this:
#model MyModel;
<script lang=, type=>
var currency = '#Model.Currency';
alert(currency);
</script>
Is there any way around this?
I tackled this problem using data attributes, along with jQuery. It makes for very readable code, and without the need of partial views or running static javascript through a ViewEngine. The JavaScript file is entirely static and will be cached normally.
Index.cshtml:
#model Namespace.ViewModels.HomeIndexViewModel
<h2>
Index
</h2>
#section scripts
{
<script id="Index.js" src="~/Path/To/Index.js"
data-action-url="#Url.Action("GridData")"
data-relative-url="#Url.Content("~/Content/Images/background.png")"
data-sort-by="#Model.SortBy
data-sort-order="#Model.SortOrder
data-page="#ViewData["Page"]"
data-rows="#ViewData["Rows"]"></script>
}
Index.js:
jQuery(document).ready(function ($) {
// import all the variables from the model
var $vars = $('#Index\\.js').data();
alert($vars.page);
alert($vars.actionUrl); // Note: hyphenated names become camelCased
});
_Layout.cshtml (optional, but good habit):
<body>
<!-- html content here. scripts go to bottom of body -->
#Scripts.Render("~/bundles/js")
#RenderSection("scripts", required: false)
</body>
There is no way to implement MVC / Razor code in JS files.
You should set variable data in your HTML (in the .cshtml files), and this is conceptually OK and does not violate separation of concerns (Server-generated HTML vs. client script code) because if you think about it, these variable values are a server concern.
Take a look at this (partial but nice) workaround: Using Inline C# inside Javascript File in MVC Framework
What you could do is passing the razor tags in as a variable.
In razor File>
var currency = '#Model.Currency';
doAlert(currency);
in JS file >
function doAlert(curr){
alert(curr);
}
Try JavaScriptModel ( http://jsm.codeplex.com ):
Just add the following code to your controller action:
this.AddJavaScriptVariable("Currency", Currency);
Now you can access the variable "Currency" in JavaScript.
If this variable should be available on the hole site, put it in a filter. An example how to use JavaScriptModel from a filter can be found in the documentation.
What i did was create a js object using the Method Invocation pattern, then you can call it from the external js file. As js uses global variables, i encapsulate it to ensure no conflicts from other js libraries.
Example:
In the view
#section scripts{
<script>
var thisPage = {
variableOne: '#Model.One',
someAjaxUrl: function () { return '#Url.Action("ActionName", "ControllerName")'; }
};
</script>
#Scripts.Render("~/Scripts/PathToExternalScriptFile.js")
}
Now inside of the external page you can then get the data with a protected scope to ensure that it does not conflict with other global variables in js.
console.log('VariableOne = ' + thisPage.variableOne);
console.log('Some URL = ' + thisPage.someAjaxUrl());
Also you can wrap it inside of a Module in the external file to even make it more clash proof.
Example:
$(function () {
MyHelperModule.init(thisPage || {});
});
var MyHelperModule = (function () {
var _helperName = 'MyHelperModule';
// default values
var _settings = { debug: false, timeout:10000, intervalRate:60000};
//initialize the module
var _init = function (settings) {
// combine/replace with (thisPage/settings) passed in
_settings = $.extend(_settings, settings);
// will only display if thisPage has a debug var set to true
_write('*** DEBUGGER ENABLED ***');
// do some setup stuff
// Example to set up interval
setInterval(
function () { _someCheck(); }
, _settings.intervalRate
);
return this; // allow for chaining of calls to helper
};
// sends info to console for module
var _write = function (text, always) {
if (always !== undefined && always === true || _settings.debug === true) {
console.log(moment(new Date()).format() + ' ~ ' + _helperName + ': ' + text);
}
};
// makes the request
var _someCheck = function () {
// if needed values are in settings
if (typeof _settings.someAjaxUrl === 'function'
&& _settings.variableOne !== undefined) {
$.ajax({
dataType: 'json'
, url: _settings.someAjaxUrl()
, data: {
varOne: _settings.variableOne
}
, timeout: _settings.timeout
}).done(function (data) {
// do stuff
_write('Done');
}).fail(function (jqxhr, textStatus, error) {
_write('Fail: [' + jqxhr.status + ']', true);
}).always(function () {
_write('Always');
});
} else {// if any of the page settings don't exist
_write('The module settings do not hold all required variables....', true);
}
};
// Public calls
return {
init: _init
};
})();
You could always try RazorJs. It's pretty much solves not being able to use a model in your js files RazorJs
I had the same problem and I did this:
View.
`var model = #Html.Raw(Json.Encode(Model.myModel));
myFunction(model);`
External js.
`function myFunction(model){
//do stuff
}`
Is there a way to add a Html.ActionLink through javascript?
For instance, I have this Edit function in my controller:
public ViewResult Edit(int companyID)
{
....
}
And I'd like to do something like this in javascript:
var id = $("#hdnID").val();
$("#editLink").html(<%: Html.ActionLink("Edit", "Edit", new { id }) %>);
A bit of a crude example, but it's basically what I'd like to do. Is it at all possible?
The id is a client script. You cannot mix server side script with client script. I am afraid that you are trying to submit HTML forms with action links instead of using submit buttons which is very bad. I see that you fetch the value of an input field with $("#hdnID").val() and then try to assign it to some action link and send to the server whereas if you used a simple submit button you wouldn't even need javascript. Your code would simply be:
<% using (Html.BeginForm("Edit", "Home")) { %>
<%: Html.HiddenFor(x => x.HdnId) %>
<input type="submit" value="Edit" />
<% } %>
Also it is clear that if you are using a hidden field it's because the user cannot change the value so an even simpler solution would be to directly generate the link you need:
<%: Html.ActionLink("Edit", "Edit", new { id = Model.SomeId }) %>
I haven't found a really good way yet. What I usually do is something like this:
var id = $("#hdnID").val();
var link = '<%: Html.ActionLink("Edit", "Edit", new { id = -999 }) %>';
$("#editLink").html(link.replace('-999', id));
The key is to select a value that id would never have in reality or exist otherwise in the link.
I found a handy way out of this problem thinking slighly out of the box. The reason I use ActionLink is really for an easy way to handle the routing. Simply supply Controller and action name and the helper generates the correct url. To get around this in JavaScript I first created an HtmlHelperExtender using the UrlHelper to resolve the url in proper context.
namespace System.Web.Mvc.Html
{
public static class HtmlHelperExtensions
{
public static string ResolveUrl(this HtmlHelper html, string url)
{
var urlHelper = new UrlHelper(html.ViewContext.RequestContext);
return urlHelper.Content(url);
}
}
}
Now in JavaScript it's easy enough to get the proper Url
$(document).ready(function () {
var action = '<%= Html.ResolveUrl("~/Controller/JsonAction") %>';
// JSON controller call for model data
$.getJSON(action, null, function (models) {
// Do something with models
action = '<%= Html.ResolveUrl("~/Controller/Details/") %>';
for (var i = 0; i < models.length; i++) {
$('#modelList').append(
'<tr><td>' + models[i].Title + '</td></tr>');
}
});
});
This is how I did it. You can use javascript replace.
var ul = document.createElement('ul');
if (data.EvidenceList != null) {
for (var i = 0; i < data.EvidenceList.length; i++) {
var li = document.createElement("li");
var evidenceId = data.EvidenceList[i].EvidenceId;
var evidenceName = data.EvidenceList[i].Name;
var candidateProgrammeComponentId = data.CandidateProgrammeComponentId;
var str1 = '#Html.ActionLink("dummyEvidenceText", "DownloadFile", new { candidateProgrammeComponentId = "dummyCandidateProgrammeComponentId", evidenceId = "dummyEvidenceId", evidenceName = "dummyEvidenceName" })';
var str2 = str1.replace('dummyEvidenceName', evidenceName);
var str3 = str2.replace('dummyCandidateProgrammeComponentId', candidateProgrammeComponentId);
var str4 = str3.replace('dummyEvidenceId', evidenceId);
var str5 = str4.replace('dummyEvidenceText', evidenceName);
li.innerHTML = li.innerHTML +str5 ;
ul.appendChild(li);
}
}
var element = document.getElementById('evidenceList_' + data.guidId);
$('#evidenceList_' + data.guidId).empty();
document.getElementById('fileUploadFreeStructure_' + data.guidId).value = '';
$('#freeTextArea_' + data.guidId).val('');
element.appendChild(ul);
The server side code (the C#) is ran on the server, and the result is sent to the client, where the client then executes the JavaScript. So as weird as it is, you have two different code environments bumping into each other but can't interact with each other very well.
I usually do something like this, although I'm open to better ways:
function SetUrl(id) {
var url = '<%: Html.ActionLink("Bar", "Foo") %>' + '?id=' + id;
return url;
}
This takes advantage of the fact that
/Foo/Bar/{id} is usually equivalent to /Foo/Bar?id={id}, depending on how your routes are set up.