Tuesday, 28 June 2016

Spot The Bug: SQL weirdness

It's time for Spot The Bug again, so get your game face on.

I'm looking at you,
buster.

I was working on a very bare-bones login procedure. Quick, dirty, throw-away code. The front-end was in HTML, back-end in PHP and interfacing with a MySQL database. Upon clicking the Login button, the login.php script would fire off and query the database. The query returns the number of records that match the email address and password. If there is exactly one match, you're logged in. And if not, you have to try again.

Sounds simple enough. I must've done it a million times. But somehow, I just could not get a match. I always got the message "No such user found.". No syntax errors were in my PHP script.

But then I discovered something really strange with the query. This is the original code from login.php.
<?php
include "inc/config.php";

$login = $POST["txtLogin"];
$password = $POST["txtPassword"];

if (db_connect()->connect_errno)
{
    die("Database connection error.");
}
else
{
    $DBConn=db_connect();

    $strsql="SELECT COUNT(user_id) FROM tb_users WHERE user_login LIKE ? AND  user_password=MD5(?)";

    $sqlresult = $DBConn->prepare($strsql);

    if (!$sqlresult)
    {
        die("Database query error.");
    }
    else
    {
        $sqlresult->bind_param("ss",$login,$password);

        $sqlresult->execute();
        $sqlresult->bind_result($num);
        $sqlresult->fetch();

        if ($num==1)
        {
             //Logged in
        }
        else
        {
             die("No such user found.");
        }
    }

    $DBConn->close();
}
?>

When I changed it, like so, I got a match!
<?php
include "inc/config.php";

$login = $POST["txtLogin"];
$password = $POST["txtPassword"];

if (db_connect()->connect_errno)
{
    die("Database connection error.");
}
else
{
    $DBConn=db_connect();

    $strsql="SELECT COUNT(user_id) FROM tb_users WHERE user_login = ? AND  user_password=MD5(?)";

    $sqlresult = $DBConn->prepare($strsql);

    if (!$sqlresult)
    {
        die("Database query error.");
    }
    else
    {
        $sqlresult->bind_param("ss",$login,$password);

        $sqlresult->execute();
        $sqlresult->bind_result($num);
        $sqlresult->fetch();

        if ($num==1)
        {
              //Logged in
        }
        else
        {
              die("No such user found.");
        }
    }

    $DBConn->close();
}
?>

But it is not enough that things work; sometimes it is just as important to know why they are working. A SQL LIKE is supposed to be more flexible than the "=" operator. So why was the "=" operator matching while the LIKE wasn't?

What went wrong

The problem wasn't in my output. It was in my input. Apparently, while entering the email address in my HTML form, I had accidentally included a trailing space. Instead of "teochewthunder@gmail.com", the input was "teochewthunder@gmail.com ". And this had resulted in the SQL query returning a false positive. The Microsoft Knowledge Base provides a decent explanation. Apparently SQL Server suffers from the same quirk as MySQL. (https://support.microsoft.com/en-us/kb/316626).


How I fixed it

The problem arose because, unlike all the other times I had built this seemingly simple module, I had not written a utility to sanitize the input first. Which included eliminating leading and trailing spaces! So after I added this to my PHP script, it ran like a charm. It was a basic sanitization function using PHP's trim() function to remove leading and trailing spaces.
<?php
include "inc/config.php";

$login = sanitize($POST["txtLogin"]);
$password = sanitize($POST["txtPassword"]);

if (db_connect()->connect_errno)
{
    die("Database connection error.");
}
else
{
    $DBConn=db_connect();

    $strsql="SELECT COUNT(user_id) FROM tb_users WHERE user_login LIKE ? AND  user_password=MD5(?)";

    $sqlresult = $DBConn->prepare($strsql);

    if (!$sqlresult)
    {
        die("Database query error.");
    }
    else
    {
        $sqlresult->bind_param("ss",$login,$password);

        $sqlresult->execute();
        $sqlresult->bind_result($num);
        $sqlresult->fetch();

        if ($num==1)
        {
              //Logged in
        }
        else
        {
              die("No such user found.");
        }
    }

    $DBConn->close();
}

function sanitize($input)
{
    $temp=$input;
    $temp=trim($temp," ");

    return $temp;
}

?>


Moral of the story

Two things to take away from this.

1) Always, always sanitize user input. If I, the developer, can make such an elementary mistake on my own bloody form, who knows what the typical clueless end-user is capable of?

2) Beware of the SQL LIKE and "=" operator. They don't always function the way you'd expect.

That was, like, totally bogus, dudes.
T___T

Thursday, 23 June 2016

Time You're Paying For

It was early days in my career when I wound up working for a friend. He was a good buddy, and we'd had great times together. People warned me that working for a friend was not the best idea. It wasn't that I didn't listen - while I acknowledged the possible pitfalls, I had simply decided to take my chances, and let the dice fall where they may.

Working for him was a mixed bag - we still hung out, we had great synergy where generating ideas was concerned, and we genuinely enjoyed each other's company. There were moments of friction, of course, but nothing to worry about.

