Showing posts with label Chrome. Show all posts
Showing posts with label Chrome. Show all posts

Sunday, 17 May 2026

Keeping your users in the Dark (Mode)!

Media queries in CSS aren't new. I usually use them for mobile-friendly layouts or print views. However, I encountered a blind spot one day when i discovered that I was able to use style Dark Mode in CSS Media Queries!

Upon further research, this has been part of the standard since 2020. Which shows you how long I've had my eye off that particular ball. To illustrate, here's a sample HTML I wrote with a poem generated from ChatGPT.
<!DOCTYPE html>
<html>
  <head>
    <title>Dark Mode test</title>
    <style>
  
    </style>
  </head>

  <body>
    <h1>TESTING DARK MODE</h1>
    <p>
      When daylight fades from glass and screen,<br />
      and white gives way to something keen,<br />
      the world grows soft, the edges thin—<br />
      a quieter place to think within.
    </p>

    <p>
      The glare that once would bite and burn<br />
      now yields to night at every turn,<br />
      where pixels rest and shadows stay<br />
      like thoughts that choose a gentler way.
    </p>

    <p>
      No blinding page, no sterile light,<br />
      just constellations born of night—<br />
      a code of ink, a calmer tone,<br />
      a world that feels more like your own.
    </p>

    <p>
      And in that dark, so still, so wide,<br />
      the noise of day steps back aside—<br />
      you read, you write, you drift, you see<br />
      how brightness isn’t clarity.
    </p>
  </body>
</html>


Example

The majority of the time, the defaults for a site tend towards the Light Mode, like so.
<!DOCTYPE html>
<html>
  <head>
    <title>Dark Mode test</title>
    <style>
      body
      {
       background: rgb(255, 255, 255);
       color: rgb(100, 100, 100);
       font-family: Georgia;
       font-size: 15px;
      }

      h1
      {
        color: rgb(150, 50, 0);
      }

    </style>
  </head>

  <body>
    <h1>TESTING DARK MODE</h1>
    <p>
      When daylight fades from glass and screen,<br />
      and white gives way to something keen,<br />
      the world grows soft, the edges thin—<br />
      a quieter place to think within.
    </p>

    <p>
      The glare that once would bite and burn<br />
      now yields to night at every turn,<br />
      where pixels rest and shadows stay<br />
      like thoughts that choose a gentler way.
    </p>

    <p>
      No blinding page, no sterile light,<br />
      just constellations born of night—<br />
      a code of ink, a calmer tone,<br />
      a world that feels more like your own.
    </p>

    <p>
      And in that dark, so still, so wide,<br />
      the noise of day steps back aside—<br />
      you read, you write, you drift, you see<br />
      how brightness isn’t clarity.
    </p>
  </body>
</html>


This is what that site would look like...


Now in order to specify a Dark Mode, we first specify a CSS Media Query, with Dark.
<style>
  body
  {
    background: rgb(255, 255, 255);
    color: rgb(100, 100, 100);
    font-family: Georgia;
    font-size: 15px;
  }

  h1
  {
    color: rgb(150, 50, 0);
  }

  @media (prefers-color-scheme: dark)
  {

  }

</style>


And then we specify what to override in Dark Mode.
@media (prefers-color-scheme: dark)
{
  body
  {
    background: rgb(0, 0, 0);
    color: rgb(200, 200, 200);
  }

  h1
  {
    color: rgb(255, 200, 50);
  }

}


Emulating Dark Mode in your browser

Right now, I'm on a Mac and this is Chrome.

1. Get into your console. Open the Command Menu by pressing Cmd + Shift + P.

2. Type in "Show Rendering" and press select.

3. Toggle between the modes..

Here's a very simple look. Black background, light grey text.


Who are you in the Dark?

That was fun, I guess. So much we could do with this. I have ideas already. My mind is going... ahem... dark places.

Let there be Light!
T___T

Saturday, 19 July 2025

Five Reasons to learn Web Development in 2025

Recent events such as the rise of generative AI, have made tech work a little less attractive than it used to be. Web development, in particular, has suffered. That's probaby because a large chunk of web development is automatable, and even before AI came on the scene, there had been numerous tools such as Content Management Systems and Low-code development platforms.

