Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML5

Why HTML5? Why Not? UI / UX Versus Storage Challenge (Part 2 of N)

5.00/5 (7 votes)
29 Dec 2021CPOL6 min read 13.7K   85  
Discussion of UX via ContextMenu & Copy to Clipboard
Desktop apps have the helpful ContextMenu feature but most web apps don't. Also, have you ever tried to interact with the Clipboard from a Web app? It is a convoluted mess, but we solve it here.

Note: This version of the software is in the branch named updateUI.

You can also try the code out at my CodePen.io => https://codepen.io/raddevus/pen/eYGVgVg[^].

Introduction

Read Part 1 of this series: Why HTML5? Why Not? UI / UX Versus Storage Challenge (Part 1 of N)[^]

Background

I decided that the next thing we needed in this little app was a way to delete entries.

That opens a huge can of worms related to UI / UX. I've decided to create a User eXperience that feels like a desktop app so I've added a context menu when an item is right-clicked (see next image). However, this does not translate to Mobile apps so in the future, I have to provide a way to do this for mobile.

Image 1

Two Main Ideas

The two things I'd like to talk about in this article are:

  1. How I built the ContextMenu & how it works
  2. Copying to clipboard via a web app -- it's so tricky for no good reason

ContextMenu In Web App

A Context Menu that displays upon the user right-clicking an element in your User Interface is a basic human right. It's the way we have interacted with Desktop apps for years and it is very intuitive. Of course, mobile apps don't require a pointing device and really only accept touch commands which are equivalent only to a left mouse button click. Mobile apps have no concept of the right-click and so do not offer a context menu. It is unfortunate.

Build Your Own ContextMenu

This also means that in order to provide the simple & intuitive functionality of a ContextMenu, you will have to build it yourself. To do this, you must understand CSS styles & Browser events.

Bootstrap Styles

In order to help me do the graphic design of the ContextMenu, I've employed the latest version of Bootstrap[^].

That provides me some nice styles that make my context menu mimic context menu styles from Windows.

However, it does not provide me with any functionality for the context menu. For that, we'll need our old friend JavaScript.

Here's what the HTML looks like:

HTML
<div class="RADcontextMenu card border border-primary">
 <div class="card-body">
 <ul>
  <li onclick="onContextMenuClick(this)" id="delete_item" 
      class="contextMenuItem">Delete item</li>
  <li onclick="onContextMenuClick(this)" id="copy_address" 
      class="contextMenuItem">Copy image address</li>
  <li onclick="onContextMenuClick(this)" id="other_thing" 
      class="contextMenuItem">Do other thing</li>
  <li onclick="onContextMenuClick(this)" id="more_stuff" 
      class="contextMenuItem">Do more stuff</li>
 </ul>
 </div>
 </div>

It's just a normal div tag and an unordered list of items. Each item in the list is a menu item that appears and is clickable. Each menu item will fire the same JavaScript method (onContextMenuClick()) which passes in the HTML element which was clicked.

From that element, we will get the associated ID value so we can easily determine which context menu logic should fire.

HTML Class Names

Many of those class names (which are used to apply CSS styles) come from Bootstrap.

card, border, border-primary and card-body all come from the Bootstrap styles.

The one style I've created is the main RADcontextMenu style.

Here are all the styles in the main.css.

CSS Styles Used to Create User Interface

When the user hovers over a context menu item, the background is turned blue and the font is turned white:

CSS
.contextMenuItem:hover {background-color: #4281F4;color:white; }

The main context menu is hidden from view (visibility:hidden; display:none;) until the user right-clicks on top of one of the images.

CSS
.RADcontextMenu {z-index:1000;
                    visibility:hidden;
                    display:none;
                    width:200px;
                    height:100px;
                    position:absolute;
                    font-family:'Microsoft Sans Serif';
                    font-size:11px;
    }

When the user right-clicks on one of the images, then the context menu appears and here is the code that handles that:

JavaScript
function drawContextMenu()
{
  isContextMenuDisplayed = true;
  console.log('drawContextMenu : ' + new Date())
  //console.log($('.EScontextMenu').text());
  document.querySelector('.RADcontextMenu').style.visibility = "visible";
  document.querySelector('.RADcontextMenu').style.display = "block";
  document.querySelector('.RADcontextMenu').style.top = mousePos.y + "px";
  document.querySelector('.RADcontextMenu').style.left = mousePos.x + "px";
}

We set the styles so that the context menu will be visible and displayed (block).

We also make sure the top left of the context menu is at the same location (mousePos.x, mousePos.y) where the user right-clicks.

We are able to use mousePos.x & mousePos.y because all while the user is moving the mouse over the page, we are tracking the movement with the following JavaScript function.

JavaScript
function onMouseMove(e)
{
  mousePos = { x: e.clientX, y: e.clientY };
}

Once the user clicks an item in the context menu, we are able to know which one is clicked by its ID which we use in a case statement.

Here's the code of the two important context menu items that we are working with.
Note: This is not the full source of onContextMenuClick(e).

JavaScript
function onContextMenuClick(e)
{
  console.log("onContextMenuClick()");
  hideContextMenu();
  
  console.log(e.id);
  switch (e.id)
  {
    case 'delete_item':
      {
        // load all localStorage images into array.
        let allImg = JSON.parse(localStorage.getItem("allImg"));
        // remove targeted item
        allImg.splice(currentHoverImageIdx,1);
        // store updated array to localStorage
        localStorage.setItem("allImg",JSON.stringify(allImg));
        removeMainDiv();
        displayImages();
        break;
      }
    case 'copy_address':
      {
        // ### --- Because of limitations on copying to clipboard 
        // ###     we have to copy from text input.
        // ####    Since we don't want that 
        let clipboard = document.querySelector("#clipboard");
        clipboard.style.visibility = "visible";
        clipboard.style.display = "block";
        clipboard.value=currentImageAddress;
        clipboard.select();
        clipboard.setSelectionRange(0,1000);
        document.execCommand("copy");
        clipboard.style.visibility = "hidden";
        clipboard.style.display = "none";

        break;
      }

Deleting an Item

Now, if the user chooses to delete an item, we simply:

  1. load all images (URLs) from localStorage into a local array
  2. call the JavaScript splice() method on the array to remove the current item by index
  3. save all the items (minus the one we just deleted) back to localStorage
  4. remove the main div (clear the screen)
  5. displayImages() - display all images which are in localStorage

Copying the Image URL

We also want to make it easy for the user to get the image URL.

I wanted to copy the image URL to the user's clipboard. Copying from the clipboard in a web app has been a strange and arduous task due to odd Browser functionality. Somewhere, some developers seem to think that copying to the clipboard is a dangerous activity. I'm not sure why. I could see that copying from the clipboard might be dangerous but not the other way.

Copying to Clipboard Workaround

In this case, to make this work cross-browser / cross-platform, I have found a workaround.

I have a hidden input text box that I've named clipboard.

I set the text value of that text box to the value of the image URL.

Next, I select all of the text that is in the text box: clipboard.setSelectionRange(0,1000);

There is a special command of the document called execCommand() that takes different argument values depending on what you want to do. It allows us to copy to the user's clipboard and it copies the last thing selected so we call that method now: document.execCommand("copy");

Immediately after we do that, we hide the clipboard input text box again and the user never sees it happen.

Now the image URL is copied to the clipboard and the user can paste it anywhere.

RFC: Have a Better Way of Copying to User's Clipboard?

Request For Comments - Please let me know if you know of a better way to copy to the clipboard.

I've researched this fairly extensively and there are numerous offerings, but not much that works everywhere.

Summary

It takes a lot of work to do things that were automatic back in the days of WinForm development.

Now we have to style out our own controls and work out the event handling ourselves, even though in Visual Studio on WinForm dev, it had become quite easy.

All of this was just to get to a good UX (User Experience) aka: intuitive interface.

Looking Forward & Data Sharing

However, none of this goes toward making our data more easily accessible. I'm going to reseach some ways to do that and we'll cover that in the next article.

History

  • 29th December, 2021: First publication

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)