0.01
The project is in a healthy, maintained state
A rails plugin for mrujs to make Turbolinks work.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
 Dependencies

Development

Runtime

~> 6.1.3, >= 6.1.3.2
 Project Readme

Purpose

To provide an upgrade path for those looking to retain the features of UJS, but use a currently maintained library written in Typescript.

What does mrujs mean?

Modern Rails UJS.

Does this support .js.erb

No. Rails 6.1+ requires a change in the content-security policy in relation to running arbitrary javascript scripts which means .js.erb is not supported. .js.erb is a security concern, and also requires a lot of nonce code generation and checks to work properly.

What can it do right now?

In it's current state, Mrujs is has about 95% feature parity with UJS. The goal of Mrujs is to be a drop-in replacement for UJS, but this is not possible in all cases.

Getting Started

  1. Install mrujs
yarn add mrujs
  1. Go to your Webpacker entrypoint and import mrujs and start it up.
// app/javascript/packs/application.js

// ... other stuff

import mrujs from "mrujs";
mrujs.start();

// If you want it to work like Rails ujs.
import Rails from "mrujs";
Rails.start()

// mrujs is available globally as window.Rails or window.mrujs
  1. Using on a form

If using Turbo, make sure to set Turbo to false.

<%= form_with scope: Model, data: {remote: "true", turbo: "false"} do |form| %>
  <%= form.label :name %>
  <%= form.text_field :name %>

  <%= form.submit "Submit", data-disable-with: "Submitting..." %>
<%= end %>

<form action="/" method="post" data-remote="true" data-turbo="false">
  <input id="foo" name="foo">
  <input type="submit" value="Submit">
</form>
  1. Stopping Mrujs

If you would like to stop Mrujs, feel free to do the following:

window.mrujs.stop();

This will remove all mutation observers and event listeners.

Ajax

Events

All events bubble and are cancellable by default.

Bubbling means they will always start from the form that submits it and continue upwards all the way to the top of the DOM. Bubbling can be stopped via event.stopImmediatePropagation(). I also allowed events to be preventable. IE: event.preventDefault() on an ajax event will cause it to stop running.

A form or link with data-remote="true" form will emit the following events:

ajax:before
ajax:beforeSend
ajax:send
ajax:request:error
ajax:response:error
ajax:error # => will catch both request and response errors.
ajax:success # => will only fire if no errors
ajax:complete
ajax:stopped # => when event.preventDefault() is called or event.detail.fetchRequest.cancel(event) is called.
  • Diagram of Ajax form submissions

Screen Shot 2021-06-10 at 3 23 02 AM

mrujs-remote-form-event-diagram.pdf

All properties available on event.detail

element // => either form or link element that initiated request
fetchRequest // => FetchRequest (wrapper around Request)
request // => Request
fetchResponse // => FetchResponse (wrapper around Response)
response // => Response
submitter // => The button clicked to initiate the submit. Button / Link element
submission // => Either FormSubmission or LinkSubmission.

Note about remote / ajax links

<a href="/" data-method="delete" data-remote="true"> does not go through the submit event, it skips to ajax:before, this is due to how submit events are intercepted.

Cancelling Events

Cancelling Ajax events is fairly straightforward with only 1 edge case with ajax:send.

You can cancel events at anytime simply by calling event.preventDefault().

Example:

document.querySelector("form").addEventListener("ajax:before", (event) => {
  event.preventDefault();
})

ajax:send is a special case and must be aborted with an abort controller. To do so, you would do the following:

document.querySelector("form").addEventListener("ajax:send", (event) => {
  event.detail.fetchRequest.cancel(event)
  // => If event is not passed in, it wont fire a `ajax:stopped` event.
})

WARNING:

Be careful if you call event.stopImmediatePropagation() or event.stopPropagation(). This will cause ajax:stopped to not fire and will leave your buttons in a disabled state that you must handle.

Fetch

Fetch is called like you would expect. Except it will also prefill the X-CSRF-TOKEN, add an AbortController, and provide a few other niceties for you.

mrujs.fetch accepts the exact same interface as window.fetch so there is no new syntax to learn.

mrujs.fetch should not be used with cross domain fetches. Cross-domain fetches should be called via window.fetch with proper options attached to it.

Examples

To receive a json response, make sure to set the Accept header to "application/json" like so:

window.mrujs.fetch(
  "/url",
  {"Accept": "application/json"}
).then(response => {}).catch(error => {})

To send a json payload, make sure to set the Content-Type header to "application/json" like so:

window.mrujs.fetch(
  "/url"
  {
    "Content-Type": "application/json",
    body: JSON.stringify(data)
  }
).then(response => {}).catch(error => {})

Remote forms

Remote forms can also negotiate the proper Accept headers. To do so, set the data-type='json' to tell the server you can only accept json.

Mrujs defined a number of predefined data-type 's for you.

  '*': '*/*',
  any: '*/*',
  text: 'text/plain',
  html: 'text/html',
  xml: 'application/xml, text/xml',
  json: 'application/json, text/javascript',

This means you can pass a data-type="*", data-type="text", data-type="xml", and so on as long as it matches with that key. If you need a custom Accept header, you will have to simply do it yourself like so:

<form data-type="application/xml, text/xml">

Examples:

<!-- Sends a `"application/json, text/javascript"` Accept header. -->
<form data-remote="true" data-type="json"></form>

<!-- Sends an XML accept header -->
<form data-remote="true" data-type="application/xml"></form>

<!--- Shorthand -->
<form data-remote="true" data-type="xml"></form>


<!-- Sends a default '*/*' Accept header. -->
<form data-remote="true"></form>

Anchor methods

Sometimes you want to add additional methods to your links. Heres how to do that:

<a data-method="delete" href="/logout">

This will create a fetch request and then navigate to the new page if redirected, or refresh the current page is no redirect found.

Roadmap

  • - add support for data-method="<REQUEST_TYPE>" for non-forms it.
  • - data-type='type' for forms.
  • - Alias window.Rails to window.mrujs (Allows drop in replacement)
  • - Allow the use of data-confirm=""
  • - Provide a confirm dialog for data-confirm
  • - Allow users to provide their own confirm function to data-confirm
  • - Allow ajax:send to be cancelled via abort controllers.
  • - Asset pipeline, if someone would like to add support im open to it

Developing locally

  1. Clone the repo
git clone https://github.com/ParamagicDev/mrujs
cd mrujs
  1. Install packages
yarn install

View Dev Server

yarn start

Run tests

yarn test

Rails

There is also a Rails dummy app attached in this repo for testing.

Installation

Top level:

bundle install

Starting

Must be run within the test/ruby/dummy directory.

cd test/ruby/dummy && bundle exec rails server

Tests

From any where outside of the test/ruby/dummy directory:

bundle exec rake test

Known Issues

If you are using the Turbolinks gem, you can safely disable it. Having it enabled means forms / ajax requests will not properly work as intended.