Thus, web development being automated by AI was par for the course.

Robots writing websites.

Still, not all is lost. While web development might have lost much of its luster, there are still good, strong reasons to pick it up in ones tech career. Unlike the tech reporters and HR executives who write listicles like these, I have actually been a web developer before. I speak from experience, my dudes. And following are some of the most compelling reasons I had, in no particular order of importance, for going down this path.

1. No complicated installation

Ever tried to learn a language like PHP or Java? Every single one of these languages requires you to set up some kind of compiler or interpreter environment. PHP requires an Apache server. Java needs the Java Runtime Environment. You can write all the code you want, but until the code gets compiled or interpreted by the environment that you have to install and set up, you're not getting even a Hello World program done.

All you need is a browser.

HTML, CSS and JavaScript, however, do not. All of them already run in any major browser - Firefox, Chrome, and so on. In effect, the environment is right there for you.

This is not to say that you will never need to do any complicated installation. But for the basic building blocks - again, HTML, CSS and JavaScript - of web development, you don't. You will need to do that when you want to pick up a server-side language and maybe databases and definitely for the NodeJS style of development. But for basic stuff? Even the slightly more advanced stuff? Nope, not even a little bit. That is a lot more than you could ever say about other programming languages or platforms.

2. Good skill spread

When you learn web development, you learn HTML, CSS and JavaScript as a base starting point. That's already a good spread right there.

HTML and CSS are where you learn front-end and possibly even design. When you learn JavaScript, in addition to all the things you pick up when learning a programming language such as operators, arrays, branching and iterative logic, you also learn asynchronous operations and DOM manipulation.

A good spread of tools.

That's not to say that other tech disciplines don't have their own unique perks. But where it comes to the skill spread, web development wins. I don't think anything else even comes close.

Once you get past the basic toolset of HTML, CSS and JavaScript, back-end programming and databases will come into play. It's never just web development. Even if you are averse to the thought of being a humble web developer for the rest of your career, there are far worse places to start.

3. Resources

Now, when I say "resources", I don't just mean documentation, references and learning materials, though there's plenty of that, yes. But web development is not special in that regard because any other tech discipline boasts plenty of learning resources and a community dedicated to helping each other learn.

A good learning
community.

Though, in this case, web development has something extra.

You see, every humble HTML page on the internet can have its source viewed and played with in the browser, reverse engineered, and so on. Every URL on the internet is potentially a resource for learning, much like how I learned to cobble together JavaScript widgets decades ago.

In contrast, it's not possible to just take any desktop application and reverse-engineer the code, because the code has already been compiled and is no longer human-readable.

4. Ready use case

Often, when learning a programming language, it's helpful to be able to use newly-acquired skills to build something, so as to really hammer home the muscle memory. Something both relevant and useful, preferably. Not that Hello World programs don't have their place, but if one wishes to level up, better use cases are the order of the day.

And with web development, those use cases are almost too easy to find. Web development creates web pages, at the minimum. And after that, at varying levels of complexity, web applications. One does not have to stretch too far to find something worth building... and because it already exists, you know that it is both worth building and possible to build.

Applying what you learn.

My larger point is that what you learn can readily be applied. Not just in creating and editing websites, but in general software development. This also means that your chances of landing a job with that skillset cannot be understated. In this day and age, web developers are perhaps not nearly as in demand as they were a decade ago, or paid nearly as well, but the skillset goes beyond just web development.

For example, a lot of existing software already leverage things like REST API endpoints. These are basically URLs, which are pretty much the backbone of the web. REST is an almost inescapable part of the whole web development deal. Ergo, if you deal in web development, at some point you are going to be dealing with REST endpoints, which overlaps a large part of software development regardless of discipline.

Or even mobile development. In case you weren't aware, a large chunk of mobile tech is basically HTML, CSS and JavaScript.

I could go on... but do I really need to?

5. No gatekeeping

In the legal profession, there's the Bar Exam. In the medical profession, there's the Medical Regulatory Authority. In tech? Other than job interviews which exist at almost every industry, there's almost no gatekeeping in tech. Even the requirement for Degrees of Diplomas is not a really hard one.

