How to build your own Thanksgiving shopping list app with JavaScript

Thanksgiving is about to arrive in the US and, as every year, millions of people and I are preparing our shopping lists for Thanksgiving dinner. I thought this is a good opportunity to flex my coding muscles again and see how quickly I can build an online shopping list using JavaScript. To my surprise, it was easier and quicker than I originally thought and perhaps you like a little challenge yourself. Here is how it went…

Obviously, there are already many shopping and todo list apps out there and, of course, there is also still good old pen and paper. However, I was curious how much it would actually take to build a quick shopping list MVP (minimum viable product) webpage myself that I can then share with my wife over the web. Not wanting to reinvent the wheel from scratch, I remembered that there was the awesome TodoMVC project out there, originally designed to help you select an MV* framework, but with a few tweaks, I figured I could probably leverage one of these examples for my purposes.

In the end, I decided to reuse the React example, not because I thought React is better than any other framework or anything like that, but just because I felt it was in line with my rusty JavaScript skills and will allow me to get going quickly. Of course, any other framework will do as well, so feel free to pick and choose whatever you prefer and feel comfortable with!

Getting the app up and running

The beauty of the TodoMVC examples is that they are super simple to get them up and running. For the particular one I chose, all I had to do was to make the example directory available to some web server. I believe that’s the very same for any other example on that website as well. As for the web server, I decided to go for Nginx, but of course, any web server will do. Installing nginx is as simple as a sudo dnf -y install nginx on Oracle Linux 8:

[opc@thanksgiving-shopping-list ~]$ sudo dnf -y install nginx
Last metadata expiration check: 0:08:29 ago on Fri 20 Nov 2020 07:14:39 PM GMT.
Dependencies resolved.
======================================================================================================================================================================
 Package                                    Architecture          Version                                                      Repository                        Size
======================================================================================================================================================================
Installing:
 nginx                                      x86_64                1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e                  ol8_appstream                    569 k
Installing dependencies:
 gd                                         x86_64                2.2.5-7.el8                                                  ol8_appstream                    144 k
 jbigkit-libs                               x86_64                2.1-14.el8                                                   ol8_appstream                     55 k
 libXpm                                     x86_64                3.5.12-8.el8                                                 ol8_appstream                     58 k
 libjpeg-turbo                              x86_64                1.5.3-10.el8                                                 ol8_appstream                    155 k
 libtiff                                    x86_64                4.0.9-18.el8                                                 ol8_appstream                    188 k
 libwebp                                    x86_64                1.0.0-1.el8                                                  ol8_appstream                    273 k
 libxslt                                    x86_64                1.1.32-5.0.1.el8                                             ol8_baseos_latest                250 k
 nginx-all-modules                          noarch                1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e                  ol8_appstream                     24 k
 nginx-filesystem                           noarch                1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e                  ol8_appstream                     25 k
 nginx-mod-http-image-filter                x86_64                1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e                  ol8_appstream                     35 k
 nginx-mod-http-perl                        x86_64                1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e                  ol8_appstream                     46 k
 nginx-mod-http-xslt-filter                 x86_64                1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e                  ol8_appstream                     34 k
 nginx-mod-mail                             x86_64                1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e                  ol8_appstream                     64 k
 nginx-mod-stream                           x86_64                1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e                  ol8_appstream                     86 k

Transaction Summary
======================================================================================================================================================================
Install  15 Packages

