I'm currently working on the front-end of a medium/large-scale data-driven Asp.net MVC application and I have some doubts about the right code-organization/design pattern to follow.
The web application is made by multiple pages containing many Kendo UI MVC widgets defined with Razor template.
For those who are unfamiliar with Kendo, the razor syntax is translated to Javascript as the following snippet:
I defined inside my Script folder two main folders, and I structured my js files as follow:
shared //Contains the shared js files
-file1.js
-file2.js
pages //One file per page
page1.js
page2.js
...
Ticket.js // page 4 :)
Each js file is a separate module defined with the following pattern:
Note: Inside init function is registered every callback function to the window events and occasionally a $(document).ready(function(){}) block.
;(function () {
"use strict";
function Ticket(settings) {
this.currentPageUrls = settings.currentPageUrls;
this.currentPageMessages = settings.currentPageMessages;
this.currentPageEnums = settings.currentPageEnums;
this.currentPageParameters = settings.currentPageParameters;
this.gridManager = new window.gridManager(); //usage of shared modules
this.init();
}
Ticket.prototype.init = function () {
$("form").on("submit", function () {
$(".window-content-sandbox").addClass("k-loading");
});
...
}
Ticket.prototype.onRequestStart = function (e) {
...
}
//private functions definition
function private(a, b, c){
}
window.Ticket = Ticket;
}());
Once I need my Javascript functions defined in a module I include the associated Javascript file in the page.
An istance of my object is stored inside a variable and, on top of that, a function is bound to the widget event (see: onRequestStart).
HTML/JAVASCRIPT
#(Html.Kendo().DropDownList()
.Name("Users")
.DataValueField("Id")
.DataTextField("Username")
.DataSource(d => d.Read(r => r.Action("UsersAsJson", "User"))
.Events(e => e.RequestStart("onRequestStart"))))
var settings = {};
var ticket = new window.Ticket(settings);
function onRequestStart(e){
ticket.onRequestStart(e);
}
I feel like my design pattern might be unfriendly to other front-end delevoper as I am, mostly because I choose not to implement the Javascript modules within Jquery plugin.
First, Am I doing everything the wrong way?
Second, is my design pattern suitable for a Javascript test-framework?
Third, which are the must-have scenarios for Jquery plugins?
Update
Added the Javascript output by the above Razor syntax.
Folder structure
In terms of functionality (shared) and modules (modular approach), the development or application code should represent what you can encounter in HTML. A simple ctrl+f over your solution should yield all possible changes. From that experience over the years I personally prefer dividing it in:
app (application code)
classes (reusable)
modules (singleton)
lib (package manager/grunt/gulp/...)
jquery (proper library names/unminified dist file or root file)
kendo
File names
Representing what something does and to be able to reuse it in a blink of an eye is what will cut your development time. Choosing proper names has value as I'm sure you are aware. My file names always starts with the namespace usually in short followed by a reusable "search" term:
app/prototypes
ns.calendar.js (multiple configs)
ns.maps.js (combinations or single uses)
ns.places.js (forms or map add-ons)
ns.validation.js (multiple forms and general handling)
app/singletons
ns.cookiebox.js (single config)
ns.socialmedia.js (single config)
ns.dom.js (provides a place for dom corrections, global resize events, small widgets, ...)
To add, what you called shared, is functionality that's meant to be global. A great example would be to use underscore library. Or create a collection of functions (device detection, throttle, helpers in general) on your own to reuse throughout projects => ns.fn.js
Since you add them only once throughout your namespace, it's also built as singleton and can be added to the modules folder or directly in the app root.
As last addition a loader file to kickstart your point of control => ns.load.js in the app root. This file holds the single DOM ready event to bind protoypes and modules.
So you might want to rethink your idea of dividing into pages. Trust me, I've been there. At some point you'll notice how functionality grows too large in order to configure all pages separately and therefor repeatedly.
File structure
To be honest I like Tip 1 of #TxRegex answer the most, with a small addition to bind the namespace and pass it from file to file as it get's loaded.
Core principle: IIFE bound to window object
window.NameSpace = (function($, ns){
'strict'
function private(){}
var x;
ns.SearchTerm = {};
return ns;
}(window.jQuery, window.NameSpace || {}));
For more example code I'd like to point out my github account.
Bundling
Try to achieve a single bundled and minified file from lib to app, loaded in the head on async for production releases. Use separated and unminified script files on defer for development and debug purposes. You must avoid inline script with global dependencies throughout the whole project if you do this.
path to js/lib/**/*.js (usually separated to keep sequential order)
path to js/app/ns.load.js
path to js/app/ns.fn.js
path to js/app/**/*.js (auto update the bundle)
Output => ns.bundle.js
=> ns.bundle.min.js
This way you'll avoid render blocking issues in JavaScript and speed up the loading process which in turn boosts SEO. Also enables you to combine functionality for mobile layouts and desktop layouts on the fly without memory issues or jerky behavior. Minifies really well and generates little overhead in calling instances from the loader file. As a single bundle will be cached throughout your pages it all depends on how many dependencies or libraries you can cut from the bundle. Ideally for medium and large projects where code can be shared and plugged in to different projects.
More info on this in another post.
Conclusion
First, Am I doing everything the wrong way?
Not at all, your modular approach seems ok...
It's missing a global namespace, which is hard to avoid without at least one. You create one for each module but it seems better to group them all under one namespace so you can differentiate library code from application code in the window object.
Kendo seems to create inline scripts? Can't you counter the placement server side?
Second, is my design pattern suitable for a Javascript test-framework?
Except for the Kendo instances, you can add a layer for testing purposes. Remember if jQuery is your dependency inline, you'll have to render block it's loading. Otherwise => jQuery is undefined
Exclude Kendo dependencies from the bundle if you can't control the inline script. Move to a </body> bundled solution.
Third, which are the must-have scenarios for Jquery plugins?
modular approach
configurable approach for multiple instances (tip: moving all strings from your logic, see how Kendo uses object literals)
package manager to separate the "junk" from the "gold"
grunt/gulp/... setup to separate scss and css from js
try to achieve a data-attribute binding, so once all is written, you configure new instances through HTML.
Write once, adapt easily where necessary and configure plenty!
The organization and pattern seems fine, but I have some tips:
Tip 1:
Instead of setting specific global variables within your module, perhaps you could return the object instead. So instead of doing this:
;(function () {
"use strict";
function Ticket(settings) {
console.log("ticket created", settings);
}
...
window.Ticket = Ticket;
}());
You would do this:
;window.Ticket = (function () {
"use strict";
function Ticket(settings) {
console.log("ticket created", settings);
}
...
return Ticket;
}());
The reason for this is to be able to take your module code and give it a different global variable name if needed. If there is a name conflict, you can rename it to MyTicket or whatever without actually changing the module's internal code.
Tip 2:
Forget Tip 1, global variables stink. Instead of creating a seperate global variable for each object type, why not create an object manager and use a single global variable to manage all your objects:
window.myCompany = (function () {
function ObjectManager(modules) {
this.modules = modules || {};
}
ObjectManager.prototype.getInstance = function(type, settings) {
if (!type || !this.modules.hasOwnProperty(type)) {
throw "Unrecognized object type:";
}
return new this.modules[type](settings);
};
ObjectManager.prototype.addObjectType = function(type, object) {
if (!type) {
throw "Type is required";
}
if(!object) {
throw "Object is required";
}
this.modules[type] = object;
};
return new ObjectManager();
}());
Now each of your modules can be managed with this single global object that has your company name attached to it.
;(function () {
"use strict";
function Ticket(settings) {
console.log("ticket created", settings);
}
...
window.myCompany.addObjectType("Ticket", Ticket);
}());
Now you can easily get an instance for every single object type like this:
var settings = {test: true};
var ticket = window.myCompany.getInstance("Ticket", settings);
And you only have one global variable to worry about.
You can try separating your files in different components asuming each component has a folder.
for example: page 1 is about rectangles so you make a folder call rectangle inside that folder you create 3 files rectangle.component.html, rectangle.component.css, rectangle.component.js (optional rectangle.spec.js for testing).
app
└───rectangle
rectangle.component.css
rectangle.component.html
rectangle.component.js
so if anything bad happends to a rectangle you know where is the problem
a good way to isolate variables and execute in the right place is to use a router basically what this does it check at the url and executes the portion of code you asign to that page
hope it helps let me know if you need more help.
I was elated to find out that I could use XML commenting in my javascript (i.e.):
/// <summary>Determines the area of a circle based on a radius parameter.</summary>
/// <param name="Name" type="String">Give this dude a NAME!</param>
/// <returns type="Nothing">The area.</returns>
That was until I realized that after I did this, my "public" methods were no longer listed in intellisense... :-(
this.greet = function (greeting) {
alert(greeting + " " + this);
}
The above would show up when I would instantiate var thePlayer = new player('bob'); ... thePlayer. <-- greet would be listed, UNTIL I threw in the above XML comments.
Does anyone have any experience with this and a work around? Being able to comment javascript into intellisense would be very nice!
I think if you remove the (type="Nothing") from the returns it should show the members on a player.
I've got a large Javascript object that I've split into multiple files. Basically it goes like this:
File 1:
MyClass.prototype.Method1 = function() { ... }
File 2:
MyClass.prototype.Method2 = function() { ... }
Etc.
Since Method1 can call Method2 and vice versa, I'd like them both to be available from IntelliSense within each other. Unfortunately it seems that IntelliSense does not like circular references when recursively crawling the files. Any idea on how I can get proper IntelliSense support in ALL my files?
I'm using ASP.NET MVC 3 and I have one JavaScript-file for each view and each partial view. So if I have the following files:
User.cshtml
User.js
_EditUser.cshtml
_EditUser.js
Now, I want to have intellisense in file '_EditUser.js' for functions and variables defined in 'User.js'. Is that possible?
Try to add
/// <reference path="User.js" />
to the top of _EditUser.js
Update:
To see more than names of variables and functions of User.js you need to add special comments with tags. They are like usual comments for documenting C# code in Visual Studio. One of minor differences is this comments are inside function body not before. You can see it in jQuery-with-doc-file as you mentioned in comment.
function Some(key, value)
{
/// <summary>Function do some value with key</summary>
/// <param name="key" type="String">Key argument</param>
/// ... and so on ...
}
I'm trying to get Intellisense to correctly work for closure. As a plugin author, I always use a closure to create an isolated environment for my plugin code:
(function($) {
// code here
})(jQuery);
But the problem here is that Intellisense doesn't pick up that jQuery is being passed in the execution of the function. Adding $ = jQuery in the above code fixes the problem, but that's just poor execution, IMHO.
Anyone here got this working without resorting to embedded ASP server tags (this is a standalone JS file)? Something preferably not including modifying existing code other than some odd /// <option .../>-like solution?
It isn't clear in your post or your comments, but at the top of your .js file, did you add:
/// <reference path="jquery.vsdoc.js" />
to the top of your file?
ScottGu's blog has more on intellisense in external libraries (not jQuery-specific).
Also, here's another possible solution, is this what you mentioned with $=jQuery?:
(function($) { // private closure; <% /*debug*/ if (false) { %>
$ = jQuery;
// <% } /*end debug*/ %>
$(function() {
// do stuff
});
})(jQuery);
Found here: http://blog.jeroenvanwarmerdam.nl/post/IntelliSense-VS08-within-private-closure.aspx
if you are looking at Visual Studio 2010 for your jQuery plugin development IDE, you have made the right choice. Here are the details for the setup:
Download the jquery and the respective jquery.vsdoc in the same directory of your project. You can download the latest version of the jQuery files from http://www.asp.net/ajaxlibrary/cdn.ashx. Here are the links for the latest jQuery links from above CDN:
http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.js
http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.min.js
http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1-vsdoc.js
In my development environment I use the uncompressed jquery file renamed to jquery.js (removing the version information [-1.7.1] in the file name, and remember to remove the version information from the vsdoc file name too).
Create your plugin file with its first line containing the line
/// <reference path="/path/to/jquery.js">
Create the plugin code with closure. Here is the full skeleton of a plugin:
/// <reference path="jquery.js" />
(function ($) {
/// <param name="$" type="jQuery" />
jQuery.fn.gallery = function () {
return this.each(function () {
// your code here
});
};
})(jQuery);
Remember to use /// <param name="$" type="jQuery" /> as the first line in the closure of the plugin as I have demonstrated in the code above. It all works for me in Visual studio 2010 SP1.
Visit My jQuery Plugin Site and Blog
But before installing the hotfix make sure you have SP1 installed in your system.
I'm surprised this doesn't work in VS2010 (I don't think you will be able to make it work in VS2008).
You could try adding an xml doc comment to the beginning closure to define the param type. Something like this:
/// <param name="$" type="Jquery" />
(I don't know what the class name for the jquery object is -- or if there is even one available).