<commit_before>// Copyright 2014-2015, University of Colorado Boulder

/**
 * Class that represents a solar panel in the view.
 *
 * @author John Blanco
 * @author Andrew Adare
 */
define( function( require ) {
  'use strict';

  // Modules
  var Bounds2 = require( 'DOT/Bounds2' );
  var EFACModelImage = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/EFACModelImage' );
  var EnergyConverter = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/EnergyConverter' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Random = require( 'DOT/Random' );
  var SunEnergySource = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/SunEnergySource' );
  var Vector2 = require( 'DOT/Vector2' );

  // Images
  // TODO: These are in the Java code, but should they be moved to view code?
  var CONNECTOR = require( 'image!ENERGY_FORMS_AND_CHANGES/connector.png' );
  var SOLAR_PANEL = require( 'image!ENERGY_FORMS_AND_CHANGES/solar_panel.png' );
  var SOLAR_PANEL_GEN = require( 'image!ENERGY_FORMS_AND_CHANGES/solar_panel_gen.png' );
  var SOLAR_PANEL_ICON = require( 'image!ENERGY_FORMS_AND_CHANGES/solar_panel_icon.png' );
  var SOLAR_PANEL_POST_2 = require( 'image!ENERGY_FORMS_AND_CHANGES/solar_panel_post_2.png' );
  var SUN_ICON = require( 'image!ENERGY_FORMS_AND_CHANGES/sun_icon.png' );
  var WIRE_BLACK_LEFT = require( 'image!ENERGY_FORMS_AND_CHANGES/wire_black_left.png' );

  // Constants
  var RAND = new Random();
  var SOLAR_PANEL_OFFSET = new Vector2( 0, 0.044 );
  var SOLAR_PANEL_IMAGE = new EFACModelImage( SOLAR_PANEL, 0.15, SOLAR_PANEL_OFFSET );
  var CONVERTER_IMAGE_OFFSET = new Vector2( 0.015, -0.040 );
  var CONVERTER_IMAGE = new EFACModelImage( SOLAR_PANEL_GEN, CONVERTER_IMAGE_OFFSET );
  var CURVED_WIRE_IMAGE = new EFACModelImage( WIRE_BLACK_LEFT, CONVERTER_IMAGE_OFFSET.plus( 0.009, 0.024 ) );
  var POST_IMAGE = new EFACModelImage( SOLAR_PANEL_POST_2, CONVERTER_IMAGE_OFFSET.plus( new Vector2( 0, 0.04 ) ) );
  var CONNECTOR_IMAGE_OFFSET = new Vector2( 0.057, -0.04 );
  var CONNECTOR_IMAGE = new EFACModelImage( CONNECTOR, CONNECTOR_IMAGE_OFFSET );

  var halfWidth = SOLAR_PANEL_IMAGE.getWidth() / 2;
  var halfHeight = SOLAR_PANEL_IMAGE.getHeight() / 2;
  var PANEL_IMAGE_BOUNDS = new Bounds2( -halfWidth, -halfHeight, halfWidth, halfHeight );

  // var PANEL_IMAGE_BOUNDS = new Rectangle2D.Double( -SOLAR_PANEL_IMAGE.getWidth() / 2, -SOLAR_PANEL_IMAGE.getHeight() / 2,
  //   SOLAR_PANEL_IMAGE.getWidth(), SOLAR_PANEL_IMAGE.getHeight() );

  // TODO
  // private static final DoubleGeneralPath ABSORPTION_SHAPE = new DoubleGeneralPath() {{
  //     double absorptionZoneWidth = PANEL_IMAGE_BOUNDS.getWidth() * 0.2;
  //     moveTo( PANEL_IMAGE_BOUNDS.getMinX(), PANEL_IMAGE_BOUNDS.getMinY() );
  //     moveTo( PANEL_IMAGE_BOUNDS.getMaxX() - absorptionZoneWidth, PANEL_IMAGE_BOUNDS.getMaxY() );
  //     lineTo( PANEL_IMAGE_BOUNDS.getMaxX(), PANEL_IMAGE_BOUNDS.getMaxY() );
  //     lineTo( PANEL_IMAGE_BOUNDS.getMinX() + absorptionZoneWidth, PANEL_IMAGE_BOUNDS.getMinY() );
  //     lineTo( PANEL_IMAGE_BOUNDS.getMinX(), PANEL_IMAGE_BOUNDS.getMinY() );
  //     closePath();
  // }};

  // Constants used for creating the path followed by the energy chunks.
  // Many of these numbers were empirically determined based on the images,
  // and will need updating if the images change.
  var OFFSET_TO_CONVERGENCE_POINT = new Vector2( CONVERTER_IMAGE_OFFSET.x, 0.01 );
  var OFFSET_TO_FIRST_CURVE_POINT = new Vector2( CONVERTER_IMAGE_OFFSET.x, -0.025 );
  var OFFSET_TO_SECOND_CURVE_POINT = new Vector2( CONVERTER_IMAGE_OFFSET.x + 0.005, -0.033 );
  var OFFSET_TO_THIRD_CURVE_POINT = new Vector2( CONVERTER_IMAGE_OFFSET.x + 0.015, CONNECTOR_IMAGE_OFFSET.y );
  var OFFSET_TO_CONNECTOR_CENTER = CONNECTOR_IMAGE_OFFSET;

  // Inter chunk spacing time for when the chunks reach the 'convergence
  // point' at the bottom of the solar panel.  It is intended to
  // approximately match the rate at which the sun emits energy chunks.
  var MIN_INTER_CHUNK_TIME = 1 / ( SunEnergySource.ENERGY_CHUNK_EMISSION_PERIOD * SunEnergySource.NUM_EMISSION_SECTORS ); // In seconds.

  /**
   * Solar panel is an energy converter
   *
   * @param {[type]} energyChunksVisible [description]
   * @constructor
   */
  function SolarPanel( energyChunksVisible ) {
    EnergyConverter.call( this, new Image( SOLAR_PANEL_ICON ) );
    this.energyChunkMovers = [];
    // TODO: simulation clock? I don't think it is needed.
    this.latestChunkArrivalTime = 0;
    this.energyOutputRate = 0;
    this.energyChunksVisible = energyChunksVisible;
  }

  // TODO fill out these stubs
  return inherit( EnergyConverter, SolarPanel, {
    stepInTime: function( dt, incomingEnergy ) {},
    moveEnergyChunks: function( dt ) {},
    preLoadEnergyChunks: function( incomingEnergyRate ) {},
    getEnergyOutputRate: function() {},
    chooseChunkVelocityOnPanel: function() {},
    clearEnergyChunks: function() {},
    createPathToPanelBottom: function() {},
    createPathThroughConverter: function() {},
    getAbsorptionShape: function() {},
    getUserComponent: function() {},
    temp: function() {
      console.log(
        SUN_ICON,
        RAND,
        CONVERTER_IMAGE,
        CURVED_WIRE_IMAGE,
        POST_IMAGE,
        CONNECTOR_IMAGE,
        PANEL_IMAGE_BOUNDS,
        OFFSET_TO_CONVERGENCE_POINT,
        OFFSET_TO_FIRST_CURVE_POINT,
        OFFSET_TO_SECOND_CURVE_POINT,
        OFFSET_TO_THIRD_CURVE_POINT,
        OFFSET_TO_CONNECTOR_CENTER,
        MIN_INTER_CHUNK_TIME
      );
    }

  } );
} );
<commit_msg>Access image dimensions correctly<commit_after>// Copyright 2014-2015, University of Colorado Boulder

/**
 * Class that represents a solar panel in the view.
 *
 * @author John Blanco
 * @author Andrew Adare
 */
