Search code examples
javascriptbackbone.js

Avoid using document.getElementById with Backbone.js


I am making an application using Backbone.js, but I'm new to the Backbone.js. I need to change style property of HTML element. Is there way of using something from Backbone.js to change style of HTML element?

Inside index.html I put paragraph which is hidden:

<input type="text" id="name" placeholder="Enter name">
<p id="error_msg_name1" style="display: none;">
    Please, enter the name of the configuration.
</p>

And when someone tries to save empty "name" inside the model, this paragraph should be shown, which I handled this way:

var nameValue = document.getElementById("name").value;
if (nameValue == '') {
    document.getElementById("error_msg_name1").style.display = 'inline';
}

My mentor told me that I shouldn't use document.getElementById, that I should use something from Backbone.js. I googled this for days, but I haven't found anything helpful.

This is my model:

var Configuration = Backbone.Model.extend({

  defaults: function() {
      return {
          name: '',
          description: '',
          version: ''
      };
  }

});

And here is the View:

var ConfigurationView = Backbone.View.extend({

    events: {
        'click #ok': 'createConf',
        'click #cancel': 'cancelConf',
        'click #preview': 'showJSON',
        'click #readFile': 'readFile'
    },

    readFile: function() {
        var files = document.getElementById('fileInput').files;

        if (!files.length) {
            alert('Please select a file!');
            return;
        }

        var file = files[0];
        var reader = new FileReader();

        reader.onloadend = function(evt) {
            if (evt.target.readyState == FileReader.DONE) {
                var fileContent = evt.target.result;
                document.getElementById('previewFile').textContent = fileContent;
                importedConfiguration = new Configuration(JSON.parse(fileContent));

                $('<div class="col-sm-offset-2 col-sm-10">\
                        <button type="button" class="btn btn-default btn-sm" id="update">Update</button>').appendTo('#for_button');
            }
        };

        reader.readAsBinaryString(file);
    },

    remove: function() {
        $(this.el).empty().detach();
        return this;
    },

    cancelConf: function() {
        this.render();
    },

    createConf: function() {

        document.getElementById("error_msg_name1").style.display = 'none';
        document.getElementById("error_msg_name2").style.display = 'none';
        document.getElementById("error_msg_description").style.display = 'none';
        document.getElementById("error_msg_version").style.display = 'none';

        var nameRegex = /^[A-Za-z]+$/;

        var nameValue = document.getElementById("name").value;
        var descriptionValue = document.getElementById("description").value;
        var versionValue = document.getElementById("version").value;

        if (descriptionValue != ''  && versionValue != '' && nameRegex.test(nameValue)) {
            configuration.set({name: nameValue, description: descriptionValue, version: versionValue});

            document.getElementById("for_preview_JSON").innerHTML = JSON.stringify(configuration);
            document.getElementById("for_preview_JSON").disabled = true;

            var data = "text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(configuration));
            $('<a href="data:' + data + '" download="data.json">download JSON</a>').appendTo('#for_download_link_JSON');

            document.getElementById("ok").disabled = true;
            document.getElementById("preview").disabled = true;
            document.getElementById("name").disabled = true;
            document.getElementById("description").disabled = true;
            document.getElementById("version").disabled = true;
        }
        else {
            if (nameValue == '') {
                document.getElementById("error_msg_name1").style.display = 'inline';
            }else if(!nameRegex.test(nameValue)) {
                document.getElementById("error_msg_name2").style.display = 'inline';
            }

            if (descriptionValue == '') {
                document.getElementById("error_msg_description").style.display = 'inline';
            }
            if (versionValue == '') {
                document.getElementById("error_msg_version").style.display = 'inline';
            }
        }
    },

    showJSON: function() {
        var preview_conf = new Configuration({
            name: document.getElementById("name").value, 
            description: document.getElementById("description").value, 
            version: document.getElementById("version").value
        });
        document.getElementById("for_preview_JSON").innerHTML = JSON.stringify(preview_conf);
        document.getElementById("for_preview_JSON").disabled = true;
    },

    initialize: function(options){
        this.template = options.template;
    },

    render: function(){
        var content = $(this.template).html();
        $(this.el).html(content);
        return this;
    }
});

Solution

  • Before any source code change I guess you need to understand how Bakcbone.View works and how you need to access/query the DOM in Backbone way.

    Every time you are initializing Backbone.View like in your example and mentioning template, you can access your template with this.$el, which is jQuery element and references your template.

    For your case you are doing a lot of unnecessary querying to the DOM. Instead of that just use this.$el to find/modify you template's HTML.

    Example:

    var MyView = Backbone.View.extend({
        template: _.template($('#your-template-id').html()),
        events: {
            "click .something": "doOtherThing"
        }, 
        doOtherThing: function (e) {
            // this.$el.find() same as this.$() within view.
            $myEl = this.$('.innerElement'); // cache element when you need to do a lot of manipulations   
            $myEl.addClass('changed'); // 
            $myEl.toggleClass('opened') // and so on...
        }
        render: function () {
            var template = this.template(this.model.toJSON);
            this.$el.append(template);
        } 
    });
    
    var myView = new MyView();
    myView.render();
    

    A few rules for you to remember:

    1. Don't query global

    If you need to add/remove/modify HTML element within view always use this.$el to access view.

    2. Cache commonly used elements

     $myEl = this.$el.find('.innerElement'); 
     $myEl.addClass('changed');  
     $myEl.toggleClass('opened');
    ....
    

    3. Within Bakcbone.View this.$el.find() is same as this.$()