We’re breaking apart with JavaScript frontends
It is not you, it is us
We’re breaking apart with JavaScript frontends
Henning Koch, makandra GmbH
@triskweline
Give it 10 minutes.
Context
- makandra is a Ruby on Rails consultancy
- We begin a brand new utility each 3 months
-
We preserve apps for a actually very long time
- 50+ apps in upkeep
- Oldest app is from 2007
- Will we be capable to do that for one more 10 years?
Tweet from 2025
Based mostly on chart by @ryanstout
Complexity in 2005
Authorization
Routing
Fashions
Controllers
Views
Dependencies
Server
Based mostly on chart by @ryanstout
Complexity in 2008
Authorization
Routing
Fashions
Controllers
Views
Dependencies
Server
Based mostly on chart by @ryanstout
Complexity in 2009
API
Authorization
Routing
Fashions
Controllers
Views
Dependencies
Server
Based mostly on chart by @ryanstout
Complexity in 2011
Asset packing
API
Authorization
Routing
Fashions
Controllers
Views
Dependencies
Server
Based mostly on chart by @ryanstout
Complexity in 2013
Asset packing
API
Authorization
Routing
Fashions
Controllers
Views
Dependencies
Server
Fashions / API shopper
Controllers
Views
Dependencies
Shopper
Based mostly on chart by @ryanstout
Complexity in 2014
Asset packing
API
Authorization
Routing
Fashions
Controllers
Views
Dependencies
Server
Authorization
Routing
Fashions / API shopper
Controllers
Views
Dependencies
Shopper
Based mostly on chart by @ryanstout
Complexity in 2015
Prerendering
Asset packing
API
Authorization
Routing
Fashions
Controllers
Views
Dependencies
Server
Digital DOM
Authorization
Routing
Fashions / API shopper
Controllers
Views
Dependencies
Shopper
Based mostly on chart by @ryanstout
A glance again on the 7 AngularJS initiatives that we wrote from 2013-2016:
In hindsight, these are the initiatives that ought to have been a SPA:
YMMV.
Learnings from 3 years of SPAs
-
SPAs are an excellent match for a sure class of UI.
For us that class is the exception, not the default. - There is a trade-off between UI constancy and code complexity.
-
We expect we are able to repair many of the issues with server-side apps
and discover a new candy spot in that trade-off.
What server-side apps
do nicely
- Huge alternative of nice and mature languages
- Low complexity stack
- Synchronous knowledge entry
- Time to first render
- Works on low-end gadgets
What server-side apps
do not do nicely
- Sluggish interplay suggestions
-
Web page hundreds destroy transient state
(scroll positions, unsaved type values, focus)
-
Layered interactions are arduous
(modals, drop-downs, drawers)
- Animation is difficult
- Complicated types
Demo of server-side app points
Link to demo app
(press Begin Basic on first web page)
Issues to try to observe:
-
Navigate between playing cards within the left pane
Scroll positions get misplaced in each panes
-
Open the second web page (“Extra playing cards” hyperlink on the backside of the left pane)
Card in the fitting pane will get misplaced
-
Edit a card, change the title, then change the sample
Unsaved type content material is gone when coming back from the sample choice
repair server-side apps?
A thought experiment
Think about HTML6 was all about server-side apps
What options could be in that spec?
Partial web page updates?
Animated transitions?
Layered interactions?
We will polyfill all of that!
As a result of it is 2016 and JavaScript is now quick.
Demo of Unpoly-enhanced app
Link to demo app
(press Begin Enhanced on first web page)
Issues to try to observe:
-
Navigate between playing cards, open and cancel the shape
Web page transitions are animated
-
Navigate between playing cards within the left pane
Scroll positions are stored in each panes
-
Open the second web page (“Extra playing cards” hyperlink on the backside of the left pane)
New web page slides in easily
Card in the fitting pane is stored
-
Edit a card, change the title, then change the sample
Sample choice occurs in a modal dialog,
preserving unsaved type values -
Examine hyperlinks and see attributes with
up-*
prefix
Basic web page stream
Server renders full pages on each request.
Any state that is not within the URL will get misplaced when switching pages.
Unpoly web page stream
Server nonetheless renders full pages, however we solely use fragments.
This solves most of our issues with transient state being destroyed.
Layers
Unpoly apps might stack as much as three HTML pages on high of one another
Every layer has its personal URL and may navigate with out altering the others
Use this to preserve context throughout interactions
Layers
<a href="http://triskweline.de/listing" up-target=".foremost">Change fragment</a>
<a href="http://triskweline.de/new" up-modal=".foremost">Open fragment in dialog</a>
<a href="http://triskweline.de/menu" up-popup=".foremost">Open fragment in dropdown</a>
Hyperlinks in a layer want to replace fragments inside the layer
Altering a fraction behind the layer will shut the layer
Navigation
- All fragment updates change the browser URL by default.
-
Again/Ahead buttons work as anticipated.
Even scroll positions are restored.
-
Linking to a fraction will scroll the viewport to disclose the fragment.
Unpoly is conscious of mounted navigation bars and can scroll additional/much less.
-
Hyperlinks to the present URL get an
.up-current
class routinely.
However I’ve this actually customized JavaScript / jQuery library / habits that I have to combine
Don’t fret, we really permit for large customization:
- Pairing JavaScript snippets with HTML parts
- Integrating libraries (WYSIWYG editor, jQuery plugins, …)
- Passing structured knowledge to JavaScript snippets
- Reuse current Unpoly performance from your personal code
- Invent your personal UJS syntax
- Configure defaults
Activating JS snippets
Each app wants a technique to pair JavaScript snippets
with sure HTML parts:
- A
textarea.wysiwyg
ought to activate Redactor on load -
An
enter[type=search]
subject ought to routinely request new outcomes
when the consumer varieties one thing -
A
button.toggle-all
ought to toggle all checkboxes
when clicked -
A
.map
ought to render a map through the Google Maps JS API
Activating JS snippets
Random.js
<div class="map"></span>
doc.addEventListener('DOMContentLoaded', perform(occasion) {
doc.querySelectorAll('.map').forEach(perform(component) {
new google.maps.Map(component)
})
})
That is what you see in JavaScript tutorials.
HTML fragments loaded through AJAX do not get Javascriptified.
Activating JS snippets
Unpoly
<div class="map"></span>
up.compiler('.map', perform(component) {
new google.maps.Map(component)
})
Unpoly routinely compiles all fragments that it inserts or updates.
On the primary web page load, your complete doc is compiled.
Getting knowledge into your JS
Random.js
<div class="map"></div>
<script sort="textual content/javascript">
initMap(doc.querySelector('.map'), { lat: 48.75, lng: 11.45 })
</script>
perform initMap(component, heart) {
new google.maps.Map(component, { heart: heart })
})
Getting knowledge into your JS
Unpoly
<div class="map" up-data="{ lat: 48.75, lng: 11.45 }"</div>
up.compiler('.map', perform(component, heart) {
new google.maps.Map(component, heart: heart)
})
The [up-data]
attribute worth is parsed as JSON and handed
to your compiler as a JavaScript object.
Symmetry to CSS elements
If you’re utilizing some CSS element structure like BEM
you’ll find a pleasant symmetry between your CSS elements and Unpoly compilers:
app/property/stylesheets/blocks
carousel.css
head.css
map.css
screenshot.css
tail.css
app/property/javascripts/compilers
carousel.js
head.js
map.js
screenshot.js
By sticking to this sample you’ll all the time know the place to seek out the CSS / JS that impacts
your
<div class="map">...</div>
component.
Response occasions
Reponse occasions
How briskly are SPAs?
We wish to approximate the snappiness of a AngularJS SPA
(since we’re pleased with these). How briskly is an SPA?
-
Most of our AngularJS interactions are making API requests
and are thus sure by server pace. -
Rendering to JSON takes time, too.
60-300ms for index views in non-trivial app
-
Shopper-side rendering takes time, too.
-
Customers do just like the instantaneous suggestions
(even when it simply reveals to an empty display that’s then populated over the wire)
Response occasions
Unpoly’s method
-
Present instantaneous suggestions to all consumer enter
so interactions
seem quicker than they are surely - Decide all the low-hanging fruit that is losing 100s of milliseconds
- Unlock sufficient time finances that we are able to afford to render
full pages on the server - Use greatest practices for server efficiency
- Present choices if all that isn’t sufficient
What you get out of the field
-
We not parse and execute CSS, JavaScript and construct the DOM on each request
makandra deck on Playing cards (140 ms), AMC frontend (360 ms)
-
Clicked hyperlinks/types get an
.up-active
class whereas loadingGet right into a behavior of styling
.up-active
for instantaneous suggestions
Use throttling and Chrome’s community tab -
Hyperlinks with an
[up-instant]
attribute load onmousedown
as an alternative ofclick on
Saves ~70 ms with a mouse (test yourself)
Saves ~300 ms on unoptimized websites with contact gadget
Your Linux/Home windows apps do this, too! -
Hyperlinks with
[up-preload]
attribute preload vacation spot whereas hovering -
Responses to
GET
requests are cached for five minutesAny non-
GET
request clears your complete cache
Really feel the response time of an Unpoly app by navigating between playing cards on
makandracards.com/makandra.
Paste this into the console to visualise mousedown occasions:
perform showEvent() {
var $div = $('<div>mousedown!</div>');
$div.css({ backgroundColor: 'blue', colour: 'white', fontSize: '20px', padding: '20px', place: 'mounted', left: '0', high: '0', zIndex: '99999999' });
$div.appendTo(doc.physique);
$div.fadeOut(500, perform() { $div.take away() });
};
doc.addEventListener('mousedown', showEvent, { seize: true });
How one can optimize additional
- Server-side fragment caching
- Tailor responses for the requested selector
- Spinners for long-running requests
- We will nonetheless implement client-side interactions
- Go nuclear with two-way bindings
Tailor responses for the requested selector
<html>
<physique>
<% if up.goal?('.facet') %>
<div class="facet">
...
</div>
<% finish %>
<% if up.goal?('.foremost') %>
<div class="foremost">
...
</div>
<% finish %>
</physique>
</html>
up.goal?(css)
seems to be on the X-Up-Goal
HTTP header
that Unpoly
sends with each request.
Spinners
For the occasional long-running request, you’ll be able to configure this globally:
<div class="spinner">Please wait!</div>
up.compiler('.spinner', perform(component) {
perform present() { component.fashion.show = 'block' }
perform cover() { component.fashion.show = 'none' }
up.on('up:proxy:sluggish', present)
up.on('up:proxy:recuperate', cover)
cover()
});
The up:proxy:sluggish
occasion is triggered after 300 ms (configurable).
We will nonetheless implement interactions on the shopper
<div class="greeter">
<enter class="greeter--input">
Hi there <span class="greeter--name"><span>!
</div>
up.compiler('.greeter', perform(component) {
let enter = component.querySelector('.greeter--input')
let identify = component.querySelector('.greeter--name')
enter.addEventListener('enter', perform() {
identify.textContent = enter.worth
})
})
Going nuclear
Two-way bindings
With Rivets.js (6 KB):
<div class="template" up-data="{ "identify": "Arne" }">
<enter rv-value="identify">
Hi there { identify }!
</div>
up.compiler('.template', perform(component, knowledge) {
let view = rivets.bind(component, knowledge)
return perform() { view.unbind } // clear up
})
Composability
Homegrown UJS syntax normally lacks composability.
Altering that was a serious design objective for Unpoly.
Composability
JavaScript API
Unpoly’s default UJS habits is a small layer round a JS API.
You should use this JS API to name Unpoly from your personal code:
Unobtrusive
<a href="http://triskweline.de/unpoly-rugb/full.html" up-target=".story">Proceed</a>
Programmatic
up.exchange('.story', "http://triskweline.de/unpoly-rugb/full.html")
Composability
Occasions
$(doc).on('up:modal:open', perform(occasion) {
if (dontLikeModals()) {
occasion.preventDefault()
}
})
Composability
Invent your personal UJS syntax
HTML
<a menu-link href="http://triskweline.de/particulars">Present extra</span>
JavaScript
up.compiler('[menu-link]', perform(component) {
component.addEventListener('click on', perform(occasion) {
occasion.preventDefault();
up.popup.connect(component, {
goal: '.menu',
place: 'bottom-left',
animation: 'roll-down'
});
});
});
The JavaScript API is in depth
View full documentation of JS capabilities,
occasions and UJS selectors on
unpoly.com.
Animation
When a brand new component enters the DOM, you’ll be able to animate the looks:
<a href="http://triskweline.de/settings" up-modal=".menu" up-animation="fade-in">
Open settings
</a>
Once you swap a component, you’ll be able to transition between the previous and new states:
<a href="http://triskweline.de/customers" up-target=".listing" up-transition="cross-fade">
Present customers
</a>
Animations are carried out through CSS transforms on a 3D-accelerated layer.
Kinds
Painful issues with types:
- Submitting a type through AJAX
- File uploads through AJAX
- Detecting redirects of an AJAX type submission
- Coping with validation errors of an AJAX type submission
- Server-side validations and not using a web page load
- Dependencies between fields
- Submitting a type inside a modal whereas maintaining the modal open
These are all solved by Unpoly.
Ajax types
A type with [up-target]
will probably be submitted through AJAX
and go away surrounding parts intact:
<type technique="publish" motion="http://triskweline.de/customers" up-target=".foremost">
...
</type>
A profitable submission (standing 200) will replace .foremost
A failed submission (non-200 standing) will replace the shape itself
(Or use an [up-fail-target]
attribute)
Return non-200 standing
when validations fail
class UsersController standing: :bad_request
finish
finish
finish
Kinds inside a modal
To keep inside the modal, goal a selector inside the modal:
<type up-target=".selector-within-modal">
...
</type>
To shut the modal, goal a selector behind the modal:
<type up-target=".selector-behind-modal">
...
</type>
Server-side validations
and not using a web page load
<type motion="http://triskweline.de/customers">
<fieldset>
<label>E-mail</label>
<enter sort="textual content" identify="e mail" up-validate>
</label>
<fieldset>
<label>Password</label>
<enter sort="password" identify="password">
</fieldset>
<button sort="submit">Register</button>
</type>
Server-side validations
and not using a web page load
<type motion="http://triskweline.de/customers">
<fieldset>
<label>E-mail</label>
<enter sort="textual content" identify="e mail" up-validate>
</label>
<fieldset>
<label>Password</label>
<enter sort="password" identify="password">
</fieldset>
<button sort="submit">Register</button>
</type>
Server-side validations
and not using a web page load
<type motion="http://triskweline.de/customers">
<fieldset class="has-error">
<label>E-mail has already been taken!</label>
<enter sort="textual content" identify="e mail" up-validate worth="foo@bar.com">
</label>
<fieldset>
<label>Password</label>
<enter sort="password" identify="password">
</fieldset>
<button sort="submit">Register</button>
</type>
Server-side validations
and not using a web page load
class UsersController < ApplicationController
def create
@consumer = Consumer.new(user_params)
if @consumer.save
sign_in @consumer
else
render 'type', standing: :bad_request
finish
finish
finish
Server-side validations
and not using a web page load
class UsersController < ApplicationController
def create
@consumer = Consumer.new(user_params)
if up.validate?
@consumer.legitimate? # run validations, however do not save to DB
render 'type' # render type with error messages
elsif @consumer.save?
sign_in @consumer
else
render 'type', standing: :bad_request
finish
finish
finish
Dependent fields
Easy instances
Use [up-switch]
to indicate or cover current parts based mostly on a management worth:
<choose identify="advancedness" up-switch=".goal">
<choice worth="primary">Primary elements</choice>
<choice worth="superior">Superior elements</choice>
</choose>
<div class="goal" up-show-for="primary">
solely proven for advancedness = primary
</div>
<div class="goal" up-hide-for="primary">
hidden for advancedness = primary
</div>
Dependent fields
Tougher instances
Use [up-validate]
to replace a subject from the server when a management worth modifications.
Within the instance under we load a listing of staff when the consumer selects a division:
<type motion="/contracts">
<choose identify="division" up-validate="[name=employee]">...</choose>
<choose identify="worker">...</choose>
</type>
What server-side apps
do not do nicely
- Sluggish interplay suggestions
-
Web page hundreds destroy transient state
(scroll positions, unsaved type values, focus)
-
Layered interactions are arduous
(modals, drop-downs, drawers)
- Animation is difficult
- Complicated types
What server-side apps
can really be okay at
- Sluggish interplay suggestions
-
Web page hundreds destroy transient state
(scroll positions, unsaved type values, focus)
-
Layered interactions are arduous
(modals, drop-downs, drawers)
- Animation is difficult
- Complicated types
Getting began
-
Take a look at unpoly.com
to get an outline of what is included npm set up unpoly --save
orgem 'unpoly-rails'
(other methods)- Change half your JavaScript with
[up-target]
attributes - Convert remaining JavaScripts into
up.compiler()
Further slides
Replace a web page fragment
with out JavaScript
brief.html
<div class="story">
<p>Story abstract</p>
<a href="http://triskweline.de/unpoly-rugb/full.html" up-target=".story">
Learn full story
</a>
</div>
<p>This textual content will not change</p>
full.html
<div class="story">
<h1>Full story</h1>
<p>Lorem ipsum dolor sit amet.</p>
<a href="http://triskweline.de/unpoly-rugb/brief.html" up-target=".story">
Learn abstract
</a>
</div>
- Unpoly requests
full.html
through AJAX - Server renders a full HTML web page
- Unpoly extracts the fragment that matches
.story
- Unpoly swaps the previous and new fragment
- Unpoly modifications the browser URL to
http://host/full.html
Open fragment in modal dialog
with out JavaScript
<div class="story">
<p>Story abstract</p>
<a href="http://triskweline.de/unpoly-rugb/full.html" up-modal=".story">
Learn full story
</a>
</div>
- Unpoly requests
full.html
through AJAX - Server renders a full HTML web page
- Unpoly extracts the fragment that matches
.story
- Unpoly shows the brand new fragment in a
<div class="up-modal">
- Unpoly modifications the browser URL to
http://host/full.html
Open fragment in a popup menu
with out JavaScript
<div class="story">
<p>Story abstract</p>
<a href="http://triskweline.de/unpoly-rugb/full.html" up-popup=".story">
Learn full story
</a>
</div>
- Unpoly requests
full.html
through AJAX - Server renders a full HTML web page
- Unpoly extracts the fragment that matches
.story
- Unpoly shows the brand new fragment in a
<div class="up-popup">
- Unpoly modifications the browser URL to
http://host/full.html
All Async actions return guarantees
up.exchange('.story', "http://triskweline.de/unpoly-rugb/full.html").then(perform() {
// Fragments had been loaded and swapped
});
up.morph('.previous', '.new', 'cross-fade').then(perform() {
// Transition has accomplished
});
Appending as an alternative of changing
<div class="duties">
<li>Wash automotive</li>
<li>Buy provides</li>
<li>Repair tent</li>
</ul>
<a category="next-page" href="http://triskweline.de/duties?web page=2"
up-target=".duties:after, .next-page">Subsequent web page</a>
This appends the second web page to the duty listing
and replaces the “Subsequent web page” button with a hyperlink to web page 3.
Persisting parts
<div class="story">
<video up-keep src="http://triskweline.de/unpoly-rugb/film.mp4"></video>
<p>Story abstract</p>
<a href="http://triskweline.de/unpoly-rugb/full.html" up-target=".story">
Learn full story
</a>
</div>
Updating persevered parts
<div class="map" up-data="[
{ lat: 48.36, lng: 10.99 },
{ lat: 48.75, lng: 11.45 }
]"></div>
<type technique="publish" motion="/pins" up-target=".map">
Lat: <enter identify="lat">
Lng: <enter identify="lng">
<button>Add pin</button>
</type>
up.compiler('.map', perform(component, pins) {
var map = new google.maps.Map(component)
pins.forEach(perform(pin) {
var place = new google.maps.LatLng(pin.lat, pin.lng);
new google.maps.Marker({
place: place,
map: map
})
})
<div class="map" up-data="<%= @pins.to_json %>"></div>
<type technique="publish" motion="/pins" up-target=".map">
Lat: <enter identify="lat">
Lng: <enter identify="lng">
<button>Add pin</button>
</type>
up.compiler('.map', perform(component, pins) {
var map = new google.maps.Map(component);
pins.forEach(perform(pin) {
var place = new google.maps.LatLng(pin.lat, pin.lng)
new google.maps.Marker({
place: place,
map: map
})
})
<div class="map" up-data="<%= @pins.to_json %>"></div>
<%= form_for Pin.new, html: { 'up-target' => '.map' } do |type| %>
Lat: <%= type.text_field :lat %>
Lng: <%= type.text_field :lng %>
<%= type.submit 'Add pin' %>
<% finish %>
up.compiler('.map', perform(component, pins) {
var map = new google.maps.Map(component)
pins.forEach(perform(pin) {
var place = new google.maps.LatLng(pin.lat, pin.lng)
new google.maps.Marker({
place: place,
map: map
})
})
<div class="map" up-data="<%= @pins.to_json %>"></div>
<%= form_for Pin.new, html: { 'up-target' => '.map' } do |type| %>
Lat: <%= type.text_field :lat %>
Lng: <%= type.text_field :lng %>
<%= type.submit 'Add pin' %>
<% finish %>
up.compiler('.map', perform(component, initialPins) {
var map = new google.maps.Map(component)
perform renderPins(pins) { ... }
renderPins(initialPins)
});
<div class="map" up-data="<%= @pins.to_json %>" up-keep></div>
<%= form_for Pin.new, html: { 'up-target' => '.map' } do |type| %>
Lat: <%= type.text_field :lat %>
Lng: <%= type.text_field :lng %>
<%= type.submit 'Add pin' %>
<% finish %>
up.compiler('.map', perform($component, initialPins) {
var map = new google.maps.Map($component);
perform renderPins(pins) { ... }
renderPins(initialPins)
component.addEventListener('up:fragment:preserve', perform(occasion) {
renderPins(occasion.newData)
})
})
Discover-as-you-type search
<type motion="http://triskweline.de/customers" up-target=".listing" up-autosubmit>
<enter sort="search" identify="question" />
</type>
<div class="listing">
<% @customers.every do |consumer| %>
<%= link_to consumer.e mail, consumer >
<% finish %>
</div>
Reminiscence leaks
Random.js
HTML
<clock></clock>
JavaScript
$.unobtrusive(perform() {
$(this).discover('clock', perform() {
var $clock = $(this);
perform updateClock() {
var now = new Date();
$clock.html(now.toString());
}
setInterval(updateClock, 1000);
});
});
Random.js: Leaky
HTML
<clock></clock>
JavaScript
$.unobtrusive(perform() {
$(this).discover('clock', perform() {
var $clock = $(this);
perform updateClock() {
var now = new Date();
$clock.html(now.toString());
}
setInterval(updateClock, 1000); // creates one interval per <clock>!
});
});
Unpoly compiler: Nonetheless leaky
HTML
<clock></clock>
JavaScript
up.compiler('clock', perform(clock) {
perform updateClock() {
var now = new Date()
clock.textContent = now.toString()
}
setInterval(updateClock, 1000) // this nonetheless leaks reminiscence!
});
Unpoly compiler: Clear
HTML
<clock></clock>
JavaScript
up.compiler('clock', perform(clock) {
perform updateClock() {
var now = new Date()
clock.textContent = now.toString()
}
var interval = setInterval(updateClock, 1000)
return perform() { clearInterval(interval) } // clear up when destroyed
})
Unpoly compiler: Leaky
up.compiler('textarea.wysiwyg', perform(textarea) {
$R(textarea)
})
Unpoly compiler: Clear
up.compiler('textarea.wysiwyg', perform(textarea) {
$R(textarea)
return perform() {
$R(textarea, 'destroy')
}
})
Why transitions are arduous
Why transitions are arduous
Ghosting
place: static
show: hidden
place: static
opacity: 0
place: absolute
place: absolute
With out ghosting
With ghosting
Predefined animations
fade-in
fade-out
move-to-top
move-from-bottom
move-to-bottom
move-from-top
move-to-left
move-from-right
move-to-right
move-from-left
Predefined transitions
cross-fade
move-top
move-bottom
move-left
move-right
Customized animations
up.animation('zoom-in', perform(component, choices) {
var firstFrame = {
opacity: 0,
remodel: 'scale(0.5)'
}
var lastFrame = {
opacity: 1,
remodel: 'scale(1)'
}
up.component.setStyle(component, firstFrame)
return up.animate(component, lastFrame, choices)
})
Toggle all: On load
<span class="toggle-all">Toggle all</span>
doc.addEventListener('DOMContentLoaded', perform() {
doc.querySelectorAll('.toggle-all').forEach(perform(component) {
component.addEventListener('click on', perform() {
let type = component.closest('type');
let checkboxes = type.querySelectorAll('enter[type=checkox]');
let someUnchecked = !!checkboxes.discover(perform(cb) { !cb.checked }
checkboxes.forEach(perform(cb) {
cb.checked = someUnchecked
})
})
})
})
Run example on codepen.io.
That is what you see in jQuery tutorials.
HTML fragments loaded through AJAX do not get Javascriptified.
Toggle all: Angular directive
<span class="toggle-all">Toggle all</span>
app.directive('toggle-all', perform() {
return {
limit: 'C',
hyperlink: perform(scope, $hyperlink) {
$hyperlink.on('click on', perform() {
var $type = $hyperlink.closest('type')
var $checkboxes = $type.discover(':checkbox')
var someUnchecked = $checkboxes.is(':not(:checked)')
$checkboxes.prop('checked', someUnchecked)
})
}
}
})
It is good how Angular lets us register a compiling perform for a CSS selector.
Additionally we needn’t manually Javascriptify new fragments
so long as we insert them by way of Angular templates
Toggle all: Unpoly compiler
<span class="toggle-all">Toggle all</span>
up.compiler('.toggle-all', perform(component) {
component.addEventListener('click on', perform() {
let type = component.closest('type');
let checkboxes = type.querySelectorAll('enter[type=checkox]');
let someUnchecked = !!checkboxes.discover(perform(cb) { !cb.checked }
checkboxes.forEach(perform(cb) {
cb.checked = someUnchecked
})
})
})
Unpoly routinely compiles all fragments that it inserts or updates.
Legacy browsers
Unpoly gracefully degrades with previous variations of Web Explorer:
Edge | Full help |
---|---|
IE 10 | Full help |
IE 9 |
Web page updates that change browser historical past fall again to a traditional web page load. Animations and transitions are skipped. |
IE8 | Unpoly prevents itself from booting, leaving you with a traditional server-side utility |
Bootstrap integration
-
Embrace unpoly-bootstrap3.js and
unpoly-bootstrap3.css -
Configures Unpoly to make use of Bootstrap kinds for dialogs,
marking present navigation tabs, and many others. - Makes Unpoly conscious of mounted Bootstrap format elements when scrolling the viewport.
- Typically we attempt to not do issues that may completely conflict with Bootstrap.
Rails integration
Embrace unpoly-rails In your Gemfile
:
gem 'unpoly-rails'
In your controllers, views and helpers:
up? # request.headers['X-Up-Target'].current?
up.goal # request.headers['X-Up-Target']
up.title="Title from server" # response.headers['X-Up-Title'] = 'Title ...'
up.validate? # request.headers['X-Up-Validate'].current?
The gem additionally offers the JS and CSS property for the most recent Unpoly.
Different set up strategies
Though the Rails bindings are good, Unpoly works with any form of backend.
E.g. unpoly.com is a static intermediary website utilizing Unpoly.
Unit testing
Use Jasmine to explain examples.
Use jasmine-jquery to create pattern parts.
Use up.hello
to compile pattern parts.
up.compiler('.current-year', perform($component) {
var 12 months = new Date().getFullYear();
$component.textual content(12 months);
});
describe('.current-year', perform() {
it("shows right now's 12 months", perform() {
$component = affix('.current-today');
up.hey($component);
12 months = new Date().getFullYear();
anticipate($component).toHaveText(12 months.toString());
});
});
Simpler integration testing
Disable animation:
up.movement.config.enabled = false;
Disable concurrent requests:
up.proxy.config.maxRequests = 1;
Wait earlier than you do issues:
AfterStep do
sleep 0.05 whereas web page.evaluate_script('window.up && up.proxy.isBusy()')
finish
(Or use patiently
).
Use jasmine-ajax to mock the community:
up.compiler('.server-time', perform($component) {
$component.textual content('Loading ...');
up.ajax('/time').then(perform(time) { $component.textual content(time) };
});
describe('.current-year', perform() {
it('fetches and shows the present time from the server', perform() {
jasmine.Ajax.set up();
var $component = affix('.server-time');
up.hey($component);
anticipate($component).toHaveText('Loading...');
jasmine.Ajax.requests.mostRecent().respondWith({
standing: 200,
contentType: 'textual content/plain',
responseText: '13:37:00'
});
anticipate($component).toHaveText('13:37:00');
});
});
Who else went again?
Venture state
Response occasions
-
0.1 second is concerning the restrict for having the consumer really feel that the system is reacting
instantaneously,
which means that no particular suggestions is critical besides to show the
end result. -
1.0 second is concerning the restrict for the consumer’s stream of thought to remain uninterrupted, even
although the consumer will discover the delay. Usually, no particular suggestions is critical throughout
delays of greater than 0.1 however lower than 1.0 second, however the consumer does lose the sensation of
working immediately on the information. -
10 seconds is concerning the restrict for maintaining the consumer’s consideration targeted on the dialogue.
For longer delays, customers will wish to carry out different duties whereas ready for the pc
to complete, so they need to be given suggestions indicating when the pc expects to be
accomplished. Suggestions in the course of the delay is particularly necessary if the response time is prone to
be extremely variable, since customers will then not know what to anticipate.
Miller 1968; Card et al. 1991; Jacob Nielsen 1993
Additionally see Google’s RAIL Performance Model.
Repurpose current UJS syntax
HTML
<a menu-link href="http://triskweline.de/particulars">Present extra</span>
JavaScript
up.macro('[menu-link]', perform($hyperlink) {
$hyperlink.attr(
'up-target': '.menu',
'up-position': 'bottom-left',
'up-animation': 'roll-down'
});
});
Is Unpoly proper for my undertaking?
☑ You aren’t writing tremendous bold UI
☑ You’ve some management over the UI necessities
☑ You are able to launch 100% of your JavaScript from up.compiler
☑ You are OK with coping with the occasional breaking change
Is your various home-growing a casual Random.js framework?