Search code examples
javascriptdesign-patternsarchitectureclient-sidemodular-design

Where to put data / logic layer in front end


I've read many articles which indicated that I should avoid putting complex logic into my HTML template, so my question is if I write a module which needs some specific attribute or class name based on the type of DATA loading to my page should I write this layer inside of my module as a method or its a wrong ideology ?

for example in this code Im loading DATA from a JSON file

{
    "slideShow": [
        {
            "img": "aaaaaaaa.jpg",
            "link": "aaaaaaaaa.html",
            "title": "aaaaaa",
            "date": "aaaaaaaa",
            "detail": "aaaaaaaaaaaaa"
        },
        {
            "img": "bbbbbbbbbbb.jpg",
            "link": "bbbbbbbbbbb.html",
            "title": "bbbbbbbbbbb",
            "date": "bbbbbbbbbbbbb",
            "detail": "bbbbbbbbbbbbb"
        },
        {
            "img": "ccccccccccc.jpg",
            "title": "ccccccc",
            "date": "ccccccccccccc"
        },
        {
            "img": "dddddddddd.jpg",
            "title": "ddddddddd",
            "date": "dddddddddd",
            "detail": "dddddddddd"
        }
    ]   
}

to this template

<section id="slideShow">
    <script id="slideShow-template" type="text/template">
        <ul>
            {{#slideShow}}
            <li class="{{{class}}}">
                <img src="{{{img}}}" alt="{{{title}}}">
                <a href="{{{link}}}">
                    <h1 class="slideShowTitle">{{title}}</h1>
                    <p class="slideShowDate">{{date}}</p>
                    <p class="slideShowDetail">{{detail}}</p>
                </a>
            </li>
            {{/slideShow}}
        </ul>
        <nav>
            {{#slideShow}}
                <a href="javascript:;"></a>
            {{/slideShow}}
        </nav>
        <a href="javscript:void(0)" class="prevSlide"></a>
        <a href="javscript:void(0)" class="nextSlide"></a>

    </script>
</section>

then I wrote this

(function() {

    var slideShow = {

        slideShow: [],

        importData: function() {
            var xhr = new XMLHttpRequest(),
                url = 'data/slideShow.json',
                _self = this,
                result;
            xhr.onreadystatechange = function() {
                if(this.readyState == 4 && this.status == 200) {
                    result = JSON.parse(this.responseText);
                    _self.slideShow = result.slideShow;
                    _self.dataProcess();
                    _self.init();
                }
            };
            xhr.open('GET', url, true);
            xhr.send();

        },

        dataProcess: function() {
            this.slideShow.forEach(function(element, index) {
                element.class = '';
                if(!element.detail) {
                    element.class += 'noDetail ';
                }
                if(!element.link) {
                    element.link = 'javascript:void';
                    element.class += 'noLink ';
                }
                element.class = element.class.replace('undefined', '');
            });
        },

        init: function() {
            this.render();
            this.cacheDom();
            this.bindEvents();
            this.autoRun();
        },

        bindEvents: function() {
            this.$el.addEventListener('click', this.actionEvent.controlDirection.bind(this));
            this.$nav.addEventListener('click', this.actionEvent.selectSlide.bind(this));
        },

        render: function(cacheDom) {
            var data = {
                slideShow: this.slideShow
            };
            this.$el = document.getElementById('slideShow');
            this.template = document.getElementById('slideShow-template').innerHTML;
            this.$el.innerHTML = Mustache.render(this.template, data);
        },

        cacheDom: function() {
            this.$ul = this.$el.querySelector('ul');
            this.$li = this.$ul.querySelectorAll('li');
            this.$nav = this.$el.querySelector('nav');
            this.$a = this.$nav.querySelectorAll('a');
            this.$next = this.$el.querySelector('.nextSlide');
            this.$prev = this.$el.querySelector('.prevSlide');
        },

        autoRun: function() {
            this.$ul.style.left = '0';
            this.$ul.style.width = this.slideShow.length * 100 + '%';
            this.$li.forEach(function(element) {
                element.style.width = (100 / this.slideShow.length) + '%';
            }.bind(this));
            this.$a[0].classList.add('activated');
            this.autoTimer();
        },

        calc(direction, index) {
            this.left = parseInt(this.$ul.style.left);
            if(direction) {
                this.newLeft = this.left + (direction * 100);
                this.index = this.newLeft / 100;
            } 
            else {
                this.index = index;
                this.newLeft = index * 100;
            }
            if(this.newLeft > ((this.slideShow.length - 1) * 100)) {
                this.index = 0;
                this.newLeft = 0;
            } else if(this.newLeft < 0) {
                this.index = (this.slideShow.length - 1);
                this.newLeft = ((this.slideShow.length - 1) * 100);
            }
        },

        autoTimer: function() {
            this.timer = setTimeout(function() {
                this.transitSlide(+1);
            }.bind(this), 3000);
        },

        transitSlide: function(direction, index) {
            this.$el.removeEventListener('click', this.actionEvent.controlDirection);
            this.calc(direction, index);
            this.$ul.classList.add('fade');
            setTimeout(function() {
                this.changeSlide();
                this.bindEvents();
            }.bind(this), 700);
        },

        changeSlide: function() {
            clearTimeout(this.timer);
            this.autoTimer();
            this.$a.forEach(function(element) {
                element.classList.remove('activated');
            });
            this.$a[this.index].classList.add('activated');
            this.$ul.style.left = this.newLeft + '%';
            this.$ul.classList.remove('fade');
        },

        eventHandling: function() {
            clearTimeout(this.timer);
            event.stopImmediatePropagation();
            event.preventDefault();
        },

        actionEvent: {

            controlDirection: function() {
                this.eventHandling();
                if(event.target == this.$next) {
                    this.transitSlide(+1);
                } else if(event.target == this.$prev) {
                    this.transitSlide(-1);
                }
            },

            selectSlide: function() {
                this.eventHandling();
                this.$a.forEach( function(element, index) {
                    if((event.target == element) && (!element.classList.contains('activated'))) {
                        this.transitSlide(false, index);
                    }
                }.bind(this));
            }

        }

    };

    slideShow.importData();

})();

Im asking about this.dataProcess() which checks the data object to adding classnames and attributes this is some sort of data which is not relevant to server side and database

shoud I keep it here or my structure is wrong ?


Solution

  • Generally when people talk about avoiding complex logic in the presentation, they are referring to business logic. Conditions in the data that change the flow of processing.

    In your case you are talking about presentation logic, not business logic, which absolutely should remain in the presentation layer where you have it.

    The sort of thing that you should avoid is if you have control logic, for example, if you were changing the title based on the date. That would be logic that would be better done inside a dedicated layer on the server side so that it can be reused/tested/enforced etc.

    The test I personally use is if logic would be useful if I rewrote the entire application for a different delivery channel (for example thick-client), would that be a useful piece of logic? Or would I have to change it dramatically? If it would remain largely intact (language differences notwithstanding), then it probably belongs server-side in a Logic/Service/Business layer. In your case, you are talking about modifying the CSS. It is specific to the presentation, and as such would likely be dramatically different if a different presentation layer was used.