Until one time, on a Sunday. He had asked another buddy and I to help him fix some Ikea DIY furniture in the office, as a personal favor. But he had forgotten to bring the tools, so there was a bit of delay while we journeyed to his home to grab them.

Me: We're wasting time here...

Him: I'm wasting his time. Yours, I'm paying for. So it doesn't matter for you.

Notwithstanding that it was a Sunday, I let that one go. What was a bit of ribbing between friends?

Then it happened again.

There was an argument that resulted because I had been holding on to one available set of keys to the office when I went out for a quick late night bite while putting in some extra hours. He had been counting on the office being open when he arrived, and was annoyed that he had to drive down to where I was, to get those keys. Next day, he brought the issue up.

Me: You could have saved yourself a lot of time and trouble by bringing your own keys, you know.

Him: I'm paying for your time, and you're not paying for mine. Therefore, my time's more important than yours.

Again, he meant it as a cheeky remark, but it stung. And it was probably at this moment that I found myself not wanting to work for this guy anymore.

Paying for time.

Sure, we were friends. And as friends who happened to share an employer-employee relationship, there were things we said to each other on a daily basis that would be anathema to most other relationships of this ilk. But he had, perhaps unwittingly, crossed a line for me. I am a professional. People pay for my time, my service and my expertise. And if you do not respect my time, you do not respect me. And if you are paying for my time and fail to respect my time, on some level, you fail to respect yourself. It was a flippant and irresponsible remark to make.

Years later, I was working for someone else. I was on holiday in Malaysia, high up in the misty mountains of Genting. My companion (old flame, long story) had gone to try her luck in her casino and I was chilling (quite literally, brrr) at the Starbucks cafe taking advantage of free WiFi over a steaming mug of hazelnut coffee, when I decided to log on to MSN Messenger and update my boss over the status of some project.

Me: So the interface is done. The new features they're asking for are also done, but I haven't activated them yet. So if they want it right away, all you need to do is set that flag in the database.

Him: Yep. Aren't you supposed to be on holiday?

Me: Yes.

Him: Then I don't want to hear from you till you get back.

That contrast in attitude really hit home for me. This boss wasn't my friend. But he knew to respect the time of his employees. It was then I knew my choice was justified.

Employees have lives outside of the office. And any employer who fails to appreciate that, isn't worth doggy-doo.

Thanks for your time,
T___T


Monday, 20 June 2016

No Internet for Public Sector?

Come July 2017, all public service computers will be detached from the internet.

Much has been made of the hubbub surrounding Infocomm Development Authority of Singapore (IDA)'s controversial decision last week. Shots have been fired, and a new waves of memes circulated.

Courtesy of SGAG

