DEV Community

Askarbek Zadauly
Askarbek Zadauly

Posted on

Quick Look feature for a Web App

Introduction

Hi Dev.to community. My name is Askarbek. I'm a frontend developer. I wanted to share my process of building one component I developed recently as a small side project and see if I can get some ideas.

Short description

One of my favorite features of MacOS is the Quick Look. and I'm sure it could be very useful for a Business Application.

What is Quick Look and why it's so good?

It's when you're browsing a folder and press Spacebar it shows the content of the current file. It can be a picture, video or a text file. The good thing is that you can select next or previous file by pressing arrow buttons without closing the Quick Look. And when you eventually close Quick Look by pressing Spacebar again the selected file would be the last viewed file.

Business Application

By Business Application I mean applications like CRM, ERP etc. Those Apps can be very complex and they do solve complex problems but, it all comes down to two tools: Table and Form views. A Table view lists some entities and a Form view shows details of a single entity. The Form view also allows a user to edit the current entity.

Use Case

When you open a Table view, you can select a line then press Spacebar that would open a Form view and then keep navigating through the table lines by pressing up/down arrows without closing the Form view.
As an additional features a user should be also able to:

  • Edit the data
  • Save or discard changes
  • Change the status (if applicable)

So let's build a very simple Business App that implements the use case.

Project stack

I'm going to use Svelte, Tailwind and Vite as my frontend stack. I'm not gonna need a backend for this project since I am not building a "full-blown app", it's just a POC so I'll just use a json file as a datasource.

Hotkeys

Table and Form view components will be made mostly with html and css, nothing complex. The main feature we'll be focusing on is the hotkeys. Here is the list of initial hotkeys we'll implement:

  • Spacebar: Open/Close View form (read mode by default)
  • Enter: Switch to edit mode of the View form
  • Ctrl + Enter: Save changes and switch back to read mode of the View form
  • Esc: Discard changes and switch back to read mode
  • N: Open an empty View form to create a new item
  • S: Change the status of the current item

So I should explain the idea of read/edit mode. When we open a View form it should display all the details of the currently selected table line (entity) as a preview (readonly). That way we can guarantee that all other hotkeys like: up/down arrows and Spacebar will keep working and made sense. If a View form would have some <input> then a user by typing a space in it could close the Form since Spacebar is a hotkey for open/close. Of course we could override the hotkey while input is focused but IMO it would overcomplicate the UX. Two modes of a Form (read/write) will strictly divide the UX, since in edit mode the hotkeys mentioned will stop working until we switch back to read mode by saving or discarding changes.

Code

We'll create a separate component called HotkeyDispatcher that will handle the hotkey logic and fire appropriate events to it's parent component. The HotkeyDispatcher will not have any layout.

To handle hotkeys we need a global dispatcher to add event listeners to, so we'll use svelte:window element for that.

<svelte:window on:keydown={onKeyDown} on:keyup={onKeyUp} />
Enter fullscreen mode Exit fullscreen mode

I'll create a boolean variable for each hotkey and will set it to true or false depending on which key is pressed right now. We should also prevent default behavior of the browser so it won't interfere with our logic. Also browser's default behavior is the reason why I'm using keydown instead of keypress.

const onKeyDown = (e: KeyboardEvent) => {
  if (isOpen) {
    if (isEditMode) {
      onCtrlEnter = e.code === 'Enter' && e.ctrlKey && !e.shiftKey;
      onEscape = e.code === 'Escape';
    } else {
      if (e.code === 'Space' || e.code === 'Enter' || e.code === 'ArrowDown' || e.code === 'ArrowUp')
        e.preventDefault();
      onArrowDown = e.code === 'ArrowDown';
      onArrowUp = e.code === 'ArrowUp';
      onSpace = e.code === 'Space';
      onEscape = e.code === 'Escape';
};

const onKeyUp = (e: KeyboardEvent) => {
  onArrowDown = false;
  onArrowUp = false;
  onSpace = false;
  onEscape = false;
  onEnter = false;
  onCtrlEnter = false;
};
Enter fullscreen mode Exit fullscreen mode

Depending on which key is pressed we dispatch a corresponding event to a parent component.

const SpaceHandler = () => {
  isOpen = !isOpen;
  if (isOpen) {
    dispatch('Open');
  } else {
    dispatch('Close');
  }
};

$: onSpace && SpaceHandler();
Enter fullscreen mode Exit fullscreen mode
const EscapeHandler = () => {
  if (isEditMode) {
    isEditMode = false;
    dispatch('Discard');
    dispatch('ViewMode');
  } else {
    isOpen = false;
    dispatch('Close');
  }
};

$: onEscape && EscapeHandler();
Enter fullscreen mode Exit fullscreen mode

Now we can drop in our HotkeyDispatcher into a Page that has all the html and css we need. In our case the Page has two components DataTable and FormView. We open or close FormView and switch its modes as a response to HotkeyDispatcher's events.

<script lang="ts">
  const OnNextRow = () => {
    if (selectedRow < rows.length - 1) selectedRow++;
  };

  const OnPrevRow = () => {
    if (selectedRow > 0) selectedRow--;
  };

  const OnOpen = () => {
    showForm = true;
  };

  const OnClose = () => {
    showForm = false;
  };

  const OnEditMode = () => {
    editMode = true;
  };

  const OnViewMode = () => {
    editMode = false;
  };
</script>

<HotkeyDispatcher 
  on:NextRow="{OnNextRow}" 
  on:PrevRow="{OnPrevRow}" 
  on:Open="{OnOpen}" 
  on:Close="{OnClose}" 
  on:EditMode="{OnEditMode}"
  on:ViewMode="{OnViewMode}"
  on:Save="{OnSave}"
  on:Discard="{OnDiscard}"
/>

<DataTable bind:selectedRow columns="{columns}" rows="{rows}" fields="{fields}" />

{#if showForm}
  <FormView
    editMode="{editMode}"
    data="{rows[selectedRow]}"
    bind:modifiedData
    fields="{fields}"
    fieldOrder="{fieldOrder}"
    hotkeyTrigger="{trigger}"
  />
{/if}
Enter fullscreen mode Exit fullscreen mode

Result

I didn't include the whole code here otherwise the post would get too big. You can checkout the full source on GitHub any suggestions are welcome!

Top comments (0)