You will need copies of the following files:
We want to be able to display content in a container tag such as a <span> or <div> tag, and then be able to detach and attach these tags to our webpage as required. Using this approach we can make content dynamically appear or disappear, and even move content from one location on the page to another. More importantly we will have defined a container tag that we can then render the contents of a Model into. We will call this creating a View, and we will want different types of Views (child classes) that can display different content or the same content in different ways.
If we were displaying simple text, we might create a TextView and use it like this:
var view: TextView;
view = new TextView("#display-here-on-page");
view.Text = "Hello World";
view.Render(); // Update the view contents
If we want to display the data contained in a Model e.g. AjaxifiedStudent, then we might have a StudentModelView to do this and use it like this:
var view: StudentModelView;
var model: AjaxifiedStudent;
model = new AjaxifiedStudent();
view = new StudentModelView("#display-here-on-page", model);
view.Render(); // Update the view contents
Before we get to this we will need to create a parent View class that uses jQuery to handle the attaching, detaching and moving of the tags that represent the View.
When creating the view, we will need to give it the initial location on the page where it should be displayed e.g. #display-here-on-page.
var view: SayHelloView();
// We choose to locate view on page at "#display-here-on-page"
view = new SayHelloView("#display-here-on-page", "hello to Fred");
view.Render(); // Update the view contents
The View class will provide a number of methods, but the most interesting of these are:
| Method Name | Description |
|---|---|
| Attach(): void | Attach the view to the page. If you want to hide the view while you change the contents, Detach(), Render() then Attach() again. |
| Detach(): void | Detach the view from the page. The contents of the view are unaffected, and can be rendered or modified as normal. However, the view is invisible because it is no longer part of a page. The view may also be attached to a different location on the page using Move(). |
| Move(newParentEl: any): JQuery | Move the view to another location on the page. The view is detached and attached by this method. |
| Render(): void | Redraw the contents of the view. For example, if you change the text that should appear in the view. First change the text then call Render() to update the contents of the view. Any alterations you make to the view may be visible while you are making them, unless you Detach() the view first. |
| BindEvents(): void | Internal Method: Called by the Render() method to attach any DOM event handlers after the drawing of the view. |
| UnbindEvents(): void | Internal Method: Remove any event handlers attached to HTML elements that have been displayed in the view. Called by Render() remove the DOM event handlers before there associated HTML elements disappear. |
Let's take an example of a HTML page that looks like this:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<div id="region1">Region One:<br/></div>
<div id="region2">Region Two:<br /></div>
<-- Allow the user to create a new view -->
<input id="user-name" type="text" />
<input id="add-view-button" type="button" value="Add View" />
<div id="region3">Region Three:<br /></div>
<script src="Scripts/jquery-2.2.0.js"></script>
<script src="IDestroyable.js"></script>
<script src="View.js"></script>
<script src="SayHelloView.js"></script>
<script src="ExampleViewPage.js"></script>
</body>
</html>
In this page, three <div> tags are used to provide three regions within which the views will be displayed. A textbox "user-name" allows the user to provide a name to be displayed in the added view and a button "add-view-button" adds the view to region 3.
The JavaScript Page class for this web page might look like this:
class ExampleViewPage
{
// Attributes
// Constructor
// Properties
// Methods
PageLoad(): void
{
// Add two views, one to region 1 and the othe to region 2.
this.NewView("#region1", "Fred");
this.NewView("#region2", "Bob");
// Add button event handler
$("#add-view-button").click($.proxy(this.AddView, this));
}
private NewView(regionEl: any, userName: string): void
{
// Create a new view in the given region "regionEl", with name to display in it "userName"
var view: View = new SayHelloView(regionEl, userName); ← For example "#region1", "Fred"
// Render the view to display user name in view.
view.Render();
}
// Button event handler
private AddView(e: JQueryEventObject): boolean
{
this.NewView("#region3", $("#user-name").val());
e.preventDefault();
return false;
}
}
// jQuery page ready
$(function ()
{
var page: ExampleViewPage = new ExampleViewPage();
page.PageLoad();
});
We add a page class with a PageLoad() method to create two views and add event handlers for each button. First it create a view to be displayed in region1 and then a second view to be displayed in region2. When the button is clicked the AddView() callback is called which uses the NewView() method. The NewView() method, creates the SayHelloView, renders it. In the Render() method, a call is made to BindEvents() to allow the view to bind jQuery events to buttons, etc. that have just been created in the view.
The SayHelloView class below inherits from the View class we will be creating. This view is fairly simple, it displays any name you provide in the constructor, say you provide the name "Fred" then the view displays "Hello Fred". We have an attribute _name to store the provided name.
To make the view a bit more interesting and to demonstrate some of the features of a view, some additional HTML has been added. A button "close-button" is provided to allow the view to be closed. The click event handler CloseView() is bound in BindEvents(). Closing a view simply involves destroying it, which detachs the view and unbinds events automatically. The view also provides a HTML <select> tag to allow the user to choose a region, and a "move-button" that moves the view to the selected region allowing the view to be move around the page. The MoveView() event handler calls Move() passing it the region id (e.g. "#region1"), which in this case is the chosen option in the <select> tag.
Note 1: We want to be able to create multiple views on the same web page using class and this gives us a problem if we use "id" attributes to identify each button, textbox, select, etc., because the same id values will be used for each instance of this view. On a web page, each "id" must be unique but with each view using the same id values this would not be the case. Instead, we use a "fake" CSS class name and then we can search the HTML elements in the view for the class name.
<input class='close-button' type='button' value='Click to Close' />
Multiple elements on a web page can use the same CSS class name, so each view instance using the same names is no longer a problem.
Note 2: In jQuery functions using $ will search for elements contained in the whole page. To restrict the selector to just the current view, the View class provides its own $ method.
// Finds first instance of text box in whole web page
$(".name-textbox").val();
// Finds text box in just the view class
// Example Usage
this.$(".name-textbox").val();
Here's the code for the SayHelloView:
class SayHelloView extends View
{
// Attributes
private _name: string;
// Constructor
constructor(parentEl: any, name: string, options?: ViewOptions) → Example Usage
{
super(parentEl, options);
// The name to be displayed in the view when we Render() it.
this._name = name;
// Change colour of view so we can see it more clearly!!
this.View.css("background-color", "pink");
}
// Properties
// Methods
RenderView(el: JQuery): void
{
var html: string;
// Create the HTML for the view and replace the view content.
html = "<input class='close-button' type='button' value='X' />";
html += "<p>Hello " + this._name + "</p>";
html += "<select class='region-name'>";
html += "<option value='region1'>Region One";
html += "<option value='region2'>Region Two";
html += "<option value='region3'>Region Three";
html += "</select>";
html += "<input class='move-button' type='button' value='Move' />";
// Overwrite the views contents
this.View.html(html);
}
BindEvents(): void
{
this.$(".close-button").one("click", $.proxy(this.CloseView, this));
this.$(".move-button").on("click", $.proxy(this.MoveView, this));
}
UnbindEvents(): void
{
this.$(".close-button").off();
this.$(".move-button").off();
}
// Button event handlers
private CloseView(e: JQueryEventObject): boolean
{
this.Destroy();
e.preventDefault();
return false;
}
private MoveView(e: JQueryEventObject): boolean
{
this.Move("#" + this.$(".region-name").val());
e.preventDefault();
return false;
}
}
You must complete the abstract View class shown below. The constructor requires an identifier for a HTML element (div or span) that the view will be displayed within and also a set of options. The view can be rendered and then attached to the page, for this reason the view is rendered into its own container tag "this._el". By default, the view creates a <div> tag but the options let you specify something differently as the "el" option. Feel free to ignore the options completely.
There are a few properties but they are of limited interest for now, although the View property is used in the child class.
The $ method was described above.
Notice that the UnbindEvents() and BindEvents() methods are called in the Render() method to firstly unbind all DOM events before the view contents is destroyed and recreated, and the rebind them after new contents has been rendered.
The Attach(), Detach(), Move() and Destroy() methods need to be completed by you.
interface ViewOptions
{
// Optionally provide the container tag within
// which the views HTML will be displayed
// i.e. (rendered).
el?: any;
}
abstract class View implements IDestroyable
{
// Attributes
// HTML tags are stored as jQuery object
private _parentEl: JQuery = null; // Parent tag to which view is attached and rendered into.
private _el: JQuery = null; // Tag containing contents of view that is attached to parent.
// Constructor
constructor(parentEl: any, options?: ViewOptions) ← Example Usage
{
this.ParentEl = parentEl;
if (options && options.el)
{
// Tag to contain view contents has been provided
this.El = options.el;
}
else
{
// By default view contents are rendered into a <div> tag
this.El = $(document.createElement('div'));
}
this.Attach();
}
// Properties
private set ParentEl(el: any)
{
this._parentEl = this.GetJQueryObject(el);
}
private set El(el: any)
{
this._el = this.GetJQueryObject(el);
}
get Parent(): JQuery
{
return this._parentEl;
}
// If you want to use the view container tag
// e.g. to style it, then you will need to
// refer to it as this.View. If you want to
// play with the views HTML then use
// this.$("#some-html-id") instead.
protected get View(): JQuery ← Example Usage
{
// The container tag into which the view contents should be placed
return this._el;
}
// Methods
protected GetJQueryObject(el: any): JQuery
{
// If input is a JS HTML object or a text selection e.g. "#someId"
// then we let jQuery convert it to a jQuery object using $(tag).
// If was a jQuery object already we just use it.
return el instanceof $ ? el : $(el);
}
// In jQuery $ refers to the whole web page.
// If you want to search for elements within
// just the view itself, then use
// this.$("#some-html-id") instead.
protected $(selector: any): JQuery → Example Usage
{
return this._el.find(selector);
}
Attach(): void
{
if (this._parentEl.find(this._el).length == 0) // Make sure it isn't already attached
{
// Your code goes here
// Remember this._el contains the view content
}
}
Detach(): void
{
// Your code goes here Remove the view from the parent but you must be able reattach it later
// Remember this._el contains the view content
}
Move(newParentEl: any): JQuery
{
var oldParent = this._parentEl;
// Your code goes here
// Think about detaching and attaching view to new parent
return oldParent;
}
Render(): void
{
this.UnbindEvents();
this.RenderView(this._el);
this.BindEvents();
}
// Child class knows the events to
// bind and unbind after the view is
// rendered
protected abstract BindEvents(): void;
protected abstract UnbindEvents(): void;
Destroy(): void
{
// Don't need events anymore since the HTML
// elements are about to be lost.
this.UnbindEvents();
// Detach from parent and destroy all jQuery objects
// associated with view.
// Your code goes here
}
protected abstract RenderView(el: JQuery): void
}