How To Build A Web App With Javascript
- Download MinimalApp.zip - 9.2 KB
Introduction
This article has been extracted from the book Building Front-End Web Apps with Plain JavaScript, which is available as an open access online book. It shows how to build a front-end app with minimal effort, not using any (third-party) framework or library. While libraries and frameworks may help to increase productivity, they also create black-box dependencies and overhead, and they prevent you from learning how to do it yourself.
If you first want to see how it works and how it looks like, you can run the minimal app discussed in this article from our server.
A front-end web app can be provided by any web server, but it is executed on the user's computer device (smartphone, tablet or notebook), and not on the remote web server, as illustrated by the following architecture diagram.
Typically, but not necessarily, a front-end web app is a single-user application, which is not shared with other users.
The minimal version of a JavaScript front-end data management app discussed in this tutorial only includes a minimum of the overall functionality required for a complete app. It takes care of only one object type (Book
) and supports the four standard data management operations (Create/Read/Update/Delete), but it needs to be enhanced by styling the user interface with CSS rules, and by adding further important parts of the app's overall functionality:
-
Part 2: Taking care of responsive (HTML5) constraint validation
-
Part 3: Dealing with enumerations
-
Part 4: Managing unidirectional associations assigning authors and publishers to books
-
Part 5: Managing bidirectional associations also assigning books to authors and to publishers
-
Part 6: Handling subtype (inheritance) relationships in a class hierarchy
Background
This section provides a brief discussion of HTML and some elements of JavaScript, assuming that the reader is already familiar with basic programming concepts and has some experience with programming, for instance, in PHP, Java or C#.
HTML
We adopt the symbolic equation
HTML = HTML5 = XHTML5
stating that when we just say "HTML", or "HTML5", we actually mean XHTML5, because we prefer the clear syntax of XML documents over the liberal and confusing HTML4-style syntax that is also allowed by HTML5.
JavaScript Objects
JavaScript objects are different from classical OO/UML objects. In particular, they need not instantiate a class . And they can have their own (instance-level) methods in the form of method slots, so they do not only have (ordinary) property slots , but also method slots . In addition, they may also have key-value slots . So, they may have three different kinds of slots, while classical objects (called "instance specifications" in UML) only have property slots.
JavaScript objects can be used in many different ways for different purposes. Here are five different use cases for, or possible meanings of, JavaScript objects:
-
A record is a set of property slots like, for instance,
var myRecord = { firstName:" Tom", lastName:" Smith", age:26}
-
A map (or 'hash map' or 'associative array') is a set of key-value slots. It supports look-ups of values based on keys like, for instance,
var numeral2number = { " one":1, " two":2, " three":3}
which associates the numeric value 1 with the key "one", 2 with "two", etc. A key need not be a valid JavaScript identifier, but can be any kind of string
-
An untyped object does not instantiate a class. It may have property slots and method slots like, for instance,
var person1 = { lastName: " Smith", firstName: " Tom", getInitials: function () { return this.firstName.charAt(0) + this.lastName.charAt(0); } };
-
A namespace may be defined in the form of an untyped object referenced by a global object variable, the name of which represents a namespace prefix. For instance, the following object variable provides the main namespace of an application based on the Model-View-Controller (MVC) architecture paradigm where we have three subnamespaces corresponding to the three parts of an MVC application:
var myApp = { model:{}, view:{}, ctrl:{} };
-
A typed object
o
that instantiates a class defined by a JavaScript constructor functionC
is created with the expression:var o = new C(...)
The type/class of such a typed object can be retrieved with the introspective expression:
o.constructor.name
Maps
A map is processed with the help of a special loop where we loop over all keys of the map using the pre-defined function Object.keys(a)
, which returns an array of all keys of a map a
. For instance,
var i=0, key=" "; for (i=0; i < Object.keys( numeral2number).length; i++) { key = Object.keys( numeral2number)[i]; alert(' The numeral '+ key +' denotes the number '+ numeral2number[key]); }
For adding a new element to a map, we simply create a new key-value entry as in:
numeral2number[" thirty two"] = 32;
For deleting an element from a map, we can use the pre-defined JavaScript delete
operator as in:
delete numeral2number[" thirty two"];
JavaScript Supports Three Types of Data Structures
The three types of data structures (or complex datatypes) are:
- records , which are special JS objects, as discussed above,
- maps , which are also special JS objects, as discussed above,
- array lists , which are special JS objects called 'arrays', but since they are dynamic, they are rather array lists (as defined by Java).
Defining and Instantiating a Class
A class can be defined in two steps. First, define the constructor function that defines the properties of the class and assigns them the values of the constructor's parameters:
function Person( first, last) { this.firstName = first; this.lastName = last; }
Next, define the instance-level methods of the class as function slots of the prototype object property of the constructor function:
Person.prototype.getInitials = function () { return this.firstName.charAt(0) + this.lastName.charAt(0); }
Finally, class-level ("static") methods can be defined as function slots of the constructor function, as in:
Person.checkName = function (n) { ... }
An instance of a class is created by applying the new
operator to the constructor function:
var pers1 = new Person(" Tom"," Smith");
The method getInitials
is invoked on the Person
object pers1
by using the 'dot notation':
alert(" The initials of the person are: " + pers1.getInitials());
Coding the App
The purpose of our example app is to manage information about books. That is, we deal with a single object type: Book
, as depicted in the following figure.
What do we need for such an information management application? There are four standard use cases, which have to be supported by the application:
-
Create: Enter the data of a book that is to be added to the collection of managed books.
-
Read: Show a list of all books in the collection of managed books.
-
Update the data of a book.
-
Delete a book record.
For entering data with the help of the keyboard and the screen of our computer, we can use HTML forms, which provide the user interface technology for web applications.
For maintaining a collection of data objects, we need a storage technology that allows to keep data objects in persistent records on a secondary storage device, such as a harddisk or a solid state disk. Modern web browsers provide two such technologies: the simpler one is called Local Storage, and the more powerful one is called IndexDB. For our minimal example app, we use Local Storage.
Step 1 - Set up the Folder Structure
In the first step, we set up our folder structure for the application. We pick a name for our app, such as "Public Library", and a corresponding (possibly abbreviated) name for the application folder, such as "publicLibrary". Then we create this folder on our computer's disk and a subfolder "src" for our JavaScript source code files. In this folder, we create the subfolders "model", "view" and "ctrl", following the Model-View-Controller paradigm for software application architectures. And finally we create an index.html file for the app's start page, as discussed below. Thus, we end up with the following folder structure:
publicLibrary src ctrl model view index.html
The start page of the app loads the Book.js model class file and provides a menu for choosing one of the CRUD data management operations performed by a corresponding page such as, for instance, createBook.html, or for creating test data with the help of the procedure Book.createTestData()
, or clearing all data with Book.clearData()
:
The minimal app's start page index.html
< !DOCTYPE html > < html xmlns =" http://www.w3.org/1999/xhtml" xml:lang =" en" lang =" en" > < head > < meta charset =" UTF-8" / > < title >Minimal JS front-end App Example< /title > < script src =" src/model/Book.js" > < / script > < /head > < body > < h1 >Public Library< /h1 > < h2 >An Example of a Minimal JavaScript front-end App< /h2 > < p >This app supports the following operations:< /p > < menu > < li > < a href =" listBooks.html" > < button type =" button" >List all books< /button > < /a > < /li > < li > < a href =" createBook.html" > < button type =" button" >Add a new book< /button > < /a > < /li > < li > < a href =" updateBook.html" > < button type =" button" >Update a book< /button > < /a > < /li > < li > < a href =" deleteBook.html" > < button type =" button" >Delete a book< /button > < /a > < /li > < li > < button type =" button" onclick =" Book.clearData()" >Clear database< /button > < /li > < li > < button type =" button" onclick =" Book.createTestData()" >Create test data< /button > < /li > < /menu > < /body > < /html >
Step 2 - Write the Model Code
In the second step, we write the code of our model class in a specific JavaScript file. In the information design model shown in above, there is only one class, representing the object type Book
. So, in the folder src/model, we create a file Book.js that initially contains the following code:
function Book( slots) { this.isbn = slots.isbn; this.title = slots.title; this.year = slots.year; };
The model class Book
is encoded as a JavaScript constructor function with a single slots
parameter, which is supposed to be a record object with properties isbn
, title
and year
, representing values for the ISBN, the title and the year attributes of the class Book
. Therefore, in the constructor function, the values of the slots
properties are assigned to the corresponding attributes whenever a new object is created as an instance of this class.
In addition to defining the model class in the form of a constructor function, we also define the following items in the Book.js file:
-
A class-level property
Book.instances
representing the collection of allBook
instances managed by the application in the form of a map. -
A class-level method
Book.loadAll
for loading all managedBook
instances from the persistent data store. -
A class-level method
Book.saveAll
for saving all managedBook
instances to the persistent data store. -
A class-level method
Book.add
for creating and storing a newBook
record. -
A class-level method
Book.update
for updating an existingBook
record. -
A class-level method
Book.destroy
for deleting aBook
instance. -
A class-level method
Book.createTestData
for creating a few example book records to be used as test data. -
A class-level method
Book.clearData
for clearing the book datastore.
1. Representing the collection of all Book instances
For representing the collection of all Book
instances managed by the application, we define and initialize the class-level property Book.instances
in the following way:
Book.instances = {};
So, initially our collection of books is empty. In fact, it's defined as an empty object, since we want to represent it in the form of a map (a set of key-value slots, also called 'hashmap') where an ISBN is a key for accessing the corresponding book object (as the value associated with the key). We can visualize the structure of such a map in the form of a lookup table, as shown in Table 1.
Table 1: A map representing a collection of books
Key | Value |
---|---|
006251587X | { isbn:"006251587X," title:"Weaving the Web", year:2000 } |
0465026567 | { isbn:"0465026567," title:"Gödel, Escher, Bach", year:1999 } |
0465030793 | { isbn:"0465030793," title:"I Am A Strange Loop", year:2008 } |
Notice that the values of this map are simple record objects corresponding to table rows. Consequently, we could represent them also in a simple table, as shown in Table 2.
Table 2: A collection of book objects represented as a table
ISBN | Title | Year |
---|---|---|
006251587X | Weaving the Web | 2000 |
0465026567 | Gödel, Escher, Bach | 1999 |
0465030793 | I Am A Strange Loop | 2008 |
2. Loading all Book instances
For persistent data storage, we use Local Storage, which is a HTML5 JavaScript API supported by modern web browsers. Loading the book records from Local Storage involves three steps:
-
Retrieving the book table that has been stored as a large
string
with the key "bookTable
" from Local Storage with the help of the assignment:bookTableString = localStorage[" bookTable"];
This retrieval is performed in line 5 of the program listing below.
-
Converting the book table string into a corresponding map
bookTable
with book rows as elements, with the help of the built-in functionJSON.parse
:bookTable = JSON.parse( bookTableString);
This conversion, performed in line 11 of the program listing below, is called deserialization.
-
Converting each row of
bookTable
(representing an untyped record object) into a corresponding object of typeBook
stored as an element of the mapBook.instances
, with the help of the procedureconvertRow2Obj
defined as a "static
" (class-level) method in theBook
class:Book.convertRow2Obj = function (bookRow) { var book = new Book( bookRow); return book; };
Here is the full code of the procedure:
Book.loadAll = function () { var i=0, key=" ", keys=[], bookTableString=" ", bookTable={}; try { if (localStorage[" bookTable"]) { bookTableString = localStorage[" bookTable"]; } } catch (e) { alert(" Error when reading from Local Storage\n" + e); } if (bookTableString) { bookTable = JSON.parse( bookTableString); keys = Object.keys( bookTable); console.log( keys.length +" books loaded."); for (i=0; i < keys.length; i++) { key = keys[i]; Book.instances[key] = Book.convertRow2Obj( bookTable[key]); } } };
Notice that since an input operation like localStorage["bookTable"]
may fail, we perform it in a try
-catch
block, where we can follow up with an error message whenever the input operation fails.
3. Saving all Book instances
Saving all book objects from the Book.instances
collection in main memory to Local Storage in secondary memory involves two steps:
-
Converting the map
Book.instances
into astring
with the help of the predefined JavaScript functionJSON.stringify
:bookTableString = JSON.stringify( Book.instances);
This conversion is called serialization.
-
Writing the resulting
string
as the value of the key "bookTable
" to Local Storage:localStorage[" bookTable"] = bookTableString;
These two steps are performed in line 5 and in line 6 of the following program listing:
Book.saveAll = function () { var bookTableString=" ", error=false, nmrOfBooks = Object.keys( Book.instances).length; try { bookTableString = JSON.stringify( Book.instances); localStorage[" bookTable"] = bookTableString; } catch (e) { alert(" Error when writing to Local Storage\n" + e); error = true; } if (!error) console.log( nmrOfBooks + " books saved."); };
4. Creating a new Book instance
The Book.add
procedure takes care of creating a new Book
instance and adding it to the Book.instances
collection:
Book.add = function (slots) { var book = new Book( slots); Book.instances[slots.isbn] = book; console.log(" Book " + slots.isbn + " created!"); };
5. Updating an existing Book instance
For updating an existing Book
instance, we first retrieve it from Book.instances
, and then re-assign those attributes the value of which has changed:
Book.update = function (slots) { var book = Book.instances[slots.isbn]; var year = parseInt( slots.year); if (book.title !== slots.title) { book.title = slots.title;} if (book.year !== year) { book.year = year;} console.log(" Book " + slots.isbn + " modified!"); };
Notice that in the case of a numeric attribute (such as year
), we have to make sure that the value of the corresponding input parameter (y
), which is typically obtained from user input via an HTML form, is converted from String
to Number
with one of the two type conversion functions parseInt
and parseFloat
.
6. Deleting an existing Book instance
A Book
instance is deleted from the Book.instances
collection by first testing if the map has an element with the given key (line 2), and then applying the JavaScript built-in delete
operator:, which deletes a slot from an object, or, in our case, an element from a map:
Book.destroy = function (isbn) { if (Book.instances[isbn]) { console.log(" Book " + isbn + " deleted"); delete Book.instances[isbn]; } else { console.log(" There is no book with ISBN " + isbn + " in the database!"); } };
7. Creating test data
For being able to test our code, we may create some test data and save it in our Local Storage database. We can use the following procedure for this:
Book.createTestData = function () { Book.instances[" 006251587X"] = new Book({isbn:" 006251587X", title:" Weaving the Web", year:2000}); Book.instances[" 0465026567"] = new Book({isbn:" 0465026567", title:" Gödel, Escher, Bach", year:1999}); Book.instances[" 0465030793"] = new Book({isbn:" 0465030793", title:" I Am A Strange Loop", year:2008}); Book.saveAll(); };
8. Clearing all data
The following procedure clears all data from Local Storage:
Book.clearData = function () { if (confirm(" Do you really want to delete all book data?")) { localStorage[" bookTable"] = " {}"; } };
Step 3 - Initialize the Application
We initialize the application by defining its namespace and MVC subnamespaces. Namespaces are an important concept in software engineering and many programming languages, including Java and PHP, provide specific support for namespaces, which help grouping related pieces of code and avoiding name conflicts. Since there is no specific support for namespaces in JavaScript, we use special objects for this purpose (we may call them "namespace objects"). First, we define a root namespace (object) for our app, and then we define three subnamespaces, one for each of the three parts of the application code: model, view and controller. In the case of our example app, we may use the following code for this:
var pl = { model:{}, view:{}, ctrl:{} };
Here, the main namespace is defined to be pl
, standing for "Public Library", with the three subnamespaces model
, view
and ctrl
being initially empty objects. We put this code in a separate file initialize.js in the ctrl folder, because such a namespace definition belongs to the controller part of the application code.
Step 4 - Implement the List Objects Use Case
This use case corresponds to the "Read" from the four basic data management use cases Create-Read-Update-Delete (CRUD).
The user interface for this use case is provided by the following HTML page containing an HTML table for displaying the book objects. For our example app, this page would be called listBooks.html (in the main folder publicLibrary) and would contain the following HTML code:
< !DOCTYPE html > < html xmlns =" http://www.w3.org/1999/xhtml" xml:lang =" en" lang =" en" > < head > < meta charset =" UTF-8" / > < title >Minimal JS front-end App Example< /title > < script src =" src/ctrl/initialize.js" > < / script > < script src =" src/model/Book.js" > < / script > < script src =" src/view/listBooks.js" > < / script > < script > window.addEventListener( " load", pl.view.listBooks.setupUserInterface); < / script > < /head > < body > < h1 >Public Library: List all books< /h1 > < table id =" books" > < thead > < tr > < th >ISBN< /th > < th >Title< /th > < th >Year< /th > < /tr > < /thead > < tbody > < /tbody > < /table > < nav > < a href =" index.html" >Back to main menu< /a > < /nav > < /body > < /html >
Notice that this HTML file loads three JavaScript files: the controller file src/ctrl/initialize.js, the model file src/model/Book.js and the view file src/view/listBooks.js. The first two files contain the code for initializing the app and for the model class Book
as explained above, and the third one, which represents the UI code of the "list books" operation, is developed now. In fact, for this operation, we just need a procedure for setting up the data management context and the UI, called setupUserInterface
:
pl.view.listBooks = { setupUserInterface: function () { var tableBodyEl = document.querySelector(" table#books>tbody"); var i=0, keys=[], key=" ", row={}; Book.loadAll(); keys = Object.keys( Book.instances); for (i=0; i < keys.length; i++) { key = keys[i]; row = tableBodyEl.insertRow(); row.insertCell(-1).textContent = Book.instances[key].isbn; row.insertCell(-1).textContent = Book.instances[key].title; row.insertCell(-1).textContent = Book.instances[key].year; } } };
The simple logic of this procedure consists of two steps:
-
Read the collection of all objects from the persistent data store (in line 6).
-
Display each object as a row in a HTML table on the screen (in the loop starting in line 9).
More specifically, the procedure setupUserInterface
first creates the book objects from the corresponding rows retrieved from Local Storage by invoking Book.loadAll()
and then creates the view table in a loop over all key-value slots of the map Book.instances
where each value represents a book
object. In each step of this loop, a new row is created in the table body element with the help of the JavaScript DOM operation insertRow()
, and then three cells are created in this row with the help of the DOM operation insertCell()
: the first one for the isbn
property value of the book object, and the second and third ones for its title
and year
property values. Both insertRow
and insertCell
have to be invoked with the argument -1
for making sure that new elements are appended to the list of rows and cells.
Step 5 - Implement the Create Object Use Case
For a data management operation with user input, such as the "create object" operation, an HTML page with an HTML form is required as a user interface. The form has a form field for each attribute of the Book
class. For our example app, this page would be called createBook.html (in the app folder publicLibrary) and would contain the following HTML code:
< !DOCTYPE html > < html xmlns =" http://www.w3.org/1999/xhtml" xml:lang =" en" lang =" en" > < head > < meta charset =" UTF-8" / > < title >Minimal JS front-end App Example< /title > < script src =" src/ctrl/initialize.js" > < / script > < script src =" src/model/Book.js" > < / script > < script src =" src/view/createBook.js" > < / script > < script > window.addEventListener(" load", pl.view.createBook.setupUserInterface); < / script > < /head > < body > < h1 >Public Library: Create a new book record< /h1 > < form id =" Book" > < p > < label >ISBN: < input name =" isbn" / > < /label > < /p > < p > < label >Title: < input name =" title" / > < /label > < /p > < p > < label >Year: < input name =" year" / > < /label > < /p > < p > < button type =" button" name =" commit" >Save< /button > < /p > < /form > < nav > < a href =" index.html" >Back to main menu< /a > < /nav > < /body > < /html >
The view code file src/view/createBook.js contains two procedures:
-
setupUserInterface
takes care of retrieving the collection of all objects from the persistent data store and setting up an event handler (handleSaveButtonClickEvent
) on the save button for handling click button events by saving the user input data; -
handleSaveButtonClickEvent
reads the user input data from the form fields and then saves this data by calling theBook.saveRow
procedure.
pl.view.createBook = { setupUserInterface: function () { var saveButton = document.forms[' Book'].commit; Book.loadAll(); saveButton.addEventListener(" click", pl.view.createBook.handleSaveButtonClickEvent); window.addEventListener(" beforeunload", function () { Book.saveAll(); }); }, handleSaveButtonClickEvent: function () { var formEl = document.forms[' Book']; var slots = { isbn: formEl.isbn.value, title: formEl.title.value, year: formEl.year.value}; Book.add( slots); formEl.reset(); } };
Step 6 - Implement the Upate Object Use Case
Again, we have a user interface page (updateBook.html
) and a view code file (src/view/updateBook.js). The HTML form for the UI of the "update object" operation has a selection field for choosing the book to be updated, and a form field for each attribute of the Book
class. However, the form field for the standard identifier attribute (ISBN) is read-only because we do not allow changing the standard identifier of an existing object.
< !DOCTYPE html > < html xmlns =" http://www.w3.org/1999/xhtml" xml:lang =" en" lang =" en" > < head > < meta charset =" UTF-8" / > < title >Minimal JS front-end App Example< /title > < script src =" src/ctrl/initialize.js" > < / script > < script src =" src/model/Book.js" > < / script > < script src =" src/view/updateBook.js" > < / script > < script > window.addEventListener(" load", pl.view.updateBook.setupUserInterface); < / script > < /head > < body > < h1 >Public Library: Update a book record< /h1 > < form id =" Book" > < p > < label >Select book: < select name =" selectBook" > < option value =" " > --- < /option > < /select > < /label > < /p > < p > < label >ISBN: < input name =" isbn" readonly =" readonly" / > < /label > < /p > < p > < label >Title: < input name =" title" / > < /label > < /p > < p > < label >Year: < input name =" year" / > < /label > < /p > < p > < button type =" button" name =" commit" >Save Changes< /button > < /p > < /form > < nav > < a href =" index.html" >Back to main menu< /a > < /nav > < /body > < /html >
The setupUserInterface
procedure now has to set up a selection field by retrieveing the collection of all book objects from the persistent data store for populating the select
element's option list:
pl.view.updateBook = { setupUserInterface: function () { var formEl = document.forms[' Book'], saveButton = formEl.commit, selectBookEl = formEl.selectBook; var i=0, key=" ", keys=[], book=null, optionEl=null; Book.loadAll(); keys = Object.keys( Book.instances); for (i=0; i < keys.length; i++) { key = keys[i]; book = Book.instances[key]; optionEl = document.createElement(" option"); optionEl.text = book.title; optionEl.value = book.isbn; selectBookEl.add( optionEl, null); } selectBookEl.addEventListener(" change", function () { var book=null, key = selectBookEl.value; if (key) { book = Book.instances[key]; formEl.isbn.value = book.isbn; formEl.title.value = book.title; formEl.year.value = book.year; } else { formEl.isbn.value = " "; formEl.title.value = " "; formEl.year.value = " "; } }); saveButton.addEventListener(" click", pl.view.updateBook.handleUpdateButtonClickEvent); window.addEventListener(" beforeunload", function () { Book.saveAll(); }); }, handleUpdateButtonClickEvent: function () { var formEl = document.forms[' Book']; var slots = { isbn: formEl.isbn.value, title: formEl.title.value, year: formEl.year.value }; Book.update( slots); formEl.reset(); } };
Step 7 - Implement the Delete Object Use Case
For the "delete object" use case, the UI form just has a selection field for choosing the book to be deleted:
< !DOCTYPE html > < html xmlns =" http://www.w3.org/1999/xhtml" xml:lang =" en" lang =" en" > < head > < meta charset =" UTF-8" / > < title >Minimal JS front-end App Example< /title > < script src =" src/ctrl/initialize.js" > < / script > < script src =" src/model/Book.js" > < / script > < script src =" src/view/deleteBook.js" > < / script > < script > window.addEventListener(" load", pl.view.deleteBook.setupUserInterface); < / script > < /head > < body > < h1 >Public Library: Delete a book record< /h1 > < form id =" Book" > < p > < label >Select book: < select name =" selectBook" > < option value =" " > --- < /option > < /select > < /label > < /p > < p > < button type =" button" name =" commit" >Delete< /button > < /p > < /form > < nav > < a href =" index.html" >Back to main menu< /a > < /nav > < /body > < /html >
The view code in src/view/deleteBook.js consists of the following two procedures:
pl.view.deleteBook = { setupUserInterface: function () { var deleteButton = document.forms[' Book'].commit; var selectEl = document.forms[' Book'].selectBook; var i=0, key=" ", keys=[], book=null, optionEl=null; Book.loadAll(); keys = Object.keys( Book.instances); for (i=0; i < keys.length; i++) { key = keys[i]; book = Book.instances[key]; optionEl = document.createElement(" option"); optionEl.text = book.title; optionEl.value = book.isbn; selectEl.add( optionEl, null); } deleteButton.addEventListener(" click", pl.view.deleteBook.handleDeleteButtonClickEvent); window.addEventListener(" beforeunload", function () { Book.saveAll(); }); }, handleDeleteButtonClickEvent: function () { var selectEl = document.forms[' Book'].selectBook; var isbn = selectEl.value; if (isbn) { Book.destroy( isbn); selectEl.remove( selectEl.selectedIndex); } } };
Run the App and Get the Code
You can run the minimal app from our server, and find more resources about web engineering, including open access books, on web-engineering.info.
Possible Variations and Extensions
Using IndexDB instead of LocalStorage
Instead of using the Local Storage API, the IndexDB API could be used for locally storing the application data. With Local Storage you only have one database (which you may have to share with other apps from the same domain) and there is no support for database tables (we have worked around this limitation in our approach). With IndexedDB you can set up a specific database for your app, and you can define database tables, called 'object stores', which may have indexes for accessing records with the help of an indexed attribute instead of the standard identifier attribute. Also, since IndexedDB supports larger databases, its access methods are asynchronous and can only be invoked in the context of a database transaction.
Alternatively, for remotely storing the application data with the help of a (REST) web API one can either use a backend solution component or a cloud storage service. The remote storage approach allows managing larger databases and supports multi-user apps.
Expressing Date/Time Information with the <time> Element
Assume that our Book
model class has an additional attribute publicationDate
, the values of which have to be included in HTML tables and forms. While date/time information items have to be formated as strings in a human-readable form on web pages, preferably in localized form based on the settings of the user's browser, it's not a good idea to store date/time values in this form. Rather we use instances of the pre-defined JavaScript class Date
for representing and storing date/time values. In this form, the pre-defined functions toISOString()
and toLocaleDateString()
can be used for turning Date
values into ISO standard date/time strings (of the form "2015-01-27") or to localized date/time strings (like "27.1.2015"). Notice that, for simplicity, we have omitted the time part of the date/time strings.
In summary, a date/time value is expressed in three different forms:
-
Internally, for storage and computations, as a
Date
value. -
Internally, for annotating localized date/time strings, or externally, for displaying a date/time value in a standard form, as an ISO standard date/time string, e.g., with the help of
toISOString()
. -
Externally, for displaying a date/time value in a localized form, as a localized date/time string, e.g., with the help of
toLocaleDateString()
.
When a date/time value is to be included in a web page, we can use the <time>
element that allows to display a human-readable representation (typically a localized date/time string) that is annotated with a standard (machine-readable) form of the date/time value.
We illustrate the use of the <time>
element with the following example of a web page that includes two <time>
elements: one for displaying a fixed date, and another (initially empty) element for displaying the date of today, which is computed with the help of a JavaScript function. In both cases we use the datetime
attribute for annotating the displayed human-readable date with the corresponding machine-readable representation.
< !DOCTYPE html > < html xmlns =" http://www.w3.org/1999/xhtml" xml:lang =" en" lang =" en" > < head > < meta charset =" UTF-8" / > < title >Using the HTML5 Time Element< /title > < script src =" assignDate.js" > < / script > < script > window.addEventListener(" load", assignDate);< / script > < /head > < body > < h1 >HTML5 Time Element< /h1 > < p >HTML 2.0 was published on < time datetime =" 1995-11-24" >November 24, 1995< /time >.< /p > < p >Today is < time id =" today" datetime =" " > < /time >.< /p > < /body > < /html >
This web page loads and executes the following JavaScript function for computing today's date as a Date
value and assigning its ISO standard representation and its localized representation to the <time>
element:
function assignDate() { var dateEl = document.getElementById(" today"); var today = new Date(); dateEl.textContent = today.toLocaleDateString(); dateEl.setAttribute(" datetime", today.toISOString()); }
Points of Attention
The code of this app should be extended by
- adding some CSS styling for the user interface pages and
- adding constraint validation .
We show how to do this in the follow-up tutorial JavaScript front-end Web Apps Tutorial Part 2: Adding Constraint Validation.
Another issue with the do-it-yourself code of this example app is the boilerplate code needed per class for the data storage management methods add
, update
, etc. While it is good to write this code a few times for learning app development, you don't want to write it again and again later when you work on real projects. In another article, Declarative and Responsive Constraint Validation with mODELcLASSjs, we present an approach how to put these methods in a generic form, such that they can be reused for all classes of an app.
Practice Projects
Feel freee to ask any questions about these projects in the CodeProject comments section below.
Project 1 - Develop a JavaScript Front-End App for Managing Movie Data
The purpose of the app is managing information about movies. Like in the book data management app discussed in the tutorial, you can make the simplifying assumption that all the data can be kept in main memory. So, on application start up, the movie data is read from a persistent data store. When the user quits the application, the data has to be saved to the persistent data store, which should be implemented with JavaScript's Local Storage API, as in the tutorial.
The app deals with just one object type: Movie
, as depicted in the Figure below. In the subsequent parts of the tutorial, you will extend this simple app by adding integrity constraints, actors and directors as further model classes, and the associations between them.
Notice that in most parts of this project you can follow, or even copy, the code of the book data management app, except that now there is an attribute with range Date, so you have to use the HTML <time>
element in the list objects table, as discussed in the Section "Possible Variations and Extensions" above.
For developing the app, simply follow the sequence of seven steps described in the tutorial:
-
Step 1 - Set up the Folder Structure
-
Step 2 - Write the Model Code
-
Step 3 - Initialize the Application
-
Step 4 - Implement the List Objects Use Case
-
Step 5 - Implement the Create Object Use Case
-
Step 6 - Implement the Upate Object Use Case
-
Step 7 - Implement the Delete Object Use Case
Project 2 - Managing Persistent Data with IndexedDB
Improve your app developed in project 1 by replacing the use of the Local Storage API for persistent data storage with using the more powerful IndexedDB API.
History
- 2 April 2014: First version created
- 3 April 2014: Corrected typos, added link, added new subsection "HTML" and new section "Points of Variation"
- 11 April 2014: Improved table formating, updated code, resolved mismatches between article and code
- 12 May 2014: Updated hyperlinks
- 6 October 2014: Updated hyperlinks, upload a diagram image, added final section "Points of Attention"
- 7 January 2015: Added the section "JavaScript Supports Three Types of Data Structures", corrected hyperlinks.
- 28 January 2015: Extended the section "Points of Variation" (and changed title to "Possible Variations and Extensions") by adding two subsections: 1) Using IndexDB instead of LocalStorage, and 2) Expressing Date/Time Information with the <time> Element. Improved teaser.
- 15 February 2015: Refactored the wrong declaration of counter variables in for loops, like in (var i=0;...)
- 17 June 2015: Added new Section "Practice Projects"
- 1 December 2015: Added front-end app architecture diagram
How To Build A Web App With Javascript
Source: https://www.codeproject.com/Articles/753724/JavaScript-Front-End-Web-App-Tutorial-Part
Posted by: yearbywartime.blogspot.com
0 Response to "How To Build A Web App With Javascript"
Post a Comment