I have been using ASP.NET MVC for six months or so and have been checking out the Nerd Dinner example created by those Microsoft guys. One thing I noticed they did when enabling AJAX to RSVP for a dinner, is put the JavaScript references in the User Control being used for RSVPing.
(FILE: RSVPStatus.ascx)
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<NerdDinner.Models.Dinner>" %>
<script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
This doesn't seem right to me, as there is a really good chance I would be using these same libraries elsewhere, like logon authentication. Plus if I change script versions, I need to hunt down all the references to the libraries.
So I ask if my thinking is correct and these references should actually be in a more central location like the master page?
Please let me know what the best practice is for this and pro's and cons if any.
I would definitely advise against putting them inside partials for exactly the reason you mention. There is a high chance that one view could pull in two partials that both have references to the same js file. You've also got the performance hit of loading js before loading the rest of the html.
I don't know about best practice but I choose to include any common js files inside the masterpage and then define a separate ContentPlaceHolder for some additional js files that are specific to a particular or small number of views.
Here's an example master page - it's pretty self explanatory.
<%# Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<head runat="server">
... BLAH ...
<asp:ContentPlaceHolder ID="AdditionalHead" runat="server" />
... BLAH ...
<%= Html.CSSBlock("/styles/site.css") %>
<%= Html.CSSBlock("/styles/ie6.css", 6) %>
<%= Html.CSSBlock("/styles/ie7.css", 7) %>
<asp:ContentPlaceHolder ID="AdditionalCSS" runat="server" />
</head>
<body>
... BLAH ...
<%= Html.JSBlock("/scripts/jquery-1.3.2.js", "/scripts/jquery-1.3.2.min.js") %>
<%= Html.JSBlock("/scripts/global.js", "/scripts/global.min.js") %>
<asp:ContentPlaceHolder ID="AdditionalJS" runat="server" />
</body>
Html.CSSBlock & Html.JSBlock are obviously my own extensions but again, they are self explanatory in what they do.
Then in say a SignUp.aspx view I would have
<asp:Content ID="signUpContent" ContentPlaceHolderID="AdditionalJS" runat="server">
<%= Html.JSBlock("/scripts/pages/account.signup.js", "/scripts/pages/account.signup.min.js") %>
</asp:Content>
HTHs,
Charles
Ps. I would agree with Andrew in saying that any common JS that is defined directly inside the master page should be concatenated and minified.
EDIT: My implementation of .JSBlock(a, b) as requested
public static MvcHtmlString JSBlock(this HtmlHelper html, string fileName)
{
return html.JSBlock(fileName, string.Empty);
}
public static MvcHtmlString JSBlock(this HtmlHelper html, string fileName, string releaseFileName)
{
if (string.IsNullOrEmpty(fileName))
throw new ArgumentNullException("fileName");
string jsTag = string.Format("<script type=\"text/javascript\" src=\"{0}\"></script>",
html.MEDebugReleaseString(fileName, releaseFileName));
return MvcHtmlString.Create(jsTag);
}
And then where the magic happens...
public static MvcHtmlString MEDebugReleaseString(this HtmlHelper html, string debugString, string releaseString)
{
string toReturn = debugString;
#if DEBUG
#else
if (!string.IsNullOrEmpty(releaseString))
toReturn = releaseString;
#endif
return MvcHtmlString.Create(toReturn);
}
In my website, www.trailbehind.com, we have a set of javascript files that belong on all pages. And then some pages include additional libraries.
For the JS files that all pages uses (there are a couple dozen files), we concatenate them and minify them on build.
There is a flag in our settings file that says whether to use the concatenated javascript or the separate files on build. This is critical so that you can debug the javascript on dev, but use the small, single-file javascript on production.
Here is our python code to combine and minify:
import os
import thetrailbehind.lib.jsmin as jsmin
JS_FILES = [ 'lib/json2.js',
'lib/markermanager.js',
'lib/labeledmarker.js',
'lib/rsh/rsh.js',
'lib/showdown.js',
'lib/yui.js',
'lib/dragzoom.js',
'gen/attribute_data.js',
'gen/national-parks.js',
'Widgets/CommentsWidget.js',
'Widgets/Search.js',
'Widgets/MenuWidget.js',
'Widgets/PhotoWidget.js',
'Widgets/ReportList.js',
'Widgets/help.js',
'attributes.js',
'rsh.js',
'map.js',
'mapcontrols.js',
'markers.js',
'amazon.js',
'plan_trip.js',
'init.js',]
def concat(files, base_path, all_file, all_file_min):
if os.path.exists(base_path + all_file):
lasttime = os.path.getmtime(base_path + all_file)
else:
lasttime = 0
out_of_date = False
for file in files:
if os.path.getmtime(base_path + file) > lasttime:
out_of_date = True
break
if out_of_date:
outfile = open(base_path + all_file, 'w')
for file in files:
outfile.write(open(base_path + file).read())
outfile.write("\n")
outfile.close()
alljs = open(base_path + all_file)
allminjs = open(base_path + all_file_min, "w+")
jsmin.JavascriptMinify().minify(alljs, allminjs)
alljs.close()
allminjs.close()
def main():
concat(JS_FILES, '/home/wibge/thetrailbehind/media/javascript/', 'gen/all.js', 'gen/all.min.js')
if __name__ == "__main__":
main()
And here is the Django/HTML template where we switch:
{% if use_all_js %}
script type=text/javascript src=/site_media/javascript/gen/all.min.js>
{% else %}
script type="text/javascript" src="/site_media/javascript/rsh.js">
script type="text/javascript" src="/site_media/javascript/amazon.js">
script type="text/javascript" src="/site_media/javascript/map.js">
A BUNCH OF SEPARATE INCLUDES...etc
{% endif %}
Related
I'm using ASP.NET Web Forms with master pages. There are so many moving parts to this that I cannot figure it out. Different tutorials use different parts of it and omit others, that I cannot determine what is needed and what is fluff.
Different Parts:
Master Pages: In the head section for my CSS I have:
<link href="Content/css" rel="stylesheet" />
<asp:PlaceHolder runat="server">
<%: Scripts.Render("~/bundles/modernizr") %>
</asp:PlaceHolder>
Before closing body tag, I have:
<script src="<%= ResolveUrl("~") %>Scripts/jquery-2.1.1.js"></script>
<script src="<%= ResolveUrl("~") %>Scripts/bootstrap.min.js"></script>
<script src="<%= ResolveUrl("~") %>Scripts/jquery.reject.js"></script>
<script src="<%= ResolveUrl("~") %>Scripts/general.js"></script>
Looks like the min one is not needed, but do I need any of these and instead use
<script src="Scripts/js"></script> ?
Global.asax.cs: this seems simple enough, registering the bundles in Application_Start method.
void Application_Start(object sender, EventArgs e)
{
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
Web.config:
I added the System.Web.Optimization namespace and the Microsoft.AspNet.Web.Optimization.WebForms assembly.
Bundle.config: I have no idea what this is for; many tutorials don't even mention it?
BundleConfig.cs: As well as the standard WebFormsJs, MsAjaxJs and modernizr custom bundles, I have the following for CSS:
bundles.Add(new StyleBundle("~/bundles/css").Include(
"~/Content*"));
This is not working. I was about to add something similar for my JS files but got confused as to why I am doing this at all when according to this tutorial, all I needed for the CSS was:
<link href="Content/css" rel="stylesheet" />
Presumably, all I needed for my JS files was:
<script src="Scripts/js"></script>
In some tutorials I have seen ScriptManager.ScriptResourceMapping.AddDefinition - what is this for?
Here is the current state of my CSS and Scripts folders - I need all the non-minifed versions of these:
http://imgur.com/XwqIOKl
http://imgur.com/q8IdhmB
Can someone help me piece this together? I am running locally with debug set to false.
Below is a list of each of the sections that need to be configured for Bundling and Minification in WebForms.
This is taken from a production code base which is running Bundling and Minification.
Libraries:
Microsoft.AspNet.Web.Optimization
Dependencies:
WebGrease
Microsoft.Web.Infrastructure (depending on version)
Global.asax
void Application_Start(object sender, EventArgs e)
{
BundleConfig.RegisterBundles(BundleTable.Bundles);
//Use this if you want to force/test bundling in debug.
BundleTable.EnableOptimizations = true;
}
BundleConfig class
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/sitejs")
//Add as many JS libraries you would like to the bundle...
.Include("~/Scripts/jquery-3.1.1.js")
.Include("~/Scripts/jquery-migrate-3.0.0.js")
);
bundles.Add(new StyleBundle("~/bundles/sitecss")
//Add as many CSS files that you would like to the bundle...
.Include("~/css/jquery-ui.css")
);
}
}
Master Page:
<!-- At the top of the Master Page -->
<%# Import Namespace="System.Web.Optimization" %>
<!-- Just after the closing `</form>` tag -->
<asp:PlaceHolder runat="server">
<%: Styles.Render("~/bundles/sitecss") %
<%: Scripts.Render("~/bundles/sitejs") %
</asp:PlaceHolder>
I've looked through all the other questions/solutions related to this issue and can't find the solution.
I have a basic aspx page with a button. the OnClick calls a JS function. the Javascript function calls document.getElementById() which works. I then call a sub-function that lives in an external JA file and the same call fails. Why?
<%# Page Language="C#" AutoEventWireup="true" CodeFile="jstest.aspx.cs" Inherits="jstest" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:CheckBox runat="server" ID="RunAtStartup" OnClick="function1();" text="Click Me" />
</div>
</form>
<script>
function function1()
{
if (document.getElementById("<%= RunAtStartup.ClientID %>") == null)
alert('function1 null');
else
alert('function1 not null');
function2();
}
</script>
<script src="./function2.js"></script>
</body>
</html>
And the external javascript file function2.js is
function function2() {
if (document.getElementById("<%= RunAtStartup.ClientID %>") == null)
alert('function2 null');
else
alert('function2 not null');
}
The result of clicking the button will show that function1 is 'not null' and function2 is 'null'.
I've tried passing document in as a parameter, that did not work. I tried to do a function2().bind(document), that did not work. I stepped through the javascript debugger and it looks like the document object in function1 is identical to the document object in function2.
Thanks in advance
Michael
The name of the element, from what I can tell, is created by the ASP.net preprocessor. Since the JS file isn't parsed in ASP.net, it treats the selector literally, rather than as a real element ID. Therefore, this script cannot be run from and external JS file. In the ASP file, it replaces
<%= RunAtStartup.ClientID %>
with an actual element ID. The external file is looking for something like this:
<span id="<%= RunAtStartup.ClientID %>"></span>
Again, it treats the ID as a literal string, as if you ran it on a server without ASP.net installed. My solution would probably be to store the ID in a variable in the ASP.net file, like this:
var id = "<%= RunAtStartup.ClientID %>";
Then, the external JS file could use the following:
var element = document.getElementByID(id);
The external JS file would, of course, have to be included after the variable id is created. Alternatively, an even better solution would be to pass the ID as a function parameter, like this:
function function2(id) {
if (document.getElementById(id) == null)
alert('function2 null');
else
alert('function2 not null');
}
The ASP.net file could call the file like this:
function2("<%= RunAtStartup.ClientID %>");
This allows all ASP.net tags to be parsed server-side before being delivered to the client with the JS code.
For example, I have a page /locations/map which I need to include Google Map library, and include a .js file (e.g. location.js) specifically for this page only.
I want to inject these 2 files to after <!--SCRIPTS END--> this line
Is it possible to do this?
NOTE: I was using Sails.js v0.10
Sails uses ejs-locals in its view rendering, so you can accomplish what you want with blocks.
In your layout.ejs file, underneath the <!--SCRIPTS END-->, add (for example):
<%- blocks.localScripts %>
Then in the view you're serving at /locations/map, call the block with your script tag, for example:
<% block('localScripts', '<script src="https://maps.googleapis.com/maps/api/js"></script>') %>
As an alternative, you could put the <!--SCRIPTS--> and <!--SCRIPTS END--> tags in the <head> of your layout, and then add your view-specific scripts directly into your view rather than using blocks. This is a fine option if you don't mind waiting for those linked scripts to load before your page content is displayed.
Scott's answer is the proper way to insert non-global JS into a specific view. Just a little comment, though: the block call from the view should not have the dash. It should be as follows:
<% block('localScripts', '<script src="https://maps.googleapis.com/maps/api/js"></script>') %>
Both calls will work, but using the dash makes the insertion twice; once the view is loaded and previous to the layout render, and then once again when the view is inserted in the rendered base layout. This leads not only to inserting/running unnecessarily twice the same code but also to errors that break your JS code if the inserted script depends on libraries that you have in your base layout (e.g. jQuery, Backbone).
EJS interprets the magic <%- as "insert unescaped". So, -I guess- what this is doing is calling the block() function, which returns our HTML <script> tag. This is replaced where the magic was called but also is executing the block() function inside of it, which is executing the layout block localScripts replacement.
On the other hand, <% means "instruction". I.e., just run this JS piece of code, which is not echoed to the view where is called.
I discover other way to do that
In MapController.js
// you can add as many as you like
res.locals.scripts = [
'//maps.googleapis.com/maps/api/js',
];
return res.view();
In layout.ejs
<!--SCRIPTS-->
<!--SCRIPTS END-->
<!-- Loop through all scripts that passed from controller -->
<% if (scripts) { %>
<% for (i = 0; i < scripts.length; i++) { %>
<script src="<%- scripts[i] %>"></script>
<% } %>
<% } %>
This method allows flexibility to locally serve js files from any page and also prevent any reference errors caused by dependencies.
In pipeline.js insert '!js/local/*.js at the bottom of jsFilesToInject like so:
var jsFilesToInject = [
// Load sails.io before everything else
'js/dependencies/sails.io.js',
// Dependencies like jQuery, or Angular are brought in here
'js/dependencies/jquery-3.3.1.min.js',
'js/dependencies/**/*.js',
// All of the rest of your client-side js files
// will be injected here in no particular order.
'js/**/*.js',
//Ignore local injected scripts
'!js/local/*.js'
];
Create a local folder inside the /assets/js folder ie /assets/js/local/. Place any locally injected scripts in here.
In your master view ejs ie layout.ejs insert <%- blocks.localScripts %> below the SCRIPTS block like this:
<!--SCRIPTS-->
<script src="/js/dependencies/sails.io.js"></script>
<script src="/js/dependencies/jquery-3.3.1.min.js"></script>
<script src="/js/dependencies/bootstrap.min.js"></script>
<script src="/js/dependencies/popper.min.js"></script>
<!--SCRIPTS END-->
<%- blocks.localScripts %>
In your local ejs view (eg. homepage.ejs) insert your localScripts block like this:
<% block('localScripts', '<script src="/js/local/homepage.js"></script>') %>
sails v0.12.14
EDIT
Is this still relevant for Sails v1.0?
My answer is a resounding YES and in my earlier answer I lacked explaining how to get the most out of the Grunt pipeline like clean, coffee, concat, uglify etc... when going into production.
The trick here is to make a local file (there should only be one per page) as small as possible.
Group and name specific your function calls
Save functions as separate files for easy maintenance and group them into folders.
Group bindings and any initialising of global variables into a couple of functions like initThisPageVariables() and initThisPageBindings() so that Grunt can crunch these later.
Set a master function call to run your app startThisPageApp()
Then simply calling the few functions from your local (master) file to get things rolling.
$(window).on('load', function(){
initThisPageVariables();
initThisPageBindings();
$(window).on("resize", function(){
initThisPageVariables();
}).resize();
startThisPageApp();
});
I know this is an old question by I discovered another option.
File:
config\routes.js
Code:
'/': 'HomeController.StartHome'
I set StartHome function in HomeController responsible for managing '/' route.
File:
api\controllers\HomeController.js
Code:
StartHome: function(req, res) {
var template_data = {
"data" : {
"view_name" : "home_view"
}
}
res.view('home_view', template_data)
}
Here, I created an object with some data which is passed to EJS template(to client).
File:
views\layout.ejs
Code:
<% if (data["view_name"] === "home_view") { %>
<script src="script_for_home_view.js"></script>
<% } %>
In my layouts.ejs I created if statement which "enables" script tag depending on on the view I am currently on.
This is how I handle this. I hope it's clear for you.
Sails 0.12.4
I've an ASP.NET MVC application with inline JavaScript that only applies to specific views.
If I put all the JavaScript on main Layout page, I get error message as not ID exists on all pages.
Is there a way to externalize the JavaScript for each view?
Here is one way. This is using razor view engine
In you layout page between the head tags create a section
<head>
#RenderSection("Head", required: false);
</head>
then in your view create a section and put your script tags in it.
#section Head
{
<script type="text/javascript">
do some java script...
</script>
}
You could also reference external libraries that are specific to that page, like a light box or something.
If you are using MVC 2 you can use a content placeholder.
// this goes in your master page in your head tag
<asp:ContentPlaceHolder id="Script" runat="server"/>
// this goes in your view -- not partial
<asp:Content ID="Content1" ContentPlaceHolderID="script" runat="server">
<script type="text/javascript" src="foo.js"></script>
</asp:Content>
In MVC 3 you can do this.
I use a HtmlHelper extension to register my used scripts on each controller-action and the "MasterPage" renders each registered script with the correct script tag.
HtmlHelper:
public static void RegisterScript(string path)
{
if (!HttpContext.Current.Items.Contains("RegisteredScripts"))
HttpContext.Current.Items.Add("RegisteredScripts", ";" + path);
else
HttpContext.Current.Items["RegisteredScripts"] = ";" + path;
}
public static MvcHtmlString RegisteredScripts(this HtmlHelper html)
{
var lines = new StringBuilder();
if (html.ViewContext.HttpContext.Items.Contains("RegisteredScripts"))
{
foreach (var k in html.ViewContext.HttpContext.Items["RegisteredScripts"].ToString().Substring(1).Split(';'))
{
lines.Append(html.Script(k));
}
}
return MvcHtmlString.Create(lines.ToString());
}
// Helper-functions
public static MvcHtmlString Script(this HtmlHelper html, string path)
{
if (!ExistsInContext(html, path))
{
return Render(html, "<script type=\"text/javascript\" src=\"{0}\"></script>", path);
}
return MvcHtmlString.Create("");
}
private static bool ExistsInContext(HtmlHelper html, string path)
{
return html.ViewContext.HttpContext.Items.Contains(path);
}
In the controller-action just call:
Helpers.HtmlHelpers.RegisterScript("~/Scripts/custom.js");
Hope this helps
The javascript could test if 'ID' is null or undefined, and not fire the code if it is.
I am using the following partial to render the editor for the Create and Edit pages:
PersonEditor.ascx
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MyProj.ViewModels.PersonEditorViewModel>" %>
<script src="../../Scripts/MicrosoftAjax.js" type="text/javascript" />
<script src="../../Scripts/MicrosoftMvcValidation.js" type="text/javascript" />
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm())
{
%>
<%= Html.ValidationSummary("Edit was unsuccessful. Please correct the errors and try again.") %>
<%= Html.EditorForModel()%>
<p>
<input type="submit" value="Save" />
</p>
<%
}
%>
I believe I have followed the instructions correctly on Scott Gu's blog post (see "Enabling Client-Side Validation"), but when I load this page in a browser, the entire editor form vanishes. If I remove the two scripts and Html.EnableClientValidation(), everything is back to normal again (except, of course, there is no client-side validation).
I have tried putting the script tags in the master page, but that made no difference.
Any ideas how I might be able to fix this?
Update
If I put the two script tags in Site.Master instead of the partial, everything disappears. It basically makes my app completely invisible. No errors, though. It just loads a blank page.
Your script tags are:
Different than what is in the blog post you reference, and
Incorrect.
They should be:
<script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="../../Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>