Create your own framework in less than 350 lines of code. Part #1

Sat Jan 26 2019 23:31:23 GMT+0100 (Hora estándar romance), David Ibáñez

Create Your Own Framework In Less Than 350 Lines Of Code

Do you know you can build your own framework, with no dependencies, compatible with all browser since IE8+ in less than 350 lines of code?

Using vanilla Javascript, no third party code, without compilers, transpilers, npm, webpack, ... Just your code! Compatible with any server technology.

Yes, Guilty! I'm not very framework friendly. I think frameworks don't fit for all teams and projects. In my case and my team, frameworks don't fit very well. I'll link soon the reasons you have to consider to use a framework before you chose to do it.

Currently, when we need to solve a problem we start looking for any framework/library that solves it. We don't want to reinvent the wheel. Ok, that's a good point, but a lot of frameworks don't have just wheels, they build entire trucks. There're a lot of times that we need only two or three pieces of these trucks to solve our problem.
We use to think that create these two/three parts will be a tedious work and a waste of time.

But, do you know how simple is to do most of what frameworks solve?

Let's start to create our own framework to see it.

create-3026190_1280.jpg

What we need to solve?

We'll create a framework with the following features.

  • Templates - In more complex apps, dealing with more HTML dynamic content, can be tedious to implement mixing in Javascript code, HTML strings and dynamic data.

  • Components - As we need more functionalities it's a good idea to break our code into smaller pieces.

  • Modules - We'll code the components in modules. This approach helps to isolate variables between different parts of the project, avoids naming collisions.

  • Event Driven - Cross functionalities between parts of the interface can be problematic and it's a good approach to isolate each part communicating to each other using events instead of directly invoking their methods. If you remove a component doesn't break the entire app.

Creating your framework will have a some benefits:

  • You will have a set of tools to simplify your repeating tasks in your projects. A cleaner way to organize your code.

  • You don't need to learn almost a new language using a new 3rd party framework.

  • Easy to develop and debug. The code is your own. When a bug appears you know exactly what your code does, if you can remember ;)

  • You can keep it up to date. You don't depend on third parties.

How I will explain?

We'll create the framework while working in a real app. You can find the code on this Github repo: Create-Your-framework

We'll create a simple photo and video browser using the real Pixabay API. Pixabay is a Over 1.6 million royalty free stock high quality photos and videos.

This is how the app looks like:

pixabay-browser.jpg

NOTE: Find the details of the project in the Readme file in the repo. I will not explain how everything works in the starting version of the code. If you find interesting, leave me a message in the comments and I will create a post about it.

Creating your own framework

We're going to start with an empty javascript file and implenting one functionality at a time. Every new functionality will be explained in an understandable way, but not in deep to make the tutorial lighter. I'll link a deeper explanation of every step if you need further details on what each code does.

If you get stuck in some point, please get in touch with me at Twitter, by email at kriyeng on a gmail account or leave a comment in this post. I'm open to do pair sessions if you want help to follow the tutorial.

NOTE: In order to keep the wide compatibility of the entire project (IE8+ and all other browsers) I'm not using not use ES6. For instance, this is why I use XMLHttpRequest instead of fetch for ajax requests.

Preparing the module to encapsulate our framework

We start preparing a module to fit our framework code. Create a new file on js folder called js/dynamic-template.js. We'll call our framework DynamicTemplate.

In order to encapsulate our code we create an anonymous self-invoked function. A self-invoked function is a function that calls itself. You write the function wrapped in parenthesis and add a () to call it immediately. Anonymous because you don't set a name for this function. It's something like this.


( function(){ /* Your code */ } )();

Thanks to the closures and variables scopes (where the variables are accesible) in javascript, all the code we write inside this function wouldn't be accessible from any other part of our app avoiding naming collisions.

NICE TO READ: You can find an explanation on this post about the advantatges of using modules and why/how you encapsulate your code using self-invoked anonymous function.

The starting code for our module looks like this:

( function(window){

    'use strict';
    var dynamicTemplate = {
        render : render
    };

    function render(){
        console.log('Hi! I will render the template');
    }

    window.dt = dynamicTemplate;

})(window);    