I have refrained from commenting thus far because it is way too easy to get caught up in criticizing the Government (not that they don't deserve it) before one has all the facts. And because - bear with me here - this might not be as crazy as it sounds.

Public servants still get to use the Internet - through use of their personal handheld devices and specially designated workstations. The aim is to keep the Internet separate from the government's Intranet, which doubtlessly contains all manner of data no mere mortal should ever access.

The technique in question here is called "air-gapping", and it is not in the least new. Singapore's certainly not the first nation in the world to do this.

The public fallout was fairly predictable. There were cries of derision pointing to Prime Minister Lee Hsien Loong's speech about a Smart Nation last year.  There were concerns about what such a drastic gesture might mean for those in Public Service. And noticeable silence from pro-Government fanboys who probably quietly worried if their idols were going bonkers. Laymen were, for the most part, appalled.

Reactions of those in the IT Sector were somewhat mixed, with some lambasting the Government for "stupidity", others reacting with approval. But from those in tech security, I have seen little comment. Were they, like myself, reserving judgment? Or did they know something the rest of us don't?

This brought to mind an episode I recently had with a hacker buddy. I had set up a website and was boasting about the security features I had set in place, and the cheeky fellow had bet me fifty bucks that he could bypass them all to hack my database. I upped the ante to a hundred bucks, and it was on. Within a couple hours, my friend admitted defeat and asked me what database I was using.

"None," was my innocent response. "This is a static website."

The murderous look on his face is something I remember fondly.

But isn't that the essence of all zen-like chop-cocky martial arts flicks? Meet an incoming force, not with resistance, but with emptiness. The Empty City Strategem. And all that crap.

Feel that emptiness yet?

Long story short - if you know it's a game you can't win, make sure you don't ever need to play that game. Which is why aging footballers leave the Premier League and go to MLS - physically, they can't compete on stamina and strength with their younger counterparts any more. Which is why I never negotiate during a job interview - not only is haggling beneath me, I cannot hope to out-haggle professionals whose main skillset is haggling. Play the game you're good at. Don't play the game you will lose.

Because cyber-security is a game one is doomed to lose at some point or other. You can only defend against an existing and known threat. Which means the threat will always exist before the solution does. Which, in turn, means that you're always playing catch-up. Bear in mind that it is possible for a threat to exist long before it makes itself known - probably by compromising some poor sod we hopefully don't care about.

How about other measures?

Of course, there are a multitude of other measures the Government could take - firewalls, domain whitelists and blacklists, user training, to name a few.

But no one is suggesting that these not be in place. By themselves, all these security measures come with their own limitations. They should be in place as complementary measures, rather than primary ones.

What's your position on this?

Whoops. Rambled on a bit there, didn't I?

So, I think the Government might have something there. And the fact that they will be taking at least one year to fully implement it, shows that this decision was not made in haste.

Not that this will stop people from making fun of the Government, mind you. Neither should they. What use is a Democracy if you can't mock the people in charge, whether or not it's deserved? Jeer away. Have fun doing it! But don't imagine for a moment that buying an anti-virus software license once every year makes you some kind of expert on the matter. That is exactly the kind of mentality that gets systems compromised.

The Million Dollar Question

This is an extreme move, which will no doubt cause loads of inconvenience to employees in the Public sector. Why now? What threats has the Government encountered, to even contemplate such a move and deem it necessary?

I would prefer to trust that the Government knows what it's doing. Because the alternative is just too awful to contemplate.


Stay safe,
T___T

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

Monday, 13 June 2016

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

Hello again. Now that we've gone through the localStorage object and figured out how to store and retrieve values from it, it's time to put those values to good use.

Before that, let's go through the data structure of this app. In here, every month's worth of data is represented by a localStorage item. This item will be an array of all the days in that month, and each item in the array will be an object consisting of the expenditure entered for each category ("A", "B", "C" and "D"). Confused? Not to worry, sit tight and allow me to demonstrate. I'm going to create the function addmonthyear().

Before that, however, let's add some global variables to the application. currentdate will hold the value of whichever date it happens to be when the app is being run. glb_archive is the array that holds the objects making up the current month. glb_currentyear, glb_currentmonth and glb_currentday are simply the year, month and day of the entry that is being edited. glb_currentyear and glb_currentmonth are initialized to the value of the current year and month, because these are the values that will be in use on the first screen of this app.
            var currentdate = new Date();
            var glb_settings,glb_archive;
            var glb_currentyear,glb_currentmonth,glb_currentday;

            glb_currentyear = currentdate.getFullYear();
            glb_currentmonth = currentdate.getMonth();


Now we create add_monthyear(). The global variable glb_archive is used here, and it's an array. The variable day starts at 0, and the variable month takes the variable glb_currentmonth and assigns it to the temporary variable monthpointer. Then the variable datepointer is assigned the value of the first day of the month represented by glb_currentmonth.
            function add_monthyear()
            {
                glb_archive = [];
                var day=0;
                var monthpointer=glb_currentmonth;

                var datepointer = new Date(glb_currentyear,monthpointer,1);
            }

            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=$("#txtBudget").val();
                glb_settings[5].interval=$("#txtInterval").val();

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

                alert ("Settings saved.");
            }


We now add a While loop. This loop runs as long as the values of month and monthpointer remain the same. Of course, since we've just assigned the value of glb_currentmonth to monthpointer, this will be true right off the bat. So the day element of glb_archive, that is the first element because day is currently at 0, will be assigned an object. The object stores the day, expenditures for categories "A", "B", "C" and "D", and comments. The variable datepointer is advanced to the next day using the setDate() method, day is incremented and monthpointer is assigned the value of the new date represented by datepointer.

monthpointer will remain the same value as glb_currentmonth until datepointer hits the first day of the following month. And at this point, the While loop is exited.
            function add_monthyear()
            {
                glb_archive = [];
                var day=0;
                var monthpointer=glb_currentmonth;

                var datepointer = new Date(glb_currentyear,monthpointer,1);

                while (monthpointer==glb_currentmonth)
                {   
                    glb_archive[day]={"day":day+1,"A":0,"B":0,"C":0,"D":0,"comments":""};

                    datepointer.setDate(datepointer.getDate() + 1);
                    day++;
                    monthpointer=datepointer.getMonth();
                }
            }


So now your glb_archive array has been filled up. We now save it to your localStorage object under the name "archive" and appending the value of the year and month. The value to be saved is the JSON representation of the glb_archive array.

And after this is done, the app automatically brings you to the entries page. I know this looks confusing, but honest to God, the reason for this will be apparent very soon.
            function add_monthyear()
            {
                glb_archive = [];
                var day=0;
                var monthpointer=glb_currentmonth;

                var datepointer = new Date(glb_currentyear,monthpointer,1);

                while (monthpointer==glb_currentmonth)
                {   
                    glb_archive[day]={"day":day+1,"A":0,"B":0,"C":0,"D":0,"comments":""};

                    datepointer.setDate(datepointer.getDate() + 1);
                    day++;
                    monthpointer=datepointer.getMonth();
                }

                localStorage.setItem("archive_" + glb_currentyear + "_" + (glb_currentmonth+1), JSON.stringify(glb_archive));
                $.mobile.changePage("#entries", {transition: "slide"});
            }


When is this function going to be used?