When I say "no gatekeeping", I don't mean that nobody tries to gatekeep. The fact is that many people try to gatekeep, but it just doesn't work because to gatekeep, one needs a unified set of standards. It's almost impossible to establish said standards in a landscape as varied as tech, whose goalposts shift constantly.

The gatekeeper.

And while this inability to gatekeep exists in many areas of tech, none moreso than web development. HTML, CSS and JavaScript are fairly stable at this point, but these are just the base technologies. Their offshoots - frameworks, libraries and the like - keep springing up like mushrooms. And when you consider databases and backend programming languages, the possibilities multiply even more.

All in all, one could come in anytime in web development, and still be relatively fresh and relevant. No one can stop you from making and publishing web pages and applications, not in the same way they can stop you from practising law. You don't need a license to write code, so nobody can revoke it.

Some clarifications

The reasons stated here are in relation to those for choosing other tech fields. Why, for instance, web development when you could go for Data Analytics or cybersecurity? Reasons specific to web development.

I was inspired to compile this list because there are a lot of vague, generic and - to be brutally honest - trite lists out there on the web that extol the virtues of web development. Hopefully this is a better list.

<html>Bye for now,</html>
T___T

Saturday, 12 April 2025

Buttons or Divs? What to use, and when

With the power of CSS, HTML is significantly more visually versatile than it was in its inception more than two decades ago. Especially with divs. You can make divs appear as anything - paragraphs, block quotes and images. In extreme examples, you could even render entire paintings using many, many divs. 

A huge variety of shapes,
especially rectangular.

The humble div tag, coupled with CSS, is no longer just a rectangle on the browser. Using properties such as transform, border-radius, width and height, among others, a web developer can achieve a myriad of looks.

And this manifests quite frequently, in buttons. Previously, I discussed whether button or input tags would be preferable, but today we make a separate comparison between divs and buttons.

Divs as buttons

Making divs look like buttons is simple enough. How about behavior? Well, for that, JavaScript accomplishes this fairly easily.

One is a button and the other is a div.
<button>This is a button</button>
<div style="cursor:pointer; background-color:rgb(230, 230, 230); border:1px solid rgb(100, 100, 100); border-radius: 3px; font-family: sans-serif; font-size: 12px; width: 8em; padding: 0.2em; text-align: center">
This is a div
</div>


But they can both be made to perform certain actions on a click.
<button onclick="alert('I am a button');">This is a button</button>
<div onclick="alert('I am a div');" style="cursor:pointer; background-color:rgb(230, 230, 230); border:1px solid rgb(100, 100, 100); border-radius: 3px; font-family: sans-serif; font-size: 12px; width: 8em; padding: 0.2em; text-align: center">
This is a div
</div>


Depending on the browser, you should see no appreciable difference between these.
This is a div


How about submitting a form? Well, a button usually does this.
<form id="frmTest">
    <button>Submit<button>
</form>


But if you want a div to do this, all you really need is a bit more code.
<form id="frmTest">
    <div onclick="document.getElementById('frmTest').submit()">Submit<div>
</form>


Definitely possible, but should we?

Visually, there's not a lot of difference. In fact, styling divs to look like buttons, could even potentially offset visual differences of button rendering between browsers. For example, we take the code written earlier.

This is how it looks on Chrome.


This is how it looks on Safari. See? There's no visual change in the div we styled, but the button looks remarkably different.


However, not everything is about the visual. Especially not to the visually-impaired. The button tag and a div tag reads differently in semantics. On a screen reader, the button tag immediately stands out as a control to be clicked, while a div is semantically no different from any other div.

That is the greatest, and most significant difference. Not being blind, it is understandably difficult to imagine perceiving anything other than in visual terms, since a large part of what people like us perceive, is in the visual medium.

Conclusion

The internet was not only made for people like myself. The internet was meant as an equalizer where it came to information access. Not only did it mean that the average person now had access to information that was not available readily in the past, people with visual disabilities were supposed to be able to access this information.

And that access could be compromised if code was written with the visual intent in mind rather than the semantic.


T___T

Tuesday, 19 September 2023

Web Tutorial: Oracle APEX Mailing Registration Form (Part 1/4)