We added here what's called a global import. We pass (window) object when we self-invoke the function in order to pass the window object as a parameter to our closure. This is not necessary at all, but The reason to do this is to make clear what global object you can access in your closure.

We finally set the name dt for our module to be accessible in the global object window

Now we have to add our new module to index.html in order to be loaded. We add the script to load dynamic-template.js. You will find it at the end of the html file.

<script type="text/javascript" src="js/pixabay-api.js"></script>
<script type="text/javascript" src="js/dynamic-template.js"></script> <!-- We added this line -->
<script type="text/javascript" src="js/app.js"></script>    

Now, you can run your app and everything has to run as before. You can open your dev tools and check if there's any error on the console.

NICE TO READ: If you don't know exactly how to use dev tools on your browser, you can read Web Development Tutorial: Understanding how to use the Browser Developer Tools. I highly recommend that you get comfortable with dev tools. They will be your friend if you work on front-end applications.

Templating

After too much jargon, let's start to create something!

What does it mean Templating? Instead of using javascript to create HTML elements, adding classes manually and concatenating strings of HTML code and dynamic data, you can write your HTML markup and then apply dynamic data on it using your custom tags.

Going back to our project, Let's recap how it worked:

  1. The user enter a query on a search input
  2. We receive the query using an event
  3. We request the Pixabay API
  4. We receive the result and render the data to the DOM.

What's the DOM?: When a browser loads and render a web page, it creates what's called Document Object Model (DOM). The DOM is the javascript interface between you and the HTML rendered in your page. You can use it to get what's rendered and to manipulate this content. The DOM is represented as a node tree.

Let's focus on how we render the new HTML and why we should need a template system. You'll find this code in js/app.js at line 54.

function renderResults(pictures, type) {

    // Select the list of photos or the list of videos depending on the type received
    var list = document.querySelector('#' + type + '-list');

    // We reset the list
    list.innerHTML = '';

    // If we received results and there are items
    if(pictures && pictures.items) {

        // We iterate for the entire items
        pictures.items.forEach(function(item) {

            // Create a new DIV element 
            var newDiv = document.createElement("div");

            // We add two classes for styling to our new div
            newDiv.classList.add('item');
            newDiv.classList.add('panel');

            // We fill the HTML for the new div using a concatenating a string and dynamic data
            newDiv.innerHTML = '    <div class="image" style="background-image: url(\'' + item.preview + '\')"></div>' +
                '    <div class="details">' +
                '        <div class="user">' +
                '            <div class="thumb" style="background-image: url(\'' + item.user_img + '\')"></div>' +
                '            <div>' + item.user + '</div>' +
                '        </div>' +
                '        <div class="views"><span class="fa fa-eye"></span> ' + formatTotals(item.views) + '</div>' +
                '        <div class="likes"><span class="fa fa-heart"></span> ' + formatTotals(item.likes) + '</div>' +
                '    </div>';

            // We append the new div to the selected list
            list.append(newDiv);
        })
    }
}

Every item rendered looks like this:

pixabay-item.jpg

Ok, this works, but as you can see:

  • We are mixing Javascript code with HTML.
  • Concatenating long strings of HTML and dynamic data with the single and double quotes can be a little nightmare. It's easy to mess up things. If you need to add ternary conditionals <div class="details ' + (item.type === 'photo' ? 'photo-class' : 'video-class') + '">, the things get worst.

NICE TO READ: We used a conditional ternary operator to switch between photo-clas or video-class. If you need more details on this, I recommend to read The Conditional (Ternary) Operator Explained

Back to the code generated by the script above, let's see the result HTML.

<div class="item panel">
  <div class="image" style="background-image: url('https://cdn.pixabay.com/photo/2017/12/15/13/51/polynesia-3021072_150.jpg')">
  </div>    
  <div class="details">        
    <div class="user">            
      <div class="thumb" style="background-image: url('https://cdn.pixabay.com/user/2017/11/30/23-14-18-786_250x250.jpg')"></div>
        <div>Julius_Silver</div>
    </div>        
    <div class="views"><span class="fa fa-eye"></span> 480K</div>
    <div class="likes"><span class="fa fa-heart"></span> 898</div>    
  </div>
</div>

Wouldn't be great if you could write your HTML nearly as the code above? You can. Take a look at my proposal for templates. I used double curly braces for dynamic data, but you can use what you like.