Well, of course it's going to be used. Just not right away. The purpose of writing this function first was to give you a good grasp of the underlying data structure for this app.

And for the next bit...

We will be creating the interface for the first page, named "selectdate". It's so named because on the first page, that's exactly what you'll do - select a date. Without further ado, here's the HTML. We've added a label and a drop-down list (id ddlMonth) for the months, then populated it with values from 0 to 11 because JavaScript months are numbered from 0 to 11. Sure, we could do this programmatically, but what the hell, it's only twelve values and one drop-down list, right?
        <div data-role="page" id="selectdate" data-theme="b">
            <div data-role="header" data-position="fixed">
                THE SCROOGE DIRECTIVE<br />A project by T___T
            </div>
            <div data-role="main" class="ui-content">
                <div data-role="fieldcontainer">
                    <label for="ddlMonth">Select a Month</label>               
                    <select name="ddlMonth" id="ddlMonth">
                        <option value="0">January</option>
                        <option value="1">February</option>
                        <option value="2">March</option>
                        <option value="3">April</option>
                        <option value="4">May</option>
                        <option value="5">June</option>
                        <option value="6">July</option>
                        <option value="7">August</option>
                        <option value="8">September</option>
                        <option value="9">October</option>
                        <option value="10">November</option>
                        <option value="11">December</option>
                    </select>
                </div>
            </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>


For now, let's see what your interface looks like.


Next, we create another drop-down list, ddlYear, for the year. This one we'll leave empty because we'll be filling it in using jQuery.
        <div data-role="page" id="selectdate" data-theme="b">
            <div data-role="header" data-position="fixed">
                THE SCROOGE DIRECTIVE<br />A project by T___T
            </div>
            <div data-role="main" class="ui-content">
                <div data-role="fieldcontainer">
                    <label for="ddlMonth">Select a Month</label>               
                    <select name="ddlMonth" id="ddlMonth">
                        <option value="0">January</option>
                        <option value="1">February</option>
                        <option value="2">March</option>
                        <option value="3">April</option>
                        <option value="4">May</option>
                        <option value="5">June</option>
                        <option value="6">July</option>
                        <option value="7">August</option>
                        <option value="8">September</option>
                        <option value="9">October</option>
                        <option value="10">November</option>
                        <option value="11">December</option>
                    </select>
                </div>
                <div data-role="fieldcontainer">
                    <label for="ddlYear">Select a Year</label>               
                    <select name="ddlYear" id="ddlYear">

                    </select>
                </div>
            </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>


The ddlYear drop-down list is empty. We'll fix that soon.


After that, we add two buttons. The first button proceeds to the "entries" page using the month selected. The second button deletes the records for the month selected. the set_currentdate() and delete_currentdate() functions will be written, of course. Let's leave that for later.
        <div data-role="page" id="selectdate" data-theme="b">
            <div data-role="header" data-position="fixed">
                THE SCROOGE DIRECTIVE<br />A project by T___T
            </div>
            <div data-role="main" class="ui-content">
                <div data-role="fieldcontainer">
                    <label for="ddlMonth">Select a Month</label>               
                    <select name="ddlMonth" id="ddlMonth">
                        <option value="0">January</option>
                        <option value="1">February</option>
                        <option value="2">March</option>
                        <option value="3">April</option>
                        <option value="4">May</option>
                        <option value="5">June</option>
                        <option value="6">July</option>
                        <option value="7">August</option>
                        <option value="8">September</option>
                        <option value="9">October</option>
                        <option value="10">November</option>
                        <option value="11">December</option>
                    </select>
                </div>
                <div data-role="fieldcontainer">
                    <label for="ddlYear">Select a Year</label>               
                    <select name="ddlYear" id="ddlYear">

                    </select>
                </div>
                <a href="#" data-role="button" data-icon="forward" onclick="set_currentdate();">Go!</a>
                <a href="#" data-role="button" data-icon="delete" onclick="delete_currentdate();">Delete</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>


And here we are...


Add this code. Here, we ensure that the ddlMonth drop-down list's value reflects the value of glb_currentmonth, then we use the selectmenu() method with the argument "refresh" to, well, refresh the display.
            $(document).ready(function()
            {
                if (localStorage.getItem("scrooge_settings") == null)
                {
                    localStorage.setItem("scrooge_settings","[{\"category\":\"A\",\"name\":\"A\"},{\"category\":\"B\",\"name\":\"B\"},{\"category\":\"C\",\"name\":\"C\"},{\"category\":\"D\",\"name\":\"D\"},{\"budget\":\"1000.00\"},{\"interval\":\"5\"}]");
                }

                    glb_settings = JSON.parse(localStorage.getItem("scrooge_settings"));
            });

        $(document).on("pagebeforeshow", "#selectdate", function(event, data) {
            $("#ddlMonth").val(glb_currentmonth).attr("selected", "selected");
            $("#ddlMonth").selectmenu( "refresh" );
        });


Refresh your code. Does the selected value change? Is it the current month?

