Javascript - replace() code help

I’m currently taking this Javascript course where I’m building a budget app.

Here is the HTML code:

<!DOCTYPE html>

<html lang="en">

<head>

  <meta charset="UTF-8" />

  <link href="https://fonts.googleapis.com/css?family=Open+Sans:100,300,400,600" rel="stylesheet" type="text/css" />

  <link href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css" rel="stylesheet" type="text/css" />

  <link type="text/css" rel="stylesheet" href="style.css" />

  <title>Budgety</title>

</head>

<body>

  <div class="top">

    <div class="budget">

      <div class="budget__title">

        Available Budget in <span class="budget__title--month">%Month%</span>:

      </div>

      <div class="budget__value">+ 2,345.64</div>

      <div class="budget__income clearfix">

        <div class="budget__income--text">Income</div>

        <div class="right">

          <div class="budget__income--value">+ 4,300.00</div>

          <div class="budget__income--percentage">&nbsp;</div>

        </div>

      </div>

      <div class="budget__expenses clearfix">

        <div class="budget__expenses--text">Expenses</div>

        <div class="right clearfix">

          <div class="budget__expenses--value">- 1,954.36</div>

          <div class="budget__expenses--percentage">45%</div>

        </div>

      </div>

    </div>

  </div>

  <div class="bottom">

    <div class="add">

      <div class="add__container">

        <select class="add__type">

          <option value="inc" selected>+</option>

          <option value="exp">-</option>

        </select>

        <input type="text" class="add__description" placeholder="Add description" />

        <input type="number" class="add__value" placeholder="Value" />

        <button class="add__btn">

          <i class="ion-ios-checkmark-outline"></i>

        </button>

      </div>

    </div>

    <div class="container clearfix">

      <div class="income">

        <h2 class="income__title">Income</h2>

        <div class="income__list">

          <!--

                        <div class="item clearfix" id="income-0">

                            <div class="item__description">Salary</div>

                            <div class="right clearfix">

                                <div class="item__value">+ 2,100.00</div>

                                <div class="item__delete">

                                    <button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button>

                                </div>

                            </div>

                        </div>

                        

                        <div class="item clearfix" id="income-1">

                            <div class="item__description">Sold car</div>

                            <div class="right clearfix">

                                <div class="item__value">+ 1,500.00</div>

                                <div class="item__delete">

                                    <button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button>

                                </div>

                            </div>

                        </div>

                        -->

        </div>

      </div>

      <div class="expenses">

        <h2 class="expenses__title">Expenses</h2>

        <div class="expenses__list">

          <!--

                        <div class="item clearfix" id="expense-0">

                            <div class="item__description">Apartment rent</div>

                            <div class="right clearfix">

                                <div class="item__value">- 900.00</div>

                                <div class="item__percentage">21%</div>

                                <div class="item__delete">

                                    <button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button>

                                </div>

                            </div>

                        </div>

                        <div class="item clearfix" id="expense-1">

                            <div class="item__description">Grocery shopping</div>

                            <div class="right clearfix">

                                <div class="item__value">- 435.28</div>

                                <div class="item__percentage">10%</div>

                                <div class="item__delete">

                                    <button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button>

                                </div>

                            </div>

                        </div>

                        -->

        </div>

      </div>

    </div>

  </div>

  <script src="app.js"></script>

</body>

</html>

Here is the CSS code:

/**********************************************
*** GENERAL
**********************************************/

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

.clearfix::after {
    content: "";
    display: table;
    clear: both;
}

body {
    color: #555;
    font-family: Open Sans;
    font-size: 16px;
    position: relative;
    height: 100vh;
    font-weight: 400;
}

