###* Enhancing elements

Up.js keeps a persistent Javascript environment during page transitions. If you wire Javascript to run on `ready` or `onload` events, those scripts will only run during the initial page load. Subsequently [inserted](/up.replace) page fragments will not be compiled.

Let's say your Javascript plugin wants you to call `lightboxify()` on links that should open a lightbox. You decide to do this for all links with an `lightbox` class:

<a href="river.png" class="lightbox">River</a>
<a href="ocean.png" class="lightbox">Ocean</a>

You should avoid doing this on page load:

$(document).on('ready', function() {
  $('a.lightbox').lightboxify();
});

Instead you should register a [`compiler`](/up.compiler) for the `a.lightbox` selector:

up.compiler('a.lightbox', function($element) {
  $element.lightboxify();
});

The compiler function will be called on matching elements when the page loads, or whenever a matching fragment is [updated through Up.js](/up.replace) later.

@class up.syntax ### up.syntax = (($) ->

u = up.util

DESTROYABLE_CLASS = 'up-destroyable'
DESTROYER_KEY = 'up-destroyer'

###*
Registers a function to be called whenever an element with
the given selector is inserted into the DOM.

    $('.action').compiler(function($element) {
      // your code here
    });

Compiler functions will be called on matching elements when
the page loads, or whenever a matching fragment is [updated through Up.js](/up.replace)
later.

If you have used Angular.js before, this resembles
[Angular directives](https://docs.angularjs.org/guide/directive).

\#\#\#\# Integrating jQuery plugins

`up.compiler` is a great way to integrate jQuery plugins.
Let's say your Javascript plugin wants you to call `lightboxify()`
on links that should open a lightbox. You decide to
do this for all links with an `lightbox` class:

    <a href="river.png" class="lightbox">River</a>
    <a href="ocean.png" class="lightbox">Ocean</a>

This Javascript will do exactly that:

    up.compiler('a.lightbox', function($element) {
      $element.lightboxify();
    });

\#\#\#\# Custom elements

You can use `up.compiler` to implement custom elements like this:

    <clock></clock>

Here is the Javascript that inserts the current time into to these elements:

    up.compiler('clock', function($element) {
      var now = new Date();
      $element.text(now.toString()));
    });

\#\#\#\# Cleaning up after yourself

If your compiler returns a function, Up.js will use this as a *destructor* to
clean up if the element leaves the DOM. Note that in Up.js the same DOM ad Javascript environment
will persist through many page loads, so it's important to not create
[memory leaks](https://makandracards.com/makandra/31325-how-to-create-memory-leaks-in-jquery).

You should clean up after yourself whenever your compilers have global
side effects, like a [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval)
or event handlers bound to the document root.

Here is a version of `<clock>` that updates
the time every second, and cleans up once it's done:

    up.compiler('clock', function($element) {

      function update() {
        var now = new Date();
        $element.text(now.toString()));
      }

      setInterval(update, 1000);

      return function() {
        clearInterval(update);
      };

    });

If we didn't clean up after ourselves, we would have many ticking intervals
operating on detached DOM elements after we have created and removed a couple
of `<clock>` elements.

\#\#\#\# Attaching structured data

In case you want to attach structured data to the event you're observing,
you can serialize the data to JSON and put it into an `[up-data]` attribute.
For instance, a container for a [Google Map](https://developers.google.com/maps/documentation/javascript/tutorial)
might attach the location and names of its marker pins:

    <div class="google-map" up-data="[
      { lat: 48.36, lng: 10.99, title: 'Friedberg' },
      { lat: 48.75, lng: 11.45, title: 'Ingolstadt' }
    ]"></div>

The JSON will parsed and handed to your event handler as a second argument:

    up.compiler('.google-map', function($element, pins) {

      var map = new google.maps.Map($element);

      pins.forEach(function(pin) {
        var position = new google.maps.LatLng(pin.lat, pin.lng);
        new google.maps.Marker({
          position: position,
          map: map,
          title: pin.title
        });
      });

    });

\#\#\#\# Migrating jQuery event handlers to `up.compiler`

Within the compiler, Up.js will bind `this` to the
native DOM element to help you migrate your existing jQuery code to
this new syntax.

So if you had this before:

    $(function() {
      $('.action').on('click', function() {
        $(this).something();
      });
    });

... you can reuse the event handler like this:

    $('.action').compiler(function($element) {
      $element.on('click', function() {
        $(this).something();
      });
    });

@function up.compiler
@param {String} selector
  The selector to match.
@param {Boolean} [options.batch=false]
  If set to `true` and a fragment insertion contains multiple
  elements matching the selector, `compiler` is only called once
  with a jQuery collection containing all matching elements. 