Editor's Note: It says "May", even though it's currently June. That's because this material was prepared last month. The code is fine. Really.

Next, we clear the ddlYear drop-down list using the empty() method. Then we use a For loop to populate it with years relative to the current year. It's 2016 now, so 2014 and 2015 will be added, then 2016, then 2017. The value of glb_currentyear is selected as default. We then use the selectmenu() method with the argument "refresh" as above.
        $(document).on("pagebeforeshow", "#selectdate", function(event, data) {
            $("#ddlMonth").val(glb_currentmonth).attr("selected", "selected");
            $("#ddlMonth").selectmenu( "refresh" );

            $("#ddlYear").empty();

            for (var i=-2;i<=1;i++)
            {
                yearoption=$("<option value=\""+(currentdate.getFullYear()+i)+"\">"+(currentdate.getFullYear()+i)+"</option>");
                $("#ddlYear").append(yearoption);
            }   

            $("#ddlYear").val(glb_currentyear).attr("selected", "selected");
            $("#ddlYear").selectmenu("refresh");
        });


See the change?


As promised, we'll now create the functions set_currentdate() and delete_currentdate(). These are simple enough. Each function begin by taking the values of the ddlMonth and ddlYear drop-down lists and setting the values of glb_currentmonth and glb_currentyear respectively.
        function fixinput(varobj,vardec)
        {
            if (isNaN(varobj.value)||varobj.value.trim()=="")
            {
                varobj.value=(0).toFixed(vardec);
            }
            else
            {
                varobj.value=parseFloat(varobj.value).toFixed(vardec);
            }
        }

        function set_currentdate()
        {
            glb_currentmonth = parseInt($("#ddlMonth").val());
            glb_currentyear = parseInt($("#ddlYear").val());
        }

        function delete_currentdate()
        {
            glb_currentmonth = parseInt($("#ddlMonth").val());
            glb_currentyear = parseInt($("#ddlYear").val());
        }

        function add_monthyear()
        {
            glb_archive = [];
            var archiveobject;
            var day=0;
            var monthpointer=glb_currentmonth;

            var datepointer = new Date(glb_currentyear,monthpointer,1);

            while (monthpointer==glb_currentmonth)
            {   
                glb_archive[day]={"day":day+1,"A":0,"B":0,"C":0,"D":0,"comments":""};

                datepointer.setDate(datepointer.getDate() + 1);
                day++;
                monthpointer=datepointer.getMonth();
            }

            localStorage.setItem("archive_" + glb_currentyear + "_" + (glb_currentmonth+1), JSON.stringify(glb_archive));
            $.mobile.changePage("#entries", {transition: "slide"});
        }


For the set_currentdate(), we redirect to the entries page immediately after that.
        function set_currentdate()
        {
            glb_currentmonth = parseInt($("#ddlMonth").val());
            glb_currentyear = parseInt($("#ddlYear").val());

            $.mobile.changePage("#entries", {transition: "slide"});
        }

        function delete_currentdate()
        {
            glb_currentmonth = parseInt($("#ddlMonth").val());
            glb_currentyear = parseInt($("#ddlYear").val());
        }


For the delete_currentdate() function, we obtain the archive name from the values of glb_currenthmonth and glb_currentdate, and remove the item from localStorage using the removeItem method. This function is just so you can delete certain archives if you so feel like it.
        function set_currentdate()
        {
            glb_currentmonth = parseInt($("#ddlMonth").val());
            glb_currentyear = parseInt($("#ddlYear").val());

            $.mobile.changePage("#entries", {transition: "slide"});
        }

        function delete_currentdate()
        {
            glb_currentmonth = parseInt($("#ddlMonth").val());
            glb_currentyear = parseInt($("#ddlYear").val());

            var archivename="archive_" + glb_currentyear + "_" + glb_currentmonth;

            localStorage.removeItem(archivename);
            alert("Requested archive deleted.");
        }


Now for the entries page!

We've blabbered long enough about it. In two separate functions, namely add_monthyear() and set_currentdate(), you'll see redirects to this page. So now we're going to work on it. The layout is fairly straightforward - it consists of one table with six columns - one to display the day, for for the categories, and one more for the total. Note that while the columns for "Day" and "Total" are filled in, those for the categories are not. Instead, they're given ids colA, colB, colC and colD respectively. We'll need those ids to fill the column headers in. Remember the previous part of this web tutorial where you tinkered with the settings? Well, that's where they come in!

The table has an id of tblDailyEntries, and I've set the font size to 0.7em because it may get a little cramped otherwise.
        <div data-role="page" id="entries" data-theme="b">
            <div data-role="header" data-position="fixed">
                ENTRIES
            </div>
            <div data-role="main" class="ui-content">               
                <table data-role="table" class="ui-responsive" id="tblDailyEntries" style="font-size:0.7em;">
                    <thead>
                        <tr>
                            <td>Day</td>
                            <td id="colA"></td>
                            <td id="colB"></td>
                            <td id="colC"></td>
                            <td id="colD"></td>
                            <td>Total</td>
                        </tr>
                    </thead>
                    <tbody>

                    </tbody>
                </table>
            </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>