Normally, when I begin a web tutorial, I say something like "let's write some code!". Well, today, let's not write some code.

Oracle APEX is a low-code app development platform. My aim today is to create a basic Registration app for a mailing list using this platform. Why? For shits and giggles, I guess. I have no really good reason. In fact, why do I need any reason at all?

With that in mind, you will need to register for an Oracle APEX workspace and log in. Please note that while all these screenshots were taken on Chrome, it was done on my Macbook. It shouldn't make a difference if you use Windows.

Well then, let's jump right in! The features we will use today are mostly found in the first two menu items at the top - App Builder and SQL Workshop.


Click on App Builder. It will bring you to this window where you will click on Create An Application. Pretty intuitive so far, yeah? Don't worry about the rest. We won't be needing it.

Here, enter a name for your application. Ignore everything else and just click on Create Application.

And here, you just created your first app! Click on it...

...and it'll take you to a place where all these pages have been pregenerated for you. Delete as many of them as you can. The only one you might not be able to delete is Page 9999, which is User Login. Oracle APEX is dead set on you having that page whether you use it or not, for reasons I won't go into.

Let's make some pages! Hit the Create Page button and this window will pop-up. There are tons of interesting options, but we are going to go with the basic one, which is the very first one.

Toggle Use Navigation and Use Breadcrumb to Off. We won't be needing those. We'll make our own Login page, which is page id 1. Click on Create Page.

Repeat the process until you have all these pages. Here's a list of pages I created, which we will be working on.
Login (id 1) - A login page for users to enter the app.
Registration (id 2) - A form for users to add themselves to your database.
Update (id 3) - A form for users to update their particulars.
Interests (id 4) - For users to view their Interests and delete them.
Descriptions (id 5) - For users to view their Descriptions and delete them.
Interest (id 6) - For users to add a new Interest.
Description (id 7) - For users to add a new Description.
Update Password  (id 8) - A form for users to update their password.
Logout (id 9) - A page that logs the users out.
Registration Thank You (id 10) - A page that shows a thank you message after registration.


It's time to work on the database. Oracle APEX comes with this nice database management feature. Click on SQL Workshop, the second item in the top menu. You will see these two tables, MAILING_LIST and MAILING_LIST_TERMS, after the next few steps.

From the previous screen, click on Object Browser. This will lead you to this page where the amount of functionality in the left menu is staggering. We will only use the Table function, which is the default page and first option.

On the right panel, click on Table. this will bring up a dialogue where you enter the name of your table. Set Semantics as "Default", and enter whatever you like for Comments.

Here, you can start creating columns for your table. There's a whole bunch of options here, and if you're not new to databases, this should be fairly intuitive.

Now you can go through this process, or you can try another route. Click Cancel, and go back to the main screen. Here, select SQL Commands.

In this screen, you can simply enter in a command to create your tables, and click Run Command. At the end of it, you should have the tables MAILING_LIST and MAILING_LIST_TERMS.

Here's the command for creating MAILING_LIST. The fields are as follows:
EMAIL - This is a string which is the Primary Key for the table. It is used to contact the user.
FIRST_NAME - This string is descriptive, used to address the user.
LAST_NAME - This string is descriptive and optional,
DOB - This is a date field, used to determine the user's age and other information such as Zodiac sign.
GENDER - This is a single character, either "M" or "F".
DAYS - An integer which specifies the number of days between emails.
LAST_SENT - This is a date field whose value tracks when the user was last contacted. Default value is "01-01-1970".
CREATE TABLE "MAILING_LIST" ( "EMAIL" VARCHAR2(100) NOT NULL ENABLE, "FIRST_NAME" VARCHAR2(50) NOT NULL ENABLE, "LAST_NAME" VARCHAR2(50), "DOB" DATE, "GENDER" CHAR(1) NOT NULL ENABLE, "DAYS" NUMBER NOT NULL ENABLE, "PASSWORD" VARCHAR2(100) NOT NULL ENABLE, "LAST_SENT" DATE DEFAULT '01-01-1970' NOT NULL ENABLE, CONSTRAINT "MAILING_LIST_PK" PRIMARY KEY ("EMAIL") USING INDEX ENABLE ) ;


