Wednesday 15 June 2016

Web Tutorial: The Scrooge Expense Tracker Mobile App (Part 4/4)

Welcome to the final installment of this web tutorial. In this, we'll be putting up the final screen of the Scrooge app.

This screen is not on featured any of the links in the footers of the previous screens. In fact, it's accessible only via the "entries" screen we set up in the previous part of this web tutorial. For this, we'll be copying the HTML template for the "settings" page, and renaming it "dailyentry".

        <div data-role="page" id="settings" data-theme="b">
            <div data-role="header" data-position="fixed">
            SETTINGS
            </div>

            <div data-role="main" class="ui-content">
                <div data-role="fieldcontainer">
                <label for="txtA">Category A: </label>
                <input id="txtA_name" name="txtA" placeholder="Enter title...">
            </div>
            <div data-role="fieldcontainer">
                <label for="txtB">Category B: </label>
                <input id="txtB_name" name="txtB" placeholder="Enter title...">
            </div>
            <div data-role="fieldcontainer">
                <label for="txtC">Category C: </label>
                <input id="txtC_name" name="txtC" placeholder="Enter title...">
            </div>
            <div data-role="fieldcontainer">
                <label for="txtD">Category D: </label>
                <input id="txtD_name" name="txtD" placeholder="Enter title...">
                </div>
            <div data-role="fieldcontainer">
                <label for="txtD">Monthly Budget: </label>
                <input id="txtBudget" name="txtD" placeholder="Enter amount..." onchange="fixinput(this,2);">
                </div>
            <div data-role="fieldcontainer">
                <label for="txtD">Interval (days): </label>
                <input id="txtInterval" name="txtD" placeholder="Enter days..." onchange="fixinput(this,0);">
                </div>
            <a href="#settings" data-role="button" data-icon="edit" onclick="update_settings();">Edit</a>
            </div>

            <div data-role="footer" data-position="fixed">
                <a href="#selectdate" data-icon="info">Select Month</a>
                <a href="#entries" data-icon="grid">Entries</a>
                <a href="#settings" data-icon="gear">Settings</a>
            </div>
        </div>

        <div data-role="page" id="dailyentry" data-theme="b">
            <div data-role="header" data-position="fixed">
               
            </div>

            <div data-role="main" class="ui-content">               

            </div>

            <div data-role="footer" data-position="fixed">
                <a href="#selectdate" data-icon="info">Select Month</a>
                <a href="#entries" data-icon="grid">Entries</a>
                <a href="#settings" data-icon="gear">Settings</a>
            </div>
        </div>


Now fill it in with the following code. You'll notice it's pretty similar to the code we did for the "settings" page, except that for categories A, B, C and D, we'll be entering in numbers instead of text. Hence each of these fields will trigger the fixinput() function when changed. When the edit button is clicked, the update_dailyentry() function is fired off. The pnlDay span element is used to display the current day the user is editing.
        <div data-role="page" id="dailyentry" data-theme="b">
            <div data-role="header" data-position="fixed">
                DAILY ENTRY for <span id="pnlDay"></span>
            </div>

            <div data-role="main" class="ui-content">               
                <div data-role="fieldcontainer">
                    <label for="txtA" id="lblA">Category A: </label>
                    <input id="txtA_amount" name="txtA" placeholder="Enter amount..." onchange="fixinput(this,2);">
                </div>
                <div data-role="fieldcontainer">
                    <label for="txtB" id="lblB">Category B: </label>
                    <input id="txtB_amount" name="txtB" placeholder="Enter amount..." onchange="fixinput(this,2);">
                </div>
                <div data-role="fieldcontainer">
                    <label for="txtC" id="lblC">Category C: </label>
                    <input id="txtC_amount" name="txtC" placeholder="Enter amount..." onchange="fixinput(this,2);">
                </div>
                <div data-role="fieldcontainer">
                    <label for="txtD" id="lblD">Category D: </label>
                    <input id="txtD_amount" name="txtD" placeholder="Enter amount..." onchange="fixinput(this,2);">
                </div>
                <div data-role="fieldcontainer">
                    <label for="txtD" id="lblD">Comments: </label>
                    <textarea id="txtComments" name="txtComments" placeholder="Enter comment..."></textarea>
                </div>
                <a href="#dailyentry" data-role="button" data-icon="edit" onclick="update_dailyentry();">Edit</a>
            </div>
           
            <div data-role="footer" data-position="fixed">
                <a href="#selectdate" data-icon="info">Select Month</a>
                <a href="#entries" data-icon="grid">Entries</a>
                <a href="#settings" data-icon="gear">Settings</a>
            </div>
        </div>


So let's see what the screen looks like. You will access this page from the table links in the "entries" page. Looks all right, except that data needs to be pre-filled in.


This is what we'll be doing next. Set up the code below. Firstly, the pnlDay span element is populated with the date of the day that data is being entered for. This is derived from the glb_currentyear, glb_currentmonth and glb_currentday global variables. The glb_currentday variabe was set in the goto_dailyentry() function prior to redirection to the "dailyentry" screen.
                $(document).on("pagebeforeshow", "#settings", function(event, data)
                {
                    $("#txtA_name").val(glb_settings[0].name);
                    $("#txtB_name").val(glb_settings[1].name);
                    $("#txtC_name").val(glb_settings[2].name);
                    $("#txtD_name").val(glb_settings[3].name);
                    $("#txtBudget").val(glb_settings[4].budget);
                    $("#txtInterval").val(glb_settings[5].interval);
                });

                $(document).on("pagebeforeshow", "#dailyentry", function(event, data) 
                {
                    var showdate=new Date(glb_currentyear,glb_currentmonth-1,glb_currentday,0,0,0,0);
                    $("#pnlDay").html(showdate.toDateString());
                });

                function fixinput(varobj,vardec)
                {
                    if (isNaN(varobj.value)||varobj.value.trim()=="")
                    {
                        varobj.value=(0).toFixed(vardec);
                    }
                    else
                    {
                        varobj.value=parseFloat(varobj.value).toFixed(vardec);
                    }
                }