Before that, check out the entries page you have so far...


So add this to your jQuery script. Upon entering the entries page, we populate the headers colA, colB, colC and colD with the values taken from the glb_settings array.
                $(document).on("pagebeforeshow", "#selectdate", function(event, data)
                {
                    $("#ddlMonth").val(glb_currentmonth).attr("selected", "selected");
                    $("#ddlMonth").selectmenu( "refresh" );

                    $("#ddlYear").empty();

                    for (var i=-2;i<=1;i++)
                    {
                        yearoption=$("<option value=\""+(currentdate.getFullYear()+i)+"\">"+(currentdate.getFullYear()+i)+"</option>");
                        $("#ddlYear").append(yearoption);
                    }   

                    $("#ddlYear").val(glb_currentyear).attr("selected", "selected");
                    $("#ddlYear").selectmenu( "refresh" );
                });

                $(document).on("pagebeforeshow", "#entries", function(event, data) 
                {
                    $("#colA").html(glb_settings[0].name);
                    $("#colB").html(glb_settings[1].name);
                    $("#colC").html(glb_settings[2].name);
                    $("#colD").html(glb_settings[3].name);

                    $("#tblDailyEntries").table( "refresh" );
                });

                $(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);
                });


See? The headers are now updated wth the values you saved in the "settings" screen previously. Don't take my word for it! Go fiddle with the values in the settings page. Save. Come back to the "entries" page. Do the headers get updated?


So next, of course, we'll use the interval and budget values you have assigned to glb_settings.
                $(document).on("pagebeforeshow", "#entries", function(event, data)
                {
                    $("#colA").html(glb_settings[0].name);
                    $("#colB").html(glb_settings[1].name);
                    $("#colC").html(glb_settings[2].name);
                    $("#colD").html(glb_settings[3].name);

                    var budget=parseFloat(glb_settings[4].budget);
                    var interval=parseInt(glb_settings[5].interval);

                    $("#tblDailyEntries").table( "refresh" );
                });


And then we'll get the archive name using the values of glb_currentyear and glb_currentmonth. Variables temprow and temptotal are created for use in populating the table later. The tempsubtotal array is used to store the cumulative subtotals of the A, B, C and D columns.
                $(document).on("pagebeforeshow", "#entries", function(event, data)
                {
                    $("#colA").html(glb_settings[0].name);
                    $("#colB").html(glb_settings[1].name);
                    $("#colC").html(glb_settings[2].name);
                    $("#colD").html(glb_settings[3].name);

                    var archivename="archive_" + glb_currentyear + "_" + glb_currentmonth;
                    var temprow,temptotal;
                    var tempsubtotal=[0,0,0,0,0];
                    var budget=parseFloat(glb_settings[4].budget);
                    var interval=parseInt(glb_settings[5].interval);

                    $("#tblDailyEntries").table( "refresh" );
                });


Next, we check for the existence of the archive in localStorage. If it's null, meaning there's no archive by that name, we run the add_monthyear() function to create one! After that, there will be such an archive, and we'll assign its value to the global variable glb_archive. Then we clear the body of the table in preparation.
                $(document).on("pagebeforeshow", "#entries", function(event, data)
                {
                    $("#colA").html(glb_settings[0].name);
                    $("#colB").html(glb_settings[1].name);
                    $("#colC").html(glb_settings[2].name);
                    $("#colD").html(glb_settings[3].name);

                    var archivename="archive_" + glb_currentyear + "_" + glb_currentmonth;
                    var temprow,temptotal;
                    var tempsubtotal=[0,0,0,0,0];
                    var budget=parseFloat(glb_settings[4].budget);
                    var interval=parseInt(glb_settings[5].interval);

                    if (localStorage.getItem(archivename) == null)
                    {
                        add_monthyear();
                    }

                    glb_archive=JSON.parse(localStorage.getItem(archivename));
                    $("#tblDailyEntries tbody").empty();

                    $("#tblDailyEntries").table( "refresh" );
                });


Take a look at your localStorage. You should see that the archive has been created.


