First you need to load jdog.js. This will define PAGE and J for your web page. PAGE and J are the same object, here for convenience. Notice to all new javascript developers, learn how to bundle your scripts. This will save a ton of bandwidth and improve the speed of your web sites. This is especially important for high traffic sites. I've chosen to keep my files unbundled to allow you all to take a look, but normally I minify my scripts, or use a bundling plugin.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="/PathToScripts/jdog.js"></script> </head> <body> </body> <!-- all of your other scripts load here --> </html>
Example of creating a Module, ie, immediately instantiated object. For those familiar with Require.js or other similar module loading scripts, this follows the same general convention with a few exceptions. Instead of a list of arguments based on the loaded library item, you have a single object with properties named after the library item. See example below. Also, I have chosen to use the dog convention here instead of the more formal exports, as it's shorter. They are interchangable, as the dog returns, so does exports.
Below is a pretty common, though truncated example of a Module.
//scripts/loginPage/page.modules.loginPage.js PAGE.waitAdd$( "Modules.loginPage", [ "Constructors.Loader" , "Constructors.Login" , "Modules.tracking" ] , function(ref) { var $form = $("#form1") var dog = { $form : $form , login : ref.Login( $form, { showError : true } ) , loader : ref.Loader().show() } dog.$form.submit(function() { ref.tracking.commonTrack("clicked submit") }) dog.login.addEvent("Success", function(id, name) { ref.tracking.commonTrack("logged in", { id : id, name : name }) }) dog.login.addEvent("Fail", function(msg, err) { ref.tracking.commonTrack("failed login", { msg : msg, err : err }) }) return dog })
Here we have a pretty common example of a Constructor. Now those who are familiar with Require.js will be scratching their heads. That is ok, the code does something pretty cool when you take a look at the details.
By convention in jDog, constructors never wait for external libraries to load before being declared. Instead, they build themselves up, then wait for any required libraries before running their init. If you notice at the bottom of the example is PAGE.wait... Also, by convention, this code demonstrates the events function. Generally, I like to first build the html, create Elements, Append to DOM, and then add events. This example shows how to reference external libraries neatly, and within scope.
The reference object takes advantage of a little realized characteristic of javaScript, namely passing objects by reference instead of value. By defining ref before init is called, then passing it into the PAGE.wait function, it gets populated, then the init function gets called.
// First Parameter is the name with namespace // in this example, a Constructor that takes two parameters // // Constructors are a special kind of function // that creates an object that has public methods and properties // this simple example has three public properties // $form, $html, and options // // it also has a public (Module) property, validation // // in addition, because it employs the events extension // the following methods are added as well // // addEvent( String, Function ) // triggerEvent ( String, property, property ... ) // // as well as this events object // events { // Success : Array // , Fail : Array // , SubmitClick : Array // } PAGE.add("Constructors.Login", function($form, options) { // define options if not defined options = options || { } // options are optional, this line of code falls back // to false if property is not defined options.showError = options.showError || false // ah, the beloved dog // a dog is the equivalent to the exports object // the dog is the thing that gets returned from the constructor // the dog is defined in this way to create a legend towards // the top of the file to create a quick reference var dog = { $form : $form , $html : undefined // see below , options : options , validation : undefined // see below } // the reference object, AKA ref is an object to house all external libraries , ref = dog.ref = { } // employs the PAGE events extension // this adds publically available events based on these names // these events are a series of functions that get fired when the named event // is triggered PAGE.ext.events(dog, { Success : [] , Fail : [] , SubmitClick : [] }) // a very common technique is to generate all of the HTML needed in this file. // Having html defined outside of the constructor is also valid // both techniques have advantages, when it comes to file management function build() { // create some html the tried and true way var html = '' html += "<div class='pad'>" html += "<div class='row'>" html += "<input type='text' name='UserName' />" html += "</div>" html += "<div class='row'>" html += "<input type='text' name='Password' />" html += "</div>" html += "<div class='row'>" html += "<button>Submit</button>" html += "</div>" html += "</div>" // empty out the container dog.$form.empty() // and then populate the container with newly formed jQuery code dog.$html = $(html).appendTo(dog.$form) } // also extremely common practice is to create an events function // this is a great place to trigger any named events, // add click events through jQuery // events should be fired after the HTML has been built // events should relate directly to the built HTML and // not the global HTML on the page // this greatly increases the speed of building out the HTML function events() { // here is an example of calling a library constructor // notice that the dog.validation object get built by the Validation Constructor // also notice that this validation constructor has // two function callbacks, success and fail dog.validation = ref.Validation(dog.$form, function success(data) { // this triggers the Success event upon the success callback firing // notice that it is passing in data from the data object dog.triggerEvent("Success", data.id, data.name) }, function fail(msg, err) { // this triggers the Fail event upon the fail callback firing // notice that it is passing in msg, and err dog.triggerEvent("Fail", msg, err) }) // the following code starts the validation code to run // this specific code keeps checking until the code is valid // showing errors as you type dog.$form.click(function() { // this triggers the SubmitClick event // notice that it is passing in dog.$form dog.triggerEvent("SubmitClick", dog.$form) // line that starts the validation, a public method of // the validation Module embedded in this constructor dog.validation.startValidation() }) } // init is the code that starts everything running // in this example, init only fires after all external libraries are loaded function init() { build() events() } PAGE.wait( "Modules.dataService.read" // example not shown here , "Constructors.Validation" // see above for usage , ref // passing in the reference object here , init) // finally firing this function when it's done loading // finally return the dog // consider dog like the exports object, or the this object return dog })
Here is an example of passing values from the server to javascript code. Being able to quickly see all your properties in the console by typing PAGE.Properties is front end heaven. It makes the building of pages very easy and ensures everything is coming back as expected.
<script> // boolean value PAGE.add("Properties.IsTrackingOn", @IsTrackingOn.ToString().ToLower()) // object with a bunch of string values PAGE.add("Properties.AnnualSubscription", { PlanId: "@annualSub.PlanId.ToString()" , Price: "@annualSub.Price.ToString()" , Description: "@annualSub.Description.ToString()" }) // backend object converted directly to JSON object PAGE.add("Properties.User", @Html.Raw(Json.Encode(ViewBag.CurrentMember))) </script>
Here is an example of splitting out the tracking away from the main Constructor. Sometimes it's nice to split off functionality, especially when working in teams. By passing in the dog to this script will give the script fulll access to all public properties and methods.
// page.augments.login.addMoreTracking.js PAGE.add("Augments.login.addMoreTracking", function(login) { login.addEvent("Success", function() { // lots of stuff }) login.addEvent("Fail", function() { // lots of stuff }) login.addEvent("SubmitClick", function() { // lots of stuff }) return login }) // page.constructors.login.js PAGE.add("Constructors.Login", function($form, options) { var dog = { // lots of code here } // lots of code here function init() { // lots of code here // passing in the dog, or export, which will extend it's // functionality ref.addMoreTracking(dog) } PAGE.wait( "Modules.dataService.read" , "Constructors.Validation" , "Augments.login.addMoreTracking" , ref , init) })
This example creates some interesting code. First it waits for jQuery to load, then Modules.dataService. Once these are ready, it creates a new object attached to the namespace Augments.Modules.dataService.read. But it also is simply adding this to Modules.dataService.read. Augmented Modules have their place, and are easily done with jDog.
PAGE.addWait$( "Augments.Modules.dataService.read" , [ "Modules.dataService" ] , function (ref) { var f = new Function() var dog = ref.dataService.read = { loadStuff : function (id, callback) { $.ajax({ url : "someUrl/blah" , data : { Id : id } , success : callback }) } , loadUser : function (memberId, callback, fail) { $.ajax({ url : "someUrl/blah" , data : { MemberId : memberId } , success : function(data) { if (data.Success) { ;(callback || f)(data) } else { ;(fail || f)(data) } } }) } } return dog })
Code that gets loaded into the window object, which is a vast majority of javascript out there, is easy to add to PAGE. I've added a special keyword, window, to all exists methods and methods that depend on exists. This means that addWait, addWait$, wait, and waitExists all look for it. See example below
// waiting for external javascript PAGE.wait( "window.jQuery.fn.select2" , "Modules.dataService.read" , {} , function(ref) { ref.read.getStates(function(states) { $("#select2").select2() }) })