Revision code

Changes Version 0.60.0
Released on January 24, 2019 with 269 commits

This is a major update with some breaking changes.

Highlights

  • jQuery is no longer required! Unpoly now has zero dependencies.
  • New up.element helpers to complement native Element methods. You might not even miss jQuery anymore.
  • Vastly improved performance on slow devices.
  • Utility functions that work with arrays and array-like values have been greatly improved.
  • The up.util module now plug the worst emissions in JavaScript's standard library: Equality-by-value, empty-by-value and shallow-copy. Your own objects may hook into those protocols.
  • You may define a padding when revealing.
  • Smooth scrolling now mimics native scroll behavior.
  • Fixed many positioning issues with popups and tooltips.
  • Several modules have been renamed to match the pattern up.thing.verb(). up.dom is now up.fragment, up.bus is now up.event, up.layout is now up.viewport.

Details below.

jQuery is no longer required

jQuery no longer required to use Unpoly. That means Unpoly no longer has any dependencies!

Due to its use of native DOM APIs, Unpoly is now a lot faster. Like, a lot. Ditching jQuery also saves you 30 KB of gzipped bundle size and speeds up your own code.

Migrating apps that use jQuery

Effort has been made to ensure that migrating to this version is smooth for existing apps that use jQuery.

All Unpoly functions that accept element arguments will accept both native elements and jQuery collections.

You will need to prefix some function calls with $ to have your callbacks called with jQuery collections instead of native elements:

Finally, all Unpoly events (up:*) are now triggered as native events that can be received with Element#addEventListener(). You may continue to use jQuery's jQuery#on() to listen to Unpoly events, but you need to access custom properties through event.originalEvent.

Also know that if you use jQuery's $.fn.trigger() to emit events, these events are not received by native event listeners (including Unpoly). Use up.emit() instead to trigger an event that can be received by both native listeners and jQuery listeners.

See below for detailed changes.

New DOM helpers

A new, experimental up.element module offers convience functions for DOM manipulation and traversal.

It complements native Element methods and works across all supported browsers without polyfills.

up.element.first() Returns the first descendant element matching the given selector.
up.element.all() Returns all descendant elements matching the given selector.
up.element.subtree() Returns a list of the given parent's descendants matching the given selector. The list will also include the parent element if it matches the selector itself.
up.element.closest() Returns the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
up.element.matches() Matches all elements that have a descendant matching the given selector.
up.element.get() Casts the given value to a native Element.
up.element.toggle() Display or hide the given element, depending on its current visibility.
up.element.toggleClass() Adds or removes the given class from the given element.
up.element.hide() Hides the given element.
up.element.show() Shows the given element.
up.element.remove() Removes the given element from the DOM tree.
up.element.replace() Replaces the given old element with the given new element.
up.element.setAttrs() Sets all key/values from the given object as attributes on the given element.
up.element.affix() Creates an element matching the given CSS selector and attaches it to the given parent element.
up.element.createFromSelector() Creates an element matching the given CSS selector.
up.element.createFromHtml() Creates an element from the given HTML fragment.
up.element.toSelector() Returns a CSS selector that matches the given element as good as possible.
up.element.setAttrs() Sets all key/values from the given object as attributes on the given element.
up.element.booleanAttr() Returns the value of the given attribute on the given element, cast as a boolean value.
up.element.numberAttr() Returns the value of the given attribute on the given element, cast to a number.
up.element.jsonAttr() Reads the given attribute from the element, parsed as JSON.
up.element.style() Receives computed CSS styles for the given element.
up.element.styleNumber() Receives a computed CSS property value for the given element, casted as a number.
up.element.setStyle() Sets the given CSS properties as inline styles on the given element.
up.element.isVisible() Returns whether the given element is currently visible.
:has() A non-standard pseudo-class that matches all elements that have a descendant matching the given selector.

