Single Page Application from scratch. Part 2: View Layer

In the previous post we wrote simple event emitter that allows us to do simple subscribe/publish events logic. Also I did some overview of inheritance in JavaScript, and today by combining all this stuff together we will build UI that might be changed according to the user interaction.

Let’s start from the main part of our application, index page. It has changed a little:

<!DOCTYPE html>
<html>

<head>
 <title>Even Emitter Demo</title>
 <link rel="stylesheet" href="css/page.css">
</head>

<body>

 <!-- Templates -->
 <script type="text/template" id="headerTpl">
 <header id="app-header">
 <div>
 <section class="logged-user-info no-user">
 <span>
 No info yet
 </span>
 <span class="logout">logout</span>
 </section>
 </div>
 </header>
 </script>

 <script type="text/template" id="loginTpl">
 <div id="popup">
 <div class="popup-content">
 <form class="login-form">
 <h1>Please login</h1>
 <fieldset>
 <ul>
 <li><input type="email" name="login" placeholder="e-mail address" required /></li>
 <li><input type="password" name="password" placeholder="password" required /></li>
 <li><button type="submit"><span>login</span></button></li>
 <li><p class="register"><a href="#">register</a></p></li>
 </ul>
 </fieldset>
 </form>
 </div>
 </div>
 </script>

 <script type="text/template" id="registrationTpl">
 <div id="popup">
 <div class="popup-content">
 <form class="login-form">
 <h1>Use this form to register</h1>
 <fieldset>
 <ul>
 <li><input type="email" name="login" placeholder="e-mail address" required /></li>
 <li><input type="password" name="password" placeholder="password" required /></li>
 <li><button type="submit"><span>register</span></button></li>
 </ul>
 </fieldset>
 </form>
 </div>
 </div>
 </script>

 <script type="text/template" id="contentTpl">
 <div>
 <section class="content">
 <ul>
 <li>
 <p>And now you can do what you want</p>
 </li>
 </ul>
 </section>
 </div>
 </script>

 <!-- End Templates-->

 <script type="text/javascript">
 window.App = {};
 </script>

 <script type="text/javascript" src="js/Helpers.js"></script>
 <script type="text/javascript" src="js/EventEmitter.js"></script>
 <script type="text/javascript" src="Application.js"></script>
 <script type="text/javascript" src="js/view/BaseView.js"></script>
 <script type="text/javascript" src="js/view/HeaderView.js"></script>
 <script type="text/javascript" src="js/view/LoginView.js"></script>
 <script type="text/javascript" src="js/view/RegistrationView.js"></script>
 <script type="text/javascript" src="js/view/MainView.js"></script>

</body>
</html>

It became a bit longer and a bit more organized,  we have new section called templates. This section as you might guess contains some templates(parts) of our web page, we will load and show them when it will be needed. Other interesting thing here is window.App = {};  It creates us an empty object in global namespace. This object will be used to keep all our logic at one place. As you will see next all the functions will be placed in that namespace. It’s common practice for most js frameworks, because by default every variable we add goes to global scope and it might cause a lot of problems, as we are not notified about replacing existing functions etc.

Let’s check one by one what has been added and why

I have introduced variable in our App namespace called Helpers, and added function that will help us to inherit objects.

Helpers.js :

App.Helpers = {
	inherits: function (Child, Parent) {
		var F = function () {};
		F.prototype = Parent.prototype;
		Child.prototype  = new F();
		Child.prototype.constructor = Child;
	}
};

So as you can see it’s quite simple method that encapsulates prototypal inheritance logic and it will allow us to easily create derived objects. The only new thing in it that was not covered in my article about inheritance is line with constructor, but it basically says use constructor from derived class instead of base class.

Next change is separation of EventEmitter logic into separate file called EventEmitter.js:

App.EventEmitter = function () {
    this._events = {};
}

App.EventEmitter.prototype.onEvent = function (event, fn, ctx) {
  if (typeof this._events[event] === 'undefined'){
    this._events[event] = [];
  }
  this._events[event].push(
    {
      fn: fn,
      ctx: ctx || window
    }
  )
};