@param {Boolean} [options.keep=false]
  If set to `true` compiled fragment will be [persisted](/up-keep) during
  [page updates](/a-up-target).

  This has the same effect as setting an `up-keep` attribute on the element.
@param {Function($element, data)} compiler
  The function to call when a matching element is inserted.
  The function takes the new element as the first argument (as a jQuery object).
  If the element has an `up-data` attribute, its value is parsed as JSON
  and passed as a second argument.

  The function may return a destructor function that destroys the compiled
  object before it is removed from the DOM. The destructor is supposed to
  clear global state such as time-outs and event handlers bound to the document.
  The destructor is *not* expected to remove the element from the DOM, which
  is already handled by [`up.destroy`](/up.destroy).
@stable
###
compilers = []

compiler = (selector, args...) ->
  # Silently discard any compilers that are registered on unsupported browsers
  return unless up.browser.isSupported()
  compiler = args.pop()
  options = u.options(args[0])
  compilers.push
    selector: selector
    callback: compiler
    batch: options.batch
    keep: options.keep

applyCompiler = (compiler, $jqueryElement, nativeElement) ->
  up.puts ("Compiling '%s' on %o" unless compiler.isDefault), compiler.selector, nativeElement
  if compiler.keep
    value = if u.isString(compiler.keep) then compiler.keep else ''
    $jqueryElement.attr('up-keep', value)
  destroyer = compiler.callback.apply(nativeElement, [$jqueryElement, data($jqueryElement)])
  if u.isFunction(destroyer)
    $jqueryElement.addClass(DESTROYABLE_CLASS)
    $jqueryElement.data(DESTROYER_KEY, destroyer)

###*
Applies all compilers on the given element and its descendants.
Unlike [`up.hello`](/up.hello), this doesn't emit any events.

@function up.syntax.compile
@param {Array<Element>} [options.skip]
  A list of elements whose subtrees should not be compiled.
@internal
###
compile = ($fragment, options) ->
  options = u.options(options)
  $skipSubtrees = $(options.skip)

  up.log.group "Compiling fragment %o", $fragment.get(0), ->
    for compiler in compilers
      $matches = u.findWithSelf($fragment, compiler.selector)

      $matches = $matches.filter ->
        $match = $(this)
        u.all $skipSubtrees, (element) ->
          $match.closest(element).length == 0

      if $matches.length
        up.log.group ("Compiling '%s' on %d element(s)" unless compiler.isDefault), compiler.selector, $matches.length, ->
          if compiler.batch
            applyCompiler(compiler, $matches, $matches.get())
          else
            $matches.each -> applyCompiler(compiler, $(this), this)

###*
Runs any destroyers on the given fragment and its descendants.
Unlike [`up.destroy`](/up.destroy), this doesn't emit any events
and does not remove the element from the DOM.

@function up.syntax.clean
@internal
###
clean = ($fragment) ->
  u.findWithSelf($fragment, ".#{DESTROYABLE_CLASS}").each ->
    $element = $(this)
    destroyer = $element.data(DESTROYER_KEY)
    destroyer()

###*
Checks if the given element has an `up-data` attribute.
If yes, parses the attribute value as JSON and returns the parsed object.

Returns an empty object if the element has no `up-data` attribute.

@function up.syntax.data
@param {String|Element|jQuery} elementOrSelector
@return
  The JSON-decoded value of the `up-data` attribute.

  Returns an empty object (`{}`) if the element has no (or an empty) `up-data` attribute.
@experimental
###

###
If an element annotated with [`up-data`] is inserted into the DOM,
Up will parse the JSON and pass the resulting object to any matching
[`up.compiler`](/up.syntax.compiler) handlers.

Similarly, when an event is triggered on an element annotated with
[`up-data`], the parsed object will be passed to any matching
[`up.on`](/up.on) handlers.

@selector [up-data]
@param {JSON} up-data
  A serialized JSON string
@stable
###
data = (elementOrSelector) ->
  $element = $(elementOrSelector)
  json = $element.attr('up-data')
  if u.isString(json) && u.trim(json) != ''
    JSON.parse(json)
  else
    {}

###*
Makes a snapshot of the currently registered event listeners,
to later be restored through `reset`.

@internal
###
snapshot = ->
  for compiler in compilers
    compiler.isDefault = true

###*
Resets the list of registered compiler directives to the
moment when the framework was booted.

@internal
###
reset = ->
  compilers = u.select compilers, (compiler) -> compiler.isDefault

up.on 'up:framework:boot', snapshot
up.on 'up:framework:reset', reset

compiler: compiler
compile: compile
clean: clean
data: data

)(jQuery)

up.compiler = up.syntax.compiler

up.ready = -> up.util.error('up.ready no longer exists. Please use up.hello instead.') up.awaken = -> up.util.error('up.awaken no longer exists. Please use up.compiler instead.')