Git Product home page Git Product logo

phoenix_ujs's Introduction

phoenix_ujs

Unobtrusive vanilla JS toolset for Phoenix framework

npm version

Purpose

A compact port of jquery-ujs for Phoenix framework (the library is agnostic, so you can use it with other backends as well). The library does not requires jQuery and covers only essential needs:

  • non-'GET' links (POST, PUT, PATCH, DELETE, etc)
  • confirmation and/or remote request on clicking link or submitting form
  • refreshing CSRF inputs in forms (fix browser form caching)

Installation

npm i --save phoenix_ujs

If your js build system supports commonjs require and/or import methods (Brunch, Webpack, Browserify) add the next code at the top of your main js file:

var UJS = require("phoenix_ujs");
// or
import "phoenix_ujs";

For other cases you can use one of the package files:

~ : ls node_modules/phoenix_ujs
ujs.js      # browser version
ujs.min.js  # minified browser version
ujs.cjs.js  # commonjs version (used in `require` by default)
ujs.es.js   # ES6 version

To setup your backend - open layout file and add next at the top of the :

<html>
  <head>
    <!-- example for Phoenix.HTML 2.7.1+ -->
    <%= csrf_meta_tag() %>
    <!-- example for Phoenix.HTML 2.7.0 and below-->
    <meta name="csrf-token" content="<%= get_csrf_token() %>"/>
    <!-- also meta tag can contain the next setting-attributes:
      csrf-param="_csrf_token"
      csrf-header="x-csrf-token"
      method-param="_method"
    -->

Usage

Markup

<!-- to specify non-get method add `ujs-method` -->
<a href="/request/post" ujs-method="post">Make a POST request</a>
<a href="/request/delete" ujs-method="delete">Make a DELETE request</a>
<a href="/request/patch" ujs-method="patch">Make a PATCH request</a>
<!-- add confirmation with `ujs-confirm`  -->
<a href="/request/get" ujs-confirm="Are you sure?">Ask confirmation to open link</a>
<a href="/request/post" ujs-confirm="Are you sure?" ujs-method="post">Ask confirmation to make a POST request</a>
<form action="/request/post" method="POST" ujs-confirm="Are you sure?">
  <!-- ask confirmation when form is submitting -->
</form>
<!-- make an ajax request on click if `ujs-remote` is present -->
<!-- the next event will be triggered:
  ajax:beforeSend - event before AJAX call. AJAX request can be canceled if handler will return `false`
  ajax:success - a success response (2xx status code)
  ajax:error - an error response (4xx, 5xx status code)
  ajax:complete - an response received with any status
-->
<a href="/request/patch" ujs-method="patch" ujs-remote>Patch remotely</a>
<form action="/request/post" method="POST" ujs-remote>
  <!--  AJAX form submitting -->
</form>

JS API

The library exports module (window.UJS in browser version) with the next properties:

  • confirm - the confirmation function which accepts a message as the first argument. You can override it for your needs
  • csrf - a CSRF configuration object:
    • token - a current CSRF token. You can skip the meta tag in the header and put the token in runtime
    • header - a header used for CSRF requests
    • param - a query/form param used for CSRF requests
  • xhr - the AJAX contructor
var UJS = require("phoenix_ujs");

UJS.xhr(url, method, options);

// a simple get request
// all ajax events (ajax:beforeSend, ajax:success, ...) will be triggered on document
UJS.xhr("/api/ping", "GET", {
  success: function(xhr) {
    console.log("pong");
  }
});

UJS.xhr("/api/posts", "POST", {
  type: 'json', // convert data into json, set Content-Type & Accept headers
  data: { post: { title: "The first post", body: "Hello world!" } }, // the request's payload
  success: function(xhr) {
    alert("The post has been published");
  }
});