define( function( require ) {
  'use strict';

  // Modules
  var Bounds2 = require( 'DOT/Bounds2' );
  var EFACModelImage = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/EFACModelImage' );
  var EnergyConverter = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/EnergyConverter' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Random = require( 'DOT/Random' );
  var SunEnergySource = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/SunEnergySource' );
  var Vector2 = require( 'DOT/Vector2' );

  // Images
  // TODO: These are in the Java code, but should they be moved to view code?
  var CONNECTOR = require( 'image!ENERGY_FORMS_AND_CHANGES/connector.png' );
  var SOLAR_PANEL = require( 'image!ENERGY_FORMS_AND_CHANGES/solar_panel.png' );
  var SOLAR_PANEL_GEN = require( 'image!ENERGY_FORMS_AND_CHANGES/solar_panel_gen.png' );
  var SOLAR_PANEL_ICON = require( 'image!ENERGY_FORMS_AND_CHANGES/solar_panel_icon.png' );
  var SOLAR_PANEL_POST_2 = require( 'image!ENERGY_FORMS_AND_CHANGES/solar_panel_post_2.png' );
  var SUN_ICON = require( 'image!ENERGY_FORMS_AND_CHANGES/sun_icon.png' );
  var WIRE_BLACK_LEFT = require( 'image!ENERGY_FORMS_AND_CHANGES/wire_black_left.png' );

  // Constants
  var RAND = new Random();
  var SOLAR_PANEL_OFFSET = new Vector2( 0, 0.044 );
  var SOLAR_PANEL_IMAGE = new EFACModelImage( SOLAR_PANEL, 0.15, SOLAR_PANEL_OFFSET );
  var CONVERTER_IMAGE_OFFSET = new Vector2( 0.015, -0.040 );
  var CONVERTER_IMAGE = new EFACModelImage( SOLAR_PANEL_GEN, CONVERTER_IMAGE_OFFSET );
  var CURVED_WIRE_IMAGE = new EFACModelImage( WIRE_BLACK_LEFT, CONVERTER_IMAGE_OFFSET.plus( 0.009, 0.024 ) );
  var POST_IMAGE = new EFACModelImage( SOLAR_PANEL_POST_2, CONVERTER_IMAGE_OFFSET.plus( new Vector2( 0, 0.04 ) ) );
  var CONNECTOR_IMAGE_OFFSET = new Vector2( 0.057, -0.04 );
  var CONNECTOR_IMAGE = new EFACModelImage( CONNECTOR, CONNECTOR_IMAGE_OFFSET );

  var halfWidth = SOLAR_PANEL_IMAGE.width / 2;
  var halfHeight = SOLAR_PANEL_IMAGE.height / 2;
  var PANEL_IMAGE_BOUNDS = new Bounds2( -halfWidth, -halfHeight, halfWidth, halfHeight );

  // var PANEL_IMAGE_BOUNDS = new Rectangle2D.Double( -SOLAR_PANEL_IMAGE.getWidth() / 2, -SOLAR_PANEL_IMAGE.getHeight() / 2,
  //   SOLAR_PANEL_IMAGE.getWidth(), SOLAR_PANEL_IMAGE.getHeight() );

  // TODO
  // private static final DoubleGeneralPath ABSORPTION_SHAPE = new DoubleGeneralPath() {{
  //     double absorptionZoneWidth = PANEL_IMAGE_BOUNDS.getWidth() * 0.2;
  //     moveTo( PANEL_IMAGE_BOUNDS.getMinX(), PANEL_IMAGE_BOUNDS.getMinY() );
  //     moveTo( PANEL_IMAGE_BOUNDS.getMaxX() - absorptionZoneWidth, PANEL_IMAGE_BOUNDS.getMaxY() );
  //     lineTo( PANEL_IMAGE_BOUNDS.getMaxX(), PANEL_IMAGE_BOUNDS.getMaxY() );
  //     lineTo( PANEL_IMAGE_BOUNDS.getMinX() + absorptionZoneWidth, PANEL_IMAGE_BOUNDS.getMinY() );
  //     lineTo( PANEL_IMAGE_BOUNDS.getMinX(), PANEL_IMAGE_BOUNDS.getMinY() );
  //     closePath();
  // }};

  // Constants used for creating the path followed by the energy chunks.
  // Many of these numbers were empirically determined based on the images,
  // and will need updating if the images change.
  var OFFSET_TO_CONVERGENCE_POINT = new Vector2( CONVERTER_IMAGE_OFFSET.x, 0.01 );
  var OFFSET_TO_FIRST_CURVE_POINT = new Vector2( CONVERTER_IMAGE_OFFSET.x, -0.025 );
  var OFFSET_TO_SECOND_CURVE_POINT = new Vector2( CONVERTER_IMAGE_OFFSET.x + 0.005, -0.033 );
  var OFFSET_TO_THIRD_CURVE_POINT = new Vector2( CONVERTER_IMAGE_OFFSET.x + 0.015, CONNECTOR_IMAGE_OFFSET.y );
  var OFFSET_TO_CONNECTOR_CENTER = CONNECTOR_IMAGE_OFFSET;

  // Inter chunk spacing time for when the chunks reach the 'convergence
  // point' at the bottom of the solar panel.  It is intended to
  // approximately match the rate at which the sun emits energy chunks.
  var MIN_INTER_CHUNK_TIME = 1 / ( SunEnergySource.ENERGY_CHUNK_EMISSION_PERIOD * SunEnergySource.NUM_EMISSION_SECTORS ); // In seconds.

  /**
   * Solar panel is an energy converter
   *
   * @param {[type]} energyChunksVisible [description]
   * @constructor
   */
  function SolarPanel( energyChunksVisible ) {
    EnergyConverter.call( this, new Image( SOLAR_PANEL_ICON ) );
    this.energyChunkMovers = [];
    // TODO: simulation clock? I don't think it is needed.
    this.latestChunkArrivalTime = 0;
    this.energyOutputRate = 0;
    this.energyChunksVisible = energyChunksVisible;
  }

  // TODO fill out these stubs
  return inherit( EnergyConverter, SolarPanel, {
    stepInTime: function( dt, incomingEnergy ) {},
    moveEnergyChunks: function( dt ) {},
    preLoadEnergyChunks: function( incomingEnergyRate ) {},
    getEnergyOutputRate: function() {},
    chooseChunkVelocityOnPanel: function() {},
    clearEnergyChunks: function() {},
    createPathToPanelBottom: function() {},
    createPathThroughConverter: function() {},
    getAbsorptionShape: function() {},
    getUserComponent: function() {},
    temp: function() {
      console.log(
        SUN_ICON,
        RAND,
        CONVERTER_IMAGE,
        CURVED_WIRE_IMAGE,
        POST_IMAGE,
        CONNECTOR_IMAGE,
        PANEL_IMAGE_BOUNDS,
        OFFSET_TO_CONVERGENCE_POINT,
        OFFSET_TO_FIRST_CURVE_POINT,
        OFFSET_TO_SECOND_CURVE_POINT,
        OFFSET_TO_THIRD_CURVE_POINT,
        OFFSET_TO_CONNECTOR_CENTER,
        MIN_INTER_CHUNK_TIME
      );
    }

  } );
} );
<|endoftext|>


<commit_before>$(document).ready(function(){
    var $ribbon = $('.ribbon');
    if ($ribbon) {
        var faded = sessionStorage.getItem('fade-test-banner', true);
        if (! faded) {
            $('.ribbon-box').removeClass('fade');
        }
        $ribbon.on('click',function(){
            $('.ribbon-box').addClass('fade');
            sessionStorage.setItem('fade-test-banner', true);
        });
    }

});<commit_msg>add pitbar js<commit_after>import PitBar from './snippets/pitbar'

$(document).ready(function(){
    var $ribbon = $('.ribbon');
    if ($ribbon) {
        var faded = sessionStorage.getItem('fade-test-banner', true);
        if (! faded) {
            $('.ribbon-box').removeClass('fade');
        }
        $ribbon.on('click',function(){
            $('.ribbon-box').addClass('fade');
            sessionStorage.setItem('fade-test-banner', true);
        });
    }

    /* dom */
    const $mainNav = $('#main-nav')
    const $mobileNav = $('#mobile-nav')
    const $siteSearch = $('#site-search')
    const $menuButton = $('.toc.item')
    const $searchButton = $('.search.item')

    /* bindings */
    new PitBar($mainNav, $mobileNav)

    $mobileNav
        .sidebar('attach events', $menuButton)
        .sidebar('setting', {
            onChange: () => {
                // swap the hamburger icon for a close icon
                $('.times.icon').toggle()
                $('.sidebar.icon').toggle()
            }
    })

    $siteSearch
        .sidebar('attach events', $searchButton)
});<|endoftext|>


<commit_before>/**
 * Arabic translation for bootstrap-datepicker
 * Mohammed Alshehri <alshehri866@gmail.com>
 */
;(function($){
  $.fn.datepicker.dates['ar'] = {
      		days: ["الأحد", "الاثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت", "الأحد"],
      		daysShort: ["أحد", "اثنين", "ثلاثاء", "أربعاء", "خميس", "جمعة", "سبت", "أحد"],
      		daysMin: ["ح", "ن", "ث", "ع", "خ", "ج", "س", "ح"],
      		months: ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"],
      		monthsShort: ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"],
		today: "هذا اليوم",
		rtl: true
	};
}(jQuery));
<commit_msg>Fix indentation<commit_after>/**
 * Arabic translation for bootstrap-datepicker
 * Mohammed Alshehri <alshehri866@gmail.com>
 */
;(function($){
    $.fn.datepicker.dates['ar'] = {
        days: ["الأحد", "الاثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت", "الأحد"],
        daysShort: ["أحد", "اثنين", "ثلاثاء", "أربعاء", "خميس", "جمعة", "سبت", "أحد"],
        daysMin: ["ح", "ن", "ث", "ع", "خ", "ج", "س", "ح"],
        months: ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"],
        monthsShort: ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"],
        today: "هذا اليوم",
        rtl: true
    };
}(jQuery));
<|endoftext|>


<commit_before>/**
 * Polish translation for bootstrap-datepicker
 * Robert <rtpm@gazeta.pl>
 */
;(function($){
        $.fn.datepicker.dates['pl'] = {
                days: ["Niedziela", "Poniedziałek", "Wtorek", "Środa", "Czwartek", "Piątek", "Sobota", "Niedziela"],
                daysShort: ["Nie", "Pn", "Wt", "Śr", "Czw", "Pt", "So", "Nie"],
                daysMin: ["N", "Pn", "Wt", "Śr", "Cz", "Pt", "So", "N"],
                months: ["Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"],
                monthsShort: ["Sty", "Lu", "Mar", "Kw", "Maj", "Cze", "Lip", "Sie", "Wrz", "Pa", "Lis", "Gru"],
                today: "Dzisiaj"
        };
}(jQuery));
<commit_msg>Update js/locales/bootstrap-datepicker.pl.js<commit_after>/**
 * Polish translation for bootstrap-datepicker
 * Robert <rtpm@gazeta.pl>
 */
;(function($){
        $.fn.datepicker.dates['pl'] = {
                days: ["Niedziela", "Poniedziałek", "Wtorek", "Środa", "Czwartek", "Piątek", "Sobota", "Niedziela"],
                daysShort: ["Nie", "Pn", "Wt", "Śr", "Czw", "Pt", "So", "Nie"],
                daysMin: ["N", "Pn", "Wt", "Śr", "Cz", "Pt", "So", "N"],
                months: ["Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"],
                monthsShort: ["Sty", "Lu", "Mar", "Kw", "Maj", "Cze", "Lip", "Sie", "Wrz", "Pa", "Lis", "Gru"],
                today: "Dzisiaj",
                weekStart: 1
        };
}(jQuery));
<|endoftext|>