Here's the command for creating MAILING_LIST_TERMS. The fields are as follows:
EMAIL - This string is a Foreign Key to the MAILING_LIST table.
TYPE - This is a string with two possible values - "INTERESTS" or "DESCRIPTIONS".
TERM - This is a non-empty string.
The combination of EMAIL, TYPE and TERM must be unique.

CREATE TABLE "MAILING_LIST_TERMS" ( "EMAIL" VARCHAR2(100 CHAR) NOT NULL ENABLE, "TYPE" VARCHAR2(100 CHAR) NOT NULL ENABLE, "TERM" VARCHAR2(100 CHAR) NOT NULL ENABLE, CONSTRAINT "MAILING_LIST_INTERESTS_PK" PRIMARY KEY ("EMAIL", "TYPE", "TERM") USING INDEX ENABLE ) ;

ALTER TABLE "MAILING_LIST_TERMS" ADD FOREIGN KEY ("EMAIL") REFERENCES "MAILING_LIST" ("EMAIL") ENABLE;



This is just setup so far. We've not done anything terribly exciting yet, but never fear, we're getting to it!

Next

The Registration Process

Wednesday, 18 September 2019

Spot The Bug: Internet Explorer Strikes Again!

Hey guys, the Spot The Bug wagon just rolled back into town, and it's time to kick some bug butt!

Die, bugs, die!

Cross-browser compatibility can be a nightmare. And every once in a while, I'm forcibly reminded of that fact.

So here I was, using a jQuery AJAX call to populate a table. The endpoint led to a PHP file (named, imaginatively, getdata.php) which was grabbing data from a MYSQL database.

index.html
        <script>
            $(document).ready
            (
                $.ajax
                (
                    {
                        url: "getdata.php?type=quotes",
                        type: "GET",
                        success: function(result)
                        {
                            var data = JSON.parse(result);

                            $(data.quotes).each
                            (
                                function(i, x)
                                {
                                    $("#tblQuotes").append("<tr><td style='text-align:right'><b>" + x.person + "</b></td><td><i>" + x.quote + "</i></td></tr>");                                                      
                                }
                            )
                        }
                    }
                )
            );
        </script>


All was fine and dandy, until I detected a typo around the last row, and fixed it in the database. See that I spelled "Linus" wrongly? It's an easy mistake to make, given that "s" and "x" are just about right nest next to each other and he did create Linux. Anyways...


I ran the code again in Chrome, everything seemed fine. But once I tried to do the same in Internet Explorer, the typo came back!

What went wrong

It certainly wasn't the PHP. I ran the file directly in the browser, and even in Internet Explorer, it produced the correct results. This was what it was sending back to the AJAX call. Or was it?

getdata.php
$result = array("quotes" => $techQuotes);
echo json_encode($result);




It seemed a little suspicious that this was only happening in Internet Explorer. And since the PHP wasn't at fault, the next moving part was the AJAX call. Which meant that it was a front-end problem, which in turn gelled with the fact that it was only happening in Internet Explorer.

Why it went wrong

Apparently, Internet Explorer caches all GET requests by default. Therefore, since the endpoint was the same, it simply recycled the previous data! Nice going, Microsoft!

How I fixed it

I just explicitly set caching to off, right there.

index.html
                $.ajax
                (
                    {
                        url: "getdata.php?type=quotes",
                        type: "GET",
                        cache: false,
                        success: function(result)
                        {
                            var data = JSON.parse(result);

                            $(data.quotes).each
                            (
                                function(i, x)
                                {
                                    $("#tblQuotes").append("<tr><td style='text-align:right'><b>" + x.person + "</b></td><td><i>" + x.quote + "</i></td></tr>");                                                      
                                }
                            )
                        }
                    }
                )


And presto! That was how it looked in both Chrome and Internet Explorer now.

Conclusion

You know in this day and age it's so easy to forget that web developers are at the mercy of the browsers of the end-users, and what an utter pain it is to ensure cross-browser compatibility. You think using jQuery will solve all your problems, and surprise, surprise, it really doesn't. Sometimes it introduces new ones.

Cache you later!
T___T

