Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript code organization data driven application

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: Kendo server side-wrappers

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.

Code snippet

like image 743
Giovanni Romio Avatar asked May 17 '17 07:05

Giovanni Romio


People also ask

Does JavaScript have built in data structures?

JavaScript has primitive (built in) and non-primitive (not built in) data structures. Primitive data structures come by default with the programming language and you can implement them out of the box (like arrays and objects).

What is structuring in JavaScript?

JavaScript has primitive and non-primitive data structures. Primitive data structures and data types are native to the programming language. These include boolean, null, number, string, etc. Non-primitive data structures are not defined by the programming language but rather by the programmer.


2 Answers

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!

like image 132
Tim Vermaelen Avatar answered Oct 04 '22 02:10

Tim Vermaelen


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.

like image 26
TxRegex Avatar answered Oct 04 '22 02:10

TxRegex