<commit_before>/**
 * Ukrainian translation for bootstrap-datepicker
 * Igor Polynets
 */
;(function($){
	$.fn.datepicker.dates['uk'] = {
		days: ["Неділя", "Понеділок", "Вівторок", "Середа", "Четвер", "П'ятниця", "Субота", "Неділя"],
		daysShort: ["Нед", "Пнд", "Втр", "Срд", "Чтв", "Птн", "Суб", "Нед"],
		daysMin: ["Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Нд"],
		months: ["Cічень", "Лютий", "Березень", "Квітень", "Травень", "Червень", "Липень", "Серпень", "Вересень", "Жовтень", "Листопад", "Грудень"],
		monthsShort: ["Січ", "Лют", "Бер", "Кві", "Тра", "Чер", "Лип", "Сер", "Вер", "Жов", "Лис", "Гру"],
		today: "Сьогодні",
		weekStart: 1
	};
}(jQuery));
<commit_msg>Add 'clear' and 'format' to Ukrainian locale<commit_after>/**
 * Ukrainian translation for bootstrap-datepicker
 * Igor Polynets
 */
;(function($){
	$.fn.datepicker.dates['uk'] = {
		days: ["Неділя", "Понеділок", "Вівторок", "Середа", "Четвер", "П'ятниця", "Субота", "Неділя"],
		daysShort: ["Нед", "Пнд", "Втр", "Срд", "Чтв", "Птн", "Суб", "Нед"],
		daysMin: ["Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Нд"],
		months: ["Cічень", "Лютий", "Березень", "Квітень", "Травень", "Червень", "Липень", "Серпень", "Вересень", "Жовтень", "Листопад", "Грудень"],
		monthsShort: ["Січ", "Лют", "Бер", "Кві", "Тра", "Чер", "Лип", "Сер", "Вер", "Жов", "Лис", "Гру"],
		today: "Сьогодні",
		clear: "Очистити",
		format: "dd.mm.yyyy",
		weekStart: 1
	};
}(jQuery));
<|endoftext|>


<commit_before>/*
 * syslog.js: Transport for logging to a remote syslog consumer
 *
 * (C) 2011 Squeeks and Charlie Robbins
 * MIT LICENCE
 *
 */

var dgram = require('dgram'),
    net = require('net'),
    util = require('util'),
    glossy = require('glossy'),
    winston = require('winston');

var levels = Object.keys({
  debug: 0,
  info: 1,
  notice: 2,
  warning: 3,
  error: 4,
  crit: 5,
  alert: 6,
  emerg: 7
});

//
// ### function Syslog (options)
// #### @options {Object} Options for this instance.
// Constructor function for the Syslog Transport capable of sending
// RFC 3164 and RFC 5424 compliant messages.
//
var Syslog = exports.Syslog = function (options) {
  options = options || {};

  //
  // Setup connection state
  //
  this.connected = false;
  this.retries = 0;
  this.queue = [];

  //
  // Merge the options for the target Syslog server.
  //
  this.host     = options.host     || 'localhost';
  this.port     = options.port     || 514;
  this.protocol = options.protocol || 'udp4';
  this.isDgram  = /^udp|unix/.test(this.protocol);

  if (!/^udp|unix|tcp/.test(this.protocol)) {
    throw new Error('Invalid syslog protocol: ' + this.protocol);
  }

  //
  // Merge the default message options.
  //
  this.localhost = options.localhost || 'localhost';
  this.type      = options.type      || 'BSD';
  this.facility  = options.facility  || 'local0';
  this.pid       = options.pid       || process.pid;

  //
  // Setup our Syslog and network members for later use.
  //
  this.socket   = null;
  this.producer = new glossy.Produce({
    type:     this.type,
    pid:      this.pid,
    facility: this.facility
  });
};

//
// Inherit from `winston.Transport`.
//
util.inherits(Syslog, winston.Transport);

//
// ### function log (level, msg, [meta], callback)
// #### @level {string} Target level to log to
// #### @msg {string} Message to log
// #### @meta {Object} **Optional** Additional metadata to log.
// #### @callback {function} Continuation to respond to when complete.
// Core logging method exposed to Winston. Logs the `msg` and optional
// metadata, `meta`, to the specified `level`.
//
Syslog.prototype.log = function (level, msg, meta, callback) {
  var self = this,
      data = meta ? winston.clone(meta) : {},
      syslogMsg,
      buffer;

  if (!~levels.indexOf(level)) {
    return callback(new Error('Cannot log unknown syslog level: ' + level));
  }

  data.message = msg;
  syslogMsg = this.producer.produce({
    severity: level,
    host:     this.localhost,
    date:     new Date(),
    message:  data
  });

  //
  // Attempt to connect to the socket
  //
  this.connect(function (err) {
    if (err) {
      //
      // If there was an error enqueue the message
      //
      return self.queue.push(syslogMsg);
    }

    //
    // On any error writing to the socket, enqueue the message
    //
    function onError (logErr) {
      if (logErr) { self.queue.push(syslogMsg) }
      self.emit('logged');
    }

    //
    // Write to the `tcp*`, `udp*`, or `unix` socket.
    //
    if (self.isDgram) {
      buffer = new Buffer(syslogMsg);
      if (self.protocol.match(/^udp/)) {
        self.socket.send(buffer, 0, buffer.length, self.port, self.host, onError);
      }
      else {
        //TODO this really should be "self.path"
        self.socket.send(buffer, 0, buffer.length, self.host, onError);
      }
    }
    else {
      self.socket.write(syslogMsg, 'utf8', onError);
    }
  });

  callback(null, true);
};

//
// ### function connect (callback)
// #### @callback {function} Continuation to respond to when complete.
// Connects to the remote syslog server using `dgram` or `net` depending
// on the `protocol` for this instance.
//
Syslog.prototype.connect = function (callback) {
  var self = this, readyEvent;

  //
  // If the socket already exists then respond
  //
  if (this.socket) {
    return this.socket.readyState === 'open'
      ? callback(null)
      : callback(true);
  }

  //
  // Create the appropriate socket type.
  //
  if (this.isDgram) {
    this.socket = new dgram.Socket(this.protocol);
    readyEvent = 'listening';
  }
  else {
    this.socket = new net.Socket({ type: this.protocol });
    this.socket.setKeepAlive(true);
    this.socket.setNoDelay();
    readyEvent = 'connect';
  }

  //
  // Indicate to the callee that the socket is not ready. This
  // will enqueue the current message for later.
  //
  callback(true);

  //
  // Listen to the appropriate events on the socket that
  // was just created.
  //
  this.socket.on(readyEvent, function () {
    //
    // When the socket is ready, write the current queue
    // to it.
    //
    if (self.isDgram) {
      var buffer = new Buffer(self.queue.join(''));
      if(self.protocol.match(/^udp/)) {
        self.socket.send(buffer, 0, buffer.length, self.port, self.host, onError);
      }
      else {
        //TODO this really should be "self.path"
        self.socket.send(buffer, 0, buffer.length, self.host, onError);
      }
    }
    else {
      self.socket.write(self.queue.join(''), 'utf8');
    }

    self.emit('logged');
    self.queue = [];
    self.retries = 0;
    self.connected = true;
  }).on('error', function (e) {
    //
    // TODO: Pass this error back up
    //
  }).on('end', function (e) {
    //
    // Nothing needs to be done here.
    //
  }).on('close', function (e) {
    //
    // Attempt to reconnect on lost connection(s), progressively
    // increasing the amount of time between each try.
    //
    var interval = Math.pow(2, self.retries);
    self.connected = false;

    setTimeout(function () {
      self.retries++;
      self.socket.connect(self.port, self.host);
    }, interval * 1000);
  }).on('timeout', function () {
    if (self.socket.readyState !== 'open') {
      self.socket.destroy();
    }
  });

  return this.isDgram
    ? this.socket.bind(this.port, this.host)
    : this.socket.connect(this.port, this.host);
};
<commit_msg>Fixing the return to deal with unix sockets as well<commit_after>/*
 * syslog.js: Transport for logging to a remote syslog consumer
 *
 * (C) 2011 Squeeks and Charlie Robbins
 * MIT LICENCE
 *
 */

var dgram = require('dgram'),
    net = require('net'),
    util = require('util'),
    glossy = require('glossy'),
    winston = require('winston');

var levels = Object.keys({
  debug: 0,
  info: 1,
  notice: 2,
  warning: 3,
  error: 4,
  crit: 5,
  alert: 6,
  emerg: 7
});

//
// ### function Syslog (options)
// #### @options {Object} Options for this instance.
// Constructor function for the Syslog Transport capable of sending
// RFC 3164 and RFC 5424 compliant messages.
//
var Syslog = exports.Syslog = function (options) {
  options = options || {};

  //
  // Setup connection state
  //
  this.connected = false;
  this.retries = 0;
  this.queue = [];

  //
  // Merge the options for the target Syslog server.
  //
  this.host     = options.host     || 'localhost';
  this.port     = options.port     || 514;
  this.protocol = options.protocol || 'udp4';
  this.isDgram  = /^udp|unix/.test(this.protocol);

  if (!/^udp|unix|tcp/.test(this.protocol)) {
    throw new Error('Invalid syslog protocol: ' + this.protocol);
  }

  //
  // Merge the default message options.
  //
  this.localhost = options.localhost || 'localhost';
  this.type      = options.type      || 'BSD';
  this.facility  = options.facility  || 'local0';
  this.pid       = options.pid       || process.pid;

  //
  // Setup our Syslog and network members for later use.
  //
  this.socket   = null;
  this.producer = new glossy.Produce({
    type:     this.type,
    pid:      this.pid,
    facility: this.facility
  });
};