Sunday, 10 February 2019

How Apple came to pull the plug on Facebook and Google

Prickling privacy pirates!

There's a story that broke about last couple weeks, when Apple caused a certain amount of chaos in Facebook and Google offices.

Apple lays the smackdown.

How this came about

Apple has this policy that no apps hosted on their App Store will infringe on user privacy in any way. Facebook was running apps that, with the permission of their users, tracked their personal data to the tune of up to 20 USD per month. So was Google. Ever wonder how is it Google seems to always know exactly where you are? There's your answer.

To get around Apple's policy, Facebook ran those tracking apps through their membership in Apple's Developer Enterprise Program. For those who don't know what that is, that's a platform which allows subscribed corporations to bypass Apple's App Store when deploying apps that are meant for internal use. Like Facebook's lunch ordering app or campus bus booking app, for example.

Facebook, as mentioned earlier, was paying users for their data, but as Apple had a data privacy policy on their App Store, elected to run those apps on the Developer Enterprise Program instead, passing the users off as their own staff. Google did much the same thing. Essentially they were in violation of the policy and hoping to bypass scrutiny.

And when Apple found out, there was hell to pay.

Well, OK, not exactly. What Apple did was to pull the plug on Facebook's membership. They did the same thing to Google a day later. The main point was to invalidate the certificates to Apple's business software, which meant that the tracking apps for both Facebook and Google were no longer able to work. However, since Facebook's legitimate internal apps were also distributed via this license, this left Facebook employees scrambling as their access to internal apps was summarily cut off from their iOS phones and Macs.

Did Apple do the right thing?

There's no argument about it - they did. Look, I'm an Android user and not at all an Apple fanboy, but let's give credit where it's due.