App.EventEmitter.prototype.emitEvent = function (event, data) {
  if (typeof this._events[event] !== 'undefined')
    {
      this._events[event].forEach(function (listener){
        listener.fn.call(listener.ctx, data);

      })
    }
};

It’s a bit changed, but nothing crucial, I have used nemaspace and replace old good for loop with something modern called forEach.

Also I have added small, by now, file Application.js that will be responsible for correct startup of our application:

window.App.Application = (function(){
  return new App.EventEmitter();
}());

This functions is self invoking, it will just create new object called Application and put EventEmitter in it. So every time we will need subscribe to or publish some event, all we need to do is just call the methods from that Application object. Last modification but not the least is calling of the onEvent and emitEvent: instead of onEvent(“send”, function(data){…}) we will use App.Application.onEvent(“send”, function(data){…}) .

Post already looks long but we haven’t even create base view for our application, lets  fix that :

App.BaseView = function () {
};

App.BaseView.prototype.getTemplate = function (templateId) {
	var templ = document.getElementById(templateId);
	if (!templ) {
		throw new Error('No template found! ' + templateId);
	}
	return templ.innerHTML;
};

App.BaseView.prototype.render = function (html, callback) {
	var el = document.createElement('div');
	el.innerHTML = html;
	return el.firstElementChild;
};

App.BaseView.prototype.hide = function (element) {
	element.classList.add('hidden');
};

App.BaseView.prototype.show = function (element) {
	element.classList.remove('hidden');
};

To base view we add functions for retrieving templates and rendering them. The trick here is that when we get element by id it’s not the object that might be added directly to the DOM, that is why when we are going to render something we need to create element first, put the content of the template in it and only after that by retrieving first child we are getting DOM ready object. Sounds a bit complicated but it’s exactly the way to inject custom elements onto page.

Also we have two methods to hide and show elements simply by adding/removing class attribute. and our css styles will do all we need.

Here is css itself:

html,
body {
	height: 100%;
	/*background-color : #565336;*/
	font: 12px Verdana, Tahoma, Arial, sans-serif;
	margin: 0;
	overflow: hidden;
}


.hidden {
	display: none;
}

As you might imagine our page will look really ugly, but it’s just matter of styles and we will fix it in the future.

The rest of the scripts are responsible for our page logic showing/hiding elements when it needed etc. Let’s take a look on one of them:

App.LoginView = function () {
	App.BaseView.call(this);

	this._el = this.render(this.getTemplate('loginTpl'));

	this._el.querySelector('.register a').addEventListener('click', this.onRegisterClick.bind(this));
	this._el.querySelector('form').addEventListener('submit', this.onSubmit.bind(this));

	document.body.appendChild(this._el);
};

App.Helpers.inherits(App.LoginView, App.BaseView);

App.LoginView.prototype.onRegisterClick = function () {
	this.hide(this._el);
	App.Application.emitEvent('registerclick');
};

App.LoginView.prototype.onSubmit = function (event) {
	var form, data;
	event.preventDefault();

	form = event.target;
	data = {
        login: form.elements['login'].value,
        password: form.elements['password'].value
	};

	this.hide(this._el);

	App.Application.emitEvent('login', data);
};

We are adding new object to our App namespace, this object will have only two functions:

  • onRegisterClick
  • onSubmit

This functions will be responsible for updating view according to the user interaction. There is no business logic yet and we are not handling data as it should be, but it will be handled in a future posts, by now we have some initial logic for our view, we can change them, read data from view and publish events with data as parameter so all subscribers can be notified about it and according to the logic use this data.

In the next chapter of this series we will add controllers layer, it will contain more logic.

I know that my series of writing SPA from scratch looks like:

BUT In this series of posts, about writing your own Single Page Application, I am not going too dip in explanation of JavaScript syntax etc. I assume, that you spend some time and learn some basics by your self. I want to leave this post short enough to not make you too bored till the end of the series. But you always might ask questions if something is not clear.

The rest of the coed can be found here https://github.com/BatsIhor/ibats.wordpress.com/tree/master/SPA%20-%20Post%202

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s