4
\$\begingroup\$

Just started learning JavaScript and I have created a basic TODO list following MVC concepts to apply what I have learnt so far. This is probably overkill for a simple todo app, but the objective is to improve my JavaScript skills.

I'd highly appreciate it if you can spot flaws, especially bad practices and suggest improvements/alternative approaches and basically anything that can help me improve myself further.

Overview of the design

The view communicates user interactions to the controller via the observer pattern. The controller notifies the model. The model updates the data representation and notifies the controller of the change via the observer pattern. The controller then asks the view to re-render itself

event.js

Implements the observer pattern for a specific event. Allows listeners to be attached and notified via prototype functions

//Event Dispatcher. maintains a list of observers for a particular event.
//Used for communication between view-controller and model-controller
function Event(notifier){

    this.notifier = notifier;
    this.listeners = [];

}

//methods to attach and notify listeners
Event.prototype = {

    attach: function(listener){this.listeners.push(listener);
    },
    notify: function(val1,val2){
                for (var i=0; i<this.listeners.length; i++){
                    (this.listeners[i])(val1,val2);}
            }
}

item.js

Object representation of an item in a list. Contains additional information for functionality implementation

//object representation of an item
function TodoItem(item){

    this.item = item; //item name
    this.isActive = true; //state maintains active status of item
    this.visible = true; //controls visibility of item in the view

}

model.js

Maintains an array of items. Handles the data and the business logic. Notifies the controller of model modification through the observer pattern

//MODEL maintains data and handles business logic
function Model(){

  //maintains items added to the list
  this.list = [];

  //Item addition event. Contains observers (controller)
  this.listModifiedEvent = new Event(this);
}

Model.prototype = {

//add item to list and notify observers
addItem: function (item){

this.list.push(item);
this.listModifiedEvent.notify(this.list);

},

//remove item from list and notify observers
removeItem: function (id){
    this.list.splice(id,1);
    this.listModifiedEvent.notify(this.list);
},

//mark item specified by id as complete and  notify observers
completeItem: function (id){

    var item = this.list[id];

        item.isActive= !item.isActive;
        this.listModifiedEvent.notify(this.list);

},

//show list with all items, complete and active
showAll: function(){

    for (var i=0; i<this.list.length; i++){
        this.list[i].visible=true;
    }
    this.listModifiedEvent.notify(this.list);
},

//sets the visible property of items to true if active, false otherwise. notifies observers
showActive: function(){

  for (var i=0; i<this.list.length; i++){

        var item = this.list[i];
        if (item.isActive)item.visible=true;
        else item.visible=false;
    }
    this.listModifiedEvent.notify(this.list);
},

//sets the visiblity property of items to true if complete, false otherwise. notifies observers
showComplete: function(){

    for (var i=0; i<this.list.length; i++){

        var item = this.list[i];
        if (!item.isActive)item.visible=true;
        else item.visible=false;
    }
    this.listModifiedEvent.notify(this.list);
},

clear: function(){

    this.list.length = 0;
    this.listModifiedEvent.notify(this.list);
},

saveToLocal: function(){
    console.log("save: " + JSON.stringify(this.list));
    localStorage.setItem("todoItemsList", JSON.stringify(this.list));
},

remember: function(){

    var localStore = localStorage.getItem("todoItemsList");
    if (localStore){
        this.list = JSON.parse(localStore);

    }
}

}

view.js

Listeners attached to handle user interactions. Communicates with the controller via the observer pattern. Renders the view once notified by the controller.

//maintains the view. handles rendering
function View(){

    var that = this;//save this context for callbacks

    //user interaction events. Event object used to implement observer pattern
    this.listButtonEvent = new Event(this);

    //retrieve dom elements. private
    var addButton = document.getElementById("addButton");
    var listContainer = document.getElementById("mainList");
    var bodyElement = document.body;

    //list modification events. add/delete/complete/show lists
    //concept of event propogation used to catch button events at the body element
    bodyElement.addEventListener('click',function(event){
            var el = event.target; //element that caused event propogation
            var input = null;
            if (el.nodeName=="BUTTON"){
                //if the event is an add operation, pass user input as second arg
                if (el.name=="add")input=that.userInput();
                that.listButtonEvent.notify(el,input);
            }
        });

    //function to extract text input
    this.userInput = function(){
        return document.getElementById("input").value;
    }
}

//method to render the view. whole list is re-rendered everytime this method is called
View.prototype.render = function(list){

    var ul = (document.getElementsByTagName("ul"))[0];
    ul.innerHTML = "";

    for (var i=0; i<list.length; i++){

        //build li element and append to ul
        var listItem = list[i];

        //only build elements that are visible. controls active and complete task visibility
        if (listItem.visible){
            var value = listItem.item;
            var li = document.createElement("li");
            li.innerHTML = value + "<button name='remove' id=" + i + ">x</button>" + "<button name='complete' id=" + i + ">&#10003;</button>";

            //cross out completed items
            if (!listItem.isActive)
                li.style.setProperty("text-decoration", "line-through");
            ul.appendChild(li);
        }
    }
}

controller.js

Fascilitates communication between the view and the model. Contains references to both the view and the model. Subscribed as an observer to both. (Retrieves user interactions from the view and communicates them to the model. Notifies the view to render itself when the model changes)

//fascilitates communication between the view and the model, data processing
function Controller(model,view){

    //process list button events. delete/complete/show lists
    this.processListButtonEvent = 
    function(el,input){

        var buttonName = el.name;

        //if input is valid, create an item object and add it to the model
        if (buttonName=="add"){
            if(input){
                model.addItem(new TodoItem(input));
            }
        }
        //remove item from model
        else if (buttonName=="remove")model.removeItem(el.id);

        //mark items as complete
        else if (buttonName=="complete"){
            model.completeItem(el.id);}

        //generate all tasks list
        else if (buttonName=="list"){
            model.showAll();
        }

        //generate active tasks list
        else if (buttonName=="activeList"){
            model.showActive();
        }

        //generate complete tasks list
        else if (buttonName=="completeList"){
            model.showComplete();
        }

        //clear list
        else if (buttonName=="reset"){
            model.clear();
        }
    }


    //model modified. update the view
    this.listModified = 
    function(list){
        view.render(list);
    }

    //Controller added as observer to events in the view and the model
    //controller notified via observer pattern on view events and model modification
    view.listButtonEvent.attach(this.processListButtonEvent);
    model.listModifiedEvent.attach(this.listModified);
}

todomvc.js

Initializes the app. Local storage used to remember state

//initialize app
var model = new Model();
var view = new View();
var controller = new Controller(model,view);

//save/load item list from local storage
window.onload = loadOnStart;
window.onbeforeunload = function(){ 
  saveOnExit();
  return null;
}

//load item list from local storage and render page
function loadOnStart(){
  model.remember();
  model.showAll();
}

//save item list to local storage on exit. remember state
function saveOnExit(){
  model.saveToLocal();
}

index.html

<!DOCTYPE html>
<html>

<head>

    <style></style>

</head>

<body>

    <input id="input" type="text">
    <button id="addButton" name="add">add</button>

    <ul id="mainList">

    </ul>

    <button id="all" name="list" >all</button>
    <button id="activeButton" name="activeList" >active</button>
    <button id="completeButton" name="completeList" >complete</button>
    <button id="resetButton" name="reset" >reset</button>
</body>



<script src="event.js"></script>
<script src="item.js"></script>
<script src="model.js"></script>
<script src="view.js"></script>
<script src="controller.js"></script>
<script src="todomvc.js"></script>

</html>
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.