I'm currently working on the front-end of a medium/large-scale data-driven Asp 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.
I'm currently working on the front-end of a medium/large-scale data-driven Asp 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.
Share Improve this question edited May 23, 2017 at 7:53 Giovanni Romio asked May 17, 2017 at 7:19 Giovanni RomioGiovanni Romio 1,0561 gold badge13 silver badges31 bronze badges 4- 2 Personally I find that there is much more "control" over the Kendo controls from within Javascript (especially with the grids). We started using them last year and after a month+ of working with the Razor implementation, decided to move everything to Javascript/jQuery instead solely because of this reason. Just my opinion though, and can't seem to find any material online which discusses the differences/pros and cons etc. – Sandman Commented May 17, 2017 at 8:51
- my opinion is also to go with kendo ui java-script framework. Because you have full flash control on everything you want to implement. Code will be much cleaner. Support is great as we have used it in two large scale applications and using it from past 3 years. Not faced any limitation yet. – Ankush Jain Commented May 17, 2017 at 9:45
- 2 Hi I am new to kendo ui and razor template. But have fair experience in implementing large scale applications. How does this transform to in terms of markup and javascript .DataValueField("Id") .DataTextField("Username") .DataSource(d => d.Read(r => r.Action("UsersAsJson", "User")) .Events(e => e.RequestStart("onRequestStart"))) – karthick Commented May 23, 2017 at 5:23
- Hi, I will post it in an hour ... I'm not a work yet – Giovanni Romio Commented May 23, 2017 at 6:04
3 Answers
Reset to default 5 +50Folder 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 (binations 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 bine 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 pany 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 ponents asuming each ponent 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.ponent.html, rectangle.ponent.css, rectangle.ponent.js (optional rectangle.spec.js for testing).
app
└───rectangle
rectangle.ponent.css
rectangle.ponent.html
rectangle.ponent.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.