What about http://unpoly.com
The unobtrusive JavaScript framework for server-side applications
Unpoly enables fast and flexible frontends with minimal changes to your server-side code.
What about http://unpoly.com
The unobtrusive JavaScript framework for server-side applications
Unpoly enables fast and flexible frontends with minimal changes to your server-side code.
That looks nice - I wonder how it differs from Turbolinks and what each of their strengths are.
Iāve tried it very recently. Unpolly is neat. It comes with dialogs and partial updates support. It does out of the box slightly more than Turbolinks.
Having said that, you can do everything with Turbolinks and some addon, or just writing some of your own code.
I like unpolly, it seems well-thought and functional out of the box, so it would be my choice, however.
Are there any github projects that demonstrate the use of unpoly with Phoenix/Elixir? It does look cool and it looks like itād be valuable for me to do some cool things thatād take me much longer if I tried to puzzle them out myself. But Iād appreciate examples with Phoenix.
I implemented once this inside a Phoenix application:
Thanks for that, while I didnāt use pjax the information on how to make these frameworks work with disqus is very useful.
Is there any github example project available which demonstrates using Unpoly.js
with Phoenix
?
What exactly would you be looking for? Itās entirely a front-end library so there is no real overlap?
When I looked at the Unpolyās source at github, it have things like Gemfile, Gemfile.lock, unpoly-rails.gemspec, .ruby-version etc. Also the project has 6.2% ruby in the source. Is it designed to work specifically with Rails, or it can be used with Phoenix?
it can be used with any server framework, as it is purely a javascript framework. Have you visited the https://unpoly.com/ website?
there it says:
The folks at Basecamp have recently released a small library called StimulusJS. Itās meant to work alongside Turbolinks and it automatically connects HTML elements to javascript code through data-attributes.
It is a small lib though. There is no modals/popups/animations/forms validationsā¦
Also read The origin of Stimulus
It includes a simple minimal Ruby library to make handling headers and such easier, but that is trivial to do in stock Phoenix anyway that Iāve never bothered. ^.^
unpoly is made by my friends at makandra or more specifically by Henning the agency part of their company does mainly Ruby so having some very lightweight Ruby/Rails integration onboard is certainly in their best interest.
Itās designed to be agnostic to whatever backend it is running on/with so itās definitely good to go with Phoenix.
I also wouldnāt call it ālike turbolinksā - itās way different in my mind or maybe I just donāt know turbolinks enough. The thing which most got me when henning introduced it was something along the lines of āWe were asking the question what would an unobtrusive way to add JavaScript enhancements to your HTML in a new standard look likeā - and thatās what they did, most of it works through additional attributes etc.
Itās a lovely design imo Iā've never played with it myself so far though.
Unpoly author here. Happy to answer any questions you might have!
Unpoly has no dependency on Ruby or Rails. There is a completely optional server protocol that improves a few edge cases like detecting redirects. You can implement that in a few lines of Elixir or just use Unpoly without it. E.g. unpoly.com is a static website that uses Unpoly and does not even have a server-side app behind it. Itās just a folder of HTML files.
That said, I have noticed that Unpoly seems popular in the Elixir community and I would really like to offer better support for Elixir. Iām currently a little lost where to start implementing something like unpoly-rails
(server protocol for Rails) for Elixir/Phoenix. I guess this would be a ā¦ plug? How would I unit test and package it? And so on. If anyone has some pointers or example code, I would be grateful.
Whooo welcome! Didnāt expect you here. ^.^
Iā¦may push it around a lot here, I really find itās API to be well designed (though I do wish I could feed it through my rollup packaging system, it doesnāt follow the JS module spec). ^.^;
Yep plugs and an api for querying and setting things. Iāve made enough of what Iāve needed in my own project. Iāve intended to pull it out for a while now and document it, but it is so super easy for anyone to make theirselves that Iāve not really gotten around to it yetā¦ ^.^;
My file as it stands, I donāt use āallā of it so I cannot claim it all to be tested, and of course itās missing a lot of obvious helpers that I otherwise do randomly in my code (bad form I know) so it does need some work on it (itās an internal fileā¦), but this is what I have so far in an unpoly.ex
file:
defmodule Unpoly do
def up?(conn), do: target(conn) !== nil
# defdelegate unpoly?, to: up?
def target(conn), do: List.keyfind(conn.req_headers, "x-up-target", 0, nil)
def target?(conn, tested_target) when is_binary(tested_target) do
if up?(conn) do
case target(conn) do
^tested_target -> true
"html" -> true
"body" -> not (tested_target in ["head", "title", "meta"])
_ -> false
end
else
true
end
end
def validate?(conn), do: validate_name(conn) !== nil
def validate_name(conn), do: List.keyfind(conn.req_headers, "x-up-validate", 0, nil)
def set_title(conn, new_title) when is_binary(new_title), do: Plug.Conn.put_resp_header(conn, "x-up-title", new_title)
defmodule Plugs do
defmodule RequestMethodCookie do
@cookie_name "_up_method"
def init(options), do: options
def call(conn, params) do
case conn.method do
"GET" -> Plug.Conn.delete_resp_cookie(conn, @cookie_name, List.wrap(params[:cookie_opts]))
method -> Plug.Conn.put_resp_cookie(conn, @cookie_name, method, List.wrap(params[:cookie_opts]))
end
end
end
defmodule RequestEchoHeaders do
def init(options), do: options
def call(conn, params) do
conn
# |> Plug.Conn.put_resp_header("x-up-location", conn.request_path)
# |> Plug.Conn.put_resp_header("x-up-method", conn.method)
end
end
defmodule AddRedirectHeaderAfter do
def init(options), do: options
def call(conn, params) do
case Plug.Conn.get_resp_header(conn, "location") do
location when is_binary(location) -> Plug.Conn.put_resp_header(conn, "x-up-location", location)
end
end
end
end
end
To package it you just run mix new unpoly
and follow the steps, the replace the pre-built lib/unpoly.ex
with the above (edited to add documentation and all such too of course).
For testing you would want to inlcude the Plug library as a testing only dependency and use itās test helpers to test that things go through properly as expected either in doctests, or the dedicated test file(s) in the test/*
directory. Iām sure someone here could create a whole scaffold project if you want.
Once itās made youāll want to include ex_doc
as a dev-only dependency then youāll be able to publish it to the hex package system with documentation and all (and of course you can keep the whole project directory inside the unpoly project if you preferred too).
@OvermindDL1 laid it out pretty neatly, other than that @triskweline - next time we see each other (you make it to Berlin or should I make it to the south againā¦) happy to assist with/pair on creating a hex package with a plug. Unless @OvermindDL1 finds the time before and just creates it
Thanks @OvermindDL1 for the sample code and also for spreading word about Unpoly.
Is there an example somewhere for a plug test? In the Rails world, testing code that works with requests and responses requires a lot of setup. Iām guessing it should be easier in a functional language, but Iād still need to dispatch and then inspect a request somehow.
I belive you can do something like this
# define your plug
defmodule Plugtest do
import Plug.Conn
def init(opts), do: opts
def call(conn, _) do
conn
|> put_resp_header("x-custom-header", "Blarg")
end
end
# define a dummy router
defmodule Plugtest.Router do
use Plug.Router
plug(Plugtest)
plug(:match)
plug(:dispatch)
get("/", do: send_resp(conn, 200, "Welcome"))
end
# write the test
defmodule PlugtestTest do
use ExUnit.Case
use Plug.Test
alias Plugtest.Router
@opts Router.init([])
test "has header" do
conn = conn(:get, "/", "")
|> Router.call(@opts)
assert Enum.member?(conn.resp_headers, {"x-custom-header", "Blarg"})
assert conn.status == 200
assert conn.state == :sent
end
end
I dont know if this is the best approach, but this is what I got by reading this guide
@cpgoās example is the traditional way as it sets up a request and all such, but that is usually for testing an entire pipeline.
For testing just a single plug, well Iād just call it.
First how a plug works, the plug call itself (for the given Unpoly.Plugs.AddRedirectHeaderAfter
in my code above), it would be added to a pipeline via the plug
command like:
plug Unpoly.Plugs.AddRedirectHeaderAfter
If it took options (it doesnāt, but if it did) then it would be like:
plug Unpoly.Plugs.AddRedirectHeaderAfter, blah: :blorp
That basically compiles into:
# Option-less:
Unpoly.Plugs.AddRedirectHeaderAfter.call(conn, unquote(Macro.escape(Unpoly.Plugs.AddRedirectHeaderAfter.init([]), __ENV__)))
# With the option
Unpoly.Plugs.AddRedirectHeaderAfter.call(conn, unquote(Macro.escape(Unpoly.Plugs.AddRedirectHeaderAfter.init([blah: :blorp]), __ENV__)))
In essence it calls the init/1
callback at compile-time and inlines the result as the second argument in the call
function, then when the pipeline is run the call
function is run with the connection and the baked-in parameters that is returned from the init
function.
This means that if you are just testing a plug that it is really easy to do so, basically you can just do this:
# Put this outside the function so it becomes static, just to make sure you don't return something unserializable like a ref or so
import Plug
use Plug.Test
alias Unpoly.Plugs.AddRedirectHeaderAfter
@params AddRedirectHeaderAfter.init([])
# And in the testing functions
conn = put_resp_header(conn(:get, "/"), "location", "/")
assert %Plug.Conn{} = conn = AddRedirectHeaderAfter.call(conn)
assert "/" = get_resp_header(conn, "location")
You can see this at the bottom of:
https://hexdocs.pm/plug/readme.html
plug is pretty much like rack. I.e. just calling it is often good enough as mentioned above. For a full runnable exampe test from our bugsnex plug.
Or from the plug repo itself (the plugs it ships with) sample1 - sample2