//
// Inherit from `winston.Transport`.
//
util.inherits(Syslog, winston.Transport);

//
// ### function log (level, msg, [meta], callback)
// #### @level {string} Target level to log to
// #### @msg {string} Message to log
// #### @meta {Object} **Optional** Additional metadata to log.
// #### @callback {function} Continuation to respond to when complete.
// Core logging method exposed to Winston. Logs the `msg` and optional
// metadata, `meta`, to the specified `level`.
//
Syslog.prototype.log = function (level, msg, meta, callback) {
  var self = this,
      data = meta ? winston.clone(meta) : {},
      syslogMsg,
      buffer;

  if (!~levels.indexOf(level)) {
    return callback(new Error('Cannot log unknown syslog level: ' + level));
  }

  data.message = msg;
  syslogMsg = this.producer.produce({
    severity: level,
    host:     this.localhost,
    date:     new Date(),
    message:  data
  });

  //
  // Attempt to connect to the socket
  //
  this.connect(function (err) {
    if (err) {
      //
      // If there was an error enqueue the message
      //
      return self.queue.push(syslogMsg);
    }

    //
    // On any error writing to the socket, enqueue the message
    //
    function onError (logErr) {
      if (logErr) { self.queue.push(syslogMsg) }
      self.emit('logged');
    }

    //
    // Write to the `tcp*`, `udp*`, or `unix` socket.
    //
    if (self.isDgram) {
      buffer = new Buffer(syslogMsg);
      if (self.protocol.match(/^udp/)) {
        self.socket.send(buffer, 0, buffer.length, self.port, self.host, onError);
      }
      else {
        //TODO this really should be "self.path"
        self.socket.send(buffer, 0, buffer.length, self.host, onError);
      }
    }
    else {
      self.socket.write(syslogMsg, 'utf8', onError);
    }
  });

  callback(null, true);
};

//
// ### function connect (callback)
// #### @callback {function} Continuation to respond to when complete.
// Connects to the remote syslog server using `dgram` or `net` depending
// on the `protocol` for this instance.
//
Syslog.prototype.connect = function (callback) {
  var self = this, readyEvent;

  //
  // If the socket already exists then respond
  //
  if (this.socket) {
    return this.socket.readyState === 'open'
      ? callback(null)
      : callback(true);
  }

  //
  // Create the appropriate socket type.
  //
  if (this.isDgram) {
    this.socket = new dgram.Socket(this.protocol);
    readyEvent = 'listening';
  }
  else {
    this.socket = new net.Socket({ type: this.protocol });
    this.socket.setKeepAlive(true);
    this.socket.setNoDelay();
    readyEvent = 'connect';
  }

  //
  // Indicate to the callee that the socket is not ready. This
  // will enqueue the current message for later.
  //
  callback(true);

  //
  // Listen to the appropriate events on the socket that
  // was just created.
  //
  this.socket.on(readyEvent, function () {
    //
    // When the socket is ready, write the current queue
    // to it.
    //
    if (self.isDgram) {
      var buffer = new Buffer(self.queue.join(''));
      if(self.protocol.match(/^udp/)) {
        self.socket.send(buffer, 0, buffer.length, self.port, self.host, onError);
      }
      else {
        //TODO this really should be "self.path"
        self.socket.send(buffer, 0, buffer.length, self.host, onError);
      }
    }
    else {
      self.socket.write(self.queue.join(''), 'utf8');
    }

    self.emit('logged');
    self.queue = [];
    self.retries = 0;
    self.connected = true;
  }).on('error', function (e) {
    //
    // TODO: Pass this error back up
    //
  }).on('end', function (e) {
    //
    // Nothing needs to be done here.
    //
  }).on('close', function (e) {
    //
    // Attempt to reconnect on lost connection(s), progressively
    // increasing the amount of time between each try.
    //
    var interval = Math.pow(2, self.retries);
    self.connected = false;

    setTimeout(function () {
      self.retries++;
      self.socket.connect(self.port, self.host);
    }, interval * 1000);
  }).on('timeout', function () {
    if (self.socket.readyState !== 'open') {
      self.socket.destroy();
    }
  });

  if(self.protocol.match(/^udp/)) {
    return this.socket.bind(this.port, this.host);
  }
  else if(self.protocol.match(/^unix/)){
    //TODO this really should be "self.path"
    return this.socket.bind(this.host);
  }
  else {
    return this.socket.connect(this.port, this.host);
  }
};
<|endoftext|>


<commit_before>/*
 * config.js: Default settings for all levels that winston knows about
 *
 * (C) 2010 Charlie Robbins
 * MIT LICENCE
 *
 */

var colors = require('colors');

var config = exports,
    allColors = exports.allColors = {};

config.addColors = function (colors) {
  mixin(allColors, colors);
};

config.colorize = function (level, message) {
  if (typeof message === 'undefined') message = level;

  var colorized = message;
  if (allColors[level] instanceof Array) {
    for (var i = 0, l = allColors[level].length; i < l; ++i) {
      colorized = colorized[allColors[level][i]];
    }
  } else if (allColors[level].match(/\s/)) {
    var colorArr = allColors[level].split(/\s+/);
    for (var i = 0; i < colorArr.length; ++i) {
      colorized = colorized[colorArr[i]];
    }
    allColors[level] = colorArr;
  } else {
    colorized = colorized[allColors[level]];
  }
  return colorized;
};

//
// Export config sets
//
config.cli    = require('./config/cli-config');
config.npm    = require('./config/npm-config');
config.syslog = require('./config/syslog-config');

//
// Add colors for pre-defined config sets
//
config.addColors(config.cli.colors);
config.addColors(config.npm.colors);
config.addColors(config.syslog.colors);

function mixin (target) {
  var args = Array.prototype.slice.call(arguments, 1);

  args.forEach(function (a) {
    var keys = Object.keys(a);
    for (var i = 0; i < keys.length; i++) {
      target[keys[i]] = a[keys[i]];
    }
  });
  return target;
};
<commit_msg>Using colors/safe instead of colors to not monkeypatch string<commit_after>/*
 * config.js: Default settings for all levels that winston knows about
 *
 * (C) 2010 Charlie Robbins
 * MIT LICENCE
 *
 */

var colors = require('colors/safe');

var config = exports,
    allColors = exports.allColors = {};

config.addColors = function (colors) {
  mixin(allColors, colors);
};

config.colorize = function (level, message) {
  if (typeof message === 'undefined') message = level;

  var colorized = message;
  if (allColors[level] instanceof Array) {
    for (var i = 0, l = allColors[level].length; i < l; ++i) {
      colorized = colors[allColors[level][i]](colorized);
    }
  } else if (allColors[level].match(/\s/)) {
    var colorArr = allColors[level].split(/\s+/);
    for (var i = 0; i < colorArr.length; ++i) {
      colorized = colors[colorArr[i]](colorized);
    }
    allColors[level] = colorArr;
  } else {
    colorized = colors[allColors[level]](colorized);
  }
  return colorized;
};

//
// Export config sets
//
config.cli    = require('./config/cli-config');
config.npm    = require('./config/npm-config');
config.syslog = require('./config/syslog-config');

//
// Add colors for pre-defined config sets
//
config.addColors(config.cli.colors);
config.addColors(config.npm.colors);
config.addColors(config.syslog.colors);

function mixin (target) {
  var args = Array.prototype.slice.call(arguments, 1);

  args.forEach(function (a) {
    var keys = Object.keys(a);
    for (var i = 0; i < keys.length; i++) {
      target[keys[i]] = a[keys[i]];
    }
  });
  return target;
};
<|endoftext|>