Total download size: 2.0 M
Installed size: 5.3 M
Downloading Packages:
(1/15): libxslt-1.1.32-5.0.1.el8.x86_64.rpm                                                                                            11 MB/s | 250 kB     00:00
(2/15): jbigkit-libs-2.1-14.el8.x86_64.rpm                                                                                            2.3 MB/s |  55 kB     00:00
(3/15): gd-2.2.5-7.el8.x86_64.rpm                                                                                                     5.9 MB/s | 144 kB     00:00
(4/15): libXpm-3.5.12-8.el8.x86_64.rpm                                                                                                 48 MB/s |  58 kB     00:00
(5/15): libjpeg-turbo-1.5.3-10.el8.x86_64.rpm                                                                                          90 MB/s | 155 kB     00:00
(6/15): libtiff-4.0.9-18.el8.x86_64.rpm                                                                                                83 MB/s | 188 kB     00:00
(7/15): libwebp-1.0.0-1.el8.x86_64.rpm                                                                                                 94 MB/s | 273 kB     00:00
(8/15): nginx-all-modules-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch.rpm                                                         19 MB/s |  24 kB     00:00
(9/15): nginx-filesystem-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch.rpm                                                          24 MB/s |  25 kB     00:00
(10/15): nginx-mod-http-image-filter-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64.rpm                                              34 MB/s |  35 kB     00:00
(11/15): nginx-mod-http-perl-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64.rpm                                                      45 MB/s |  46 kB     00:00
(12/15): nginx-mod-http-xslt-filter-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64.rpm                                               33 MB/s |  34 kB     00:00
(13/15): nginx-mod-mail-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64.rpm                                                           63 MB/s |  64 kB     00:00
(14/15): nginx-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64.rpm                                                                    97 MB/s | 569 kB     00:00
(15/15): nginx-mod-stream-1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64.rpm                                                         50 MB/s |  86 kB     00:00
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
Total                                                                                                                                  56 MB/s | 2.0 MB     00:00
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                                                                                                              1/1
  Installing       : libjpeg-turbo-1.5.3-10.el8.x86_64                                                                                                           1/15
  Running scriptlet: nginx-filesystem-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch                                                                         2/15
  Installing       : nginx-filesystem-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch                                                                         2/15
  Installing       : libwebp-1.0.0-1.el8.x86_64                                                                                                                  3/15
  Installing       : libXpm-3.5.12-8.el8.x86_64                                                                                                                  4/15
  Installing       : jbigkit-libs-2.1-14.el8.x86_64                                                                                                              5/15
  Running scriptlet: jbigkit-libs-2.1-14.el8.x86_64                                                                                                              5/15
  Installing       : libtiff-4.0.9-18.el8.x86_64                                                                                                                 6/15
  Installing       : gd-2.2.5-7.el8.x86_64                                                                                                                       7/15
  Running scriptlet: gd-2.2.5-7.el8.x86_64                                                                                                                       7/15
  Installing       : libxslt-1.1.32-5.0.1.el8.x86_64                                                                                                             8/15
  Installing       : nginx-mod-http-image-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                              9/15
  Running scriptlet: nginx-mod-http-image-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                              9/15
  Installing       : nginx-mod-http-perl-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                                     10/15
  Running scriptlet: nginx-mod-http-perl-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                                     10/15
  Installing       : nginx-mod-mail-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                                          11/15
  Running scriptlet: nginx-mod-mail-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                                          11/15
  Installing       : nginx-mod-stream-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                                        12/15
  Running scriptlet: nginx-mod-stream-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                                        12/15
  Installing       : nginx-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                                                   13/15
  Running scriptlet: nginx-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                                                   13/15
  Installing       : nginx-mod-http-xslt-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                              14/15
  Running scriptlet: nginx-mod-http-xslt-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                              14/15
  Installing       : nginx-all-modules-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch                                                                       15/15
  Running scriptlet: nginx-all-modules-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch                                                                       15/15
  Verifying        : libxslt-1.1.32-5.0.1.el8.x86_64                                                                                                             1/15
  Verifying        : gd-2.2.5-7.el8.x86_64                                                                                                                       2/15
  Verifying        : jbigkit-libs-2.1-14.el8.x86_64                                                                                                              3/15
  Verifying        : libXpm-3.5.12-8.el8.x86_64                                                                                                                  4/15
  Verifying        : libjpeg-turbo-1.5.3-10.el8.x86_64                                                                                                           5/15
  Verifying        : libtiff-4.0.9-18.el8.x86_64                                                                                                                 6/15
  Verifying        : libwebp-1.0.0-1.el8.x86_64                                                                                                                  7/15
  Verifying        : nginx-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                                                    8/15
  Verifying        : nginx-all-modules-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch                                                                        9/15
  Verifying        : nginx-filesystem-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch                                                                        10/15
  Verifying        : nginx-mod-http-image-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                             11/15
  Verifying        : nginx-mod-http-perl-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                                     12/15
  Verifying        : nginx-mod-http-xslt-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                              13/15
  Verifying        : nginx-mod-mail-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                                          14/15
  Verifying        : nginx-mod-stream-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64                                                                        15/15