Events

  • The up.bus module has been renamed to up.event. We want to normalize Unpoly's API to the pattern up.thing.verb() in the future.
  • All Unpoly events (up:*) are now triggered as native events that can be received with Element#addEventListener(). You may continue to use jQuery's jQuery#on() to listen to Unpoly events, but you need to access custom properties from event.originalEvent.
  • Properties named event.$target and event.$element have been removed from all Unpoly events. Use the standard event.target to retrieve the element on which the element was emitted.
  • up.on() may now bind to a given element by passing it as an (optional) first argument:

    up.on(element, '.button', 'click', (event) => { ... })
    

    You may use this for event delegation.

  • The event handler passed to up.on() now receives an element instead of a jQuery collection:

    up.on('click', (event, element) => {
      alert("Clicked on an " + element.tagName)
    })
    

    For the old behavior, use up.$on().

  • up.emit() may now trigger an event on a given element by passing the element as an (optional) first argument:

    up.emit(element, 'app:user:login', { email: 'foo@example.com' })
    
  • up.emit() option { message } is now { log }.
  • up.emit() no longer logs by default. You can enable the old efault message with { log: true }.
  • up.event.nobodyPrevents() option { message } is now { log }.
  • The experimental function up.reset() was removed without replacement.
  • The experimental event up:framework:reset was removed without replacement.

Custom JavaScript

  • Compilers may again return an array of destructor functions. The previous deprecation was removed.
  • The up.compiler() callback now receives a native element instead of a jQuery collection:

    up.compiler('.button', function(button) {
      alert("We have a new button with class " + button.className)
    })
    

    For the old behavior, use up.$compiler().

  • The up.macro() callback now received a native element instead of a jQuery collection:

    up.compiler('a.fast-link', function(element) {
      element.setAttribute('up-preload', 'up-preload')
      element.setAttribute('up-instant', 'up-instant')
    })
    

    For the old behavior, use up.$macro().

Forms

  • up:form:submit no longer has a { $form } property. The event is now emitted on the form that is being submitted.
  • up.observe() now accepts a single form field, multiple fields, a <form> or any container that contains form fields. The callback is called once for each change in any of the given elements.
  • The callback for up.observe() now receives the arguments (value, name), where value is the changed field value and name is the [name] of the field element:

    up.observe('form', function(value, name) {
      console.log('The value of %o is now %o', name, value);
    });
    

    The second argument was previously the observed input element as a jQuery collection.

  • up.observe() now accepts a { batch: true } option to receive all changes since the last callback in a single object:

    up.observe('form', { batch: true }, function(diff) {
      console.log('Observed one or more changes: %o', diff);
    });
    
  • The default up.form.config.validateTargets no longer includes the selector '[up-fieldset]'.

Animation

  • CSS property names for custom animations and transitions must be given in kebab-case. camelCase properties are no longer supported.

Fragment update API

  • The module up.dom has been renamed to up.fragment. We want to normalize Unpoly's API to the pattern up.thing.verb() in the future.
  • The experimental function up.all() has been removed without replacement
  • The function up.first() has been renamed to up.fragment.first() to not be confused with the low-level up.element.first().
  • The event up:fragment:destroy has been removed without replacement. This event was previously emitted before a fragment was removed. The event up:fragment:destroyed (emitted after a fragment was removed), remains in the API.
  • The up:fragment:destroyed event no longer has a { $element } property. It now has a { fragment } property that contains the detached element. Like before, it is emitted on the former parent of the destroyed element.
  • The properties for the up:fragment:keep event have been renamed.
  • The properties for the up:fragment:kept event have been renamed.
  • The properties for the up:fragment:inserted event have been renamed.
  • The properties for the up:fragment:destroyed event have been renamed.

Utility functions

