18

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?

BlueChippy
  • 5,935
  • 16
  • 81
  • 131
  • If you could generate the external js file then their browser would cache your dynamically generated view model data. Probably not what you want? – BritishDeveloper Apr 25 '12 at 10:07
  • 1
    I recently blogged about how you can [generate external JavaScript files using partial Razor views][blogpost]. The blog post shows how to use a custom action filter to parse Razor code within an external JavaScript file. **tl;dr:** Yes, it is possible using a simple, but clever workaround. [blogpost]: http://blog.mariusschulz.com/generating-external-javascript-files-using-partial-razor-views – Marius Schulz Jul 11 '13 at 22:31
  • http://stackoverflow.com/a/41312348/2592042 I have explained in detail here. – Rajshekar Reddy Dec 24 '16 at 11:45
  • @Rajshekar Reddy - none of your implementations work outside of the View page - i.e. in an external js file. – Spencer Sullivan Feb 05 '20 at 04:14

7 Answers7

11

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>
arserbin3
  • 6,010
  • 8
  • 36
  • 52
9

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

Community
  • 1
  • 1
Ofer Zelig
  • 17,068
  • 9
  • 59
  • 93
  • 1
    Thats a great solution. I can keep all of the "static" javascript in a file, and then put the "vars" part into the partial view and just load that. Nice :) – BlueChippy Oct 03 '11 at 07:17
2

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

  })();
Mike
  • 1,525
  • 1
  • 14
  • 11
  • I like this. It's simple, standard, no external library bloat. Now I can move my js code in my razor views to TypeScript in external .ts files and still access model variables. Perfect. – Eric Sassaman Feb 06 '19 at 00:22
  • 1
    Im glad you like it, I also added a little more to the example for the external script. Kind of a template. That way I keep all of the scripts cached and for performance and use off the the great razor features. and I just create a Module for each partial view needed. This way it all plays together nice. – Mike Feb 15 '19 at 15:02
2

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);
}
Patrick
  • 2,730
  • 4
  • 33
  • 55
1

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.

acuntex
  • 147
  • 13
0

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
 }`
Sebastian
  • 13
  • 4
0

You could always try RazorJs. It's pretty much solves not being able to use a model in your js files RazorJs

ElvisLives
  • 2,275
  • 2
  • 18
  • 24