Facebook and Google broke clearly defined rules, and even attempted to sneakily bypass those rules in Apple's own house. Purely from a selfish point of view, Apple couldn't let that stand. From an ethical point of view, even if Facebook had technically broken no rules, there are exceedingly few iPhone users (who aren't also Facebook employees) who would take Facebook's side where data privacy was concerned. Hell, there are very few smartphone users who would trust Facebook where their data privacy is concerned, period! (I personally don't give a monkey's left nut. I'm of the opinion that if something comes free, like Facebook, there's always a catch. That said, I don't use Facebook much anymore so...)

This isn't even similar to the Patreon case, where users were arbitrarily de-platformed. In this case, Apple had more than ample cause for action, and past precedent to back it up. It's telling that Facebook and Google did not argue the decision but instead issued public apologies.

Did Apple do it for the right reasons?

Now, this one's mighty contentious. While Apple said they did this to "protect user privacy", I'm of the opinion that this came at a very convenient time for Apple.

Just prior to this episode, the iPhone app FaceTime had a serious bug that enabled anyone to turn someone else's iPhone into a live mic. And just when Apple was dealing with the inevitable fallout, who else should screw up but Facebook and Google, right in their backyard. Talk about a fall guy (or guys) to distract users from Apple's own mess!

Also, it's no secret that Apple's CEO Tim Cook and Facebook head honcho Mark Zuckerberg aren't exactly enamored of each other...  Cook might have done the right thing here, but I'd bet my last dollar he was experiencing no small amount of personal satisfaction while doing it.

Is it over?

Oh hell, no it ain't over. Apple might have shown Facebook and Google who's boss in this one instance, and there's no disputing that Apple accounts for a huge chunk of mobile web traffic in case any of them feel like pulling this stunt again in future, but Apple's dominance doesn't quite extend itself to desktop browsing, where Safari is currently languishing behind the likes of Chrome.

This is just the beginning, folks. The battle of the tech giants started a while back, but now shots have been fired. And some have landed. If nothing else, Facebook and Google have egg on their faces. Round One to Apple - now let's see how it goes.

How'd you like them apples? (heh heh)
T___T

Wednesday, 18 April 2018

Spot The Bug: Full Transparency

Welcome back, you bug-ridden programmers.

I spy bugs with my little eye...

Today's Spot The Bug edition will deal with one of the pitfalls of client-side scripting. I used to be very sure of what was going on with the Document Object Model (DOM) until this happened to me.

I was just going for a simple Proof Of Concept. There was supposed to be a square in the middle of the page, orange in color. Upon clicking the square,  once, it was supposed to become 50% transparent. And if I clicked on the semi-transparent square a second time, it was supposed to become fully transparent, i.e. invisible.

Here's the HTML code and JavaScript.
<!DOCTYPE html>
<html>
    <head>
        <title>Opacity Test</title>

        <style>
            .square
            {
                width: 200px;
                height: 200px;
                border: 1px solid #000000;
                cursor: pointer;
                margin: 10% auto 0 auto;
            }
        </style>

        <script>
            function adjustOpacity(obj)
            {
                var bgcolor = obj.style.backgroundColor;
                var opacity = parseFloat(bgcolor.replace("rgba(255,100,0,", "").replace(")", ""));

                if (opacity > 0)
                {
                    opacity -=0.5;
                }

                obj.style.backgroundColor = "rgba(255,100,0," + opacity + ")";
            }
        </script>
    </head>

    <body>
        <div class="square" style="background-color:rgba(255,100,0,1)" onclick="adjustOpacity(this);">
            Click me
        </div>   
    </body>
</html>


So, I had this bright idea to simply grab the transparency values from the DOM, meddle with the Alpha value and alter the DOM. No need to produce a lot of If cases, right? Sounded good in theory...

What went wrong

It simply didn't work. The square remained stubbornly, solidly orange no matter how many times I clicked it.


Why it went wrong

See, what happened was that I had coded my CSS inline, which is usually a bad idea anyway. What the browser (in this case, Chrome) did was to parse the HTML and build the DOM with the appropriate properties, making little simplifying tweaks along the way. I did a little test...
        <script>
            function adjustOpacity(obj)
            {
                var bgcolor = obj.style.backgroundColor; console.log(bgcolor);
                var opacity = parseFloat(bgcolor.replace("rgba(255,100,0,", "").replace(")", ""));

                if (opacity > 0)
                {
                    opacity -=0.5;
                }

                obj.style.backgroundColor = "rgba(255,100,0," + opacity + ")";
            }
        </script>


Which meant the background-color property ended up looking like this...


You see now why my JavaScript didn't work? It was looking for a pattern that didn't exist.

How I fixed it

Man, that took a lot of fixing. First, I defined three classes - full, half and none.
        <style>
            .square
            {
                width: 200px;
                height: 200px;
                border: 1px solid #000000;
                cursor: pointer;
                margin: 10% auto 0 auto;
            }

            .full
            {
                background-color: rgba(255, 100, 0, 1);
            }

            .half
            {
                background-color: rgba(255, 100, 0, 0.5);
            }

            .none
            {
                background-color: rgba(255, 100, 0, 0);
            }
        </style>


Then I fixed the JavaScript to change the class name instead of the transparency values, after commenting out my previous code.
        <script>
            function adjustOpacity(obj)
            {
                /*
                var bgcolor = obj.style.backgroundColor;console.log(bgcolor);
                var opacity = parseFloat(bgcolor.replace("rgba(255,100,0,", "").replace(")", ""));

                if (opacity > 0)
                {
                    opacity -=0.5;
                }

                obj.style.backgroundColor = "rgba(255,100,0," + opacity + ")";
                */

                var bgclass = obj.className.replace("square ", "");

                if (bgclass == "full")
                {
                    bgclass = "half";
                }
                else
                {
                    if (bgclass == "half")
                    {
                        bgclass = "none";
                    }
                }

                obj.className = "square " + bgclass;
            }
        </script>


Plus, some changes to the HTML.
    <body>
        <!--<div class="square" style="background-color:rgba(255,100,0,1)" onclick="adjustOpacity(this);">-->
        <div class="square full" onclick="adjustOpacity(this);">
            Click me
        </div>   
    </body>


And yep! It worked just fine now. This is how it looks after the first click...


And after the second click.


Conclusion

Try not to get cute with the DOM. Remember that all web development is basically writing code for browsers to execute, and as such, you're always at the mercy of the browser.

Also, avoid inline styling. Use CSS class names!

Stayed tuned for the next Spot The Bug. Be there or be square!
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