The up.util module now plug the worst emissions in JavaScript's standard library: Equality-by-value, empty-by-value, shallow copy:

  • New experimental function up.util.isEqual(). It returns whether the given arguments are equal by value.
  • New experimental property up.util.isEqual.key. This property contains the name of a method that user-defined classes may implement to hook into the up.util.isEqual() protocol.
  • up.util.isBlank() now returns false for objects with a constructor.
  • New experimental property up.util.isBlank.key. This property contains the name of a method that user-defined classes may implement to hook into the up.util.isBlank() protocol.
  • New experimental property up.util.copy.key. This property contains the name of a method that user-defined classes may implement to hook into the up.util.copy() protocol.

More utility functions to have been added to work with lists:

  • New experimental function up.util.findResult(). It consecutively calls the given function which each element in the given list and returns the first truthy return value.
  • New experimental function up.util.flatten(). This flattens the given list a single level deep.
  • New experimental function up.util.flatMap(). This maps each element using a mapping function, then flattens the result into a new array.

Some list functions have been renamed to names used in the standard Array API:

All functions that worked for arrays now also work for array-like values:

And some minor changes:

  • up.util.nextFrame() has been renamed to up.util.task().
  • up.util.setTimer() has been renamed to up.util.timer().
  • `up.util.toArray() now returns its unchanged argument if the argument is already an array.
  • up.util.copy() now works with Date objects.
  • up.util.isBoolean() is now stable
  • up.util.escapeHtml() is now stable
  • up.util.isJQuery() now returns false if no jQuery is loaded into the window.jQuery global
  • up.util.unresolvablePromise() was removed without replacement.
  • up.util.trim() has been removed without replacement. Use the standard String#trim() instead.
  • up.util.parseUrl() now returns the correct { hostname }, { protocol } and { pathname } properties on IE11.
  • up.util.selectorForElement() is now up.element.toSelector()

Scrolling Viewports

  • The up.layout module has been renamed to up.viewport. We want to normalize Unpoly's API to the pattern up.thing.verb() in the future.
  • Smooth scrolling now mimics native scroll behavior:
    • up.scroll() no longer takes a { duration } or { easing } option.
    • up.scroll() now takes a { behavior } option. Valid values are auto (no animation) and smooth (animates the scroll motion).
    • You may control the pace of { behavior: 'smooth' } by also passing a { speed } option`.
    • New config property up.viewport.scrollSpeed. This sets the default speed for smooth scrolling. The default value (1) roughly corresponds to the default speed of Chrome's native smooth scrolling.
  • Options for up.reveal() have been changed:
    • Options { duration } and { easing } have been removed.
    • New option { padding } to pass the desired padding between the revealed element and the closest viewport edge (in pixels).
    • New option { snap }. It can be true, false or a pixel number.
    • New option { behavior }
    • New option { speed }. Defaults to up.viewport.scrollSpeed.
    • Config property up.layout.config.snap has been renamed to up.viewport.config.revealSnap.
    • New config option up.viewport.revealPadding.
  • New experimental function up.viewport.root(). It return the scrolling element for the browser's main content area.
  • New experimental function up.viewport.closest(). It returns the scrolling container for the given element.
  • When a #hash anchor is revealed during the initial page load, Unpoly will look for an [up-id=hash] before looking for [id=hash] and a[name=hash].
  • Fix issues with restoring scroll positions when going back on some browsers.
  • [up-alias] now accepts one or more asterisks (*) anywhere in the pattern. It was previously limited to match URLs with a given prefix.

Performance

  • Use of native browser APIs has improved performance drastically.
  • [up-preload] and [up-instant] links no longer bind to the touchstart event, increasing frame rate while scrolling.

Request parameters

The experimental up.params module has been replaced with the up.Params class. Wrap any type of parameter representation into up.Params to get consistent API for reading and manipulation.

The following types of parameter representation are supported:

  1. An object like { email: 'foo@bar.com' }
  2. A query string like 'email=foo%40bar.com'
  3. An array of { name, value } objects like [{ name: 'email', value: 'foo@bar.com' }]
  4. A FormData object. On IE 11 and Edge, FormData payloads require a polyfill for FormData#entries().

Supported methods are:

new up.Params() Constructor.
up.Params#add() Adds a new entry with the given name and value.
up.Params#addAll() Adds all entries from the given list of params.
up.Params#addField() Adds params from the given HTML form field.
up.Params#delete() Deletes all entries with the given name.
up.Params#get() Returns the first param value with the given name from the given params.
up.Params#set() Sets the value for the entry with given name.
up.Params#toArray() Returns an array representation of this up.Params instance.
up.Params#toFormData() Returns a FormData representation of this up.Params instance.
up.Params#toObject() Returns an object representation of this up.Params instance.
up.Params#toQuery() Returns an query string for this up.Params instance.
up.Params#toURL() Builds an URL string from the given base URL and this up.Params instance as a query string.
up.Params.fromFields() Constructs a new up.Params instance from one or more HTML form field.
up.Params.fromForm() Constructs a new up.Params instance from the given <form>.
up.Params.fromURL() Constructs a new up.Params instance from the given URL's query string.

Popups

  • The HTML markup for a popup has been changed to make it easier to style with CSS. The new structure is:

    <div class="up-popup">
      <div class="up-popup-content">
        Fragment content here
      </div>
    </div>
    
  • The default CSS styles for .up-popup has been changed. If you have customized popup styles, you should check if your modifications still work with the new defaults.
  • Popups now update their position when the screen is resized.
  • Popups now follow scrolling when placed within viewports other than the main document.
  • The [up-position] attribute has been split into two attributes [up-position] and [up-align]. Similarly the { position } option has been split into two options { position } and { align }:
    • { position } defines on which side of the opening element the popup is attached. Valid values are 'top', 'right', 'bottom' and 'left'.
    • { align } defines the alignment of the popup along its side.
    • When the popup's { position } is 'top' or 'bottom', valid { align } values are 'left', center' and 'right'.
    • When the popup's { position } is 'left' or 'right', valid { align } values are top', center' and bottom'.
  • New experimental function up.popup.sync(). It forces the popup to update its position when a layout change is not detected automatically.
  • popup elements are now appended to the respective viewport of the anchor element. They were previously always appended to the end of the <body>.
  • The events up:popup:open,up:popup:opened, up:popup:close and up:popup:closed have an { anchor } property. It references the element that the popup was attached to.

Tooltips

  • The HTML markup for a popup has been changed to make it easier to style with CSS. The new structure is:

    <div class="up-tooltip">
      <div class="up-tooltip-content">
        Tooltip text here
      </div>
    </div>
    
  • The default CSS styles for .up-tooltip have been changed. If you have customized tooltip styles, you should check if your modifications still work with the new defaults.
  • Tooltips now update their position when the screen is resized.
  • Tooltips now follow scrolling when placed within viewports other than the main document.
  • The [up-position] attribute has been split into two attributes [up-position] and [up-align]. Similarly the { position } option has been split into two options { position } and { align }:
    • { position } defines on which side of the opening element the popup is attached. Valid values are 'top', 'right', 'bottom' and 'left'.
    • { align } defines the alignment of the popup along its side.
    • When the tooltip's { position } is 'top' or 'bottom', valid { align } values are 'left', center' and 'right'.
    • When the tooltip's { position } is 'left' or 'right', valid { align } values are top', center' and bottom'.
  • New experimental function up.tooltip.sync(). It forces the popup to update its position when a layout change is not detected automatically.
  • Tooltip elements are now appended to the respective viewport of the anchor element. They were previously always appended to the end of the <body>.

Ruby on Rails integration

AJAX acceleration

  • Opening/closing a modal will now manipulate the { overflow-y } style on the same element that was chosen by the CSS author (nasty details).

Various

  • Renamed some files so they won't be blocked by over-eager ad blockers on developer PCs.
  • Deprecation warnings are only printed once per environment.

Upgrading

If you're upgrading from an older Unpoly version you should load unpoly-migrate.js to polyfill deprecated APIs. Changes handled by unpoly-migrate.js are not considered breaking changes.

See our upgrading guide for details.