Next, we add a For loop to iterate through the contents of the glbarchive array. temptotal is assigned the total of the values of the A, B, C and D properties of each object in the glbarchive array. Then temprow is a HTML snippet containing the day, the values of the A, B, C and D properties, and the subtotal, which is, of course, temptotal. temptotal needs to be given two denimal places, hence the use of the toFixed() method. The A, B, C and D properties are already formatted in the archive, so there's no need for us to repeat the process. The temprow is then appended to the tbody portion of the tblDailyEntries table via the append() method.
                $(document).on("pagebeforeshow", "#entries", function(event, data)
                {
                    $("#colA").html(glb_settings[0].name);
                    $("#colB").html(glb_settings[1].name);
                    $("#colC").html(glb_settings[2].name);
                    $("#colD").html(glb_settings[3].name);

                    var archivename="archive_" + glb_currentyear + "_" + glb_currentmonth;
                    var temprow,temptotal;
                    var tempsubtotal=[0,0,0,0,0];
                    var budget=parseFloat(glb_settings[4].budget);
                    var interval=parseInt(glb_settings[5].interval);

                    if (localStorage.getItem(archivename) == null)
                    {
                        add_monthyear();
                    }

                    glb_archive=JSON.parse(localStorage.getItem(archivename));
                    $("#tblDailyEntries tbody").empty();

                    for (var i=0;i<glb_archive.length;i++)
                    {
                        temptotal=parseFloat(glb_archive[i].A)+parseFloat(glb_archive[i].B)+parseFloat(glb_archive[i].C)+parseFloat(glb_archive[i].D);
                        temprow=$("<tr><td>"+i+"</td><td>"+glb_archive[i].A+"</td><td>"+glb_archive[i].B+"</td><td>"+glb_archive[i].C+"</td><td>"+glb_archive[i].D+"</td><td>"+temptotal.toFixed(2)+"</td></tr>");
                        $("#tblDailyEntries tbody").append(temprow);
                    }

                    $("#tblDailyEntries").table( "refresh" );
                });


Let's see what we have. The table is populated... but wait, the days seem wrong. That's because they're numbered 0 to 30.


So just amend like so.
                    for (var i=0;i<glb_archive.length;i++)
                    {
                        temptotal=parseFloat(glb_archive[i].A)+parseFloat(glb_archive[i].B)+parseFloat(glb_archive[i].C)+parseFloat(glb_archive[i].D);
                        temprow=$("<tr><td>"+(i+1)+"</td><td>"+glb_archive[i].A+"</td><td>"+glb_archive[i].B+"</td><td>"+glb_archive[i].C+"</td><td>"+glb_archive[i].D+"</td><td>"+temptotal.toFixed(2)+"</td></tr>");
                        $("#tblDailyEntries tbody").append(temprow);
                    }


There you go.


Next, we use the budget and interval properties. For each temptotal, the budget is decremented by that value. And the values of A, B, C and D columns are incremented into the tempsubtotal array.
                    for (var i=0;i<glb_archive.length;i++)
                    {
                        temptotal=parseFloat(glb_archive[i].A)+parseFloat(glb_archive[i].B)+parseFloat(glb_archive[i].C)+parseFloat(glb_archive[i].D);
                        temprow=$("<tr><td>"+(i+1)+"</td><td>"+glb_archive[i].A+"</td><td>"+glb_archive[i].B+"</td><td>"+glb_archive[i].C+"</td><td>"+glb_archive[i].D+"</td><td>"+temptotal.toFixed(2)+"</td></tr>");
                        $("#tblDailyEntries tbody").append(temprow);

                        tempsubtotal[0]+=parseFloat(glb_archive[i].A);
                        tempsubtotal[1]+=parseFloat(glb_archive[i].B);
                        tempsubtotal[2]+=parseFloat(glb_archive[i].C);
                        tempsubtotal[3]+=parseFloat(glb_archive[i].D);
                        tempsubtotal[4]+=temptotal;
                        budget-=temptotal;
                    }


And then we use a Modulus in a conditional to check if it is time for interval to kick in. If you had an interval of 5, for example, every 5 rows a new row will be appended after the current row to display the subtotals so far.
                    for (var i=0;i<glb_archive.length;i++)
                    {
                        temptotal=parseFloat(glb_archive[i].A)+parseFloat(glb_archive[i].B)+parseFloat(glb_archive[i].C)+parseFloat(glb_archive[i].D);
                        temprow=$("<tr><td>"+(i+1)+"</td><td>"+glb_archive[i].A+"</td><td>"+glb_archive[i].B+"</td><td>"+glb_archive[i].C+"</td><td>"+glb_archive[i].D+"</td><td>"+temptotal.toFixed(2)+"</td></tr>");
                        $("#tblDailyEntries tbody").append(temprow);

                        tempsubtotal[0]+=parseFloat(glb_archive[i].A);
                        tempsubtotal[1]+=parseFloat(glb_archive[i].B);
                        tempsubtotal[2]+=parseFloat(glb_archive[i].C);
                        tempsubtotal[3]+=parseFloat(glb_archive[i].D);
                        tempsubtotal[4]+=temptotal;
                        budget-=temptotal;

                        if ((i+1)%interval==0)
                        {
                            temprow=$("<tr><td>SUBT.</td><td>"+tempsubtotal[0].toFixed(2)+"</td><td>"+tempsubtotal[1].toFixed(2)+"</td><td>"+tempsubtotal[2].toFixed(2)+"</td><td>"+tempsubtotal[3].toFixed(2)+"</td><td>"+tempsubtotal[4].toFixed(2)+"</td></tr>");
                            $("#tblDailyEntries tbody").append(temprow);

                            temprow=$("<tr><td>REM.</td><td></td><td></td><td></td><td></td><td>"+budget.toFixed(2)+"</td></tr>");
                            $("#tblDailyEntries tbody").append(temprow);
                        }
                    }