.right { float: right; }
.red { color: #FF5049 !important; }
.red-focus:focus { border: 1px solid #FF5049 !important; }

/**********************************************
*** TOP PART
**********************************************/

.top {
    height: 40vh;
    background-image: linear-gradient(rgba(0, 0, 0, 0.35), rgba(0, 0, 0, 0.35)), url(back.png);
    background-size: cover;
    background-position: center;
    position: relative;
}

.budget {
    position: absolute;
    width: 350px;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    color: #fff;
}

.budget__title {
    font-size: 18px;
    text-align: center;
    margin-bottom: 10px;
    font-weight: 300;
}

.budget__value {
    font-weight: 300;
    font-size: 46px;
    text-align: center;
    margin-bottom: 25px;
    letter-spacing: 2px;
}

.budget__income,
.budget__expenses {
    padding: 12px;
    text-transform: uppercase;
}

.budget__income {
    margin-bottom: 10px;
    background-color: #28B9B5;
}

.budget__expenses {
    background-color: #FF5049;
}

.budget__income--text,
.budget__expenses--text {
    float: left;
    font-size: 13px;
    color: #444;
    margin-top: 2px;
}

.budget__income--value,
.budget__expenses--value {
    letter-spacing: 1px;
    float: left;
}

.budget__income--percentage,
.budget__expenses--percentage {
    float: left;
    width: 34px;
    font-size: 11px;
    padding: 3px 0;
    margin-left: 10px;
}

.budget__expenses--percentage {
    background-color: rgba(255, 255, 255, 0.2);
    text-align: center;
    border-radius: 3px;
}


/**********************************************
*** BOTTOM PART
**********************************************/

/***** FORM *****/
.add {
    padding: 14px;
    border-bottom: 1px solid #e7e7e7;
    background-color: #f7f7f7;
}

.add__container {
    margin: 0 auto;
    text-align: center;
}

.add__type {
    width: 55px;
    border: 1px solid #e7e7e7;
    height: 44px;
    font-size: 18px;
    color: inherit;
    background-color: #fff;
    margin-right: 10px;
    font-weight: 300;
    transition: border 0.3s;
}

.add__description,
.add__value {
    border: 1px solid #e7e7e7;
    background-color: #fff;
    color: inherit;
    font-family: inherit;
    font-size: 14px;
    padding: 12px 15px;
    margin-right: 10px;
    border-radius: 5px;
    transition: border 0.3s;
}

.add__description { width: 400px;}
.add__value { width: 100px;}

.add__btn {
    font-size: 35px;
    background: none;
    border: none;
    color: #28B9B5;
    cursor: pointer;
    display: inline-block;
    vertical-align: middle;
    line-height: 1.1;
    margin-left: 10px;
}

.add__btn:active { transform: translateY(2px); }

.add__type:focus,
.add__description:focus,
.add__value:focus {
    outline: none;
    border: 1px solid #28B9B5;
}

.add__btn:focus { outline: none; }

/***** LISTS *****/
.container {
    width: 1000px;
    margin: 60px auto;
}

.income {
    float: left;
    width: 475px;
    margin-right: 50px;
}

.expenses {
    float: left;
    width: 475px;
}

h2 {
    text-transform: uppercase;
    font-size: 18px;
    font-weight: 400;
    margin-bottom: 15px;
}

.icome__title { color: #28B9B5; }
.expenses__title { color: #FF5049; }

.item {
    padding: 13px;
    border-bottom: 1px solid #e7e7e7;
}

.item:first-child { border-top: 1px solid #e7e7e7; }
.item:nth-child(even) { background-color: #f7f7f7; }

.item__description {
    float: left;
}

.item__value {
    float: left;
    transition: transform 0.3s;
}

.item__percentage {
    float: left;
    margin-left: 20px;
    transition: transform 0.3s;
    font-size: 11px;
    background-color: #FFDAD9;
    padding: 3px;
    border-radius: 3px;
    width: 32px;
    text-align: center;
}

.income .item__value,
.income .item__delete--btn {
    color: #28B9B5;
}

.expenses .item__value,
.expenses .item__percentage,
.expenses .item__delete--btn {
    color: #FF5049;
}


.item__delete {
    float: left;
}

.item__delete--btn {
    font-size: 22px;
    background: none;
    border: none;
    cursor: pointer;
    display: inline-block;
    vertical-align: middle;
    line-height: 1;
    display: none;
}

.item__delete--btn:focus { outline: none; }
.item__delete--btn:active { transform: translateY(2px); }

.item:hover .item__delete--btn { display: block; }
.item:hover .item__value { transform: translateX(-20px); }
.item:hover .item__percentage { transform: translateX(-20px); }


.unpaid {
    background-color: #FFDAD9 !important;
    cursor: pointer;
    color: #FF5049;

}

.unpaid .item__percentage { box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.1); }
.unpaid:hover .item__description { font-weight: 900; }

Here is the app.js code

// add event handler -> Controller module
// get input values -> UI module
// add the new item to our internal data structures -> Data module
// add the new item to the UI -> UI module
// calculate budget -> Data module
// update the UI -> UI module


// budget controller
var budgetController = (function () {

    var Expense = function(id, description, value) {
        this.id = id,
        this.description = description,
        this.value = value
    };

    var Income = function(id, description, value) {
        this.id = id,
        this.description = description,
        this.value = value
    };

    var data = {
        allItems: {
            exp: [],
            inc: []
        },
        totals: {
            exp: 0,
            inc: 0
        }
    };

    return {
        addItem: function(type, des, val) {
            var newItem, ID;

            // ID: we need to select the last element of the array
            // ID = last ID + 1
            // create new ID
            if (data.allItems[type].length > 0) {
                ID = data.allItems[type][data.allItems[type].length -1].id + 1;
            } else {
                ID = 0;
            }
            
            // create new item based on 'inc' or 'exp' type
            if (type === 'exp') {
                newItem = new Expense(ID, des, val);
            } else if (type === 'inc') {
                newItem = new Income(ID, des, val);
            }

            // push it to our data structure
            data.allItems[type].push(newItem);

            // return the new element
            return newItem;
        },
        testing: function() {
            console.log(data);
        }
    };

})();

// UI controller
var UIController = (function() {
    var DOMstrings = {
        inputType: '.add__type',
        inputDescription: '.add__description',
        inputValue: '.add__value',
        inputBtn: '.add__btn',
        incomeContainer: '.income__list',
        expensesContainer: '.expenses__list'
    }
    return {
        getInput: function() {
            return {
                type: document.querySelector(DOMstrings.inputType).value, // will be inc or exp;
                description: document.querySelector(DOMstrings.inputDescription).value,
                value: document.querySelector(DOMstrings.inputValue).value
            }
        },

        addListItem: function(obj, type) {

            var html, newHtml, element;

            // create HTML string with placeholder text
            if(type === 'inc') {
                element = DOMstrings.incomeContainer;
                html = '<div class="item clearfix" id="income-%id%"><div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
            } else if (type === 'exp') {
                element = DOMstrings.expensesContainer;
                html = '<div class="item clearfix" id="expense-%id%"><div class="item__description">%description%</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__percentage">21%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';
            }

            // replace the placeholder text with some actual data
            newHtml = html.replace('%id%', obj.id);
            newHtml = html.replace('%description%', obj.description);
            newHtml = html.replace('%value%', obj.value);

            // Insert the HTML into the DOM
            document.querySelector(element).insertAdjacentHTML('beforeend', newHtml);
        },
        getDOMstrings: function() {
            return DOMstrings;
        }
    };
})();

// global app controller
var controller = (function(budgetCtrl, UICtrl) {

    var setupEventListeners = function() {
        var DOM = UICtrl.getDOMstrings();

        document.querySelector(DOM.inputBtn).addEventListener('click', ctrlAddItem);

        document.addEventListener('keypress', function (event) {
            if (event.keyCode === 13 || event.which === 13) {
                ctrlAddItem();
            }
        });
    };

    var ctrlAddItem = function() {

        var input, newItem;

        // 1. Get the field input data from UI controller
        input = UICtrl.getInput();

        // 2. Add the item to the budget controller from UI controller
        newItem = budgetCtrl.addItem(input.type, input.description, input.value);

        // 3. Add the item to the UI
        UICtrl.addListItem(newItem, input.type);

        // 4. Calculate the budget

        // 5. Display the budget on the UI

        console.log("Testing... it works!")
    };
    
    return {
        init: function() {
            console.log('Application has started');
            setupEventListeners();
        }
    };
})(budgetController, UIController);

controller.init();


The problem is, I cannot understand why I see %description% showing up under the expense and income list.

Whatever I’m typing in the description box, that should description show up under the income or expenses UI. But I see %description%. If I hard code Salary in the HTML code in the js file:

html = '<div class="item clearfix" id="income-%id%"><div class="item__description">Salary</div><div class="right clearfix"><div class="item__value">%value%</div><div class="item__delete"><button class="item__delete--btn"><i class="ion-ios-close-outline"></i></button></div></div></div>';

Then I see Salary, but when I do the replace() of %description%, I do not see the desired description displayed on the UI.

These three lines are giving you problems. In the first line, you use your original html string, and replace the tag as you want. The second line, you RETURN to that original html string (not the replaced one, but the original…), and replace the description tag.

The third time, you also go back to the original html string. You simply overwrite your changes each time. Instead, do

let newHtml = html.replace('%id%', obj.id);
newHtml = newHtml.replace('%description', obj.description);
newHtml = newHtml.replace('%value', obj.value);

Or, for the sake of making things clean, take a look at template literals: https://devdocs.io/javascript/template_literals

1 Like

I was just about to comment this jajaja So @snowmonkey, do you think using template literales is a better approach? can you tell us why? thanks in advance.

I do think template literals would be the better way here, as the OP is creating a string, then replacing literals in that string with variable data. Basically, re-creating the template literal with a lot more effort.

I don’t think the template literal is as robust as, say, EJS or JSX, nor is it intended to be. You can’t run a myArray.map() inside a template literal, or a forEach, but you can take a straightforward text block, and insert variables into that. No longer with

console.log("User is "+user.name+" and "+user.name+"'s age is "+user.age+".");

With the template literal, you’re telling the JS engine what you want. How it parses it and makes it happen is not the point. I no longer have to say “concatenate all these strings” or “hey, substitute in for this arbitrary placeholder” - I simply write out my template literal, and done.

Not advocating for template literals,
and I 100% agree with @snowmonkey when states:

I don’t think the template literal is as robust as, say, EJS or JSX

But you can still do some pretty good stuff with it and embed functions inside.

let a = [{name: 'foo'}, {name: 'bar'}, {name: 'baz'}];

 `Hello, we are ${a.map(x => x.name).join(' ')}, and we are pleased to meet you`

// 'Hello, we are foo bar baz, and we are pleased to meet you'
1 Like
let newHtml = html.replace('%id%', obj.id);
newHtml = newHtml.replace('%description', obj.description);
newHtml = newHtml.replace('%value', obj.value);

That did not resolve though. This is what gave me the desired result.

// replace the placeholder text with some actual data
newHtml = html.replace('%id%', obj.id);
newHtml = newHtml.replace('%description%', obj.description);
newHtml = newHtml.replace('%value%', obj.value);

I was wrongly using html

newHtml = html.replace('%description%', obj.description);
newHtml = html.replace('%value%', obj.value);

Replaced html with newHtml.

newHtml = newHtml.replace('%description%', obj.description);
newHtml = newHtml.replace('%value%', obj.value);

Regarding template strings, yes, it looks so much better and easier. Found an article on this: https://wesbos.com/template-strings-html/

Thank you