<div class="item panel">
    <div class="image" style="background-image: url({{item.preview}})"></div>
    <div class="details">
        <div class="user">
            <div class="thumb" style="background-image: url({{item.user_img}})"></div>
            <div>{{item.user}}</div>
        </div>
        <div class="views"><span class="fa fa-eye"></span> {{item.views}}</div>
        <div class="likes"><span class="fa fa-heart"></span> {{item.likes}}</div>
    </div>
</div>

Another great thing is that you can put this template on your HTML, not mixed inside your javascript code.

How we could do that

We will encapsulate the template in a script tag inside our HTML.

 <script type="text/template" id="template-item">
     <!-- your template HTML Markup Here -->
 </script>

Let's try it in our project!

Back to the index.html file and let's start creating our first HTML template. Create a <script> tag to wrap our HTML markup. We set the script type as text/template. Finally set an unique identifier for the element as the code above

Now, get the HTML for each item we saw before. Replace dynamic data with the variables names, using double curly braces to wrap our dynamic variables {{dinamic.variable}}. For instance, we replace:

<div class="image" style="background-image: url('https://cdn.pixabay.com/photo/2017/12/15/13/51/polynesia-3021072_150.jpg')">

by

<div class="image" style="background-image: url('{{item.preview}}')">

If you do in every dynamic value you will end with the following HTML.

 <script type="text/template" id="template-item">
     <div class="item panel">
        <div class="image" style="background-image: url({{item.preview}})"></div>
        <div class="details">
            <div class="user">
                <div class="thumb" style="background-image: url({{item.user_img}})"></div>
                <div>{{item.user}}</div>
            </div>
            <div class="views"><span class="fa fa-eye"></span> {{item.views}}</div>
            <div class="likes"><span class="fa fa-heart"></span> {{item.likes}}</div>
         </div>
     </div>
 </script>

Let's explain what we are using.

  • Script element with type="text/template" - Wrapping the template in a script tag has some advantages.

    • Script tag has a wide compatibility with all browsers since IE6
    • The content is not rendered in the screen
    • The HTML markup inside the script tag is not accessible from the DOM. If you use document.getElementById, document.querySelector the DOM will not return the elements inside our script tag
    • We can retrieve the HTML inside the script using innnerHTML property. We set a unique id="template-item" to the template to query easily using the DOM
  • Curly braces: - We encapsulate our dynamic data inside curly braces. This allows us to easily find these variables using regular expressions and replace these contents with our data values.

Converting the template to useful HTML with the data

Let's go back to our module dynamic-template.js and let's code a method that gets our templates and replaces the curly braces by the data coming from an object.

We prepared a render method some lines before. Now it's time to start coding it. What argument do this method need?

  • A selector - A string containing the unique id to find our templates in the index.html
  • An Object - Containing the dynamic data we will use to replace variables in the template.
function render(template_selector, object){ /* render code here */ }

TRY FOR YOURSELF - Before going further I propose you to try for yourself first. This is what render method needs to do.

  1. You receive an unique identifier of the template as first argument. You need to find the template in your HTML. Hint - document.getElementById()
  2. Check if you found the template. If not return an empty string. Maybe you can throw an error to the console to show why is failing the render function. Hint - console.error or console.warn
  3. You have your template as HTMLElement. You can get the content as string. Hint - innerHTML property
  4. You have your template as string. Find the content inside curly braces. Hint - A regular expression is you friend. If you are not familiar with RegEx, I will put the expression after this list of steps.
  5. Iterate through the values found. No matter what you use to iterate. Use what you feel comfortable with.
  6. On every iteration find the value on the object received in the method.
  7. Replace every {{variable}} by it's value from the object.
  8. Finally return the new HTML string with the replaced values.

How we call the render function

In order to test your code, you need to call your render() method from your app.js file as follows:

Back to app.js file, you need your template unique id and the object containing your dynamic data. In our app, we set the id to template-item and the object will be an every item received by Pixabay API.
Because we populated our DynamicTemplate with the name dt, we call the render function as dt.render.

dt.render('template-item', { item : item });

dt.render returns us a string containing the template HTML replaced with the data values.

Using the original code we replace the renderResults function.