options accepts the next params:

  • headers - additional headers
  • target - js event target (by default - document)
  • beforeSend - the callback before xhr executed. It passes the config with xhr & options properties. return false will stop the ajax request
  • success - the success callback (200 <= status_code < 300)
  • error - the error callback (status_code >= 400)
  • complete - the complete callback (any status_code)
  • type - indicated additional processings;
    • "json" - converts data into json string, sets Content-Type & Accept headers to application/json
    • "text" - sets Content-Type & Accept headers to text/plain
    • Array(2..3) - sets Content-Type - the first array's element, sets Accept - the second array's element and process data with the third element (if it's exist)

AJAX events

Each ajax request triggers the following events:

  • ajax:beforeSend is triggered before making an ajax call. In this event you can cancel the ajax request:
document.addEventListener('ajax:beforeSend', function(e) {
   if(e.target.nodeName === 'I') {
     // if <i> triggered an ajax request - stop the ajax
     e.preventDefault();
   } else {
     e.data.xhr.setRequestHeader('my-vendor-header', e.data.options.someValue);
   }
});
  • ajax:success is triggered after getting an response and response is 2xx.
document.addEventListener('ajax:success', function(e) {
  console.log(e.data.xhr.responseText);
});
  • ajax:error is triggered after getting an response and response is 4xx or 5xx.
document.addEventListener('ajax:error', function(e) {
  console.error(e.data.xhr.responseText);
});
  • ajax:complete is triggered after any response:
document.addEventListener('ajax:complete', function(e) {
  console.log(e.data.xhr.responseText);
});

Things which are not included

  • executing a response js code. It's highly recommended to not do it, but if you need - there's an example:
document.addEventListener('ajax:success', function(e) {
  if(e.target && e.target.hasAttribute('ujs-eval')) {
    eval(e.data.xhr.responseText);
  }
});
<a href="/some/url" ujs-method="get" ujs-remote ujs-eval>Eval the server code</a>

Old browser support

If you're working on a project with old browsers support (IE 9-, Android 2.x, etc) - the library will not work unless you add a polyfill for FormData. For example this one https://www.npmjs.com/package/js-polyfills

License

MIT © Sergey Pchelintsev

TODO

  • tests
  • examples

phoenix_ujs's People

Contributors

denispeplin avatar gbh avatar jalkoby avatar markschultz avatar ulissesalmeida avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

phoenix_ujs's Issues

Returning JS is not interpreted

I've added a link

<%= link "⇧", to: post_upvote_path(@conn, :create, post), "ujs-method": "post", "ujs-remote": true %>

And upvoted.js.eex file

console.log("upvoted!");

Server responds with application/javascript; charset=utf-8, but console.log doesn't work.

It is preferred way to get JS executed (like in Rails), but I've also tried to use $(document).ajaxSuccess and it doesn't work for me too.

Origin missing from links form

When I use ujs-remote with form_for, phoenix_ujs sends Origin header among others. But when using with link, it doesn't.
This leads to Plug.CSRFProtection.InvalidCrossOriginRequestError error on backend.

Submit form by Ajax from JS code

Hello!

I have this form:

<form accept-charset="UTF-8" action="/api/foo/9" id="form" method="post"  ujs-remote="ujs-remote">
    <input name="_method" type="hidden" value="put">
    <input name="_csrf_token" type="hidden" value="---">
    <input name="_utf8" type="hidden" value="">
</form>

And when I execute this js-code, it triggered native submit event (not ajax):

form = document.getElementById("form");
form.submit();

@jalkoby what I do wrong?

FormData: Form remote fails in older browers

I'm getting lots of forms not working on my site. They seem to submit without xhr, or are missing form fields.

The common denominator seems to be older Windows and Android environments. My hunch is that lack of support for FormData is the culprit.

I'm trying to alter form.js to handle this case, will submit a PR if I succeed. It might just be easier for me to add a jquery dependency however (I'm already using it for other reasons).

Thanks!

Form parameters doesnt pass to controller

I get console.log succes but paramaters doesnt pass to controller only empty map %{}

<%= form_for @conn, search_path(@conn, :search), [ method: :get, ujs_remote: "ujs-remote"] , fn f ->%>
<%= text_input f, :q, placeholder: "search", required: true, id: "search-input", class: "fontAwesome form-control input-lg", data: [behaviour: "autocomplete"] %>
<%= submit "Search", class: "btn btn-bordered btn-green btn-block btn-lg"%>
      </div>
  </div>
<% end%>    
<script> document.addEventListener('ajax:success', function(e) { console.log("succes") }); </script>

case sensitivity of x-requested-with

it seems that the value of the header being case sensitive is undefined by http spec. In this library you use
xhr.setRequestHeader('X-Requested-With', 'XmlHttpRequest');
when the standard I've seen around other libraries is "XMLHttpRequest". Could this be changed? I can submit a pr if yes.

Meta tag not present on page load

To enable full page caching, we no longer render a CSRF token in the meta tag that UJS depends on.

We've made two workarounds:

  • render a blank meta tag (so UJS doesn't log a warning)
  • inject the token on ajax:beforeSend

Some ideas

  • ability to tell UJS when to read the meta tag (we do set it eventually)
  • ability to tell UJS the token to use (rather than read from the meta tag)

Great library, thanks!

possible race condition in xhr

I believe there may be a race condition between the xhr sending and associated events. Running a phoenix server locally I'll register the following listeners:

document.addEventListener('ajax:complete', function (e) {
    console.log("complete");
});
document.addEventListener('ajax:beforeSend', function (e) {
    console.log("before");
});

In the console I'll see 9 calls to each listener and in the network tab see 3 xhr requests.

If I break and step through the xhr code down to xhr.send(setXHRData(xhr, options.data, options.type));, everything works correctly.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.