AMP

Add custom JavaScript to AMP pages with amp-script

AMP strives to provide solutions that get developers where they want to be quickly and hassle free. However, some types of functionality are too tailored to individual use cases or require custom JavaScript. The AMP framework is expanding to accommodate these needs with <amp-script>. The amp-script component allows AMP developers to introduce custom JavaScript that expand page features and render as valid AMP.

This tutorial will help you build an element with amp-script that communicates password requirements to end users.

Prerequisites:

  • Familiarity with JavaScript and AMP.
  • Node.js and npm installed. You can install Node.js here and npm here.
  • A local code editor.

Get started

Use the following commands to download and install the starter code:

$ git clone git@github.com:CrystalOnScript/vanilla-js-amp-script.git
$ cd starter-code
$ npm install

Or download the starter code here.

Test the app

Run npm start and open the browser window to http://localhost:8080/. Our base application is an <amp-form> component that requires an email and password for user signups. It includes some basic styling and layout.

After selecting the password input element, the AMP on="tap:rules.show" action is triggered. This reveals the <div id="rules" hidden> element, where our password requirements are listed.

Play around with different passwords! If you press the submit button before all requirements are met <amp-form>’s pattern attribute will throw an error.

Frustratingly, it will not explain what is missing. The tool tip only states that the required pattern was not met. With the introduction of <amp-script> we can build out additional functionality that evaluate the user’s input and communicate what is missing!

Import amp-script

Like nearly all AMP components, <amp-script> requires a script tag. Open index.html and add the amp-script script tag to the head of the document.

<head>
 ... 
<script async custom-element="amp-script" src="https://cdn.ampproject.org/v0/amp-script-0.1.js"></script>
  ...
</head>

The amp-script component must be wrapped around the elements it wishes to mutate and interact with. amp-script is unable to mutate or interact with anything that is not a direct child of it.

Our functionality relays and changes the DOM of our form. Place the <form> element inside the <amp-script> component.

<amp-script src="http://localhost:8080/js/script.js">
    <form method="post"
    action-xhr="/form"
    target="_top"
    class="card">
        . . . 
    </form>
</amp-script>

Note: Currently, the src attribute must point to an absolute URL.

The src attribute points to the filepath http://localhost:8080/js/script.js. Create a directory titled js at the root of your repositior and add the script.js file.

Inject custom script

Open the newly created script.js file and add console.log("amp-script here"). Reload the page and open the DevTools console to verify it successfully logged "amp-script here".

Important: amp-script is still in experimental mode. You will need to enable it inside the console by running AMP.toggleExperiment('amp-script') and confirming that it returns true. Read more about experimental components here.

Imported script logic from the amp-script src attribute is debugged in the console, same as JavaScript inside non-AMP pages.

Add logic

Now that we’ve confirmed our script.js file is being injected correctly, let’s add functionality!

HTML elements within the <amp-script> tag are now accessible via standard DOM methods from within script.js, minus a few small caveats.

Declare elements

Our password checker needs to capture the password input box, declare it on script.js.

const passwordBox = document.getElementById("passwordBox");

Like non-AMP pages, we can test if it was captured correctly by logging elements into the console.

Set checks

The script file will hold all of our password requirement logic by using regular expressions to check that passwords pass validation requirements. We want to communicate to our users when each check has been met, so each requirement has its own RegEx. Declare the checks object by adding the code below to script.js:

const passwordChecks = [
  {
    test: (password) => (password.search(/[a-z]/) >= 0),
    elementId: "lowercase"
  },
  {
    test: (password) => (password.search(/[A-Z]/) >= 0),
    elementId: "capital"
  },
  {
    test: (password) => (password.search(/[0-9]/) >= 0),
    elementId: "number"
  },
  {
    test: (password) => (password.search(/[^a-z0-9]/i) >= 0),
    elementId: "special"
  },
  {
    test: (password) => (password.length >= 8),
    elementId: "eight"
  }
]

Create functionality

Here is where we add our functionality, by creating a function! We'll declare one called initCheckPassword that takes a single argument. The argument will be the value our user enters into the password input. We’ll run it through our checks above and change our defined password rules text green or red, depending on if it has been met or not.

function initCheckPassword(element) {

};

We’ll then see if the element we pass as our argument can pass the defined checks.

function initCheckPassword(element) {
 const checkPassword = () => {
    passwordChecks.forEach((item) => {
      let passed = item.test(element.value);
       // passed/fail logic 
    });
};

User actions can trigger mutations within the amp-script component , allowing amp-script to register event listeners.

Our function will listen for two events, keyup for when a user types into the input box, and change in case our user pastes their password.

function initCheckPassword(element) {
 const checkPassword = () => {
    passwordChecks.forEach((item) => {
      let passed = item.test(element.value);
       // passed/fail logic 
    });
    // is called when user types in input
  element.addEventListener("keyup", checkPassword);
  // is called if user pastes into input 
  element.addEventListener("change", checkPassword); 
  };
};    

Inside the checkPassword function, we’ll toggle a class that changes our text to green if the check is passed.

function initCheckPassword(element) {
  const checkPassword = () => {
    passwordChecks.forEach((item) => {
      let passed = item.test(element.value);
      // captures element
      let checkText = document.getElementById(item.elementId)
       // passed/fail logic 
       checkText.classList.toggle('pass', passed)
    });
  } 
    // is called when user types in input
    element.addEventListener("keyup", checkPassword);
    // is called if user pastes into input 
    element.addEventListener("change", checkPassword);
};

For this to work, we will need to add a check class to all the items and define both check and pass within the <style amp-custom> tag in the page head.

<ul>
  <li id="lowercase" class="check">Lowercase letter</li>
  <li id="capital" class="check">Capital letter</li>
  <li id="number" class="check">Number</li>
  <li id="special" class="check">Special character (@$!%*?&)</li>
  <li id="eight" class="check">At least 8 characters long</li>
</ul> 
.check {
  color:#c11136;
}
.check.pass {
  color: #2d7b1f;
} 

Refresh the page and type into the password input. The elements corresponding to whether the check passed or failed should turn red or green accordingly!

Ensure you call to initCheckPassword in script.js and pass it passwordBox as an argument to setup the event handlers. Our logic is now complete!

initCheckPassword(passwordBox);

Congratulations!

You have successfully imported custom JavaScript into a valid AMP page and created functionality not possible with the <amp-form> component alone.