<commit_before>/* global django,Downcoder,ContentEditor */
/* eslint indent:[2,4] */
/* eslint comma-dangle:[2,"never"] */
django.jQuery(function($){
    var context = document.getElementById('content-editor-context');
    if (!context) return;

    window.ContentEditor = {
        addContent: function addContent(plugin) {
            $('#' + plugin + '_set-group .add-row a').click();
        },
        addPluginButton: function addPluginButton(plugin, html) {
            var doAdd = function(plugin, html, title) {
                var unit = document.querySelector('.control-unit.plugin-buttons');
                if (!unit) {
                    unit = document.createElement('div');
                    unit.className = 'control-unit plugin-buttons';
                    document.querySelector('.machine-control').appendChild(unit);
                }

                var button = document.createElement('a');
                var $button = $(button);
                $button.data("plugin", plugin);
                button.className = 'plugin-button';
                button.title = title;
                button.addEventListener('click', function() {
                    ContentEditor.addContent(plugin);
                });
                button.innerHTML = html;

                unit.appendChild(button);

                hideNotAllowedPluginButtons($button);
            };

            for (var i=0; i<ContentEditor.plugins.length; i++) {
                if (ContentEditor.plugins[i][0] == plugin) {
                    doAdd(plugin, html, ContentEditor.plugins[i][1]);
                    break;
                }
            }
        }
    };

    // .dataset.context instead of getAttribute would be nicer
    $.extend(window.ContentEditor, JSON.parse(context.getAttribute('data-context')));

    // Add basic structure. There is always at least one inline group if
    // we even have any plugins.
    $('.inline-group:first').before(
        '

' +
        '
' +
        '

' +
        '
'
    );

    var orderMachine = $('.order-machine'),
        machineEmptyMessage = $('
').text(ContentEditor.messages.empty).appendTo(orderMachine);

    // Pre map plugin regions
    var pluginRegions = (function() {
        var result = {};
        var plugins = ContentEditor.plugins;
        for (var i = 0; i < plugins.length; i++) {
            result[plugins[i][0]] = plugins[i][2];
        }
        return result;
    })();

    function moveEmptyFormsToEnd() {
        orderMachine.append(orderMachine.find('.empty-form').detach());
    }

    function reorderInlines(context) {
        context = context || orderMachine;
        var inlines = context.find('.inline-related');
        inlines.not('.empty-form').each(function() {
            $(document).trigger('content-editor:deactivate', [$(this)]);
        });
        inlines.detach();
        inlines.sort(function inlinesCompareFunction(a, b) {
            var aOrdering = $(a).find('.field-ordering input').val() || 1e9;
            var bOrdering = $(b).find('.field-ordering input').val() || 1e9;
            return aOrdering - bOrdering;
        });
        orderMachine.append(inlines);
        moveEmptyFormsToEnd();
        inlines.not('.empty-form').each(function() {
            $(document).trigger('content-editor:activate', [$(this)]);
        });
    }

    function buildDropdown(contents, title) {
        var select = document.createElement('select'),
            idx = 0;

        if (title)
            select.options[idx++] = new Option(title, '', true);

        for (var i = 0; i < contents.length; i++) {
            select.options[idx++] = new Option(contents[i][1], contents[i][0]);
        }
        return select;
    }

    // Hides plugins that are not allowed in this region
    function hideNotAllowedDropdown() {
        $(ContentEditor.machineControlSelect).find("option").each(function() {
            var $option = $(this);
            var allowed = pluginRegions[$option.val()];
            if (!allowed || $.inArray(ContentEditor.currentRegion, allowed) >= 0) {
                $option.show();
            } else {
                $option.hide();
            }
        });
    }

    // Hide not allowed plugin buttons
    // If $buttons only checks this buttons, else checks all
    function hideNotAllowedPluginButtons($buttons) {
        $buttons = $buttons ? $buttons :
                            $('.control-unit.plugin-buttons .plugin-button');

        var region = ContentEditor.currentRegion;

        $buttons.each(function() {
            var $button = $(this);
            var plugin = $button.data('plugin');
            var allowed = pluginRegions[plugin];

            if (!allowed || $.inArray(region, allowed) >= 0) {
                // Allowed
                $button.show();
            } else {
                // Not allowed
                $button.hide();
            }
        });
    }

    // Fetch the inline type from id
    function getInlineType(inline) {
        var match = /^([a-z0-9_]+)_set-\d+$/g.exec($(inline).attr('id'));
        if (match) {
            return match[1];
        }
        return null;
    }

    function attachMoveToRegionDropdown(inline) {

        // Filter allowed regions
        var inlineType = getInlineType(inline);
        var regions = [];
        for (var i = 0; i < ContentEditor.regions.length; i++) {
            if (!inlineType || !pluginRegions[inlineType] || $.inArray(ContentEditor.regions[i][0], pluginRegions[inlineType]) >= 0) {
                regions.push(ContentEditor.regions[i]);
            }
        };

        if (regions.length < 2) return;

        var controls = document.createElement('div'),
            select = buildDropdown(regions),
            regionInput = inline.find('.field-region input');

        select.value = regionInput.val();
        controls.className = 'inline-controls';
        controls.appendChild(select);
        inline.append(controls);

        select.addEventListener('change', function() {
            inline.attr('data-region', select.value);
            regionInput.val(select.value);
            hideInlinesFromOtherRegions();
            setBiggestOrdering(inline);
            reorderInlines();
        });
    }

    // Assing data-region to all inlines.
    // We also want to the data attribute to be visible to selectors (that's why we're using $.attr)
    function assignRegionDataAttribute() {
        orderMachine.find('.inline-related:not(.empty-form)').each(function() {
            var $this = $(this),
                region = $this.find('.field-region input').val();

            $this.attr('data-region', region);
            attachMoveToRegionDropdown($this);
        });
    }

    function setBiggestOrdering(row) {
        var orderings = [];
        orderMachine.find('.field-ordering input').each(function() {
            if (!isNaN(+this.value)) orderings.push(+this.value);
        });
        row.find('.field-ordering input').val(10 + Math.max.apply(null, orderings));
    }

    function hideInlinesFromOtherRegions() {
        var inlines = orderMachine.find('.inline-related:not(.empty-form)');
        inlines.addClass('content-editor-hidden');
        var shown = inlines.filter('[data-region="' + ContentEditor.currentRegion + '"]');
        machineEmptyMessage.addClass('hidden');
        if (shown.length) {
            shown.removeClass('content-editor-hidden');
        } else {
            machineEmptyMessage.removeClass('hidden');
        }
    }

    var pluginInlineGroups = (function selectPluginInlineGroups() {
        var selector = [];
        for (var i=0; i < ContentEditor.plugins.length; i++) {
            selector.push('#' + ContentEditor.plugins[i][0] + '_set-group');
        }
        return $(selector.join(', '));
    })();

    reorderInlines(pluginInlineGroups);
    pluginInlineGroups.hide();
    assignRegionDataAttribute();

    // Always move empty forms to the end, because new plugins are inserted
    // just before its empty form. Also, assign region data.
    $(document).on('formset:added', function newForm(event, row) {
        moveEmptyFormsToEnd();

        row.find('.field-region input').val(ContentEditor.currentRegion);
        row.attr('data-region', ContentEditor.currentRegion);

        setBiggestOrdering(row);
        attachMoveToRegionDropdown(row);

        machineEmptyMessage.addClass('hidden');

        $(document).trigger('content-editor:activate', [row]);
    });

    $(document).on('formset:removed', function resetInlines() {
        if (!orderMachine.find('.inline-related[data-region="' + ContentEditor.currentRegion + '"]').length) {
            machineEmptyMessage.removeClass('hidden');
        }
        orderMachine.find('.inline-related.last-related:not(.empty-form)').each(function() {
            $(document).trigger('content-editor:deactivate', [$(this)]);
        });

        // As soon as possible, but not sooner (let the inline.js code run to the end first)
        setTimeout(function() {
            orderMachine.find('.inline-related.last-related:not(.empty-form)').each(function() {
                $(document).trigger('content-editor:activate', [$(this)]);
            });
        }, 0);

    });

    // Initialize tabs and currentRegion.
    (function() {

        var tabContainer = $('.tabs.regions');
        for (var i=0; i < ContentEditor.regions.length; i++) {
            var t = document.createElement('h2');
            t.textContent = ContentEditor.regions[i][1];
            t.setAttribute('data-region', ContentEditor.regions[i][0]);
            tabContainer.append(t);
        }

        var tabs = tabContainer.children(), tab;
        tabs.on('click', function() {
            ContentEditor.currentRegion = $(this).data('region');
            tabs.removeClass('active').filter('[data-region="' + ContentEditor.currentRegion + '"]').addClass('active');
            hideInlinesFromOtherRegions();
            window.location.hash = 'tab_' + ContentEditor.currentRegion;

            // Make sure only allowed plugins are in select
            hideNotAllowedDropdown();
            hideNotAllowedPluginButtons();
        });

        // Restore tab if location hash matches.
        if (window.location.hash && (tab = tabs.filter('[data-region="' + window.location.hash.substr(5) + '"]')) && tab.length) {
            tab.click();
        } else {
            tabs.eq(0).click();
        }

    })();

    $(document).on(
        'content-editor:deactivate',
        function(event, row) {
            row.find('fieldset').addClass('content-editor-hidden');
        }
    ).on(
        'content-editor:activate',
        function(event, row) {
            row.find('fieldset').removeClass('content-editor-hidden');
        }
    );

    // Start sortable; hide fieldsets when dragging, and hide fieldsets of to-be-deleted inlines.
    orderMachine.sortable({
        handle: 'h3',
        placeholder: 'placeholder',
        start: function(event, ui) {
            $(document).trigger('content-editor:deactivate', [ui.item]);
        },
        stop: function(event, ui) {
            $(document).trigger('content-editor:activate', [ui.item]);
        }
    }).on('click', '.delete>input[type=checkbox]', function toggleForDeletionClass() {
        $(this).closest('.inline-related')[this.checked ? 'addClass' : 'removeClass']('for-deletion');
    });

    // Fill in ordering field and try to keep the current region tab (location hash).
    $('form').submit(function(){
        orderMachine.find('.field-ordering input').each(function assignOrdering(index) {
            this.value = 10 * (index + 1); // Avoid default=0 just because.
        });

        var form = $(this);
        form.attr('action', form.attr('action') + window.location.hash);
        return true;
    });

    // Cmd-S and Escape behavior.
    $(document).keydown(function handleKeys(event) {
        if (event.which == 83 && event.metaKey) {
            $('form input[name=' + (event.shiftKey ? '_continue' : '_save') + ']').click();
            return false;
        } else if (event.which == 27) {
            orderMachine.sortable('cancel');
        }
    });

    (function buildPluginDropdown() {
        var select = buildDropdown(ContentEditor.plugins, ContentEditor.messages.createNew);
        select.addEventListener('change', function() {
            ContentEditor.addContent(select.value);
            select.value = '';
        });
        ContentEditor.machineControlSelect = select;
        hideNotAllowedDropdown();
        $(select).appendTo('.machine-control').wrap('

');
    })();

    $(document).trigger('content-editor:ready');
});
<commit_msg>Fix sorting of hidden and empty inlines<commit_after>/* global django,Downcoder,ContentEditor */
/* eslint indent:[2,4] */
/* eslint comma-dangle:[2,"never"] */
django.jQuery(function($){
    var context = document.getElementById('content-editor-context');
    if (!context) return;

    window.ContentEditor = {
        addContent: function addContent(plugin) {
            $('#' + plugin + '_set-group .add-row a').click();
        },
        addPluginButton: function addPluginButton(plugin, html) {
            var doAdd = function(plugin, html, title) {
                var unit = document.querySelector('.control-unit.plugin-buttons');
                if (!unit) {
                    unit = document.createElement('div');
                    unit.className = 'control-unit plugin-buttons';
                    document.querySelector('.machine-control').appendChild(unit);
                }

                var button = document.createElement('a');
                var $button = $(button);
                $button.data("plugin", plugin);
                button.className = 'plugin-button';
                button.title = title;
                button.addEventListener('click', function() {
                    ContentEditor.addContent(plugin);
                });
                button.innerHTML = html;

                unit.appendChild(button);

                hideNotAllowedPluginButtons($button);
            };

            for (var i=0; i<ContentEditor.plugins.length; i++) {
                if (ContentEditor.plugins[i][0] == plugin) {
                    doAdd(plugin, html, ContentEditor.plugins[i][1]);
                    break;
                }
            }
        }
    };

    // .dataset.context instead of getAttribute would be nicer
    $.extend(window.ContentEditor, JSON.parse(context.getAttribute('data-context')));

    // Add basic structure. There is always at least one inline group if
    // we even have any plugins.
    $('.inline-group:first').before(
        '

' +
        '
' +
        '

' +
        '
'
    );

    var orderMachine = $('.order-machine'),
        machineEmptyMessage = $('
').text(ContentEditor.messages.empty).appendTo(orderMachine);

    // Pre map plugin regions
    var pluginRegions = (function() {
        var result = {};
        var plugins = ContentEditor.plugins;
        for (var i = 0; i < plugins.length; i++) {
            result[plugins[i][0]] = plugins[i][2];
        }
        return result;
    })();

    function moveEmptyFormsToEnd() {
        orderMachine.append(orderMachine.find('.empty-form').detach());
    }

    function reorderInlines(context) {
        context = context || orderMachine;
        var inlines = context.find('.inline-related');
        inlines.not('.empty-form').each(function() {
            $(document).trigger('content-editor:deactivate', [$(this)]);
        });
        inlines.detach();
        inlines.sort(function inlinesCompareFunction(a, b) {
            var aOrdering = $(a).find('.field-ordering input').val() || 1e9;
            var bOrdering = $(b).find('.field-ordering input').val() || 1e9;
            return aOrdering - bOrdering;
        });
        orderMachine.append(inlines);
        moveEmptyFormsToEnd();
        inlines.not('.empty-form').each(function() {
            $(document).trigger('content-editor:activate', [$(this)]);
        });
    }

    function buildDropdown(contents, title) {
        var select = document.createElement('select'),
            idx = 0;

        if (title)
            select.options[idx++] = new Option(title, '', true);

        for (var i = 0; i < contents.length; i++) {
            select.options[idx++] = new Option(contents[i][1], contents[i][0]);
        }
        return select;
    }

    // Hides plugins that are not allowed in this region
    function hideNotAllowedDropdown() {
        $(ContentEditor.machineControlSelect).find("option").each(function() {
            var $option = $(this);
            var allowed = pluginRegions[$option.val()];
            if (!allowed || $.inArray(ContentEditor.currentRegion, allowed) >= 0) {
                $option.show();
            } else {
                $option.hide();
            }
        });
    }

    // Hide not allowed plugin buttons
    // If $buttons only checks this buttons, else checks all
    function hideNotAllowedPluginButtons($buttons) {
        $buttons = $buttons ? $buttons :
                            $('.control-unit.plugin-buttons .plugin-button');

        var region = ContentEditor.currentRegion;

        $buttons.each(function() {
            var $button = $(this);
            var plugin = $button.data('plugin');
            var allowed = pluginRegions[plugin];

            if (!allowed || $.inArray(region, allowed) >= 0) {
                // Allowed
                $button.show();
            } else {
                // Not allowed
                $button.hide();
            }
        });
    }

    // Fetch the inline type from id
    function getInlineType(inline) {
        var match = /^([a-z0-9_]+)_set-\d+$/g.exec($(inline).attr('id'));
        if (match) {
            return match[1];
        }
        return null;
    }

    function attachMoveToRegionDropdown(inline) {

        // Filter allowed regions
        var inlineType = getInlineType(inline);
        var regions = [];
        for (var i = 0; i < ContentEditor.regions.length; i++) {
            if (!inlineType || !pluginRegions[inlineType] || $.inArray(ContentEditor.regions[i][0], pluginRegions[inlineType]) >= 0) {
                regions.push(ContentEditor.regions[i]);
            }
        };

        if (regions.length < 2) return;

        var controls = document.createElement('div'),
            select = buildDropdown(regions),
            regionInput = inline.find('.field-region input');

        select.value = regionInput.val();
        controls.className = 'inline-controls';
        controls.appendChild(select);
        inline.append(controls);

        select.addEventListener('change', function() {
            inline.attr('data-region', select.value);
            regionInput.val(select.value);
            hideInlinesFromOtherRegions();
            setBiggestOrdering(inline);
            reorderInlines();
        });
    }

    // Assing data-region to all inlines.
    // We also want to the data attribute to be visible to selectors (that's why we're using $.attr)
    function assignRegionDataAttribute() {
        orderMachine.find('.inline-related:not(.empty-form)').each(function() {
            var $this = $(this),
                region = $this.find('.field-region input').val();

            $this.attr('data-region', region);
            attachMoveToRegionDropdown($this);
        });
    }

    function setBiggestOrdering(row) {
        var orderings = [];
        orderMachine.find('.field-ordering input').each(function() {
            if (!isNaN(+this.value)) orderings.push(+this.value);
        });
        row.find('.field-ordering input').val(10 + Math.max.apply(null, orderings));
    }

    function hideInlinesFromOtherRegions() {
        var inlines = orderMachine.find('.inline-related:not(.empty-form)');
        inlines.addClass('content-editor-hidden');
        var shown = inlines.filter('[data-region="' + ContentEditor.currentRegion + '"]');
        machineEmptyMessage.addClass('hidden');
        if (shown.length) {
            shown.removeClass('content-editor-hidden');
        } else {
            machineEmptyMessage.removeClass('hidden');
        }
    }

    var pluginInlineGroups = (function selectPluginInlineGroups() {
        var selector = [];
        for (var i=0; i < ContentEditor.plugins.length; i++) {
            selector.push('#' + ContentEditor.plugins[i][0] + '_set-group');
        }
        return $(selector.join(', '));
    })();

    reorderInlines(pluginInlineGroups);
    pluginInlineGroups.hide();
    assignRegionDataAttribute();

    // Always move empty forms to the end, because new plugins are inserted
    // just before its empty form. Also, assign region data.
    $(document).on('formset:added', function newForm(event, row) {
        moveEmptyFormsToEnd();

        row.find('.field-region input').val(ContentEditor.currentRegion);
        row.attr('data-region', ContentEditor.currentRegion);

        setBiggestOrdering(row);
        attachMoveToRegionDropdown(row);

        machineEmptyMessage.addClass('hidden');

        $(document).trigger('content-editor:activate', [row]);
    });

    $(document).on('formset:removed', function resetInlines() {
        if (!orderMachine.find('.inline-related[data-region="' + ContentEditor.currentRegion + '"]').length) {
            machineEmptyMessage.removeClass('hidden');
        }
        orderMachine.find('.inline-related.last-related:not(.empty-form)').each(function() {
            $(document).trigger('content-editor:deactivate', [$(this)]);
        });

        // As soon as possible, but not sooner (let the inline.js code run to the end first)
        setTimeout(function() {
            orderMachine.find('.inline-related.last-related:not(.empty-form)').each(function() {
                $(document).trigger('content-editor:activate', [$(this)]);
            });
        }, 0);

    });

    // Initialize tabs and currentRegion.
    (function() {

        var tabContainer = $('.tabs.regions');
        for (var i=0; i < ContentEditor.regions.length; i++) {
            var t = document.createElement('h2');
            t.textContent = ContentEditor.regions[i][1];
            t.setAttribute('data-region', ContentEditor.regions[i][0]);
            tabContainer.append(t);
        }

        var tabs = tabContainer.children(), tab;
        tabs.on('click', function() {
            ContentEditor.currentRegion = $(this).data('region');
            tabs.removeClass('active').filter('[data-region="' + ContentEditor.currentRegion + '"]').addClass('active');
            hideInlinesFromOtherRegions();
            window.location.hash = 'tab_' + ContentEditor.currentRegion;

            // Make sure only allowed plugins are in select
            hideNotAllowedDropdown();
            hideNotAllowedPluginButtons();
        });

        // Restore tab if location hash matches.
        if (window.location.hash && (tab = tabs.filter('[data-region="' + window.location.hash.substr(5) + '"]')) && tab.length) {
            tab.click();
        } else {
            tabs.eq(0).click();
        }

    })();

    $(document).on(
        'content-editor:deactivate',
        function(event, row) {
            row.find('fieldset').addClass('content-editor-hidden');
        }
    ).on(
        'content-editor:activate',
        function(event, row) {
            row.find('fieldset').removeClass('content-editor-hidden');
        }
    );

    // Start sortable; hide fieldsets when dragging, and hide fieldsets of to-be-deleted inlines.
    orderMachine.sortable({
        handle: 'h3',
        placeholder: 'placeholder',
        items: '.inline-related:not(.empty-form)',
        start: function(event, ui) {
            $(document).trigger('content-editor:deactivate', [ui.item]);
        },
        stop: function(event, ui) {
            $(document).trigger('content-editor:activate', [ui.item]);
        }
    }).on('click', '.delete>input[type=checkbox]', function toggleForDeletionClass() {
        $(this).closest('.inline-related')[this.checked ? 'addClass' : 'removeClass']('for-deletion');
    });

    // Fill in ordering field and try to keep the current region tab (location hash).
    $('form').submit(function(){
        orderMachine.find('.field-ordering input').each(function assignOrdering(index) {
            this.value = 10 * (index + 1); // Avoid default=0 just because.
        });

        var form = $(this);
        form.attr('action', form.attr('action') + window.location.hash);
        return true;
    });

    // Cmd-S and Escape behavior.
    $(document).keydown(function handleKeys(event) {
        if (event.which == 83 && event.metaKey) {
            $('form input[name=' + (event.shiftKey ? '_continue' : '_save') + ']').click();
            return false;
        } else if (event.which == 27) {
            orderMachine.sortable('cancel');
        }
    });

    (function buildPluginDropdown() {
        var select = buildDropdown(ContentEditor.plugins, ContentEditor.messages.createNew);
        select.addEventListener('change', function() {
            ContentEditor.addContent(select.value);
            select.value = '';
        });
        ContentEditor.machineControlSelect = select;
        hideNotAllowedDropdown();
        $(select).appendTo('.machine-control').wrap('

');
    })();

    $(document).trigger('content-editor:ready');
});
<|endoftext|>


<commit_before>/* global angular */
'use strict'; // jshint ignore:line

angular.module('lumx.select', [])
    .controller('LxSelectController', ['$scope', '$compile', '$filter', '$interpolate', '$sce', '$timeout',
                                       function($scope, $compile, $filter, $interpolate, $sce, $timeout)
    {
        var self = this,
            newModel = false,
            newSelection = true,
            modelToSelectionDefined = false,
            selectionToModelDefined = false;

        function arrayObjectIndexOf(arr, obj)
        {
            for (var i = 0; i < arr.length; i++)
            {
                if (angular.equals(arr[i], obj))
                {
                    return i;
                }
            }
            return -1;
        }

        // Link methods
        this.init = function(element, attrs)
        {
            $scope.multiple = angular.isDefined(attrs.multiple);
            $scope.tree = angular.isDefined(attrs.tree);
            modelToSelectionDefined = angular.isDefined(attrs.modelToSelection);
            selectionToModelDefined = angular.isDefined(attrs.selectionToModel);
        };

        this.registerTransclude = function(transclude)
        {
            $scope.data.selectedTransclude = transclude;
        };

        this.getScope = function()
        {
            return $scope;
        };

        // Selection management
        function select(choice)
        {
            if ($scope.multiple)
            {
                if (arrayObjectIndexOf($scope.data.selected, choice) === -1)
                {
                    $scope.data.selected.push(choice);
                }
            }
            else
            {
                $scope.data.selected = [choice];
            }
        }

        function unselect(element, event)
        {
            if (!$scope.allowClear && !$scope.multiple)
            {
                return;
            }

            if (angular.isDefined(event) && !$scope.multiple)
            {
                event.stopPropagation();
            }

            var index = arrayObjectIndexOf($scope.data.selected, element);
            if (index !== -1)
            {
                $scope.data.selected.splice(index, 1);
            }
        }

        function toggle(choice, event)
        {
            if (angular.isDefined(event) && $scope.multiple)
            {
                event.stopPropagation();
            }

            if ($scope.multiple && isSelected(choice))
            {
                unselect(choice);
            }
            else
            {
                select(choice);
            }
        }

        // Getters
        function isSelected(choice)
        {
            return angular.isDefined($scope.data.selected) && arrayObjectIndexOf($scope.data.selected, choice) !== -1;
        }

        function hasNoResults()
        {
            return angular.isUndefined($scope.choices) || $filter('filter')($scope.choices, $scope.data.filter).length === 0;
        }

        function filterNeeded()
        {
            return angular.isDefined($scope.minLength) && $scope.data.filter.length < $scope.minLength;
        }

        function isHelperVisible()
        {
            return $scope.loading !== 'true' && (filterNeeded() || (hasNoResults() && !filterNeeded()));
        }

        function isChoicesVisible()
        {
            return $scope.loading !== 'true' && !hasNoResults() && !filterNeeded();
        }

        /**
         * Return the array of selected elements. Always return an array (ie. returns an empty array in case
         * selected list is undefined in the scope).
         */
        function getSelectedElements()
        {
            return angular.isDefined($scope.data.selected) ? $scope.data.selected : [];
        }

        function getSelectedTemplate()
        {
            return $sce.trustAsHtml($scope.data.selectedTemplate);
        }

        function convertValue(newValue, conversion, defaultValue, callback)
        {
            var convertedData = angular.copy(defaultValue);
            var loading = [];

            if (!newValue)
            {
                callback(defaultValue);
                return;
            }

            $scope.data.loading = true;
            if ($scope.multiple)
            {
                if (angular.isDefined(conversion))
                {
                    var callbackCalled = false;
                    var convertionCallback = function(idx)
                    {
                        return function(data)
                        {
                            // Timeout to be sure for the callbacks to be executed after the for loop is finished
                            $timeout(function()
                            {
                                // Add the result in the selected list and remove the index from the loading list
                                if (data !== undefined)
                                {
                                    convertedData.splice(idx, 0, data);
                                }
                                loading.splice(loading.indexOf(idx), 1);

                                // If the loading list is empty, update the $scope and stop the loading animation
                                if (loading.length === 0 && !callbackCalled)
                                {
                                    callbackCalled = true;
                                    $scope.data.loading = false;
                                    callback(convertedData);
                                }
                            });
                        };
                    };

                    for (var idx in newValue)
                    {
                        loading.push(idx);

                        // Call the method
                        conversion({
                            data: newValue[idx],
                            callback: convertionCallback(idx)
                        });
                    }
                }
                else
                {
                    callback(newValue);
                }
            }
            else
            {
                if (angular.isDefined(conversion))
                {
                    $scope.data.loading = true;
                    conversion({
                        data: newValue,
                        callback: function(data)
                        {
                            $scope.data.loading = false;
                            callback(data);
                        }
                    });
                }
                else
                {
                    callback(newValue);
                }
            }
        }

        // Watchers
        $scope.$watch('model', function(newValue)
        {
            if (newModel)
            {
                newModel = false;
                return;
            }

            convertValue(newValue,
                         modelToSelectionDefined ? $scope.modelToSelection : undefined,
                         undefined,
                         function(newConvertedValue)
            {
                newSelection = true;

                var value = newConvertedValue !== undefined ? angular.copy(newConvertedValue) : [];
                if (!$scope.multiple)
                {
                    value = newConvertedValue !== undefined ? [angular.copy(newConvertedValue)] : [];
                }

                $scope.data.selected = value;
            });
        }, true);

        $scope.$watch('data.selected', function(newValue)
        {
            if (angular.isDefined(newValue) && angular.isDefined($scope.data.selectedTransclude))
            {
                var newScope = $scope.$new();
                $scope.data.selectedTemplate = '';

                angular.forEach(newValue, function(selectedElement)
                {
                    newScope.$selected = selectedElement;

                    $scope.data.selectedTransclude(newScope, function(clone)
                    {
                        var div = angular.element('<div/>'),
                        element = $compile(clone)(newScope),
                        content = $interpolate(clone.html())(newScope);

                        element.html(content);

                        div.append(element);

                        if ($scope.multiple)
                        {
                            div.find('span').addClass('lx-select__tag');
                        }

                        $scope.data.selectedTemplate += div.html();
                    });
                });
            }

            if (newSelection)
            {
                newSelection = false;
                return;
            }

            var data = newValue;
            if(!$scope.multiple)
            {
                if (newValue)
                {
                    data = newValue[0];
                }
                else
                {
                    data = undefined;
                }
            }

            convertValue(data,
                         selectionToModelDefined ? $scope.selectionToModel : undefined,
                         $scope.multiple ? [] : undefined,
                         function(newConvertedValue)
            {
                newModel = true;

                $scope.change({ newValue: angular.copy(newConvertedValue), oldValue: angular.copy($scope.model) });
                $scope.model = angular.copy(newConvertedValue);
            });
        }, true);

        $scope.$watch('data.filter', function(newValue, oldValue)
        {
            if(angular.isUndefined($scope.minLength) || (newValue && $scope.minLength <= newValue.length))
            {
                $scope.filter({ newValue: newValue, oldValue: oldValue });
            }
        });

        // Public API
        $scope.select = select;
        $scope.unselect = unselect;
        $scope.toggle = toggle;
        $scope.isChoicesVisible = isChoicesVisible;
        $scope.isHelperVisible = isHelperVisible;
        $scope.isSelected = isSelected;
        $scope.filterNeeded = filterNeeded;
        $scope.getSelectedElements = getSelectedElements;
        $scope.getSelectedTemplate = getSelectedTemplate;
        $scope.hasNoResults = hasNoResults;
    }])
    .directive('lxSelect', function()
    {
        return {
            restrict: 'E',
            controller: 'LxSelectController',
            scope: {
                model: '=?',
                placeholder: '@',
                choices: '=',
                loading: '@',
                minLength: '@',
                allowClear: '@',
                change: '&', // Parameters: newValue, oldValue
                filter: '&', // Parameters: newValue, oldValue
                selectionToModel: '&', // Parameters: data, callback
                modelToSelection: '&' // Parameters: data, callback
            },
            templateUrl: 'lumx.select.html',
            transclude: true,
            replace: true,
            link: function(scope, element, attrs, ctrl)
            {
                ctrl.init(element, attrs);
                scope.data = {
                    filter: '',
                    selected: [],
                    loading: false
                };
            }
        };
    })
    .directive('lxSelectSelected', function()
    {
        return {
            restrict: 'E',
            require: '^lxSelect',
            templateUrl: 'lumx.select_selected.html',
            transclude: true,
            link: function(scope, element, attrs, ctrl, transclude)
            {
                ctrl.registerTransclude(transclude);
            }
        };
    })
    .directive('lxSelectChoices', function()
    {
        return {
            restrict: 'E',
            require: '^lxSelect',
            templateUrl: 'lumx.select_choices.html',
            transclude: true
        };
    });
<commit_msg>fix select: null pointer for non-multiple<commit_after>/* global angular */
'use strict'; // jshint ignore:line

angular.module('lumx.select', [])
    .controller('LxSelectController', ['$scope', '$compile', '$filter', '$interpolate', '$sce', '$timeout',
                                       function($scope, $compile, $filter, $interpolate, $sce, $timeout)
    {
        var self = this,
            newModel = false,
            newSelection = true,
            modelToSelectionDefined = false,
            selectionToModelDefined = false;

        function arrayObjectIndexOf(arr, obj)
        {
            for (var i = 0; i < arr.length; i++)
            {
                if (angular.equals(arr[i], obj))
                {
                    return i;
                }
            }
            return -1;
        }

        // Link methods
        this.init = function(element, attrs)
        {
            $scope.multiple = angular.isDefined(attrs.multiple);
            $scope.tree = angular.isDefined(attrs.tree);
            modelToSelectionDefined = angular.isDefined(attrs.modelToSelection);
            selectionToModelDefined = angular.isDefined(attrs.selectionToModel);
        };

        this.registerTransclude = function(transclude)
        {
            $scope.data.selectedTransclude = transclude;
        };

        this.getScope = function()
        {
            return $scope;
        };

        // Selection management
        function select(choice)
        {
            if ($scope.multiple)
            {
                if (arrayObjectIndexOf($scope.data.selected, choice) === -1)
                {
                    $scope.data.selected.push(choice);
                }
            }
            else
            {
                $scope.data.selected = [choice];
            }
        }

        function unselect(element, event)
        {
            if (!$scope.allowClear && !$scope.multiple)
            {
                return;
            }

            if (angular.isDefined(event) && !$scope.multiple)
            {
                event.stopPropagation();
            }

            var index = arrayObjectIndexOf($scope.data.selected, element);
            if (index !== -1)
            {
                $scope.data.selected.splice(index, 1);
            }
        }

        function toggle(choice, event)
        {
            if (angular.isDefined(event) && $scope.multiple)
            {
                event.stopPropagation();
            }

            if ($scope.multiple && isSelected(choice))
            {
                unselect(choice);
            }
            else
            {
                select(choice);
            }
        }

        // Getters
        function isSelected(choice)
        {
            return angular.isDefined($scope.data.selected) && arrayObjectIndexOf($scope.data.selected, choice) !== -1;
        }

        function hasNoResults()
        {
            return angular.isUndefined($scope.choices) || $filter('filter')($scope.choices, $scope.data.filter).length === 0;
        }

        function filterNeeded()
        {
            return angular.isDefined($scope.minLength) && $scope.data.filter.length < $scope.minLength;
        }

        function isHelperVisible()
        {
            return $scope.loading !== 'true' && (filterNeeded() || (hasNoResults() && !filterNeeded()));
        }

        function isChoicesVisible()
        {
            return $scope.loading !== 'true' && !hasNoResults() && !filterNeeded();
        }

        /**
         * Return the array of selected elements. Always return an array (ie. returns an empty array in case
         * selected list is undefined in the scope).
         */
        function getSelectedElements()
        {
            return angular.isDefined($scope.data.selected) ? $scope.data.selected : [];
        }

        function getSelectedTemplate()
        {
            return $sce.trustAsHtml($scope.data.selectedTemplate);
        }

        function convertValue(newValue, conversion, defaultValue, callback)
        {
            var convertedData = angular.copy(defaultValue);
            var loading = [];

            if (!newValue)
            {
                callback(defaultValue);
                return;
            }

            $scope.data.loading = true;
            if ($scope.multiple)
            {
                if (angular.isDefined(conversion))
                {
                    var callbackCalled = false;
                    var convertionCallback = function(idx)
                    {
                        return function(data)
                        {
                            // Timeout to be sure for the callbacks to be executed after the for loop is finished
                            $timeout(function()
                            {
                                // Add the result in the selected list and remove the index from the loading list
                                if (data !== undefined)
                                {
                                    convertedData.splice(idx, 0, data);
                                }
                                loading.splice(loading.indexOf(idx), 1);

                                // If the loading list is empty, update the $scope and stop the loading animation
                                if (loading.length === 0 && !callbackCalled)
                                {
                                    callbackCalled = true;
                                    $scope.data.loading = false;
                                    callback(convertedData);
                                }
                            });
                        };
                    };

                    for (var idx in newValue)
                    {
                        loading.push(idx);

                        // Call the method
                        conversion({
                            data: newValue[idx],
                            callback: convertionCallback(idx)
                        });
                    }
                }
                else
                {
                    callback(newValue);
                }
            }
            else
            {
                if (angular.isDefined(conversion))
                {
                    $scope.data.loading = true;
                    conversion({
                        data: newValue,
                        callback: function(data)
                        {
                            $scope.data.loading = false;
                            callback(data);
                        }
                    });
                }
                else
                {
                    callback(newValue);
                }
            }
        }

        // Watchers
        $scope.$watch('model', function(newValue)
        {
            if (newModel)
            {
                newModel = false;
                return;
            }

            convertValue(newValue,
                         modelToSelectionDefined ? $scope.modelToSelection : undefined,
                         [],
                         function(newConvertedValue)
            {
                newSelection = true;

                var value = newConvertedValue !== undefined ? angular.copy(newConvertedValue) : [];
                if (!$scope.multiple)
                {
                    value = newConvertedValue !== undefined ? [angular.copy(newConvertedValue)] : [];
                }

                $scope.data.selected = value;
            });
        }, true);

        $scope.$watch('data.selected', function(newValue)
        {
            if (angular.isDefined(newValue) && angular.isDefined($scope.data.selectedTransclude))
            {
                var newScope = $scope.$new();
                $scope.data.selectedTemplate = '';

                angular.forEach(newValue, function(selectedElement)
                {
                    newScope.$selected = selectedElement;

                    $scope.data.selectedTransclude(newScope, function(clone)
                    {
                        var div = angular.element('<div/>'),
                        element = $compile(clone)(newScope),
                        content = $interpolate(clone.html())(newScope);

                        element.html(content);

                        div.append(element);

                        if ($scope.multiple)
                        {
                            div.find('span').addClass('lx-select__tag');
                        }

                        $scope.data.selectedTemplate += div.html();
                    });
                });
            }

            if (newSelection)
            {
                newSelection = false;
                return;
            }

            var data = newValue;
            if(!$scope.multiple)
            {
                if (newValue)
                {
                    data = newValue[0];
                }
                else
                {
                    data = undefined;
                }
            }

            convertValue(data,
                         selectionToModelDefined ? $scope.selectionToModel : undefined,
                         $scope.multiple ? [] : undefined,
                         function(newConvertedValue)
            {
                newModel = true;

                $scope.change({ newValue: angular.copy(newConvertedValue), oldValue: angular.copy($scope.model) });
                $scope.model = angular.copy(newConvertedValue);
            });
        }, true);

        $scope.$watch('data.filter', function(newValue, oldValue)
        {
            if(angular.isUndefined($scope.minLength) || (newValue && $scope.minLength <= newValue.length))
            {
                $scope.filter({ newValue: newValue, oldValue: oldValue });
            }
        });

        // Public API
        $scope.select = select;
        $scope.unselect = unselect;
        $scope.toggle = toggle;
        $scope.isChoicesVisible = isChoicesVisible;
        $scope.isHelperVisible = isHelperVisible;
        $scope.isSelected = isSelected;
        $scope.filterNeeded = filterNeeded;
        $scope.getSelectedElements = getSelectedElements;
        $scope.getSelectedTemplate = getSelectedTemplate;
        $scope.hasNoResults = hasNoResults;
    }])
    .directive('lxSelect', function()
    {
        return {
            restrict: 'E',
            controller: 'LxSelectController',
            scope: {
                model: '=?',
                placeholder: '@',
                choices: '=',
                loading: '@',
                minLength: '@',
                allowClear: '@',
                change: '&', // Parameters: newValue, oldValue
                filter: '&', // Parameters: newValue, oldValue
                selectionToModel: '&', // Parameters: data, callback
                modelToSelection: '&' // Parameters: data, callback
            },
            templateUrl: 'lumx.select.html',
            transclude: true,
            replace: true,
            link: function(scope, element, attrs, ctrl)
            {
                ctrl.init(element, attrs);
                scope.data = {
                    filter: '',
                    selected: [],
                    loading: false
                };
            }
        };
    })
    .directive('lxSelectSelected', function()
    {
        return {
            restrict: 'E',
            require: '^lxSelect',
            templateUrl: 'lumx.select_selected.html',
            transclude: true,
            link: function(scope, element, attrs, ctrl, transclude)
            {
                ctrl.registerTransclude(transclude);
            }
        };
    })
    .directive('lxSelectChoices', function()
    {
        return {
            restrict: 'E',
            require: '^lxSelect',
            templateUrl: 'lumx.select_choices.html',
            transclude: true
        };
    });
<|endoftext|>