Purpose

To provide a Rails-UJS alternative since Rails UJS is currently deprecated. Uses modern javascript instead of coffeescript.

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 a native fetch wrapper and a form wrapper that can marshal an HTML / JSON / XML / any response you want and can be listened for via event listeners. For a list of things to be implemented in the future, checkout the Roadmap below.

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";
window.mrujs = mrujs.start();
  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 fetch request or data-remote="true" form will emit the following events:

List of AJAX events ```js const AJAX_EVENTS = { /** * Before the ajax event gets sent. * You can view what data will be sent via: `event.detail.request` */ ajaxBefore: `ajax:before`, /** * When the fetch request is sent. You can view whats being sent via: * `event.detail.request` */ ajaxSend: `ajax:send`, /** * When a response error occurs. IE: 400, 404, 422, 500, etc (any status code not between 200 - 299) * The response error can be viewed via: `event.detail.response` */ ajaxResponseError: `ajax:response:error`, /** * When a >= 200 and <= 299 response is returned * You can view the full response via: `event.detail.response` */ ajaxSuccess: `ajax:success`, /** * When an actual error is raised. This doesnt include 404, 500, * errors, just like native fetch. * You can view the error via: `event.detail.error` * This will also generate an error in your console.log */ ajaxError: `ajax:error`, /** * After any fetch request, regardless of outcome * Does not have any accessible data besides the event itself */ ajaxComplete: `ajax:complete`, // NOT CURRENTLY IMPLEMENTED from ujs `ajax:aborted:required` `ajax:aborted:file` } ```

Cancelling Events

How to cancel AJAX events You can cancel events at anytime simply by calling `event.preventDefault()` or `event.stopImmediatePropagation()`: Example: ```js document.querySelector("form").addEventListener("ajax:before", (event) => { event.preventDefault(); }) document.querySelector("form").addEventListener("ajax:before", (event) => { event.stopImmediatePropagation(); }) // For extra certainty document.querySelector("form").addEventListener("ajax:before", (event) => { event.preventDefault(); event.stopImmediatePropagation(); }) ``` `ajax:send` is a special case and must be aborted with an abort controller.

Fetch

Fetch is called like you would expect. Except it will also prefill the X-CSRF-TOKEN for you. The difference between window.fetch and mrujs.fetch is that the first argument for the url is positional in window.fetch, but in mrujs.fetch it is required as part of the object.

mrujs.fetch can also leverage the events listed above for Ajax and fire the ajax:<lifecycle> events if dispatchEvents === true. When called this way, mrujs.fetch will not return a promise. Instead you should listen for ajax:success and then parse the response from event.detail.response.

document.addEventListener("ajax:success", (event) => {
  // equivalent to fetch().then((response) => response)
  event.detail.response
})

Examples

// Native Fetch
window.fetch("/url", { ... })
  .then((response) => ...)
  .catch((error) => ...)

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

// If you would prefer, you can also listen to the above ajax events.
// `mrujs.fetch({url: "/url", dispatchEvents: true})` fires all the above listed ajax events, `ajax:before`, `ajax:send`, etc

// mrujs fetch with events

window.mrujs.fetch({url: "url", method: "POST", dispatchEvents: true})
window.addEventListener("ajax:success", (event) => doStuff(event.detail.response))

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

window.mrujs.fetch({
  url: "/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: "/url",
  "Content-Type": "application/json"
}).then(response => {}).catch(error => {})

Using native fetch

Maybe you dont like my fetch wrapper, thats fine! To use native fetch heres all you have to do to include the CSRF-Token.

import mrujs from "mrujs"

window.mrujs =  mrujs.start()
window.fetch("url", {
  headers: {
    "X-CSRF-TOKEN": window.mrujs.csrfToken
  }
})

Remote forms

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

List of predefined `data-response` values ```js '*': '*/*', any: '*/*', text: 'text/plain', html: 'text/html', xml: 'application/xml, text/xml', json: 'application/json, text/javascript', ``` The above are all predefined for you

Example:

<!-- Sends a `application/json` and `text/javascript` accept header. -->
<form data-remote="true" data-response="json">
</form>

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

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

Roadmap

  • [ ] - 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.
Example of `data-confirm` ```html Logout Logout Logout ```
  • [ ] - add support for data-method="<REQUEST_TYPE>" for non-forms
  • [ ] - Asset pipeline, if someone would like to add support im open to it.
  • [x] - data-response='type' for forms.
  • [ ] - Other UJS features deemed necessary.

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