OK, so take a look now. The day has been filled in nicely.


Next, we take the values of the glb_settings global array and apply them to the labels lblA, lblB, lblC and lblD. So instead of saying "Category A" and so on, the screen will display all the values you entered in the "settings" screen.
                $(document).on("pagebeforeshow", "#dailyentry", function(event, data)
                {
                    var showdate=new Date(glb_currentyear,glb_currentmonth-1,glb_currentday,0,0,0,0);
                    $("#pnlDay").html(showdate.toDateString());

                    $("#lblA").html(glb_settings[0].name);
                    $("#lblB").html(glb_settings[1].name);
                    $("#lblC").html(glb_settings[2].name);
                    $("#lblD").html(glb_settings[3].name);
                });


Awesome, right?


Next, we populate the textboxes using the values found in the archive for this particular date. Since you haven't entered anything, all of the values will be "0.00".
                $(document).on("pagebeforeshow", "#dailyentry", function(event, data)
                {
                    var showdate=new Date(glb_currentyear,glb_currentmonth-1,glb_currentday,0,0,0,0);
                    $("#pnlDay").html(showdate.toDateString());

                    $("#lblA").html(glb_settings[0].name);
                    $("#lblB").html(glb_settings[1].name);
                    $("#lblC").html(glb_settings[2].name);
                    $("#lblD").html(glb_settings[3].name);

                    dayindex=glb_currentday-1;

                    $("#txtA_amount").val(glb_archive[dayindex].A);
                    $("#txtB_amount").val(glb_archive[dayindex].B);
                    $("#txtC_amount").val(glb_archive[dayindex].C);
                    $("#txtD_amount").val(glb_archive[dayindex].D);
                    $("#txtComments").val(glb_archive[dayindex].comments);
                });


This is what you should see now.


Finally, it's time to create the update_dailyentry() function.
        function update_dailyentry()
        {

        }

        function update_settings()
        {
            glb_settings[0].name=$("#txtA_name").val();
            glb_settings[1].name=$("#txtB_name").val();
            glb_settings[2].name=$("#txtC_name").val();
            glb_settings[3].name=$("#txtD_name").val();
            glb_settings[4].budget=parseFloat($("#txtBudget").val()).toFixed(2);
            glb_settings[5].interval=$("#txtInterval").val();

            localStorage.setItem("scrooge_settings", JSON.stringify(glb_settings));

            alert ("Settings saved.");
        }


Not too much to explain here. We take the values of each of the textboxes, cast them into floating point values with two decimal places, and save them into the glb_archive array where we got the values from in the first place. The txtComments textbox too, of course. Then we turn the array into a JSON string using the stringify() method and overwrite the corresponding localStorage value using the setItem() method. And of course, a nice popup tells the user that the data is saved.
        function update_dailyentry()
        {
            dayindex=glb_currentday-1;

            glb_archive[dayindex].A=parseFloat($("#txtA_amount").val()).toFixed(2);
            glb_archive[dayindex].B=parseFloat($("#txtB_amount").val()).toFixed(2);
            glb_archive[dayindex].C=parseFloat($("#txtC_amount").val()).toFixed(2);
            glb_archive[dayindex].D=parseFloat($("#txtD_amount").val()).toFixed(2);
            glb_archive[dayindex].comments=$("#txtComments").val();

            localStorage.setItem("archive_" + glb_currentyear + "_" + glb_currentmonth, JSON.stringify(glb_archive));

            alert ("Daily entry saved.");
        }


Try it!


Check out the changes in your localStorage.



Now, go to your "entries" page. What do you see?


Now keep filling in values for the other days. Then go back to the "entries" page. Do your numbers add up? Now it's time to test the Delete button on your "selectmonth" screen. Click it. Does the archive in the localStorage disappear? Now go back to your "entries" page. Is everything back to "0.00"?

The Scrooge app is complete!

Congratulations, you have a web app, which you can now export into a hybrid mobile app by following these instructions.

Post mortem

This is my virgin attempt at explaining how to create a HTML5-based app for mobile. As such, I'd like to explain a few design decisions I made.

The use of global variables - normally this is frowned on and not really considered "good" programming. Well, it was either use global variables, or install a plug-in to pass variables from one screen to another. The latter seemed totally unnecessary because this app technically consists of one single file. Also, I wanted to keep things as simple as possible for a web tutorial, and including a plug-in seemed counter-intuitive to that.

Lack of structure and coherence - unlike earlier web tutorials where almost every question was addressed right away, there were controls and functions on many screens that had to be explained much later while we dealt with more pressing issues. This is because it's not a single program - it's an app with many moving parts integrated together. And as such, I had to choose what to explain first and what to explain later. It could be because I suck - if that's the case, I can only get better with practice.

Some of the screenshots, especially in the latter parts of this web tutorial, look different. That's because this web tutorial took a while to complete. In between the intervals, the web browser I use for testing and screenshots - Google Chrome - got updated.

This app will be useful. I'd put money on it!
T___T

No comments:

Post a Comment