Now Reading
We’re breaking apart with JavaScript frontends

We’re breaking apart with JavaScript frontends

2022-12-10 11:35:12

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

  1. SPAs are an excellent match for a sure class of UI.

    For us that class is the exception, not the default.
  2. There is a trade-off between UI constancy and code complexity.
  3. 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

  1. Huge alternative of nice and mature languages
  2. Low complexity stack
  3. Synchronous knowledge entry
  4. Time to first render
  5. Works on low-end gadgets

What server-side apps

do not do nicely

  1. Sluggish interplay suggestions
  2. Web page hundreds destroy transient state

    (scroll positions, unsaved type values, focus)

  3. Layered interactions are arduous

    (modals, drop-downs, drawers)

  4. Animation is difficult
  5. 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:

  1. Navigate between playing cards, open and cancel the shape

    Web page transitions are animated

  2. Navigate between playing cards within the left pane

    Scroll positions are stored in each panes

  3. 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

  4. Edit a card, change the title, then change the sample

    Sample choice occurs in a modal dialog,
    preserving unsaved type values

  5. 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 loading

    Get 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 on mousedown as an alternative of click 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 minutes

    Any 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

See Also

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

  1. Sluggish interplay suggestions
  2. Web page hundreds destroy transient state

    (scroll positions, unsaved type values, focus)

  3. Layered interactions are arduous

    (modals, drop-downs, drawers)

  4. Animation is difficult
  5. Complicated types

What server-side apps

can really be okay at

  1. Sluggish interplay suggestions
  2. Web page hundreds destroy transient state

    (scroll positions, unsaved type values, focus)

  3. Layered interactions are arduous

    (modals, drop-downs, drawers)

  4. Animation is difficult
  5. Complicated types

Getting began

  • Take a look at unpoly.com
    to get an outline of what is included
  • npm set up unpoly --save or gem 'unpoly-rails' (other methods)
  • Change half your JavaScript with [up-target] attributes
  • Convert remaining JavaScripts into up.compiler()

henning.koch@makandra.de

@triskweline

Further slides

Replace a web page fragment

with out JavaScript

Run example on unpoly.com

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

Run example on unpoly.com

<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

Run example on unpoly.com

<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
});

Curriculum lesson on promises

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

  • An everyday Random.js app is filled with reminiscence leaks.
  • We simply do not discover as a result of the JavaScript VM is reset between web page hundreds!
  • We won’t have reminiscence leaks in persistent JavaScript VMs

    like Angular.js, Unpoly, Turbolinks

  • Background: one,
    two.

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

Outdated

place: static
show: hidden

New

place: static
opacity: 0

Outdated (ghost)

place: absolute

New (ghost)

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?



Source Link

What's Your Reaction?
Excited
0
Happy
0
In Love
0
Not Sure
0
Silly
0
View Comments (0)

Leave a Reply

Your email address will not be published.

2022 Blinking Robots.
WordPress by Doejo

Scroll To Top