function renderResults(pictures, type) {

    // Select the list of photos or the list of videos depending on the type received
    var list = document.querySelector('#' + type + '-list');

    // We reset the list
    list.innerHTML = '';

    // If we received results and there are items
    if(pictures && pictures.items) {

        // We iterate for the entire items
        list.innerHTML = pictures.items.reduce(function(str_html, item) {

            // Appends the new item to the list calling our new dynamic template render function
            return str_html + dt.render('template-item', { item : item });
        }, '');
    }
}

The code solution

Before going into the code solution I leave you here the Regular Expression to find the {{curly braces}} definitions.

Regular Expression

The following Regular Expression will return every content wrapped inside curly braces

// Regex Explanation:
// (?<={{) Require opening curly braces before match, but not include in the result
// ([^]*?) Accept the minimum string length before the next condition below.
// (?=}}) - Require closing curly braces after match

// Returns matches or an empty array if there's no matches
var result = (str.match(/(?<={{)([^]*?)(?=}})/g) || []);

NICE TO READ - Regular Expressions usually frightens when not known. I've been my first 10 years googling regex every time I need one. One day, I spent 30 minutes doing The RegexOne interactive tutorial. I discovered how easy is to get into the basics and how powerful Regex are.

The code solution

I hope you tried for yourself. Congratulations! Depending on your skills this can be a very hard task!

This is my proposal for this code. Take a look at the comments to find the process in more detail. I split the render() method into little functions to keep the code cleaner.

The Render() function

Functionality

  • Find the template in the index.html file.
  • Check if not exists to return an error.
  • Call the function applyDynamicValues that will do the job of replacing dynamic content.
  • Returns the result.
// Gets the template, finds dynamic value queries, replaces with its value and return a string of the new HTML
function render(template_selector, object){

    // Similar as jQuery we distinct with a $ the variables that contains HTMLElements.
    // This can help to distinguish when you are dealing with strings or HTMLElements
    var $template = document.getElementById(template_selector);

    // Check if template exists
    if(!$template){
        console.error("Template doesn't exists! Check Your template Selector: " + template_selector);
        return '';
    }

    // We cal the function to extract and replace the dynamic data, and returns the result
    return applyDynamicValues($template.innerHTML, object);
}

Before showing you applyDynamicValues() function let's create a function to find our dynamic definitions {{item.picture}} in the HTML string. I called getDynamicVariables()

We will use another function to find the values in the object received in the render function. I called getValueFromObject()

The getDynamicVariables() function

Functionality

  • Receives the template HTML as string.
  • Applies a Regular Expression to the string to match the dynamic variables contained in the HTML string. See the code comments for more details.
  • Uses filter array method to remove duplicates.
  • Returns an array of the dynamic variables found.
// Extract the dynamic values queried in curly braces
function getDynamicVariables(str){

    // Regex Explanation:
    // (?<={{) Require opening curly braces before match, but not include in the result
    // ([^]*?) Accept the minimum string length before the next condition below.
    // (?=}}) - Require closing curly braces after match

    // Returns matches or an empty array if there's no matches
    var result = (str.match(/(?<={{)([^]*?)(?=}})/g) || []);

    // Filter the results to remove duplicates
    return result.filter(function(item, pos) {
        return result.indexOf(item) === pos;
    })
}

You could avoid the last filter to remove duplicates. You can find a variable multiple times in your template, but maybe not.

The getValueFromObject() function

