Introduction
This topic is a "how to" create a fully dynamic JavaScript application intended for
the Classic ASP programmers.
Background
There was an MVC pattern in the Classic ASP VBScript applications discussed in the
previous topic.
[^]
Now we are going apply
MVC pattern to Classic ASP application and use JavaScript as a language for the Server-side and
Client-side code.
The value of this topic is an experience of server-side MVC JavaScript Classic ASP
and client-side MVC JavaScript applications for those who work with classic ASP IIS applications.
Server-side code
There is a router as an entry point for the MVC web application.
Router
- parses the URL, calculates the Controller/Action
- passes the execution to the Controller/Action with parameters
There will be some other functions in the router.asp (as it is a single entry point of an application) :
- load required libraries
- load Controllers
- load Model
- authenticate/authorize user
- forms sanitation, etc.
You will need to capture 404 IIS error to enable nice URLs.
You can set up IIS error handler manually or using the script. Here is an instruction on how to set up 404 error handler
to route nice formatted URLs through the custom Classic ASP router.(select "Architect" from the menu.)
Let's store the server-side router in the /Router.asp file.
Controller/Action
Usually there is a number of different Controllers in the application. Controllers are classes with business logic of application. Actions are the functions of these classes.
{an example of business logic of Controller/Actions}
<%
var SomethingController = {
About: function(){
var model = someCalculation();
%> <!--#include file="../Views/Something/About.asp" --> <%
},
Read: function(callback){
var model = {data : {items: SomethingHelper.SelectAll()} };
callback(JSON.stringify(model),"application/json") ;
},
Create: function(callback){
var model = {data : new Something()};
callback(JSON.stringify(model),"application/json") ;
},
CreatePost: function(callback){
var obj = getArguments();
var result = createModelSomething(obj);
var model = {result : result, data: obj };
callback(JSON.stringify(model),"application/json") ;
},
Edit: function (callback,vars){
var model = {data: readModelSomething(vars)};
callback(JSON.stringify(model),"application/json") ;
},
EditPost: function (callback){
var obj = getArguments();
var result = updateModelSomething(obj);
var model = {result : result, data: obj };
callback(JSON.stringify(model),"application/json") ;
},
Delete: function(callback,vars){
var model = {data: updateModelSomething(vars)};
callback(JSON.stringify(model),"application/json") ;
},
DeletePost: function(callback){
var obj = getArguments();
var result = deleteModelSomething(obj);
var model = {result : result, data: obj };
callback(JSON.stringify(model),"application/json") ;
}
};
controllers['Something']=SomethingController;
%>
In the MVC pattern Controller handles the User input, and manipulates
the Model, and updates the View.
Early web MVC pattern took a thin client approach that placed the entire
model, view and controller logic on the server.
In this approach, the client sends either "get" or "post"
requests through the Router to the Controller and then receives a complete
and updated web page.
This thin client approach will be used to display the whole page. For example,
a default server-side HomeController.Index() will display the main page of the sample application.
Logic when server-side Controller,Action and View produce the whole page:
Once you may want to add more liveliness to your web-pages. The client-side JavaScript
code will help. The client-side code would send AJAX requests to the server and update the
DOM(document object model).
Then after several updates/features the client-side code becomes tangled, poor structured,
hard-to-maintain. To resolve the problem, again, there was an effort to structure the client-side
code.
As client-side technologies have matured , a number of client-side MVC frameworks have been
created that allow the MVC components to execute partly on the client.
You may have heard about Backbone.js, Knockout, AngularJs and other.
For the purpose of this topic none of them will be used, but the bare MVC pattern will
be applied to the Client-side JavaScript code.
The extensive client-side code affects the server-side code too: most of the
server-side Controllers/Actions will return the data rather than the compiled HTML.
For the JavaScript code it is natural to use JSON to transfer data, so the most of the
server-side controllers in the sample application will return JSON data to the client side.
Server will not be merging Model with Views to produce HTML.
The following example shows the server-side Controller and Action produce the JSON string:
Server-side Model
There is wide and narrow meaning of the term "Model" in the MVC pattern
The wide is the set of classes for CRUD(Create, Read, Update, Delete) operations over the objects in the database and
the database itself.
The narrow meaning of the term "Model" is the data we send to the client at once.
It can be an object serialized to JSON as in the example above, or false/true as a
result of operation, or any other data that the server may display with the HTML template,
or send to the client.
I will use the set of classes as the Data Model. These classes will have methods
with SQL clauses to update/retrieve data from/to relational databases. Files of
Data Model will be stored in the /Server/Model/(Name).asp files.
Server-side View
Although there are options now when selecting the template engine,
I'm going to use Classic ASP templating for the purpose of this topic.
Classic ASP engine extracts and executes code between <% %> tags while
parsing .ASP files.
I will use the term Server-side View for .asp pages with less logic and more content.
The code-generator that I'm using here creates a single server-side view /Server/View/Home/Index.asp
Client-side code
When user requests the page from server browser sends the Request to the server with URL to the requested resource. It forms the protocol of interaction between user and server-side code.
With the client-side JavaScript code such interaction is usually built on events bound to the HTML controls, such as onclick Event [^]. Frameworks like JQuery may help set your events to appropriate client-side event handler.
However, for an applying of the MVC pattern there should be some common way
used to capture the user interactions to transform them into the calls to the Router.
For the purpose of this topic the client-side URLs will be used to signal about the user requests.
Client-side part of URLs.
The URLs contains the part that identify the server-side resource and the client-side resource. Client-side resource are often called "links with the same page" or "named anchors". For example the url
http://localhost/Home/Index/#User/List
consist of server-side part:
http://localhost/Home/Index/
and client-side part:
#User/List.
When user clicks a link with such URL the browser will request a new page if the server-side part is different than the current page location.
If the server-side part has not been changed browser will not request a page, but instead will try to find an anchor on the page and set the focus on it.
For the purpose of this topic we will modify this behavior. Jquery-hashchange-plugin will capture the change of the client-side part of URL and send them to the client-side Router.
{code to hook the changes to URL}
$(document).ready(function() {
$(window).hashchange(function() {
route();
})
});
Router
The client-side Router will parse client-side part of URLs and pass the execution to the client-side Controller/Action.
{code of client-side Router}
var controllers = {};
function route() {
var defaultController = ""
var defaultAction = ""
var controller = null;
var action = null;
var vars = null;
var globalpath = location.hash.split('#');
if (globalpath.length < 2) return;
var path = globalpath[1];
if (path.charAt(0) != "/") path = "/" + path;
var pathchuncks = path.split('/');
if (pathchuncks.length) {
if (pathchuncks.length >= 3) {
controller = pathchuncks[1];
action = pathchuncks[2];
}
if (pathchuncks.length > 3) {
vars = pathchuncks.slice(3).join('/');
}
else {vars='';}
};
if (!controller) {
controller = defaultController;
};
if (!action) {
action = defaultAction;
};
if (controllers[controller]){
if (controllers[controller][action]){
var ControllerAction = controllers[controller][action];
ControllerAction(null,vars);
}
}
}
When a controller is initialized it is added to the list of allowed controllers.
var ProjectController = {
List: function(callback, args){ ... },
Create: function(callback, args){ ... },
CreatePost: function(callback, args){ ... },
...
Details: function(callback, args){ ... }
}
controllers['Project']=ProjectController;
So in the line :
var ControllerAction = controllers['Project']['List'];
the variable ControllerAction takes the pointer to the ProjectController.List() function, which is called in the next row.
ControllerAction(null,vars);
The
javascript object square bracket notation is used.
The client-side router is located in the /Client/router.js file
Client side Controllers/Actions
When user hit the link and the URL has changed, the "hashchange"
event is fired and the client-side Router is called. Router parses URL. It calls the client-side Controller/Action.
Client side Controller/Action has an access to the DOM(document object model) and it can update the interface, but it should get the Model and View first to produce the HTML.
Thus there will be 2 calls to the server to get the Model and View. While Model is a JSON sent by the server-side Controller/Action, the View is just a file in the server's file system.
Client side Controller/Action does the following:
- prepares the callback function to update the DOM when the Model and the View are ready.
- calls the server-side controller/action and receive JSON data as Model via ModelProxy.
- calls the server-side controller/action to retrieve the View via ViewProxy.
- when callback is called, and both Model in View are ready, the client-side Controller/Action renders the Model in the View to produce the HTML - a part of of the page.
- updates the part of the page with the new content.
An example of client-side controller ProjectController /action List
var ProjectController = {
List: function(callback, args){
var view=null, model=null;
var UpdateDOM = function(Model,View) {
if (Model != null){ model = Model; }
if (View != null){ view = View; }
if ((model != null) && (view != null)) {
var templateOutput = Mustache.render(view, model);
$("#dialog1-body").empty();
$("#dialog1-body").append(templateOutput);
$("#dialog1-label").empty().append("list of Project");
$("#dialog1").modal("show");
NormalizeHash();
}
}
ModelProxy(UpdateDOM, 'Project','List', args, 'get');
ViewProxy(UpdateDOM, 'Project','List');
},
Create: function(callback, args){
var view=null, model=null;
var UpdateDOM = function(Model,View) {
if (Model != null){ model = Model; }
if (View != null){ view = View; }
if ((model != null) && (view != null)) {
var templateOutput = Mustache.render(view, model);
$("#dialog2-body").empty();
$("#dialog2-body").append(templateOutput);
$("#dialog2-label").empty().append("");
$("#dialog2").modal("show");
NormalizeHash();
}
}
ModelProxy(UpdateDOM, 'Project','Create', args, 'get');
ViewProxy(UpdateDOM, 'Project','Create');
},
CreatePost: function(callback, args){
var view=null, model=null;
var UpdateDOM = function(Model,View) {
if (Model != null){ model = Model; }
if (View != null){ view = View; }
if ((model != null) && (view != null)) {
if (model.result == true) {
$("#dialog2-body").empty();
$("#dialog2").modal("hide");
NormalizeHash();
var templateOutput = Mustache.render(view, model);
if ($("#Projectdatatable")[0]!=undefined){
$("#Projectdatatable tr:last").after(templateOutput);
bindEvents();
}
}
if (model.result == false) {
var templateOutput = Mustache.render(view, { error: model.result });
$("#dialog2-label").empty().append(templateOutput);
}
}
}
ModelProxy(UpdateDOM, 'Project','CreatePost', args, 'post');
ViewProxy(UpdateDOM, 'Project','CreatePost');
},
...
}
controllers['Project']=ProjectController;
Architecture overview
Client-side View (mustache templates)
Client-side controller receives the JSON string with data via ModelProxy . This data should be transformed into the HTML.
It is possible to parse JSON data to the HTML in the JavaScript. However, the better way is to use Templates and special library for templates.
There is a number of Template engines for JavaScript: dustjs, underscore.js, jade.js, mustache, etc. I'm going to
use mustache for the purpose of this topic.
It is convenient to store the client-side Views in the /Client/Views/(Controller)/(Action).html
Views are stored in the server but they are transformed in the HTML in the Client code. That is why they are called Client-side Views.
After the Client-side Controller/Action produces the HTML it updates the DOM. For example it could be: display data received from server , display the status when post form to the server, add/remove rows in the tables, show/hide dialogues, menus, etc.
A set of client-side controllers will be in the files /Client/Controllers/(nameController).js
The code-generator I'm going to use creates one file per controller.
An example of client-side view /Views/Project/List.html
{{#data}}<div id='ProjectListContainer'>
<ul class="pagination pagination-sm pull-right"><li><a class="btn" href='#/Project/About'>?</a></li></ul>
<ul class="pagination pagination-sm">
<li><a id="create_button" href="#/Project/Create" class="btn"> </a>
</li><li class="button_container" style="display: none;" > <a id="edit_button" class="btn"> </a>
</li><li class="button_container" style="display: none;" ><a id="delete_button" class="btn"> </a>
</li><li class="button_container" style="display: none;" ><a id="details_button" class="btn"> </a>
</li></ul>
{{#paginator}}
<div >
<ul class="pagination pagination-sm">
<li><a href="#/Project/List/{{first}}"></a>
{{#previous}}</li><li><a href="#/Project/List/{{previous}}"></a>{{/previous}}
{{#links}}
</li><li><a href="#/Project/List/{{n}}">{{n}}</a>
{{/links}}
{{#next}}</li><li><a href="#/Project/List/{{next}}"></a>{{/next}}
</li><li><a href="#/Project/List/{{last}}"></a>
</li></ul>
</div>
{{/paginator}}
<table id="Projectdatatable" class="table table-striped"><thead><tr><td>ProjectName</td><td>POP3Address</td><td>Active</td><td></td></tr></thead><tbody>{{#items}}
<tr rowid='{{id}}'><td>{{ProjectName}}</td><td>{{POP3Address}}</td><td>{{Active}}</td><td></td></tr>{{/items}}
</tbody></table>
{{^items}}
<div>No records</div>
{{/items}}
<script type="text/javascript">
ProjectListModule = (function() {
var onSelectorClick = function(_selector) {
if ($(_selector).hasClass('active')) {
$(_selector).removeClass('active');
disableButtons();
}
else {
$(_selector).addClass('active').siblings().removeClass('active');
$(_selector).find("tr").addClass('active');
var rID = $(_selector).attr('rowid');
enableButtons(rID);
}
};
var enableButtons = function(id) {
$("div#ProjectListContainer li.button_container").fadeIn(100);
$("div#ProjectListContainer #edit_button")[0].href = '#/Project/Edit/' + String(id);
$("div#ProjectListContainer #delete_button")[0].href = '#/Project/Delete/' + String(id);
$("div#ProjectListContainer #details_button")[0].href = '#/Project/Details/' + String(id);
};
var disableButtons = function() {
$("div#ProjectListContainer li.button_container").fadeOut(100);
$("div#ProjectListContainer #edit_button")[0].href = '#';
$("div#ProjectListContainer #delete_button")[0].href = '#';
$("div#ProjectListContainer #details_button")[0].href = '#';
};
var bindEvents = function() {
$("div#ProjectListContainer #Projectdatatable > tbody tr").each(function() {
$(this).unbind();
$(this).bind('click', function() {
onSelectorClick(this);
});
});
};
return {bindEvents : bindEvents ,
disableButtons : disableButtons
}
})();
$(document).ready(function() {
ProjectListModule.bindEvents();
});
</script>
</div>{{/data}}
These templates work similar to web applets. They may contain HTML as well as supporting client-side JavaScript code.
Both are loaded dynamically when the generated HTML is added to DOM. The templates work even better with caching as it
discussed later.
As you can see the part of client-side logic is stored out of the Controllers/Actions. The architecture has suffered, but the coding is easier.
Other considerations
A typical server-based MVC application has very simple interface between user and server code:
there is just a single get or post HTTP request/response.
Browser requests, Server sends, and then browser loads the whole page, then the user clicks
the link or submit the form.
However there is no such simplicity in an application with massive client-side code .
One page may get and post data to/from a number of server-side URLs, without refreshing the whole page.
An application with massive client-side code and built with the MVC pattern is often
called a "single-page application". It does not mean that the application should have
exactly one page. It means that the point of view (or your focus of attention) should be shifted
to the page that you are working on.
You should be looking for resources for your page. Development becomes very page-centric.
When on the page the focus is on the questions like:
- where should the page call to get a form for user input
- or where should the page get a data for a list
- or where should a form send an input from the user, etc.
The page becomes very much like Microsoft's WebForms with it's call-backs. However the big
difference is that here the events are driven to multiple server-side controllers/actions,
that the requests are sent from the client-side code (without reloading the page), that the
updates to the interface happened in the client-side code.
Let's look how this system may handle standard tasks like: getting, displaying and sending Forms,
loading and displaying lists and grids, loading heavy bootstrap carousel sliders, etc.
Forms
Forms are shown as bootstrap modal dialogs on page.
The code loads Model and Template from the server, produce the HTML form and update DOM:
insert the HTML fragment with the form into the dialog.
A sample of code to display the Edit form (/Client/Controllers/Project.js)
Edit: function(callback, args){
var view=null, model=null;
var UpdateDOM = function(Model,View) {
if (Model != null){ model = Model; }
if (View != null) { view = View; }
if ((model != null) && (view != null)) {
var templateOutput = Mustache.render(view, model);
$("#dialog2-body").empty();
$("#dialog2-body").append(templateOutput);
$("#dialog2-label").empty().append("Update Project");
$("#dialog2").modal("show");
NormalizeHash();
}
}
ModelProxy(UpdateDOM, 'Project','Edit', args, 'get');
ViewProxy(UpdateDOM, 'Project','Edit');
}
The Client-side code validates a form and send it to the server with POST request.
Posting the forms happened from the separate client-side POST Controller/Action.
A sample of code that handles the "Edit" form values (/Client/Views/Project/Edit.html)
var SubmittingProjectEditForm = function() {
var args = {id: $('#ProjectEditContainer #Projectid').val(),
ProjectName: $('#ProjectEditContainer #ProjectName').val() ,
POP3Address: $('#ProjectEditContainer #POP3Address').val() ,
Active:($('#ProjectEditContainer #Active').is(':checked')) ? "on" : ""
};
ProjectController.EditPost(null, args);
};
Server-side controller sends result of POST form processing. The result is available in the client-side code
with data or status from server-side.
A sample of code that posts the "Edit" form values to the server (/Client/Controllers/Project.js)
var ProjectController = {
EditPost: function(callback, args){
var UpdateDOM = function(Model,View) {
show results of post request here
};
ModelProxy(UpdateDOM, 'Project','EditPost', args, 'post');
}
}
Lists(Grids)
When you need to display list, call the appropriate Controller/Action, for example
/ProjectController/List
Client-side controller (ProjectController.List()) will call the server-side
controller/action and get the data for List(Model). In the same time it will load the
appropriate view - a mustache template (/Client/Views/Project/List.html).
Then the client-side Controller/Action will update the DOM: display the rendered
List in the selected element of the Page.
Heavy bootstrap carousel sliders, menu, language resources
There is a great benefit from dynamic applications - the ability to load the lightweight page
in the beginning, and then later load and update heavy components. Bootstrap slider may have
a lot of images inside. It may take a while to load the page if you embed heavy images
into the page. You may load the carousel slider with one or two items at the beginning,
and then load the rest of them in the following way:
On the "page load" event call the client-side Controller/Action. It will call the server-side
controller/action and get the data for Carousel(Model).
In the same time it will load the appropriate View: a mustache template to render the
bootstrap carousel.
Then the client-side Controller/Action will update the DOM: display the rendered carousel
items in the selected element of the Page.
I'm using this background page update class to display menu and model in the code-generator.
Client-side caching
You can implement caching for Models and Templates on the client to enable the offline
application use. There are Web SQL , IndexedDB and localStorage available in the modern browsers.
ModelProxy and ViewProxy are places to fix for Models and Views caching
Client-side View caching
It is an obvious idea to cache Views on the client if cache is supported.
The benefits are: decreasing the network traffic, lower latency when call local controller/action.
{the ViewProxy without caching}
function ViewProxy (callback, controller, action) {
$.get('Client/Views/' + controller + '/' + action + '.html', {}, function(view) {
callback(null,view);
}).fail(function() {
var view = "";
callback(null,view);
});
};
On the screenshot when using ViewProxy without caching you could see
that 2 queries are made every time when user clicks the (Project) link.
Note that some of them are cached by browser.
{the ViewProxy with caching}
function ViewProxy(callback, controller, action) {
var cache = new ViewCache(controller, action);
if (!cache.read(callback)) {
$.get(baseurl + 'Client/Views/' + controller + '/' + action + '.html', {}, function(view) {
callback(null, view);
cache.write(view);
}).fail(function() {
var view = "";
callback(null, view);
});
}
};
ViewCache = function(controller, action, data) {
var enabled = true;
var Controller = String(controller);
var Action = String(action);
var Data = null;
var appName = "test";
if (data)
Data = String(data);
var storagename = appName+ "/Views/" + Controller + "/" + Action;
return {
write: function(data) {
if (data)
Data = String(data);
if (typeof (Storage) !== "undefined") {
localStorage[storagename] = Data;
return true;
}
else {
return false;
}
},
read: function(callback) {
if (!enabled) return false;
if (typeof (Storage) !== "undefined") {
Data = localStorage[storagename];
if (Data) {
if (Data.length > 1) {
callback(null, Data);
return true;
}
}
return false;
}
else {
return false;
}
},
clear: function(callback) {
if (!enabled) return false;
if (typeof (Storage) !== "undefined") {
for (var key in localStorage) {
if (key.substring(0, appName.length) === appName)
localStorage.removeItem(key);
}
return true;
}
else {
return false;
}
}
}
}
On the screenshot when ViewProxy use caching you could see that 1 query
is made every time when user clicks the (Project) link.
Model (data) caching
While the reasons for View caching are obvious, they are not so evident for data caching.
- why you may need it
You may need data caching to store often-used-but-rarely-updated data. It could be a menu or
dropdown values or some data rendered to the interface, e.g. language resources, etc. For the
purpose of this topic the caching will be applied to the dropdowns.
- why you may not need it
Although there are some benefits from data caching, there are reasons to do not use it:
- some actions requires alive (unconditionally non-cached data). This includes Create, Update,Delete operations.
- the actions mentioned above change the data, thus the cache invalidation is required.
- in multi-user systems cache invalidation will not help: someone else may change the database while local cache still
remain marked as valid.
- where it could lead you
If you implement the local data caching it could lead you later to the following things:
- Data caching for the CRUD operation may give you the power of offline/online applications.
Your client-side code could read, update, delete data in the cache while offline and sync changes with server when get back online.
- You could replace the data caching with client-side storage. This could give you a simple way to build hybrid mobile
applications.
{code of ModelProxy without caching}
function ModelProxy(callback, controller, action, vars, method) {
if (!method) method = 'get';
method = method.toLowerCase();
if (method == 'post') {
$.post( baseurl + controller + '/' + action + '/', vars, function(model, status) {
callback(model,null);
});
}
else if (method == 'get') {
$.get( baseurl + controller + '/' + action + '/' + vars, {}, function(model, status) {
callback(model,null);
});
}
};
{code of ModelProxy with caching (only DropDowns are cached)}
function ModelProxy(callback, controller, action, vars, method) {
var cache = new ModelCache(controller, action);
if (cache.cacherequired) {
if (cache.read(callback)) {
return;
}
}
if (!method) method = 'get';
method = method.toLowerCase();
if (method == 'post') {
$.post(baseurl + controller + '/' + action + '/', vars, function(model, status) {
callback(model, null);
if (cache.cacherequired) { cache.write(model); }
});
}
else if (method == 'get') {
$.get(baseurl + controller + '/' + action + '/' + vars, {}, function(model, status) {
callback(model, null);
if (cache.cacherequired) { cache.write(model); }
});
}
};
ModelCache = function(controller, action, data) {
var enabled = true;
var Controller = String(controller);
var Action = String(action);
var Data = null;
var appName = "test";
if (data) { Data = JSON.stringify(data); }
var storagename = appName + "/Model/" + Controller + "/" + Action;
var cachedActions = new Array("DropDown");
var dataChangeActions = new Array("CreatePost", "EditPost", "DeletePost");
var invalidate = function() {
if (typeof (Storage) !== "undefined") {
var iAction;
for (iAction in cachedActions) {
localStorage.removeItem(appName + "/Model/" + Controller + "/" + cachedActions[iAction]);
}
}
};
var invalidationrequired = (dataChangeActions.indexOf(action) >= 0);
if (invalidationrequired) {
invalidate();
}
var cacherequired = (cachedActions.indexOf(action) >= 0);
return {
write: function(data) {
if (data)
Data = JSON.stringify(data);
if (typeof (Storage) !== "undefined") {
localStorage[storagename] = Data;
return true;
}
else {
return false;
}
},
read: function(callback) {
if (!enabled) return false;
if (typeof (Storage) !== "undefined") {
Data = localStorage[storagename];
if (Data) {
if (Data.length > 1) {
Data = JSON.parse(Data);
callback(Data, null);
return true;
}
}
return false;
}
else {
return false;
}
},
invalidate: invalidate,
cacherequired: cacherequired,
invalidationrequired: invalidationrequired
}
}
code assembly and placement.
general
JavaScript is an interpreted language. We need to store the code in separate files.
There is a number of ways to keep the code modules together as a system.
- When doing a spaghetti code there are lot of global variables reused.
You just can't join files together; they won't work.
With MVC pattern you could simply join all script code files together starting from router in the order of inclusion.
If it still works then you could compress/minify the code using one of the free available
services(e.g. http://jscompress.com/).
As a bonus the minification provides the natural obfuscation.
Both Server-side and client-side code could be compressed. - Keep the code together with built-in code-linkers
server-side code linking
Classic ASP uses
#include directives to link in modules. The file Router.asp contains directives to
link Controllers, Model files, Lib files. These 3 work as modules
and include corresponding custom files.
The Classic ASP Server.Execute() command could be used to include code modules.
There is another less known option for including sever-side modules: a standard
<script> tag with an option runat="server".
<script language="JScript" src="Controller/controllers.inc" runat="server"></script>
<script language="JScript" src="Model/lib.inc" runat="server"></script>
<script language="JScript" src="lib/utils.inc" runat="server"></script>
This last option comes from MS Scripting Engine. Including files in this way allows
the code in the different scripting language, e.g. VBScript:
<script language="VBScript" src="lib/VBModule.inc" runat="server"></script>
However you should carefully verify how your IIS processes file extensions of your modules.
If you leave modules in the ".vbs" or ".js" files, and if someone directly requests these
files from server, they will be sent to the client as is. This will expose the server-side
logic and constants you may have in the code. The extension ".inc" should be safe in IIS
default settings.
If you are in doubts regarding accessing the server-side code then you can relocate
an entire server-side code out of IIS folder structure. Since the pattern we have used
here assumes the single entry-point of the application, there can be a single
#include directive in the single file in the IIS application folder.
client-side code linking
There are 2 general ways to link JavaScript on the client:
- Link scripts with a standard <script> tag
- Link scripts dynamically loading them into the code: load scripts with an Ajax and then
call them with use of eval().
The first option is OK when JavaScript code files are not too large, otherwise it may take
a time to load them all. If scripts are large unfortunately there is another problem with standard
<script> tag: browsers do not load scripts and other resources in parallel, so it takes
even longer lo display the page for the first time.
The second option is easy with use of jQuery.getScript(), RequireJS, labjs, etc. libraries. However there is a couple of problems you may encounter using them:
- There may be an interface glitch when the DOM is loaded but supporting JavaScript libraries are still not loaded.
- There may be a broken sequence of events: Suppose you have the module, which is loaded from Require.js. jQuery
document.ready() may not call a function defined in the module, as it is still loading when the event is fired
Debugging
You can debug the server-side application using MS Visual Studio. You will need:
Advantages and disadvantages
- The similar dialects of the same JavaScript are used both on the server and client. Less context switching due to the
similar versions of the language for both client and server.
- The similar implementation of the same MVC pattern is used on the server and client. Less context switching due to the
similar versions of the language for both client and server .
- The code is portable and relocatable between client and server with the minimal modifications.
- Availability of JavaScrip libraries from the most unexpected sources when doing the server-side JavaScript
programming.
- It is IIS. You can bring your VBScript code and mix it with server side. Don't forget about the ActiveX extensions on
Windows through Server.CreateObject() and ASP.NET pages
- Scripts are loaded and unloaded for every server call, thus it is truly stateless, but should be slower than all-in-memory systems
- Classic ASP does not support asynchronous
code execution.
- Classic ASP is available on a number of MS operating systems, starting from XP.
Points of Interest
- The MVC pattern applied to Classic ASP Server-side JavaScript code
- The MVC pattern applied to Client-side JavaScript code
- The code-generator is created
- The client-side caching and templating has been applied
Classic ASP reloaded.
History
- Mobile application for Intel XDK and classic ASP/IIS.
- Using the code generator to create the node.js based application.
- Application for Chrome and Classic ASP/IIS.