Access a Model property in a javascript file? - c#

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
}`

Related

AngularJs - Assign Value to scope variable inline

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.

Downloading a file with Knockout bindings, ASP.NET MVC stack and no Razor

Trying to find a resource that could point me in the right direction for downloading a file with this particular stack. It's more challenging than it seems, especially since I'm unable to use Razor in accordance with house rules.
The code execution can get from the markup, to the knockout, and then the C#, but it doesn't start a download like I would expect in ordinary webforms non-MVC ASP.NET.
mark up:
<div class="row">
<div class="col-2"><img data-bind="attr: {src: image}, click: $root.downloadFile/></div>
the knockout/javascript call:
self.downloadFile = function(e){
if(e) {
attachmentId = e.id;
helpers.ajax.getJson(root, "/Files/DownloadFile/", {fileId: attachmentId }, function(x){
attachmentId=0;
getFiles();
});
}
...
related javascript functions called here:
helpers.ajax.getJson = function(path, url, data, onSuccess, onError){
helpers.ajax.async('GET', path, url, {
data: data,
cache: false,
async: true,
error: onError,
success: onSuccess
});
};
function getFiles(){
self.files([]);
helpers.ajax.getJson(root, "/Files/GetFiles",
{ profileId: self.ProfileId() },
function (files) {
if(files) {
$.each(files, function (i, v) {
self.files().push(new file(v.AttachmentId, v.FileTypeDescr, v.FileExtension, v.FileName, v.UploadedBy, v.UploadDate, v.CompletionDate));
self.files.valuehasMutated();
});
}
});
}
C#
public FileResult DownloadFile(int fileId)
{
ODSAPI.AttachmentFile file = FileFunctions.GetById(fileId);
if(file != null)
{
return File(file.FileData, file.ContentType);
}
return null;
}
this returns the correct file information and the bits from the database when I step through the code and view the file variable.
you could use http://danml.com/download.html to download file from javascript AJAX return
for exmaple
download(data, 'Export.csv', 'application/csv');
where data will be return from your ajax request and file name and file type.
The JSON call in the Javascript was incorrect as it called for a JSON object. Rather, it should have been:
window.open(root + "/Files/DownloadFile?fileId=" + attId, '_blank');
instead of helpers.ajax.getJson()

What is the best way to pass data from ASP.NET to AngularJS?

I am using ng-grid in an ASP.NET web application to display data from a WCF Service. In this Plunker example the data is stored in a JSON file and then converted by the Angular module.
var app = angular.module('myApp', ['ngGrid']);
app.controller('MyCtrl', function($scope, $http) {
$scope.setPagingData = function(data, page, pageSize){
var pagedData = data.slice((page - 1) * pageSize, page * pageSize);
$scope.myData = pagedData;
$scope.totalServerItems = data.length;
if (!$scope.$$phase) {
$scope.$apply();
}
};
$scope.getPagedDataAsync = function (pageSize, page, searchText) {
setTimeout(function () {
var data;
if (searchText) {
var ft = searchText.toLowerCase();
$http.get('largeLoad.json').success(function (largeLoad) {
data = largeLoad.filter(function(item) {
return JSON.stringify(item).toLowerCase().indexOf(ft) != -1;
});
$scope.setPagingData(data,page,pageSize);
});
} else {
$http.get('largeLoad.json').success(function (largeLoad) {
$scope.setPagingData(largeLoad,page,pageSize);
});
}
}, 100);
};
$scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage);
$scope.gridOptions = {
data: 'myData',
enablePaging: true,
showFooter: true,
enableCellSelection: true,
enableCellEdit: true,
enableRowSelection: false,
totalServerItems:'totalServerItems',
pagingOptions: $scope.pagingOptions,
filterOptions: $scope.filterOptions
};
});
Plunker
This piece of code was interesting because it read dicrectly from a JSON file and dynamically bound it to the ng-grid. I was hoping that I would be able to use the ASP.NET (C#) codebehind to pass a JSON file to the AngularJS code and let it do all of the work to parse it.
It seems like you cannot do this, so what is the best way to pass data to AngularJS from ASP.NET?
Thanks in advance.
The question you're really asking is, how do I pass some server-side information to Javasript? Angular is irrelevant here.
Solution 1: just write it to a client-side variable. Something like:
If you're using MVC:
var myJson = '#ViewBag.MyJson';
var json = JSON.parse(myJson);
If you're using WebForms:
var myJson = '<% Response.Write(MyJson); %>';
var json = JSON.parse(myJson);
Solution 2: define a server-side method that returns your json string, and call it from the client side using Ajax. Then parse it as previously. There's a decent example here.
Solution 3: can't you write to your Json file from the server side, before loading it into Angular?
You could create an Angular directive that handles parsing, and then pass it a JSON file name in your ASP.NET page, like this:
// ASP.NET page
<%
JSONFile.Text = "path/to/json/file.json";
%>
<!DOCTYPE html>
<html>
<head runat="server">
// including our parse-json directive as "metadata" in the head
<parse-json data-json-file="<%JSONFile%>"></parse-json>
</head>
<body>
...
...
</body>
</html>
And then your angular directive could look something like this:
app.directive('parseJson', ['$http', function ($http) {
return {
restrict: 'E',
scope: {
jsonFile: '#'
}
link: function (scope) {
$http.get('my/url/' + scope.jsonFile)
.success(function (data) {
// your parsing logic would go here.
});
}
};
}]);
The data prefix on the data-json-file attribute is the HTML5 standard for custom data attributes.
So, to summarize the code above. In our ASP.NET page we are including a custom tag (our angular directive) named <parse-json> in the <head> of our html page (it doesn't have to be there, but it is sorta metadata so I figured it fit). We are setting the attribute data-json-file to equal the path of the JSON file on our server. When angular loads on the client, it will request this JSON file, and then from there you can do whatever you want with it (in your case, parse it).
Hope this helps!

Set a javascript variable from mvc controller

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
}
});

ActionLink in jQuery

This is my method:
$(document).ready(function () {
$('td.clickableCell').click(function () {
var currentObject = null;
currentObject = $(this).text();
#Html.ActionLink("GetThis", "Get", new {theName = currentObject} )
});
});
but it says that currentObject doesn't exist in the current context. How to resolve this?
Instead of #Html.ActionLink you should use the jQuery.get function. `#Html.ActionLink is run on the server whereas the javascript is run on the client.
$(document).ready(function () {
$('td.clickableCell').click(function () {
var currentObject = $(this).text();
$.get('#Url.Action("GetThis", "Get")', {theName : currentObject});
});
});
The Url.Action is rendered on the server and will give you the appropriate url. The $.get will run a get request on the client.
Keep in mind, if this javascript is in a .js file, the Url.Action will not be run. In that case you may simply want to replace it with /Get/GetThis or render the url in a hidden field on the page and get the value of the hidden field in your .js file.
You need an action method that looks like this in order to access the parameter:
public ActionResult GetThis(string theName)
{
// manipulate theName
return View();
}
currentObject is a JavaScript String object that you are trying to pass into server side code. If you need to do this on the client side,
$(function () {
$('td.clickableCell').click(function () {
var currentObject = $(this).text();
// find the anchor element that you need to change,
// then change the property on it to the value
// of currentObject
$('a').attr('title', currentObject);
});
});
Alternatively, it's possible that you need to send the value to the server in some way. If the JavaScript above is within a Razor view, then
$(function () {
$('td.clickableCell').click(function () {
var currentObject = $(this).text();
// make a HTTP GET request and pass currentObject as a queryparam
window.location = '#Url.Action("Action", "Controller")' + '?theName=' + encodeURIComponent(currentObject);
});
});
The '#Url.Action("Action", "Controller")' portion will have been evaluated on the server-side and been resolved by the UrlHelper to the URL to route to that controller action. We put this value in single quotes as we need to use it on the client side in a JavaScript variable. Then we add currentObject as a query parameter (and encode it at the same time).
You're mixing client-side code with server-side code. This line is being executed on the server before anything is sent to the client:
#Html.ActionLink("GetThis", "Get", new {theName = currentObject} )
That line, by itself, references something which doesn't exist. currentObject won't exist until it's created in JavaScript on the client. That JavaScript code, from the perspective of the server, is nothing more than text.

Categories

Resources