Intercooler.js – Making AJAX as easy as anchor tags
github.com
github.com
What's the point? This would lead to your API being comprised of blocks of HTML which are probably only useable for one product. Why not just use REST + JSON? It would take no more than five minutes to set up client-side rendering, and you could even make it attribute-based like this with barely any more effort. Is it really not worth spending the extra five minutes it takes to set things up in a way that is reusable and standard? All I see is piles of legacy code being generated where it hurts most - in the backend.
This took me 10 minutes to cook up. It would have taken about three if I hadn't forgotten the jQuery and Handlebars APIs. This allows you to POST to a JSON API using two attributes. Untested of course, but you get the idea:
Example: <button ic-post-to="/api/resource" ic-template="#a-handlebars-template" />
$('[ic-post-to]').click((button) => {
fetch($(button).attr('ic-post-to')), { method: 'post' })
.then((result) => {
let templateText = $($(button).attr("ic-template")).html();
let template = Handlebars.compile(templateText);
let resultHtml = template(result);
$(button).replaceWith(resultHtml);
});
});Most of the time a django/rails rendered backend is desirable and easier to maintain over the long run.
The longer answer can be found in these posts:
http://intercoolerjs.org/2016/01/18/rescuing-rest.html
http://intercoolerjs.org/2016/05/08/hatoeas-is-for-humans.ht...
http://intercoolerjs.org/2016/02/17/api-churn-vs-security.ht...
Of course, you will need to go into all of this with an open mind, or you will not find any of it convincing.
In the second article you explained that HTML could implement HATEOS.
In the third article you argue that GraphQL is the natural progression of REST but its security model is complex to the point of being unsafe.
---
The first article, in my opinion, consisted of straw men arguments.
The second is understandable.
The third made no sense to me. With a system like GraphQL you can use a declarative column-based security model. This is, in my opinion, easier than the imperative stuff you're probably using to make your HTML endpoints secure. With GraphQL you need to set up your security constraints once. With HTML endpoints you need to remember to toggle off certain blocks of HTML for every single request. Is that what you're doing?
I'm glad the second article makes sense.
The core point of the third article is that when you increase the expressive power of a JSON API (with something like GraphQL) you are putting this power in the hand of the end user, not just your developers. This is not the same as giving, for example, full SQL access to your server-side only developer, where the code is executing in a trusted computing environment.
I'm only superficially familiar with GraphQL, (and not at all familiar with Intercooler), but I always felt that security was glossed-over and not a core part of what it offers.[0]
Authorization is challenging enough on the server, but having a query-language power client-side, feels like a pretty fragile thing to me to secure properly. Definitely not something you just set once and forget about...
[0] http://graphql.org/learn/authorization/ - if I get it right, it gives a good example of row-based authorization and essentially tells you to figure it out for yourself in your business logic layer.
It works for us because the site is extremely static…
Thinking of almost all of the full stack, I feel like you could have more flexibility with something like Rails with Angular on the front-end. You can set up a Rails controller to return JSON or HTML depending on the request type.
Throw in UI Router and have your Angular routes match your Rails routes... You can have your pages rendered server and client side and still have the flexibility to render JSON.
It would be sick if there was a stack where you can take make your routes more functional and have the routing work clientside and serverside. I know Turbolinks is supposed to solve this problem, but it sort of messes up how events are fired.
Also, Angular templates cannot be modified on the front end... but these intercooler attributes can.
I will admit, the author is probably solving a problem that I haven't encountered or even fully understand. If people are digging it, more power to him.
If I speak from a place of experience, is that less valid if I use a language that you do not particularly like?
It is not always the correct language in a lot of cases, but it can be for creating the feature set that I have described above - an approach for a more functional application structure that would provide server and client-side routing without Turbolinks.
You don't even need Angular or Rails for this, but these are both tools that I have enough familiarity with to know that it would work.
It's not a language it's a framework. And it will take me (and others on my team) time to learn it, and maintain it in the future. But if you like it, more power to you!
There are elements that Rails (predictable routing, simplified MVC, templating rendering, and general ease of set up and deployment) and Angular (flexibility, directives, safe templates, state based routing, imperative but can be functional with filters) provide the feature set that I have described above (aside from the routing object idea for generating server and client side routing).
The underlying technologies are not important; Node already has isomorphic javascript. I think Phoenix might be on track to remove the need for a heavy use of javascript for creating a single page application by simply using channels.
However, I cannot speak on Node and Phoenix because I lack the experience with these languages to comment. I do have experience with Rails and Angular to generate an example of how I would like a full stack, AJAX heavy application to work.
The author did post a story about intercooler four times in the last 3 years. Where is the problem?
> This would lead to your API being comprised of blocks of HTML which are probably only useable for one product.
Whats the problem with that if their is no need to reuse the API, or if there is no API at all, just a bunch of uncool php scripts?
> It would take no more than five minutes to set up client-side rendering?
Is client-side rendering better per se?
> Is it really not worth spending the extra five minutes it takes to set things up in a way that is reusable and standard?
What standard do you refer to?
I think it is obvious that this lib isn't meant to be the foundation for the next facebook app, be a contender for the current hip bloatware frameworks or might be the best lib for single page apps generally. But I can see lots of use cases where this lib will help to spice up some projects without getting burden under some boatload of unnecessary tooling and boilerplate codes.
Look at the author's comment history, rather than story history. He has a reputation of plugging intercooler in every discussion related to JS. Not that I mind; but most of the "reposts" that GP is talking about are comments, not actual stories.
I assure you I am the person most relieved by this. ;)
Anyway, I'd like to thank him for it. It was because of this pushing that I learned about the lib, and I'm very glad for that.
It's easiest to keep everything in one templating system and language. (Note: I said easiest, not most robust or ideal.)
Options:
1. Isomorphic code. This requires either NodeJS or compile-to-JS tooling.
2. Client-side-only: bad for SEO and usability.
3. Server-side-only: You either write several lives of boilerplate every time line your example, or use intercooler and write a single-liner.
For smaller sites, the number of parts often isn't huge, so boilerplate isn't a big deal.
Generating code in a Turing-complete language use a program written in another Turing-complete language can get messy.
Just because something is turing complete doesn't mean that a generator has to be able to generate anything possible.
If boilerplate is an issue, scaffolding / code generators can be a quick fix.
Having said that, I've been doing a side project in KnockoutJS and it isn't much harder than IC. You mark up the HTML and need a little bit of JS to call the api. So it would be like your example but with a line or two removed. And like your example as soon as you desire to do something custom you just add some more code.
On the service side you emit JSON instead of HTML and this can be easier IMO - virtually every language has a JSON library and you just convert your ORM objects to JSON (or just use them as-is!). That is less work than rendering HTML I would have thought.
Only usable for one product? There aren't many platforms without UI layers that have some kind of component for rendering/presenting HTML these days. But even for those that don't:
> Why not just use REST + JSON?
Markup is as much a data-exchange format as JSON is. If you're writing/generating it well, anyway.
I'm not saying never use JSON (I have and do). I am saying it's a little weird, though, that we've somehow got to the point where anyone who's commenting on this discussion has forgotten that markup has done and can do the job JSON does, or that we're at a place where as an industry it seems weird or potentially an interop problem to serve markup instead.
if accept==html then return html(template, data)
else return json(data)
Which is sure as hell less work than setting up React/Angular and a bunch of other junk on your HTML frontend to consume JSON and turn it into HTML in the browser. Probably higher performance, too, since JSON-consuming HTML frontends in the wild don't seem to exhibit (putting it mildly) the performance improvements that AJAX originally promised—then again, that X was XML (and remember XHTML? I, for one, really liked it) so it's not necessarily AJAX's fault that we've decided to rube-goldberg up the web the way we have.Because for probably the majority of developers there IS only one product.
Here's what I think is happening. A bunch of us are working on websites that need to scale across multiple users and adapt to multiple clients and will have to grow and pivot as the business needs change. And they've learnt the hard way about building scalable systems.
But what is going horribly wrong is that their war stories and best practices and tools and processes are being used by a bunch of us who will never face those problems. And we're paying the price in terms of complexity for problems we aren't ever going to face.
If you're building a SaaS for a startup that might reasonably expect exponential growth, unfortunately your advice is being taken to heart by people building blogs and small web-shops and we're seeing some terrible technical decisions being made because everyone wants to do things 'right'.
We need a healthy dose of YAGNI drilled into people. Whatever happened to progressive enhancement? It still suits the vast majority of web projects perfectly well.
I'd also say that JSON APIs either tend to get tuned for specific UX needs or become more and more expressive. The first option calls into question the general, reusable nature of the API, and the second introduces security issues in an untrusted computing environment[1].
I typically split out the JSON API for my system from the web application proper so that my web application needs don't screw up the public API of the system. They end up being two separate concerns with different shapes, authentication methods, etc.
[1] - http://intercoolerjs.org/2016/02/17/api-churn-vs-security.ht...
And then you've added a ton of complexity, bringing us full circle.
Based on the parent comments, we're talking about simple websites where serving HTML through AJAX is an effective approach. In the name of "purity", you've introduced JSON APIs, GraphQL, (most likely) React and Relay, more complex deployment with multiple services, probably a complex front-end buildchain, and who knows what else.
Why not?
Yes, a lot of companies are founded around a single product or family of products. They can afford to use a building block that isn't re-usable across different projects. They're not making things for public consumption, or have multiple very divergent codebases.
But these aren't small web shops, and people building blogs. In the DC area at least, the above describes every midsized company ( midsized as in > $1 monthly revenue < $50 million monthly revenue). These guys are makers of the software that runs in doctors offices, hotels, non-profits, political organizations, and government contractors.
They're already bigger than most SasS companies in SV, can ever expect to be.
HTML is standard. Even more importantly it has rich and standard semantics. Your homebrew protocol based on JSON is not and does not. You write an interpreter for it every single time without even realizing it.
The thing you cooked up in 10 minutes does not follow the principles of progressive enhancement. This immediately closes up lots of doors.
No problem. It's just views. If the need comes to make it general, and output JSON from everywhere, you just write another set of views. They are discardable. (And you'll probably need different URLs for the JSON anyway, so you'll probably keep both sets.)
Intercooler.js makes them seem a little more "built in" to html, which I like.
http://intercoolerjs.org/2016/01/18/rescuing-rest.html
http://intercoolerjs.org/2016/05/08/hatoeas-is-for-humans.ht...
And then makes a lot of stuff that currently requires javascript (e.g. CSS transitions) accessible via plain old HTML.
A PUT tells the server to make the payload available as is at the given URI. And if you do a GET on the URI, you should receive the exact payload that you put there. That may also include headers you send along with the PUT request (especially the Content-Type header). That's why a PUT is also able to invalidate a cache. So PUT and POST are very different.
The problem with DELETE is this part of the specification: "The client cannot be guaranteed that the operation has been carried out, even if the status code returned from the origin server indicates that the action has been completed successfully."
That's why most applications are fine with using only GET and POST.
edit:
Better is to use a PUT for different representations:
PUT /foo
Content-Type: application/json
{"foo":""}
PUT /foo
Content-Type: text/xml
<foo></foo>You can add your own guarantees on top of this specification. So in my application, the DELETE is guaranteed to have succeed if you get a positive status code.
That said though, IMO it's way more intuitive to use DELETE vs POSTing op=delete or even worse POSTing to a /resource/delete endpoint.
Well the XForms spec itself is alive and on the path to its 2.0 version. There's a number of implementations, but it seems most of dev world has an allergy to XML, which is a pity. XSLTForms works well enough, even on mobile.
The other methods are merely optimisations to these core operations, and hence, entirely optional.
EDIT: Though, honestly, I must say that I also question the value of these things. For example, HTTP error codes are notoriously ambiguous and you usually need to transmit an actual response (and sometimes are forbidden spec-wise!) with an error code. I was just assuming the position of an advocate for HTTP/REST.
Yes, but these verbs often don't yield any meaningful results or see much use in my experience. Even so, this could easily be achieved by convention using just GET/POST.
Consider the HTTP OPTIONS verb. If you invoke it with * as the request-URI, it's an operation that applies to the server as a whole rather than any specific resource.
This provides the solution: HTTP already reserves one URI for a specific use, and we can do the same for all of the other verbs and eliminate them from the spec if we so desired, ie. OPTIONS /some/resource => GET /options?url=/some/resource
Intercooler.js is so logically designed it basically requires no documentation - I read the introduction and a handful of examples and thought, "shit of course it works this way" and could basically derive how every other feature mapped to its implementation in that moment.
Congratulations!
I built it for my startup and wasn't sure it was going to work out, but my co-founders let me give it a try and I've been really happy with how it turned out. We have been able to build a pretty modern web app with very little javascript.
(We are hitting levels of shilling that shouldn't even be possible)
I tried out several client side frameworks but always felt like it was way overkill for the apps I built.
I gave intercooler.js a try a few months ago and was extremely pleased. There's very little server side that's required and the extra functionality I had wanted from pjax was there.
If you're wanting the simplicity of server side rendering plus the feel of an SPA without the frontend complexity give this library a try.
Saying "Front-end javascript is too complex, let's go back to our progressive enhancement roots" in one breath and "Requires webpack and ES6" in another doesn't jive IMO.
Always happy to take pull requests though...
Isn't it a pragmatic technical decision rather than - what you almost make sound - rather an emotional one? There are times adding a dependency is exactly the right thing to do and times when it isn't. It's surely something that is project and situation dependent.
If I do it myself I retain full control over the functionality, it's faster because fewer "kitchen sink" properties need to be evaluated. It's easier for future developers to pick up and understand. There isn't any chance that the dependency or any of it's sub-dependencies go out of date and need to be replaced.
In software development there is always more than one way to do something, and this way will work today. But in what circumstance is it better? I don't want to attack this library. I'm trying to say there are too many libraries that act as an abstraction of something that would otherwise be simple to do.
Only if your 'utility function' stays small. However my hunch is that on any real-world project over time you hit a Greenspun 10th variant*
* Any sufficiently complicated 'pure' .js project contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of jQuery
[0]: https://en.wikipedia.org/wiki/Jamie_Zawinski#Principles
I don't see this. Can you elaborate?
> combined with the overhead
In what sense? Bandwidth? Rendering? Conceptual?
> and need for graceful degradation of a full-ajax approach.
I've found intercooler suits a 'graceful degradation' approach rather well. It is a small evolution of on from pjax/turbolinks - which itself epitomises progressive enhancement/graceful degradation.
> You have the API inflexibility and UX restrictions of a pure-HTML approach I don't see this. Can you elaborate?
No client-side rendering. More precisely, the core idea of intercooler is that every UI update polls the server for new markup. So for each non-trivial UI change, you have to take into account the latency of a network request, even if you already sent the data to the client before in another request. That makes the "feels like a desktop app" experience impossible that originally was one of the major selling points of ajax.
The "markup snippet" means that if you want to change your UI, congratulations, you have to change your template and your API - whereas with client-side rendering you wouldn't have to touch the API at all in many cases.
> In what sense? Bandwidth? Rendering? Conceptual?
Conceptual. You have the same complexity costs as a full ajax page compared to a plain-html page (data gathering spread over multiple requests, need to define an API boundary, need to care about clients without JS support, need to track client-side state, etc.) - but it seems to me you get a lot less benefits for that cost.
> It is a small evolution of on from pjax/turbolinks - which itself epitomises progressive enhancement/graceful degradation.
I admit I'm not familiar with that. But how would it deal with clients without JS support?
The vast majority of single-page applications I see on the internet nowadays still function in a way that would be perfectly serviceable if they were implemented without AJAX.
Absolutely there's cases where the request-response cycle doesn't map well to what actually happens in the app, but I don't think anyone is claiming that Intercooler, or serving HTML from an AJAX request, is the final answer in building web apps.
Everyone seems to forget about using the right tool for the job when the right tool is unsexy.
1. You may not plan to switch to JSON anyway. Serving HTML, whether through a full request or AJAX can be perfectly fine. The site we're on now has no real Javascript of note, for instance.
2. You might introduce a JSON API for customers, but that doesn't mean that it needs to or even should be the same API you use internally.
3. Even assuming that you know that you'll ultimately move to a JSON API, it can be advantageous to take a quicker approach while you're still figuring out the solution. When you come back to the API, you'll have a better idea of precisely how it should be used.
> No client-side rendering
That's a good thing. Devs should really spend some time thinking why they do something in particular way instead of just parroting others.One could include two attributes:
<a href="/foobar" ic-get-from="/foobar">foobar</a>
There's likely some edge cases that would fail doing it this way, like when including other inputs outside of the form tags when submitting forms.This gives clients that do have java script the feel of an SPA and clients that do not have java script see the traditional full page refresh without any additional server side logic deciding to return snippets or not.
Happy to see people are enjoying it!
I really feel that the web is moving towards a components/modularized system where every tag is built from subtags, a bit like how iframes used to work but without the security implications. For example, I was shocked when I learned back in the 90s that server-side includes where generally disabled on servers, which led to the proliferation of cgi-bin, php, ruby, node.js, etc etc which may not have been necessary otherwise.
To get to my question - is adding websocket/socket.io on the horizon? If intercooler.js can get to a truly push-based method of state synchronization, it would be huge.
http://intercoolerjs.org/docs.html#philosophy
I'm looking to add server-side event support in the next month or so, which would give us a nice way to implement push-based updates:
https://github.com/LeadDyno/intercooler-js/issues/131
I'm working on a demo chat UI myself, so I will be motivated to add it soon. :)
(I was unaware of EventSource. It looks so, so much nicer to use than raw WebSockets...)
As an aside: Intercooler obviously requires Javascript to work; is there any fallback if Javascript's not available? It sounds like, at least for interactions on things like form buttons, it ought to be possible to send an old fashioned POST to the server which rerenders the entire page.
http://stackoverflow.com/questions/3508338/what-is-the-bound...
Now unfortunately there will be a requirement client-side to decode binary data or strings with escaped newlines. We'll get the benefit of deterministic behavior at the cost of escaping payloads, and I'm not certain it's worth it.
This seems like an opportunity lost to me. Big enough that this may be one of the basic building blocks we skipped over in the rush to javascript. Maybe the community could wrap this to look like a client-side form until there is a standard.
It's on the list.
You can use the data-prefix though if you want by including the following meta tag:
<meta name="intercoolerjs:use-data-prefix" content="true"/>Or, better, I'd like you to implement that. :)
Unfortunately, our implementation is rather scatter-brained and non-uniform. That's partly due to its gradual evolution and partly due to lack of free employee time to clean up bit-rot. I'm going to investigate this a bit more and mock out some examples for our product. I definitely think it'd help us organize our unruly mass of code. Good job!
But I guess there would be a single event handler that shows all the error messages and server would push down HTML describing errors, e.g. validation.
Why not make it part of the lib then, so "no js" thing stands? :)
<button ic-post-to="/query" ic-on-http-404="/errortemplate"> or similar.https://msdn.microsoft.com/en-us/library/bb399001.aspx
Commercial control providers in .Net world support these scenario with something called "CallbackPanel".
https://demos.devexpress.com/MVCxMultiUseExtensionsDemos/Cal...
Real conufsion starts when you have nested HTML controls that automagcally making ajax calls. Nice idea as long as you can get away with minimal work.
The moment you want to use any moden SPA framework, you are up for a big rewrite.
The motivation of UpdatePanel was not to provide the best design pattern for AJAX, but provide the fastest path to incremental page updates for ASP.NET Web Forms developers (with minimal code changes) - with all its limitations.
It makes a lot less sense to have a pattern like this in a modern SPA.
Disclaimer: I'm one of the original authors of Patternslib.
<!-- When this button is clicked an AJAX POST request is sent to /example and the
response content is swapped in to the body of the button -->
<button ic-post-to="/example">
Click Me!
</button>
But what if I want the response to populate the content of another HTML element? E.g. if I wanted an accordion-style FAQ where when you click on the question (or on a plus-sign before the question), the div below the question is loaded with the answer from the server via AJAX.https://validator.w3.org/nu/?doc=http%3A%2F%2Fintercoolerjs....
<meta name="intercoolerjs:use-data-prefix" content="true"/>I've tried to log the most obvious problems to console to make it easier to understand what's going on.