Take a gander, we're almost done with this part!


And right at the very end of the table, we append another row displaying the grand totals along with what's remaining of the budget value.
                $(document).on("pagebeforeshow", "#entries", function(event, data)
                {
                    $("#colA").html(glb_settings[0].name);
                    $("#colB").html(glb_settings[1].name);
                    $("#colC").html(glb_settings[2].name);
                    $("#colD").html(glb_settings[3].name);

                    var archivename="archive_" + glb_currentyear + "_" + glb_currentmonth;
                    var temprow,temptotal;
                    var tempsubtotal=[0,0,0,0,0];
                    var budget=parseFloat(glb_settings[4].budget);
                    var interval=parseInt(glb_settings[5].interval);

                    if (localStorage.getItem(archivename) == null)
                    {
                        add_monthyear();
                    }

                    glb_archive=JSON.parse(localStorage.getItem(archivename));
                    $("#tblDailyEntries tbody").empty();

                    for (var i=0;i<glb_archive.length;i++)
                    {
                        temptotal=parseFloat(glb_archive[i].A)+parseFloat(glb_archive[i].B)+parseFloat(glb_archive[i].C)+parseFloat(glb_archive[i].D);
                        temprow=$("<tr><td>"+(i+1)+"</td><td>"+glb_archive[i].A+"</td><td>"+glb_archive[i].B+"</td><td>"+glb_archive[i].C+"</td><td>"+glb_archive[i].D+"</td><td>"+temptotal.toFixed(2)+"</td></tr>");
                        $("#tblDailyEntries tbody").append(temprow);

                        tempsubtotal[0]+=parseFloat(glb_archive[i].A);
                        tempsubtotal[1]+=parseFloat(glb_archive[i].B);
                        tempsubtotal[2]+=parseFloat(glb_archive[i].C);
                        tempsubtotal[3]+=parseFloat(glb_archive[i].D);
                        tempsubtotal[4]+=temptotal;
                        budget-=temptotal;

                        if ((i+1)%interval==0)
                        {
                            temprow=$("<tr><td>SUBT.</td><td>"+tempsubtotal[0].toFixed(2)+"</td><td>"+tempsubtotal[1].toFixed(2)+"</td><td>"+tempsubtotal[2].toFixed(2)+"</td><td>"+tempsubtotal[3].toFixed(2)+"</td><td>"+tempsubtotal[4].toFixed(2)+"</td></tr>");
                            $("#tblDailyEntries tbody").append(temprow);

                            temprow=$("<tr><td>REM.</td><td></td><td></td><td></td><td></td><td>"+budget.toFixed(2)+"</td></tr>");
                            $("#tblDailyEntries tbody").append(temprow);
                        }
                    }

                    temprow=$("<tr><td>TOT.</td><td>"+tempsubtotal[0].toFixed(2)+"</td><td>"+tempsubtotal[1].toFixed(2)+"</td><td>"+tempsubtotal[2].toFixed(2)+"</td><td>"+tempsubtotal[3].toFixed(2)+"</td><td>"+tempsubtotal[4].toFixed(2)+"</td></tr>");
                    $("#tblDailyEntries tbody").append(temprow);

                    temprow=$("<tr><td>REM.</td><td></td><td></td><td></td><td></td><td>"+budget.toFixed(2)+"</td></tr>");
                    $("#tblDailyEntries tbody").append(temprow);

                    $("#tblDailyEntries").table( "refresh" );
                });


It's all zeros right now, so you won't see what wonder you've created. But that's OK, this will be covered in the next and final part of this web tutorial.


Now modify this line...
                    for (var i=0;i<glb_archive.length;i++)
                    {
                        temptotal=parseFloat(glb_archive[i].A)+parseFloat(glb_archive[i].B)+parseFloat(glb_archive[i].C)+parseFloat(glb_archive[i].D);
                        temprow=$("<tr><td><a href=\"#\" onclick=\"goto_dailyentry("+(i+1)+");\">"+(i+1)+"</a></td><td>"+glb_archive[i].A+"</td><td>"+glb_archive[i].B+"</td><td>"+glb_archive[i].C+"</td><td>"+glb_archive[i].D+"</td><td>"+temptotal.toFixed(2)+"</td></tr>");
                        $("#tblDailyEntries tbody").append(temprow);


This turns the day display into a link. Upon clicking it, the goto_dailyentry() function is fired off, and the day passed in as an argument.



Yep, we'll need to add the goto_dailyentry() function. This one's simple enough. It basically sets the value of glb_currentday to the value of the parameter (which is the day depicted by the row) and then redirects you to the "dailyentry" page.
            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=$("#txtBudget").val();
                glb_settings[5].interval=$("#txtInterval").val();

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

                alert ("Settings saved.");
            }

            function goto_dailyentry(varday)
            {
                    glb_currentday=varday;
                    $.mobile.changePage("#dailyentry", {transition: "slide"});
            }


That's all for now!

Phew. That was long. No sweat though, the end is in sight.

Next

Updating the entries of the archive. You don't want to miss this one.