JavaScript technologies

Step-by-step encapsulation example

Horváth, Győző
senior lecturer
horvath.gyozo@inf.elte.hu

Financed from the financial support ELTE won from the Higher Education Restructuring Fund of the Hungarian Government

Task

A collection of code snippets are given. Update the filtered list according to the filter text on every keypress!

ECMAScript 2015 application skeleton

<!DOCTYPE html>
<html>
<head>
    <!-- ... -->
    <!-- Stylesheets -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
    <link rel="stylesheet" href="css/app.css">
</head>
<body>
    <!-- ... -->
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.25/browser.min.js"></script>
    <script type="text/babel" src="js/app.js"></script>
    
</body>
</html>

The HTML

<div id="list-container">
    <div id="filter">
        <form class="form-inline">
            <div class="form-group">
                <label for="text-filter">Filter</label>
                <input type="text" class="form-control" id="text-filter">
            </div>
        </form>
    </div>
    <div id="list">
        <ul id="code-list" class="list-group">
            <li class="list-group-item">item1</li>
            <li class="list-group-item">item2</li>
        </ul>
    </div>
</div>

The naive JavaScript logic

// Helper function
function $(id) {
    return document.getElementById(id);
}

// Model
var snippets = [
    {
        title:  'foo',
        code:   'bar',
        type:   'js',
    },
    {
        title:  'foo2',
        code:   'bar2',
        type:   'js',
    },
];

function getTitles(snippets, filterText) {
    var out = [];
    for (var i = 0; i < snippets.length; i++) {
        if (snippets[i].title.indexOf(filterText) > -1) {
            out.push(snippets[i].title);
        }
    };
    return out;
}

// Helper function for output generation
function makeListItemsFromArray(arr) {
    var startLi = '<li class="list-group-item">',
        endLi   = '</li>';
    return startLi + arr.join(endLi + startLi) + endLi;
}

// Event handler and UI logic
$('text-filter').addEventListener('keyup', function (e) {
    var filterText = this.value;
    var filteredTitles = getTitles(snippets, filterText);
    $('code-list').innerHTML = makeListItemsFromArray(filteredTitles);
}, false);

Screenshot

Encapsulation

Model

Data, state

var snippets = [
    {
        title:  'foo',
        code:   'bar',
        type:   'js',
    },
    {
        title:  'foo2',
        code:   'bar2',
        type:   'js',
    },
];

Processing function

function getTitles(snippets, filterText) {
    var out = [];
    for (var i = 0; i < snippets.length; i++) {
        if (snippets[i].title.indexOf(filterText) > -1) {
            out.push(snippets[i].title);
        }
    };
    return out;
}

The CodeSnippet objects

Constructor function with module pattern

var CodeSnippet = (function () {
    function CodeSnippet(title, code, type) {
        this.title = title || 'title';
        this.code  = code  || 'code';
        this.type  = type  || 'js';
    }

    CodeSnippet.prototype.getCode = function() {
        return this.code;
    };

    CodeSnippet.prototype.setCode = function(c) {
        this.code = c;
    };

    return CodeSnippet;
}());

The CodeSnippet object (ES6 class)

class CodeSnippet {
    constructor(title, code, type) {
        this.title = title || 'title';
        this.code  = code  || 'code';
        this.type  = type  || 'js';
    }

    getCode() {
        return this.code;        
    }

    setCode(c) {
        this.code = c;
    }
}

Collections

class SnippetArray {
    constructor(arr = []) {
        this.snippets = arr;
    }
    
    getCodes() {
        return this.snippets.map(e => e.code);
    }
};

var snarr = new SnippetArray(snippets);
console.log(snarr.getTitles());

Gyűjteményosztály példányokkal

class SnippetArray {
    constructor(arr = []) {
        this.snippets = arr.map(e => new CodeSnippet(e.title, e.code, e.type));
    }
    
    getCodes() {
        return this.snippets.map(e => e.getCode());
    }

    getTitles(filterText = '') {
        return this.snippets
            .map(sn => sn.title)
            .filter(t => t.indexOf(filterText) > -1);
    }
}

var snarr = new SnippetArray(snippets);
console.log(snarr.getCodes());
console.log(snarr.snippets[0].getCode());

Modifying the UI logic

$('text-filter').addEventListener('keyup', function (e) {
    var filterText = this.value;
    var filteredTitles = snarr.getTitles(filterText);
    $('code-list').innerHTML = makeListItemsFromArray(filteredTitles);
}, false);

Encapsulating the UI logic

class ListView {
    constructor(props) {
        props = props || {};
        this.list = props.snippets;
        $('text-filter').addEventListener('keyup', this.onKeyUp.bind(this), false);
    }
    makeListItemsFromArray(arr) {
        /* ... */
    }
    onKeyUp(e) {
        var filterText = e.currentTarget.value;
        var filteredTitles = this.list.getTitles(filterText);
        $('code-list').innerHTML = this.makeListItemsFromArray(filteredTitles);
    };
}

Starting the application

new ListView({
    snippets: snarr
});