Installed:
  gd-2.2.5-7.el8.x86_64                                                                 jbigkit-libs-2.1-14.el8.x86_64
  libXpm-3.5.12-8.el8.x86_64                                                            libjpeg-turbo-1.5.3-10.el8.x86_64
  libtiff-4.0.9-18.el8.x86_64                                                           libwebp-1.0.0-1.el8.x86_64
  libxslt-1.1.32-5.0.1.el8.x86_64                                                       nginx-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64
  nginx-all-modules-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch                  nginx-filesystem-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.noarch
  nginx-mod-http-image-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64        nginx-mod-http-perl-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64
  nginx-mod-http-xslt-filter-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64         nginx-mod-mail-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64
  nginx-mod-stream-1:1.14.1-9.0.1.module+el8.0.0+5347+9282027e.x86_64

Complete!
[opc@thanksgiving-shopping-list ~]$ sudo systemctl enable nginx
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.
[opc@thanksgiving-shopping-list ~]$ sudo systemctl start nginx
[opc@thanksgiving-shopping-list ~]$

All that’s left to do is to clone the TodoMVC repo and copy the particular example folder into the web server root directory:

[opc@thanksgiving-shopping-list ~]$ git clone https://github.com/tastejs/todomvc
Cloning into 'todomvc'...
remote: Enumerating objects: 33889, done.
remote: Total 33889 (delta 0), reused 0 (delta 0), pack-reused 33889
Receiving objects: 100% (33889/33889), 71.29 MiB | 20.18 MiB/s, done.
Resolving deltas: 100% (13144/13144), done.
[opc@thanksgiving-shopping-list ~]$ sudo cp -r todomvc/examples/react/* /usr/share/nginx/html/

Once you have done that, you should be able to bring up the ToDo example by hitting your machine’s IP address in the browser:

TodoMVC example app

Amending the app to become a shopping list

As the next step, I had to amend the app to make it look more like a shopping list and less like a todo list. The example is self-contained and doesn’t include many files.

[opc@thanksgiving-shopping-list ~]# tree /usr/share/nginx/html/
.
├── 404.html
├── 50x.html
├── index.html
├── js
│   ├── app.jsx
│   ├── footer.jsx
│   ├── todoItem.jsx
│   ├── todoModel.js
│   └── utils.js
├── nginx-logo.png
├── node_modules
│   ├── classnames
│   │   └── index.js
│   ├── director
│   │   └── build
│   │       └── director.js
│   ├── react
│   │   └── dist
│   │       ├── JSXTransformer.js
│   │       └── react-with-addons.js
│   ├── todomvc-app-css
│   │   └── index.css
│   └── todomvc-common
│       ├── base.css
│       └── base.js
├── package.json
├── poweredby.png
└── readme.md

9 directories, 19 files

In order for it to become a shopping list, all that needed to change were a couple of lines in the index.html and js/app.jsx files:

In the index.html, I changed the title in line 5 and the “Double-click to edit a todo” in line 12. Optionally, you may want to change or remove lines 13 and 14.

Original index.html:

<!doctype html>
<html lang="en" data-framework="react">
        <head>
                <meta charset="utf-8">
                <title>React • TodoMVC</title>
                <link rel="stylesheet" href="node_modules/todomvc-common/base.css">
                <link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
        </head>
        <body>
                <section class="todoapp"></section>
                <footer class="info">
                        <p>Double-click to edit a todo</p>
                        <p>Created by <a href="http://github.com/petehunt/">petehunt</a></p>
                        <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
                </footer>

                <script src="node_modules/todomvc-common/base.js"></script>
                <script src="node_modules/react/dist/react-with-addons.js"></script>
                <script src="node_modules/classnames/index.js"></script>
                <script src="node_modules/react/dist/JSXTransformer.js"></script>
                <script src="node_modules/director/build/director.js"></script>

                <script src="js/utils.js"></script>
                <script src="js/todoModel.js"></script>
                <!-- jsx is an optional syntactic sugar that transforms methods in React's
                `render` into an HTML-looking format. Since the two models above are
                unrelated to React, we didn't need those transforms. -->
                <script type="text/jsx" src="js/todoItem.jsx"></script>
                <script type="text/jsx" src="js/footer.jsx"></script>
                <script type="text/jsx" src="js/app.jsx"></script>
        </body>
</html>

Modified index.html:

<!doctype html>
<html lang="en" data-framework="react">
        <head>
                <meta charset="utf-8">
                <title>Thanksgiving Shopping List</title>
                <link rel="stylesheet" href="node_modules/todomvc-common/base.css">
                <link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
        </head>
        <body>
                <section class="todoapp"></section>
                <footer class="info">
                        <p>Double-click to edit a item</p>
                        <p>Created by <a href="http://github.com/gvenzl/">gvenzl</a></p>
                </footer>

                <script src="node_modules/todomvc-common/base.js"></script>
                <script src="node_modules/react/dist/react-with-addons.js"></script>
                <script src="node_modules/classnames/index.js"></script>
                <script src="node_modules/react/dist/JSXTransformer.js"></script>
                <script src="node_modules/director/build/director.js"></script>

                <script src="js/utils.js"></script>
                <script src="js/todoModel.js"></script>
                <!-- jsx is an optional syntactic sugar that transforms methods in React's
                `render` into an HTML-looking format. Since the two models above are
                unrelated to React, we didn't need those transforms. -->
                <script type="text/jsx" src="js/todoItem.jsx"></script>
                <script type="text/jsx" src="js/footer.jsx"></script>
                <script type="text/jsx" src="js/app.jsx"></script>
        </body>
</html>

At this point a quick shout-out to petehunt for putting this example together! Thanks a lot!

Then I changed that big “todos” and “What needs to be done?” on the page itself. Both of these are located in the js/app.jsx file at line 157 and 160:

Original js/app.jsx:

<div>
    <header className="header">
        <h1>todos</h1>
        <input
            className="new-todo"
            placeholder="What needs to be done?"
            value={this.state.newTodo}
            onKeyDown={this.handleNewTodoKeyDown}
            onChange={this.handleChange}
            autoFocus={true}
        />
    </header>
    {main}
    {footer}
</div>

Modified js/app.jsx:

<div>
    <header className="header">
        <h1>Shopping list</h1>
        <input
            className="new-todo"
            placeholder="What needs to be bought?"
            value={this.state.newTodo}
            onKeyDown={this.handleNewTodoKeyDown}
            onChange={this.handleChange}
            autoFocus={true}
        />
    </header>
    {main}
    {footer}
</div>

Once I did that and hit refresh in my browser, the page appeared much more like a shopping list:

Shopping list website

The app was now ready to accept shopping list items and to check them off:

Shopping list with items

While at that stage the app was up and running and appeared just fine as a shopping list, it unfortunately did persist the items on local browser storage, meaning that if I wanted to open the list on a different device, it would be empty again and none of my items would show up. What I needed was some remote storage and changes to the app to send and retrieve the items from there.

Making the app “persistent”

The example app uses local browser storage via the built-in localStorage JavaScript object, which you can find in the store function at line 28 in the js/utils.js file:

store: function (namespace, data) {
  if (data) {
    return localStorage.setItem(namespace, JSON.stringify(data));
  }

  var store = localStorage.getItem(namespace);
  return (store && JSON.parse(store)) || [];
},
Storage of the shopping list items in the local browser

In order to make the app show the same data across different devices and in the long run, the data needed to be stored somewhere else in a more permanent manner. Luckily, in the day and age of cloud, cloud-based storage and databases are readily available so my idea was to swap out the calls to localStorage with some asynchronous REST calls with as minimal effort as possible, that will store and retrieve the data from somewhere else. The good news is that Oracle’s Always Free Autonomous Database cloud service offerings do come with REST Data Services out of the box and, as the name suggests, they are always free. Hence they were a perfect candidate to take on storage for the JSON object that the app produces.

Want to know more about how to create an Autonomous Database? Check out my previous blog post and look out for the “Always Free” option.

When you look at the app you will see that it only stores and retrieves a single JSON object that contains an array of the items on the shopping list, completed as well as still outstanding ones. In the case that one adds to the list, removes an item from it, or changes an existing one, the app will regenerate that JSON object to reflect these changes.

[
  {
    "id": "646e079c-6eff-41fc-b9a5-dbe825e11635",
    "title": "Turkey",
    "completed": false
  },
  {
    "id": "4fc91b3b-4d4a-41c5-8a70-6bdfe823decc",
    "title": "Gravy",
    "completed": false
  },
  {
    "id": "20074b58-40a7-435c-8ac2-c802bc8ec595",
    "title": "Beer",
    "completed": true
  }
]

As I wanted to keep this really simple and without major refactoring to the code (as said at the beginning, MVP), all I really needed was a single REST API that stores and retrieves that JSON object. Essentially that translates to a single-column table that would only ever have 1 row in it and then putting a GET and POST REST handler on top of it. Luckily, this can be easily done via just a few SQL commands with Autonomous Database (or Oracle Database + ORDS):

-- Create table "SHOPPINGLIST" to hold shopping list object
CREATE TABLE shoppinglist (items CLOB);

-- Create REST endpoints
BEGIN
  -- Enable schema for REST calls
  ORDS.ENABLE_SCHEMA(
      p_enabled             => TRUE,
      p_schema              => 'ADMIN',
      p_url_mapping_type    => 'BASE_PATH',
      p_url_mapping_pattern => 'shoppinglist',
      p_auto_rest_auth      => FALSE);

  -- Create new REST module "Shopping List persistence"
  ORDS.DEFINE_MODULE(
      p_module_name    => 'Shopping List persistence',
      p_base_path      => '/storage/',
      p_items_per_page => 25,
      p_status         => 'PUBLISHED',
      p_comments       => NULL);

  -- Create new template
  ORDS.DEFINE_TEMPLATE(
      p_module_name    => 'Shopping List persistence',
      p_pattern        => 'items',
      p_priority       => 0,
      p_etag_type      => 'HASH',
      p_etag_query     => NULL,
      p_comments       => NULL);

  -- Define POST REST handler to store JSON object
  ORDS.DEFINE_HANDLER(
      p_module_name    => 'Shopping List persistence',
      p_pattern        => 'items',
      p_method         => 'POST',
      p_source_type    => 'plsql/block',
      p_items_per_page => 25,
      p_mimes_allowed  => '',
      p_comments       => NULL,
      p_source         => 
         'DECLARE
            v_rows     PLS_INTEGER;
            v_payload  CLOB := :body_text;
         BEGIN
            SELECT COUNT(1) INTO v_rows FROM shoppinglist;
            IF v_rows = 0 THEN
               INSERT INTO shoppinglist (items) VALUES (v_payload);
            ELSE
               UPDATE shoppinglist SET items = v_payload;
            END IF;
         END;'
  );

  -- Define GET handler to retrieve JSON object
  ORDS.DEFINE_HANDLER(
      p_module_name    => 'Shopping List persistence',
      p_pattern        => 'items',
      p_method         => 'GET',
      p_source_type    => 'json/collection',
      p_items_per_page => 25,
      p_mimes_allowed  => '',
      p_comments       => NULL,
      p_source         => 
         'SELECT items FROM shoppinglist'
  );

  COMMIT;
END;

The important lines in the SQL script above are lines:

  • 2: which creates the single-column table called “shoppinglist”
  • 11, 17 & 25 which define the URL that the endpoint will be available at .../shoppinglist/storage/items
  • 32, 35, 41 – 51 which define the POST handler that executes the DML for inserting or updating the shopping list items object
  • 55, 58 & 64 which define the GET handler that retrieves the object from the table

Next and as the last step, the app itself needed to be modified to issue a REST call instead of using localStorage. The changes are mostly in js/utils.js with small modifications in js/app.jsx and js/todoModel.js to allow for the change to an asynchronous behavior.

In js/utils.js I introduced a new object remoteStorage to deal with the remote storage and to keep the original code as little changed as possible. Note that it has a placeholder for the REST URL on line 8:

var app = app || {};

(function () {
  'use strict';

  app.remoteStorage = {

    restURL: "<Your REST URL>",

    setItem: async function (namespace, data) {
      const request = await fetch(this.restURL, {
        method: 'POST',
          body: data
      });
    },

    getItem: async function (namespace) {
      const request = await fetch(this.restURL)
      const result = await request.json();
      if( request.ok ) {
        const items = result.items;
        if (result.items.length > 0) {
          return JSON.parse(result.items[0].items);
        }
      }
    }
  };

Then I changed the original store function to call the new remoteStorage instead of localStorage. Because the REST calls are done as an asynchronous call, the function itself also needed to be defined as async:

store: async function (namespace, data) {
  if (data) {
    // localStorage.setItem(namespace, JSON.stringify(data));
    await app.remoteStorage.setItem (namespace, JSON.stringify(data));
    return;
  }

  // var store = localStorage.getItem(namespace);
  var store = await app.remoteStorage.getItem(namespace);
  return store || [];
},

In js/todoModel.js at line 18, instead of just retrieving the list of items from Utils.store() which used to issue a synchronous call to localStorage, I instead initialized it to an empty array and then added a new asynchronous function to fetch the items from the remote storage:

app.TodoModel = function (key) {
  this.key = key;
  this.todos = [];
  this.onChanges = [];
};

app.TodoModel.prototype.fetchTodos = async function() {
  this.todos = await Utils.store(this.key);
};

All that was left was to call the new fetchTodos function in js/app.jsx file as part of the didComponentMount function, which now also had to become asynchronous…

componentDidMount: async function () {
  await this.props.model.fetchTodos();
  var setState = this.setState;

… and tell the render function to return an empty array in case there are no items yet:

 render: function () {
  var footer;
  var main;
  var todos = this.props.model.todos || [];

The result is an over the web persistent shopping list backed by a REST enabled database that can be shared with others:

Click on the image to try it for yourself!

Last but not least, a quick shout-out to Todd Sharp, who saved my sanity by helping me with the asynchronous function calls. 🙂

What’s next? What’s missing?

As mentioned at the beginning, the app I have put together was always intended as an MVP. Because of that, there are areas that are lacking or could be better, of course.

Now that the app is using a database as the backend storage, there is really no need to persist just one big JSON object with all the shopping list items within it. Instead, the app could be rewritten to treat each shopping list item as an individual record that represents a row within a three-column table within the database.

Alternatively, given that Oracle Database and Autonomous Database both have native JSON support, the app could also be rewritten to store the shopping list items in a JSON collection within the database, each item on the list representing a single JSON document.

The REST endpoint itself should only be available to properly authenticated applications and not, like right now, to the public.

And, of course, as the app is already using a database, it could also provide individual shopping lists to users that can log in and out from the app.

Are you up for a challenge?

If this little app inspired you and you would like to flex your coding muscles a bit, why don’t you have a go at the outstanding items I’ve just listed? Or perhaps build your own little persistent shopping list app? You can find the code at https://github.com/gvenzl/Oracle-shopping-list-app. Feel free to share the results with me either as a comment on this post or on Twitter (just tag me @GeraldVenzl and use the hashtag #shoppinglistapp).

Hey @GeraldVenzl, I have just built my own #shoppinglistapp. Check it out here: [URL]

This post was originally published at https://blogs.oracle.com/developers/thanksgiving-shopping-list

Rating: 1 out of 5.

One thought on “How to build your own Thanksgiving shopping list app with JavaScript

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.