Popups with Javascript - A quick introduction

header image

We all know how annoying popups are. But have you ever thought how these popups work? How do popups interact with their parent windows? This article provides a quick introduction to working with popups in Javascript.

Opening a popup

A popup is just a new browser window that is usually displayed over the existing one. Javascript has a method to open a new window and control its behaviour.

window.open(url, name, features)

The open method is defined on the global window object. Hence it's only available on the client side. The first argument is the URL to open. The second argument is either the name or target of the window. The window target is similar to the target attribute in the HTML anchor tag. A target of _blank opens the window separately and that of _self replaces the current page.

The third argument is the most useful of all. It is a comma separated list of features that we want the window to have. We can control the height, width, positioning, statusbar, menubar, scrollbar and many other things. For a complete list, refer to w3schools here. An example to open a popup is shown below:

window.open('https://google.com', 'Google', 'scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,
width=0,height=0,left=-1000,top=-1000')

This opens Google in an unresizable popup with no scrollbars, statusbar, toolbar, menubar which is centered on the screen.

Passing data between windows

We can only interact with the popup through the window that created it (parent window). The open() method returns a reference to the child window which we can use to alter the contents of the popup and programatically control it. There's very limited control when it comes to opening popups with a different domain URL due to security reasons. For example, you can't just inject a script in a website by opening it in a popup. With same domain popups, you can perform all operations on them just like you normally would.

let childPopup1 = window.open('https://google.com', 'google')
// childPopup1 references the window object of the newly opened window
// for example you can close the popup like so
childPopup1.close()

let childPopup2 = window.open('', 'My Popup')
// childPopup2 opens a blank page
// this is not cross-domain, hence you can do stuff like this
childPopup2.document.write(`<script>alert('Hello World')</script>`)

In order to pass data from child window to the parent window, there is a property window.opener which refers to the parent window object. You can use this to communicate events/data from child to parent window.

<button onclick="start()">Open popup</button>

<script>
    // parent HTML file
    function greet(name) {
        alert(`Hello ${name}`)
    }
    function start() {
      window.open('/test.html')
    }
</script>

The parent HTML file is given above. One thing to note here is that every function or variable you define in the outermost scope is defined as a property on the window object (when run in browser). Hence the greet function can be called in two ways: greet('abc') or window.greet('abc').

<input type="text" id="name" placeholder="name" />
<button id="button">Send greetings</button>

<script>
    // child popup HTML content
    const btn = document.querySelector('#button')
    const input = document.querySelector('#name')

    btn.addEventListener('click', function() {
        const name = input.value
        if (window.opener) {
            // window.opener references parent window when opened as popup
            window.opener.greet(name)
        } else {
            // when this file is opened standalone, window.opener is null
            alert(`Hi ${name}`)
        }
    })
</script>

The child HTML file when opened inside of a popup can refer to the parent via window.opener and access the greet function in there. You can define functions in the parent window and invoke them from the child window, essentially passing events between the two.

Demo

I've put together a simple demo to show how popups can be used to login users. You can find it on glitch by clicking here. The source code is available on Github here. I'll explain the main parts of the app here.

It is an Express powerd application using Handlebars to render the views. The homepage has a button that opens a popup which leads to a login screen where the user can enter their credentials and the response of the API is sent back to the home page which then handles successful/unsuccessful login attempts. The homepage view code is given below:

<div class="wrapper fadeInDown">
  <div id="formContent">
    <h2 class="active">JS Popup Demo</h2>
    <input type="submit" class="fadeIn fourth" value="Click to Login" id="openPopupBtn" onclick="openPopup()">
    <div id="formFooter">
      <p id="status"></p>
    </div>
  </div>
</div>

<script>
  const popupBtn = document.querySelector('#openPopupBtn')
  const statusArea = document.querySelector('#status')
  let loginPopup

  function openPopup() {
    if (loginPopup && !loginPopup.closed) {
      alert('A popup is already open!')
      return
    }
    localStorage.removeItem('status')
    statusArea.innerText = ''
    const params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,
width=0,height=0,left=-1000,top=-1000`
    loginPopup = window.open('/login', 'Login', params)
  }

  // called when popup closes
  function popupClosed() {
    const status = localStorage.getItem('status')
    if (status === 'success') {
      initLogin()
    } else {
      alert('Popup was closed!')
    }
  }

  async function initLogin() {
    try {
      popupBtn.innerText = 'Loading...'
      popupBtn.setAttribute('disabled', 'true')
      const resp = await fetch('/users/me', {
        credentials: 'include'
      })
      if (resp.status !== 200) {
        throw new Error('error while logging in!')
      }
      const json = await resp.json()
      const { email } = json
      statusArea.innerText = `You are logged in as ${email}!`
    } catch(err) {
      console.error(err)
    } finally {
      popupBtn.removeAttribute('disabled')
      popupBtn.innerText = 'Click to Login'
    }
  }
</script>

When the popup is closed, it sets a key in localStorage which tells the main window what action to take. If the login was successful, the main window confirms this by making an API call to obtain user's profile using the cookie that was set by the server. This ensures that the screens after the homepage can only be accessed by a logged in user.

<div class="wrapper fadeInDown">
  <div id="formContent">
    <h2 class="active"> Sign In </h2>
    <form>
      <input type="text" id="email" class="fadeIn second" name="email" placeholder="email">
      <input type="password" id="password" class="fadeIn third" name="login" placeholder="password">
      <input type="submit" id="submit" class="fadeIn fourth" value="Log In">
    </form>
    <div id="formFooter">
      <p>Enter any random credentials</p>
    </div>
  </div>
</div>

<script>
  if (window.opener) {
    window.onbeforeunload = window.opener.popupClosed
    window.onunload = window.onbeforeunload // for compatibility purposes on older browsers
  }
  const form = document.querySelector('form')
  form.addEventListener('submit', async e => {
    e.preventDefault()
    const email = document.querySelector('#email').value
    const password = document.querySelector('#password').value
    const submitBtn = document.querySelector('#submit')

    if (!email) {
      window.alert('Email cannot be empty!')
      return
    }
    try {
      submitBtn.setAttribute('disabled', 'true')
      await fetch('/users/login', {
        method: 'POST',
        body: JSON.stringify({ email, password }),
        headers: {
          'Content-Type': 'application/json'
        }
      })
      localStorage.setItem('status', 'success')
      window.close()
    } catch(error) {
      // handle error here
      // localStorage.setItem('status', 'error')
    } finally {
      submitBtn.removeAttribute('disabled')
    }
  })
</script>

The login page's code is pretty self explanatory here: it runs the popupClosed function (from the parent window) when it gets closed. On a successful/unsuccessful login, it also sets a key in localStorage for some extra information. This means that if the key was not present in localStorage, the popup was force closed by the user and the UI can update accordingly. Similar is the case with other statuses.

I hope this quick introduction was useful and informative. If you have any doubts, comment below.

Share :
Tags :