Functionality

  • Receive the dynamic variable queried (the content wrapped in the curly braces) and the object containing the data (You received as a parameter in the render() method.
  • Split dot notation names to an array of properties. For instance:
    • item.preview -> [ 'item', 'preview']
  • Uses reduce array method to iterate through the array to concatenate the object properties and check if the object has that properties. Given item.user.name the reduce method will do the following on each iteration:
    1. Checks if the object has item property (Object.item) and returns the value to the next iteration.
    2. Checks if the object.item has the user property (Object.item.user) and returns the value to the next iteration.
    3. Checks if the object.item.user has the name property (Object.item.user.name) and returns the value.
  • Returns the value found

If you are not familiar with reduce Array method you'll find a further explanation some lines below. You'll find some resources linked as well

NOTE - We encapsulate the code into try catch to prevent unhandled errors caused by trying to access to properties of undefined objects. The template system will throw a warning in the console.

// Gets the value from the data
function getValueFromObject(str_property, object){
    // Clean white spaces
    str_property=str_property.trim();

    // set the default value for the object
    var value = null;

    // We protect the code to throw an error when trying to access a property of undefined
    try {
        // Iterates through the dot notation checking if the object has the property
        value = str_property.split('.').reduce(function(props, item){
            return props.hasOwnProperty(item) ? props[item] : null;
        }, object);
    } catch(e){
        // We catch errors when trying to access properties for undefined objects
        console.warn("Tried to read a property of undefined, str_property: " + str_property);
        if(typeof object === 'undefined'){
            console.warn("The Object is undefined. Check the Object passed to fill the data");
        }
        value = null;
    }

    return value;
}

Now, we have all necessary to go for the applyDynamicValues() called in the render function.

The applyDynamicValues() function

Functionality

  • Receives the HTML of the template as string and the object containing the data values.
  • Call the function getDynamicVariables() to get all the variables queried in the template as an array.
  • Using reduce array method to iterate over variables, call the function getValueFromObject to get the data value for these variables.
  • Replaces the original position in the template by the value received on the step before.
// We create a function to replace the curly braces with data values
function applyDynamicValues(str_html, object){
    // Iterates over all the dynamic queries found and replace by its values
    return getDynamicVariables(str_html).reduce(function(html, dyn_value){

        //Gets the value from the object data
        var value = getValueFromObject(dyn_value, object);

        // Creates a regex to replace the value for each element found
        var regexp = new RegExp("{{" + dyn_value + "}}", 'g');

        // Apply the replace to all items in the original HTML string
        return html.replace(regexp, value !== null ? value : '');
    }, str_html);
}

What we're really doing with reduce is:

  • In reduce method on every iteration you receive the object you have returned on the previous iteration. In the first iteration we are passing the str_html that contains the original HTML string.
  • We start with the original HTML string.
  • On every iteration of dynamic values, we return the same string but with the that value replaced. The next iteration will receive the string with the previous values already replaced.
  • At last iteration, reduce will return the final HTML string with all replaces done.

NICE TO READ - If you're not familiar with Array reduce method I highly recommend: Knowing well javascript Array methods will give you the advantage to create better code. Mastering these methods allows you to drive your development a more functional and declarative approach. I recommend the following articles:

Great! You have your own render method ready to use! If you implemented the code for your app.js described above, you can test your render() method.

undraw_winners_ao2o.png

Congratulations! You have created your own basic template system from scratch. You don't have to deal with HTML strings inside Javascript anymore. We can still improve our dynamic template. In the next articles I will explain how to add conditionals, computed functions and iterations to our templates.

If you feel it's a hard journey, you can think that you'll only do this one time. You will have your own template system. You just need to add dynamic-template.js to your projects, and that's it!

You can find the next chapter in Create Your Own Framework In Less Than 350 Lines Of Code. Part #2..

I'm looking forward for your comments below. If you find too much detailed or not enough detailed, please let me know!

I'm David. If you get stuck in some point, please get in touch with me at Twitter, by email at kriyeng on a gmail account or leave a comment in this post. I'm open to do pair sessions if you want help to follow the tutorial or anything you're stuck.


Tags

Follow us

Latest blogs
Creating a banking chat bot hosted at IBM Cloud connected to IBM Watson using No
Fri Mar 15 2019 15:24:18 GMT+0100 (Hora estándar romance)
Getting back to #100DaysOfCode
Wed Mar 06 2019 22:30:42 GMT+0100 (Hora estándar romance)
50 days of the 100DaysOfCode challenge
Sun Feb 17 2019 16:47:31 GMT+0100 (Hora estándar romance)
creating your own framework part #2
Tue Jan 29 2019 22:48:31 GMT+0100 (Hora estándar romance)
Create your own framework in less than 350 lines of code. Part #1
Sat Jan 26 2019 23:31:23 GMT+0100 (Hora estándar romance)

Latest comments
Thank you for your fantastic article ~ , help a lot !
icy
Thu Mar 21 2019 03:28:29 GMT+0100 (Hora estándar romance)