<commit_before>(function($) {
  'use strict';

  var DEFAULTS = {
    months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
    monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
    days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
    daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
    daysMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
    container: '

',
    firstDay: 0,
    defaultDate: null,
    altField: null,
    dateFormat: 'mm/dd/yy',
    altFormat: null,
    align: 'bottom-left',
    selectOtherMonths: true,
    prevText: '«',
    nextText: '»'
  };

  var regextOneOrTwoDigit = /\d\d?/;
  var regexTwoDigit = /\d\d/;
  var regexFourDigit = /\d{4}/;
  // any word (or two) characters or numbers including two/three word month in arabic.
  var regexWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;

  var tokenRegex = {
    d: regextOneOrTwoDigit,
    dd: regexTwoDigit,
    D: regexWord,
    DD: regexWord,
    m: regextOneOrTwoDigit,
    mm: regexTwoDigit,
    M: regexWord,
    MM: regexWord,
    y: regexTwoDigit,
    yy: regexFourDigit,
    '@': /\d+/
  };

  var formattingTokens = /(\[[^\[]*\])|(\\)?(dd|DD|mm|MM|yy|@|.)/g;

  // DATEPICKER HELPER FUNCTIONS DEFINITION
  // ======================================

  function formatNumber(number) {
    number = '' + number;
    return number.length < 2 ? '0' + number : number;
  }

  var formatters = {
    d: function(options) {
      return this.getDate();
    },
    dd: function(options) {
      return formatNumber(this.getDate());
    },
    D: function(options) {
      return options.daysShort[this.getDay()];
    },
    DD: function(options) {
      return options.days[this.getDay()];
    },
    m: function(options) {
      return this.getMonth() + 1;
    },
    mm: function(options) {
      return formatNumber(this.getMonth() + 1);
    },
    M: function(options) {
      return options.monthsShort[this.getMonth()];
    },
    MM: function(options) {
      return options.months[this.getMonth()];
    },
    y: function(options) {
      return formatNumber(this.getFullYear() % 100);
    },
    yy: function(options) {
      return this.getFullYear();
    },
    '@': function(options) {
      return this.getTime();
    }
  };

  // d - day of month (no leading zero)
  // dd - day of month (two digit)
  // D - day name short
  // DD - day name long
  // m - month of year (no leading zero)
  // mm - month of year (two digit)
  // M - month name short
  // MM - month name long
  // y - year (two digit)
  // yy - year (four digit)
  // @ - Unix timestamp (ms since 01/01/1970)
  // \\ - escape proceeding characters
  // [] - escape characters between braces
  // anything else - literal text
  function formatDate(format, date, option) {
    var options = $.extend({}, DEFAULTS, option);

    var i, value, token,
        tokens = format.match(formattingTokens),
        output = '';

    for (i = 0; i < tokens.length; i++) {
      token = tokens[i];

      if (formatters[token]) {
        value = formatters[token].call(date, options);
      } else {
        value = token.match(/\[[\s\S]/) ? token.replace(/^\[|\]$/g, '') : token.replace(/\\/g, '');
      }

      output += value;
    }

    return output;
  }

  function parseDate(format, string, option) {
    var options = $.extend({}, DEFAULTS, option);

    var dateObject = {}, i, token, regex, matched;

    function addPartToDateObject(token, matched) {
      switch (token) {
        case 'd':
        case 'dd':
          dateObject.day = +matched;
          break;
        case 'm':
        case 'mm':
          dateObject.month = matched - 1;
          break;
        case 'y':
          dateObject.year = +matched + (+matched > 68 ? 1900 : 2000);
          break;
        case 'yy':
          dateObject.year = +matched;
          break;
        case 'M':
          dateObject.month = options.monthsShort.findIndex(function(el) { return el === matched });
          break;
        case 'MM':
          dateObject.month = options.months.findIndex(function(el) { return el === matched });
          break;
        case '@':
          dateObject.epoch = +matched;
          break;
      }
    }

    var tokens = format.match(formattingTokens);

    for (i = 0; i < tokens.length; i++) {
      token = tokens[i];

      regex = tokenRegex[token];
      if (!regex) regex = new RegExp(token, 'i');

      matched = (string.match(regex) || [])[0];
      string = string.substr(matched.length);

      addPartToDateObject(token, matched);
    }

    if (dateObject.epoch) {
      return new Date(dateObject.epoch);
    } else {
      return new Date(dateObject.year, dateObject.month, dateObject.day);
    }
  }

  // DATEPICKER CLASS DEFINITION
  // ===========================

  var Datepicker = function(element, options) {
    this.$element = $(element);
    this.options  = options;

    this.isInput = this.$element.is('input');

    this.selectedDate = new Date;

    var defaultDate = this.options.defaultDate;
    if (typeof defaultDate === 'string') {
      this.selectedDate = parseDate(this.options.dateFormat, defaultDate, this.options);
    } else if (typeof defaultDate === 'object' && defaultDate !== null) {
      this.selectedDate = defaultDate;
    }

    if (this.isInput && this.$element.val() !== '') {
      this.selectedDate = parseDate(this.options.dateFormat, this.$element.val(), this.options);
    }

    this.currentDate = this.selectedDate;

    this.$container = $(this.options.container);

    if (this.isInput) {
      this.$element
        .on('focus', $.proxy(this.show, this))
        .on('keydown', $.proxy(keydown, this));
    } else {
      this.$element.on('click', $.proxy(this.show, this));
    }

    this.$container
      .on('click', function(e) { e.stopPropagation(); })
      .on('click', '.prev-link, .next-link', $.proxy(changeMonth, this))
      .on('click', 'table a', $.proxy(select, this));
  };

  Datepicker.prototype.show = function(e) {
    if (e) e.preventDefault();

    var ev = $.Event('show.datepicker');
    this.$element.trigger(ev);
    if (ev.isDefaultPrevented()) return;

    this.currentDate = this.selectedDate;

    this.render();
    $(document.body).append(this.$container);

    positionContainer.call(this);
  };

  Datepicker.prototype.hide = function() {
    var ev = $.Event('hide.datepicker');
    this.$element.trigger(ev);
    if (ev.isDefaultPrevented()) return;

    this.$container.detach();
  }

  Datepicker.prototype.render = function() {
    this.$container.html(renderCalendar.call(this));
  }

  Datepicker.prototype.val = function(val) {
    var formattedDate = formatDate(this.options.dateFormat, this.selectedDate, this.options);

    if (this.isInput) {
      this.$element.val(formattedDate);
    } else {
      this.$element.html(formattedDate);
    }

    if (this.options.altField) {
      var altFormat = this.options.altFormat || this.options.dateFormat;
      $(this.options.altField).val(formatDate(altFormat, this.selectedDate, this.options));
    }
  }

  Datepicker.prototype.getDate = function() {
    return this.selectedDate;
  };

  Datepicker.prototype.setDate = function(date) {
    if (typeof date === 'string') date = parseDate(this.options.dateFormat, date, this.options);

    this.selectedDate = this.currentDate = date;
    this.render();
    this.val(this.selectedDate);
  };

  Datepicker.prototype.setOptions = function(options) {
    this.options = $.extend({}, this.options, options);
  };

  // DATEPICKER PRIVATE FUNCTIONS DEFINITION
  // =======================================

  function keydown(e) {
    if (e.which === 9) this.hide();
  }

  function changeMonth(e) {
    e.preventDefault();

    var dateString = $(e.target).data('date');
    var date = parseDate('yy-m-d', dateString, this.options);

    var ev = $.Event('changeMonth.datepicker', {selectedDate: date});
    this.$element.trigger(ev);
    if (!ev.isDefaultPrevented()) {
      this.currentDate = date;
      this.$container.html(renderCalendar.call(this));
    }
  }

  function select(e) {
    e.preventDefault();

    var dateString = $(e.target).data('date');
    var date = parseDate('yy-m-d', dateString, this.options);

    var ev = $.Event('select.datepicker', {selectedDate: date});
    this.$element.triggerHandler(ev);
    if (!ev.isDefaultPrevented()) {
      this.selectedDate = date;

      this.$container.html(renderCalendar.call(this));
      this.val(this.selectedDate);
    }

    this.hide();
  }

  function positionContainer() {
    var cWidth = this.$container.outerWidth();
    var cHeight = this.$container.outerHeight();

    var offset = this.$element.offset();
    var height = this.$element.outerHeight();
    var width = this.$element.outerWidth();

    var top = offset.top;
    var left = offset.left;

    var align = this.options.align.split('-');

    if (align[0] === 'bottom') top += height;
    if (align[0] === 'top')    top -= cHeight;
    if (align[0] === 'left')   left -= cWidth;
    if (align[0] === 'right')  left += width;

    if (align[1] === 'center') left += width / 2 - cWidth / 2;
    if (align[1] === 'right')  left += width - cWidth;

    if (align[1] === 'top')    top -= cHeight - height;
    if (align[1] === 'middle') top -= cHeight / 2 - height / 2;

    this.$container.css({left: left, top: top});
  }

  function renderCalendar() {
    var i, j, cell, day, classes, rYear, rMonth, isCellSelectable;

    var today  = new Date,
        tYear  = today.getFullYear(),
        tMonth = today.getMonth(),
        tDay   = today.getDate(),
        sYear  = this.selectedDate.getFullYear(),
        sMonth = this.selectedDate.getMonth(),
        sDay   = this.selectedDate.getDate();

    var year            = this.currentDate.getFullYear(),
        month           = this.currentDate.getMonth(),
        prevDate        = new Date(year, month - 1, 1),
        nextDate        = new Date(year, month + 1, 1),
        daysCount       = 32 - (new Date(year, month, 32)).getDate(),
        prevDaysCount   = 32 - (new Date(year, month - 1, 32)).getDate(),
        firstDayOffset  = (new Date(year, month, 1)).getDay() - this.options.firstDay,
        offsetDaysCount = firstDayOffset + daysCount,
        rows            = Math.ceil(offsetDaysCount / 7);

    var dataDateFormat = '\\data-\\date="yy-m-d"';

    var output = '';

    output += '
';
    output += '' + this.options.prevText + '';
    output += '' + formatDate('MM yy', this.currentDate, this.options) + '';
    output += '' + this.options.nextText + '';
    output += '
';

    output += '
';

    output += '
';
    output += '
';

    for (i = 0; i < 7; i++) {
      output += ''
      output += this.options.daysMin[(i + this.options.firstDay) % 7];
      output += '	'
    }

    output += '
';
    output += '
';

    output += '
';

    for (i = 0; i < rows; i++) {
      output += '
';

      for (j = 0; j < 7; j++) {
        classes = [];

        cell = i * 7 + j;
        if (cell < firstDayOffset) {
          rYear = prevDate.getFullYear();
          rMonth = prevDate.getMonth();
          classes.push('prev-month');
          day = prevDaysCount - firstDayOffset + j + 1;
          isCellSelectable = this.options.selectOtherMonths;
        } else if (cell >= offsetDaysCount) {
          rYear = nextDate.getFullYear();
          rMonth = nextDate.getMonth();
          classes.push('next-month');
          day = cell - offsetDaysCount + 1;
          isCellSelectable = this.options.selectOtherMonths;
        } else {
          rYear = year;
          rMonth = month;
          day = cell - firstDayOffset + 1;
          if (year === tYear && month === tMonth && day === tDay) classes.push('today');
          isCellSelectable = true;
        }

        if (sYear === rYear && sMonth === rMonth && sDay === day) classes.push('selected');

        output += ' 0 ? ' class="' + classes.join(' ') + '"' : '') + '>';
        output += isCellSelectable ? '' : '';

        output += day;

        output += isCellSelectable ? '' : '';
        output += '	';
      }

      output += '
';
    }

    output += '
';
    output += '
';

    return output;
  };

  // DATEPICKER PLUGIN DEFINITION
  // ============================

  $.fn.datepicker = function(option, val) {
    if (option === 'getDate') {
      var $this = $(this[0]);
      $this.datepicker();
      var data = $this.data('datepicker');

      return data[option](val);
    }

    return this.each(function() {
      var $this   = $(this);
      var data    = $this.data('datepicker');
      var options = $.extend({}, DEFAULTS, typeof option === 'object' && option);

      if (!data) {
        $this
          .data('datepicker', (data = new Datepicker(this, options)))
          .attr('data-datepicker-instance', '');
      }
      if (typeof option === 'string') data[option](val);
    });
  };

  // expose helper functions
  $.datepicker = {
    formatDate: formatDate,
    parseDate: parseDate,
    setDefaults: function(options) {
      $.extend(DEFAULTS, options);
      $('[data-datepicker-instance]').datepicker('setOptions', options);
    }
  };

  $(document).on('click', function(e) {
    $('[data-datepicker-instance]').each(function() {
      if (this !== e.target) $(this).datepicker('hide');
    });
  });

  // DATEPICKER DATA-API
  // ===================

  $(document)
    .on('click.datepicker', '[data-datepicker]:not(input)', function(e) {
      e.preventDefault();
      $(this).datepicker('show');
    })
    .on('focus.datepicker', 'input[data-datepicker]', function() {
      $(this).datepicker();
    });

})(window.jQuery);
<commit_msg>Add .disabled class for cells if selectOtherMonths option is set to false<commit_after>(function($) {
  'use strict';

  var DEFAULTS = {
    months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
    monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
    days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
    daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
    daysMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
    container: '

',
    firstDay: 0,
    defaultDate: null,
    altField: null,
    dateFormat: 'mm/dd/yy',
    altFormat: null,
    align: 'bottom-left',
    selectOtherMonths: true,
    prevText: '«',
    nextText: '»'
  };

  var regextOneOrTwoDigit = /\d\d?/;
  var regexTwoDigit = /\d\d/;
  var regexFourDigit = /\d{4}/;
  // any word (or two) characters or numbers including two/three word month in arabic.
  var regexWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;

  var tokenRegex = {
    d: regextOneOrTwoDigit,
    dd: regexTwoDigit,
    D: regexWord,
    DD: regexWord,
    m: regextOneOrTwoDigit,
    mm: regexTwoDigit,
    M: regexWord,
    MM: regexWord,
    y: regexTwoDigit,
    yy: regexFourDigit,
    '@': /\d+/
  };

  var formattingTokens = /(\[[^\[]*\])|(\\)?(dd|DD|mm|MM|yy|@|.)/g;

  // DATEPICKER HELPER FUNCTIONS DEFINITION
  // ======================================

  function formatNumber(number) {
    number = '' + number;
    return number.length < 2 ? '0' + number : number;
  }

  var formatters = {
    d: function(options) {
      return this.getDate();
    },
    dd: function(options) {
      return formatNumber(this.getDate());
    },
    D: function(options) {
      return options.daysShort[this.getDay()];
    },
    DD: function(options) {
      return options.days[this.getDay()];
    },
    m: function(options) {
      return this.getMonth() + 1;
    },
    mm: function(options) {
      return formatNumber(this.getMonth() + 1);
    },
    M: function(options) {
      return options.monthsShort[this.getMonth()];
    },
    MM: function(options) {
      return options.months[this.getMonth()];
    },
    y: function(options) {
      return formatNumber(this.getFullYear() % 100);
    },
    yy: function(options) {
      return this.getFullYear();
    },
    '@': function(options) {
      return this.getTime();
    }
  };

  // d - day of month (no leading zero)
  // dd - day of month (two digit)
  // D - day name short
  // DD - day name long
  // m - month of year (no leading zero)
  // mm - month of year (two digit)
  // M - month name short
  // MM - month name long
  // y - year (two digit)
  // yy - year (four digit)
  // @ - Unix timestamp (ms since 01/01/1970)
  // \\ - escape proceeding characters
  // [] - escape characters between braces
  // anything else - literal text
  function formatDate(format, date, option) {
    var options = $.extend({}, DEFAULTS, option);

    var i, value, token,
        tokens = format.match(formattingTokens),
        output = '';

    for (i = 0; i < tokens.length; i++) {
      token = tokens[i];

      if (formatters[token]) {
        value = formatters[token].call(date, options);
      } else {
        value = token.match(/\[[\s\S]/) ? token.replace(/^\[|\]$/g, '') : token.replace(/\\/g, '');
      }

      output += value;
    }

    return output;
  }

  function parseDate(format, string, option) {
    var options = $.extend({}, DEFAULTS, option);

    var dateObject = {}, i, token, regex, matched;

    function addPartToDateObject(token, matched) {
      switch (token) {
        case 'd':
        case 'dd':
          dateObject.day = +matched;
          break;
        case 'm':
        case 'mm':
          dateObject.month = matched - 1;
          break;
        case 'y':
          dateObject.year = +matched + (+matched > 68 ? 1900 : 2000);
          break;
        case 'yy':
          dateObject.year = +matched;
          break;
        case 'M':
          dateObject.month = options.monthsShort.findIndex(function(el) { return el === matched });
          break;
        case 'MM':
          dateObject.month = options.months.findIndex(function(el) { return el === matched });
          break;
        case '@':
          dateObject.epoch = +matched;
          break;
      }
    }

    var tokens = format.match(formattingTokens);

    for (i = 0; i < tokens.length; i++) {
      token = tokens[i];

      regex = tokenRegex[token];
      if (!regex) regex = new RegExp(token, 'i');

      matched = (string.match(regex) || [])[0];
      string = string.substr(matched.length);

      addPartToDateObject(token, matched);
    }

    if (dateObject.epoch) {
      return new Date(dateObject.epoch);
    } else {
      return new Date(dateObject.year, dateObject.month, dateObject.day);
    }
  }

  // DATEPICKER CLASS DEFINITION
  // ===========================

  var Datepicker = function(element, options) {
    this.$element = $(element);
    this.options  = options;

    this.isInput = this.$element.is('input');

    this.selectedDate = new Date;

    var defaultDate = this.options.defaultDate;
    if (typeof defaultDate === 'string') {
      this.selectedDate = parseDate(this.options.dateFormat, defaultDate, this.options);
    } else if (typeof defaultDate === 'object' && defaultDate !== null) {
      this.selectedDate = defaultDate;
    }

    if (this.isInput && this.$element.val() !== '') {
      this.selectedDate = parseDate(this.options.dateFormat, this.$element.val(), this.options);
    }

    this.currentDate = this.selectedDate;

    this.$container = $(this.options.container);

    if (this.isInput) {
      this.$element
        .on('focus', $.proxy(this.show, this))
        .on('keydown', $.proxy(keydown, this));
    } else {
      this.$element.on('click', $.proxy(this.show, this));
    }

    this.$container
      .on('click', function(e) { e.stopPropagation(); })
      .on('click', '.prev-link, .next-link', $.proxy(changeMonth, this))
      .on('click', 'table a', $.proxy(select, this));
  };

  Datepicker.prototype.show = function(e) {
    if (e) e.preventDefault();

    var ev = $.Event('show.datepicker');
    this.$element.trigger(ev);
    if (ev.isDefaultPrevented()) return;

    this.currentDate = this.selectedDate;

    this.render();
    $(document.body).append(this.$container);

    positionContainer.call(this);
  };

  Datepicker.prototype.hide = function() {
    var ev = $.Event('hide.datepicker');
    this.$element.trigger(ev);
    if (ev.isDefaultPrevented()) return;

    this.$container.detach();
  }

  Datepicker.prototype.render = function() {
    this.$container.html(renderCalendar.call(this));
  }

  Datepicker.prototype.val = function(val) {
    var formattedDate = formatDate(this.options.dateFormat, this.selectedDate, this.options);

    if (this.isInput) {
      this.$element.val(formattedDate);
    } else {
      this.$element.html(formattedDate);
    }

    if (this.options.altField) {
      var altFormat = this.options.altFormat || this.options.dateFormat;
      $(this.options.altField).val(formatDate(altFormat, this.selectedDate, this.options));
    }
  }

  Datepicker.prototype.getDate = function() {
    return this.selectedDate;
  };

  Datepicker.prototype.setDate = function(date) {
    if (typeof date === 'string') date = parseDate(this.options.dateFormat, date, this.options);

    this.selectedDate = this.currentDate = date;
    this.render();
    this.val(this.selectedDate);
  };

  Datepicker.prototype.setOptions = function(options) {
    this.options = $.extend({}, this.options, options);
  };

  // DATEPICKER PRIVATE FUNCTIONS DEFINITION
  // =======================================

  function keydown(e) {
    if (e.which === 9) this.hide();
  }

  function changeMonth(e) {
    e.preventDefault();

    var dateString = $(e.target).data('date');
    var date = parseDate('yy-m-d', dateString, this.options);

    var ev = $.Event('changeMonth.datepicker', {selectedDate: date});
    this.$element.trigger(ev);
    if (!ev.isDefaultPrevented()) {
      this.currentDate = date;
      this.$container.html(renderCalendar.call(this));
    }
  }

  function select(e) {
    e.preventDefault();

    var dateString = $(e.target).data('date');
    var date = parseDate('yy-m-d', dateString, this.options);

    var ev = $.Event('select.datepicker', {selectedDate: date});
    this.$element.triggerHandler(ev);
    if (!ev.isDefaultPrevented()) {
      this.selectedDate = date;

      this.$container.html(renderCalendar.call(this));
      this.val(this.selectedDate);
    }

    this.hide();
  }

  function positionContainer() {
    var cWidth = this.$container.outerWidth();
    var cHeight = this.$container.outerHeight();

    var offset = this.$element.offset();
    var height = this.$element.outerHeight();
    var width = this.$element.outerWidth();

    var top = offset.top;
    var left = offset.left;

    var align = this.options.align.split('-');

    if (align[0] === 'bottom') top += height;
    if (align[0] === 'top')    top -= cHeight;
    if (align[0] === 'left')   left -= cWidth;
    if (align[0] === 'right')  left += width;

    if (align[1] === 'center') left += width / 2 - cWidth / 2;
    if (align[1] === 'right')  left += width - cWidth;

    if (align[1] === 'top')    top -= cHeight - height;
    if (align[1] === 'middle') top -= cHeight / 2 - height / 2;

    this.$container.css({left: left, top: top});
  }

  function renderCalendar() {
    var i, j, cell, day, classes, rYear, rMonth, isCellSelectable;

    var today  = new Date,
        tYear  = today.getFullYear(),
        tMonth = today.getMonth(),
        tDay   = today.getDate(),
        sYear  = this.selectedDate.getFullYear(),
        sMonth = this.selectedDate.getMonth(),
        sDay   = this.selectedDate.getDate();

    var year            = this.currentDate.getFullYear(),
        month           = this.currentDate.getMonth(),
        prevDate        = new Date(year, month - 1, 1),
        nextDate        = new Date(year, month + 1, 1),
        daysCount       = 32 - (new Date(year, month, 32)).getDate(),
        prevDaysCount   = 32 - (new Date(year, month - 1, 32)).getDate(),
        firstDayOffset  = (new Date(year, month, 1)).getDay() - this.options.firstDay,
        offsetDaysCount = firstDayOffset + daysCount,
        rows            = Math.ceil(offsetDaysCount / 7);

    var dataDateFormat = '\\data-\\date="yy-m-d"';

    var output = '';

    output += '
';
    output += '' + this.options.prevText + '';
    output += '' + formatDate('MM yy', this.currentDate, this.options) + '';
    output += '' + this.options.nextText + '';
    output += '
';

    output += '
';

    output += '
';
    output += '
';

    for (i = 0; i < 7; i++) {
      output += ''
      output += this.options.daysMin[(i + this.options.firstDay) % 7];
      output += '	'
    }

    output += '
';
    output += '
';

    output += '
';

    for (i = 0; i < rows; i++) {
      output += '
';

      for (j = 0; j < 7; j++) {
        classes = [];

        cell = i * 7 + j;
        if (cell < firstDayOffset) {
          rYear = prevDate.getFullYear();
          rMonth = prevDate.getMonth();
          day = prevDaysCount - firstDayOffset + j + 1;
          classes.push('prev-month');
          isCellSelectable = this.options.selectOtherMonths;
          if (!isCellSelectable) classes.push('disabled');
        } else if (cell >= offsetDaysCount) {
          rYear = nextDate.getFullYear();
          rMonth = nextDate.getMonth();
          day = cell - offsetDaysCount + 1;
          classes.push('next-month');
          isCellSelectable = this.options.selectOtherMonths;
          if (!isCellSelectable) classes.push('disabled');
        } else {
          rYear = year;
          rMonth = month;
          day = cell - firstDayOffset + 1;
          if (year === tYear && month === tMonth && day === tDay) classes.push('today');
          isCellSelectable = true;
        }

        if (sYear === rYear && sMonth === rMonth && sDay === day) classes.push('selected');

        output += ' 0 ? ' class="' + classes.join(' ') + '"' : '') + '>';
        output += isCellSelectable ? '' : '';

        output += day;

        output += isCellSelectable ? '' : '';
        output += '	';
      }

      output += '
';
    }

    output += '
';
    output += '
';

    return output;
  };

  // DATEPICKER PLUGIN DEFINITION
  // ============================

  $.fn.datepicker = function(option, val) {
    if (option === 'getDate') {
      var $this = $(this[0]);
      $this.datepicker();
      var data = $this.data('datepicker');

      return data[option](val);
    }

    return this.each(function() {
      var $this   = $(this);
      var data    = $this.data('datepicker');
      var options = $.extend({}, DEFAULTS, typeof option === 'object' && option);

      if (!data) {
        $this
          .data('datepicker', (data = new Datepicker(this, options)))
          .attr('data-datepicker-instance', '');
      }
      if (typeof option === 'string') data[option](val);
    });
  };

  // expose helper functions
  $.datepicker = {
    formatDate: formatDate,
    parseDate: parseDate,
    setDefaults: function(options) {
      $.extend(DEFAULTS, options);
      $('[data-datepicker-instance]').datepicker('setOptions', options);
    }
  };

  $(document).on('click', function(e) {
    $('[data-datepicker-instance]').each(function() {
      if (this !== e.target) $(this).datepicker('hide');
    });
  });

  // DATEPICKER DATA-API
  // ===================

  $(document)
    .on('click.datepicker', '[data-datepicker]:not(input)', function(e) {
      e.preventDefault();
      $(this).datepicker('show');
    })
    .on('focus.datepicker', 'input[data-datepicker]', function() {
      $(this).datepicker();
    });

})(window.jQuery);
<|endoftext|>


<commit_before>/**
 * Module dependencies
 */

var express = require('express'),
	filed = require('filed'),
	http = require('http');

// Web server

var app = express();

app.get('/inspectocat.jpg',function(req,res){
	req.pipe(filed(__dirname + '/resources/inspectocat.jpg')).pipe(res);
});

var server = http.createServer(app);
server.listen(3000,function(err){
	if(err) return console.error(err);
	console.log('listening on port 3000');
});

// Test

http.get('http://localhost:3000/inspectocat.jpg',function(res){
	var body = '';
	res.destroy(new Error('Fucking error'));
	res.on('data',function(chunk){
		body += chunk;
		console.log('length of the body ' + body.length + ' bytes');
	});
	res.on('end',function(){
		console.log('END');
	})
	res.on('close',function(){
		console.log('CLOSE');
	});
	res.on('error',function(err){
		console.log('ERROR RESPONSE');
		console.error(err);
	});
})
.on('error',function(err){
	console.log('ERROR')
	console.error(err);
});<commit_msg>FIX: Remove old test.<commit_after><|endoftext|>


<commit_before>import {Meteor} from 'meteor/meteor';
import {check} from 'meteor/check';

export default function (methodName, ClassName) {
  Meteor.methods({
    [`${methodName}.create`](toInsert) {
      check(toInsert, Object);

      const instance = new ClassName();
      instance.create(toInsert);
    },

    [`${methodName}.edit`](Id, updates) {
      check(Id, String);
      check(updates, Object);

      const instance = ClassName.findOne(Id);
      instance.edit(updates);
    },

    [`${methodName}.delete`](Id) {
      check(Id, String);

      const instance = ClassName.findOne(Id);
      instance.remove();
    }
  });
}
<commit_msg>Update default create method to return instance id<commit_after>import {Meteor} from 'meteor/meteor';
import {check} from 'meteor/check';

export default function (methodName, ClassName) {
  Meteor.methods({
    [`${methodName}.create`](toInsert) {
      check(toInsert, Object);

      const instance = new ClassName();
      instance.create(toInsert);
      return instance._id;
    },

    [`${methodName}.edit`](Id, updates) {
      check(Id, String);
      check(updates, Object);

      const instance = ClassName.findOne(Id);
      instance.edit(updates);
    },

    [`${methodName}.delete`](Id) {
      check(Id, String);

      const instance = ClassName.findOne(Id);
      instance.remove();
    }
  });
}
<|endoftext|>


<commit_before>const _ = require('lodash');
const d3 = require('d3');
const debug = require('debug')('scope:nodes-chart');
const React = require('react');
const timely = require('timely');
const Spring = require('react-motion').Spring;

const AppActions = require('../actions/app-actions');
const AppStore = require('../stores/app-store');
const Edge = require('./edge');
const Naming = require('../constants/naming');
const NodesLayout = require('./nodes-layout');
const Node = require('./node');

const MARGINS = {
  top: 130,
  left: 40,
  right: 40,
  bottom: 0
};

// make sure circular layouts lots of nodes spread out
const radiusDensity = d3.scale.sqrt()
  .domain([12, 2]).range([2.5, 5]).clamp(true);

const NodesChart = React.createClass({

  getInitialState: function() {
    return {
      nodes: {},
      edges: {},
      nodeScale: d3.scale.linear(),
      translate: [0, 0],
      panTranslate: [0, 0],
      scale: 1,
      hasZoomed: false
    };
  },

  componentWillMount: function() {
    const state = this.updateGraphState(this.props, this.state);
    this.setState(state);
  },

  componentDidMount: function() {
    this.zoom = d3.behavior.zoom()
      .scaleExtent([0.1, 2])
      .on('zoom', this.zoomed);

    d3.select('.nodes-chart')
      .on('click', this.handleBackgroundClick)
      .call(this.zoom);
  },

  componentWillReceiveProps: function(nextProps) {
    // gather state, setState should be called only once here
    const state = _.assign({}, this.state);

    // wipe node states when showing different topology
    if (nextProps.topologyId !== this.props.topologyId) {
      _.assign(state, {
        nodes: {},
        edges: {}
      });
    }
    // FIXME add PureRenderMixin, Immutables, and move the following functions to render()
    if (nextProps.nodes !== this.props.nodes) {
      _.assign(state, this.updateGraphState(nextProps, state));
    }
    if (this.props.selectedNodeId !== nextProps.selectedNodeId) {
      _.assign(state, this.restoreLayout(state));
    }
    if (nextProps.selectedNodeId) {
      this.centerSelectedNode(nextProps, state);
    }

    this.setState(state);
  },

  componentWillUnmount: function() {
    // undoing .call(zoom)

    d3.select('.nodes-chart')
      .on('click', null)
      .on('mousedown.zoom', null)
      .on('onwheel', null)
      .on('onmousewheel', null)
      .on('dblclick.zoom', null)
      .on('touchstart.zoom', null);
  },

  getTopologyFingerprint: function(topology) {
    const fingerprint = [];

    _.each(topology, function(node) {
      fingerprint.push(node.id);
      if (node.adjacency) {
        fingerprint.push(node.adjacency.join(','));
      }
    });
    return fingerprint.join(';');
  },

  renderGraphNodes: function(nodes, scale) {
    const hasSelectedNode = this.props.selectedNodeId && this.props.nodes.has(this.props.selectedNodeId);
    const adjacency = hasSelectedNode ? AppStore.getAdjacentNodes(this.props.selectedNodeId) : null;
    return _.map(nodes, function(node) {
      const highlighted = _.includes(this.props.highlightedNodeIds, node.id)
        || this.props.selectedNodeId === node.id;
      const blurred = hasSelectedNode
        && this.props.selectedNodeId !== node.id
        && !adjacency.includes(node.id);

      return (
        <Node
          blurred={blurred}
          highlighted={highlighted}
          onClick={this.props.onNodeClick}
          key={node.id}
          id={node.id}
          label={node.label}
          pseudo={node.pseudo}
          subLabel={node.subLabel}
          rank={node.rank}
          scale={scale}
          dx={node.x}
          dy={node.y}
        />
      );
    }, this);
  },

  renderGraphEdges: function(edges) {
    const selectedNodeId = this.props.selectedNodeId;
    const hasSelectedNode = selectedNodeId && this.props.nodes.has(selectedNodeId);

    return _.map(edges, function(edge) {
      const highlighted = _.includes(this.props.highlightedEdgeIds, edge.id);
      const blurred = hasSelectedNode
        && edge.source.id !== selectedNodeId
        && edge.target.id !== selectedNodeId;
      return (
        <Edge key={edge.id} id={edge.id} points={edge.points} blurred={blurred}
          highlighted={highlighted} />
      );
    }, this);
  },

  render: function() {
    const nodeElements = this.renderGraphNodes(this.state.nodes, this.state.nodeScale);
    const edgeElements = this.renderGraphEdges(this.state.edges, this.state.nodeScale);
    const scale = this.state.scale;

    // only animate shift behavior, not panning
    const panTranslate = this.state.panTranslate;
    const shiftTranslate = this.state.translate;
    let translate = panTranslate;
    let wasShifted = false;
    if (shiftTranslate[0] !== panTranslate[0] || shiftTranslate[1] !== panTranslate[1]) {
      translate = shiftTranslate;
      wasShifted = true;
    }

    return (
      <svg width="100%" height="100%" className="nodes-chart">
        <Spring endValue={{val: translate, config: [80, 20]}}>
          {function(interpolated) {
            let interpolatedTranslate = wasShifted ? interpolated.val : panTranslate;
            const transform = 'translate(' + interpolatedTranslate + ')' +
              ' scale(' + scale + ')';
            return (
              <g className="canvas" transform={transform}>
                <g className="edges">
                  {edgeElements}
                </g>
                <g className="nodes">
                  {nodeElements}
                </g>
              </g>
            );
          }}
        </Spring>
      </svg>
    );
  },

  initNodes: function(topology) {
    const centerX = this.props.width / 2;
    const centerY = this.props.height / 2;
    const nodes = {};

    topology.forEach(function(node, id) {
      nodes[id] = {};

      // use cached positions if available
      _.defaults(nodes[id], {
        x: centerX,
        y: centerY
      });

      // copy relevant fields to state nodes
      _.assign(nodes[id], {
        id: id,
        label: node.get('label_major'),
        pseudo: node.get('pseudo'),
        subLabel: node.get('label_minor'),
        rank: node.get('rank')
      });
    });

    return nodes;
  },

  initEdges: function(topology, nodes) {
    const edges = {};

    topology.forEach(function(node, nodeId) {
      const adjacency = node.get('adjacency');
      if (adjacency) {
        adjacency.forEach(function(adjacent) {
          const edge = [nodeId, adjacent];
          const edgeId = edge.join(Naming.EDGE_ID_SEPARATOR);

          if (!edges[edgeId]) {
            const source = nodes[edge[0]];
            const target = nodes[edge[1]];

            if (!source || !target) {
              debug('Missing edge node', edge[0], source, edge[1], target);
            }

            edges[edgeId] = {
              id: edgeId,
              value: 1,
              source: source,
              target: target
            };
          }
        });
      }
    });

    return edges;
  },

  centerSelectedNode: function(props, state) {
    const layoutNodes = state.nodes;
    const layoutEdges = state.edges;
    const selectedLayoutNode = layoutNodes[props.selectedNodeId];

    if (!selectedLayoutNode) {
      return {};
    }

    const adjacency = AppStore.getAdjacentNodes(props.selectedNodeId);
    const adjacentLayoutNodes = [];

    adjacency.forEach(function(adjacentId) {
      adjacentLayoutNodes.push(layoutNodes[adjacentId]);
    });

    // circle layout for adjacent nodes

    const centerX = selectedLayoutNode.x;
    const centerY = selectedLayoutNode.y;
    const adjacentCount = adjacentLayoutNodes.length;
    const density = radiusDensity(adjacentCount);
    const radius = Math.min(props.width, props.height) / density;

    _.each(adjacentLayoutNodes, function(node, i) {
      const angle = Math.PI * 2 * i / adjacentCount;
      node.x = centerX + radius * Math.sin(angle);
      node.y = centerY + radius * Math.cos(angle);
    });

    // fix all edges for circular nodes

    _.each(layoutEdges, function(edge) {
      if (edge.source === selectedLayoutNode
        || edge.target === selectedLayoutNode
        || _.includes(adjacentLayoutNodes, edge.source)
        || _.includes(adjacentLayoutNodes, edge.target)) {
        edge.points = [
          {x: edge.source.x, y: edge.source.y},
          {x: edge.target.x, y: edge.target.y}
        ];
      }
    });

    // shift canvas selected node out of view
    const visibleWidth = Math.max(props.width - props.detailsWidth, 0);
    const translate = state.translate;
    const offsetX = translate[0];
    // normalize graph coordinates by zoomScale
    const zoomScale = state.scale;
    const outerRadius = radius + this.state.nodeScale(2);
    if (offsetX + (centerX + outerRadius) * zoomScale > visibleWidth) {
      // shift left if blocked by details
      const shift = (centerX + outerRadius) * zoomScale - visibleWidth;
      translate[0] = -shift;
    } else if (offsetX + (centerX - outerRadius) * zoomScale < 0) {
      // shift right if off canvas
      const shift = offsetX - offsetX + (centerX - outerRadius) * zoomScale;
      translate[0] = -shift;
    }
    const offsetY = translate[1];
    if (offsetY + (centerY + outerRadius) * zoomScale > props.height) {
      // shift up if past bottom
      const shift = (centerY + outerRadius) * zoomScale - props.height;
      translate[1] = -shift;
    } else if (offsetY + (centerY - outerRadius) * zoomScale - props.topMargin < 0) {
      // shift down if off canvas
      const shift = offsetY - offsetY + (centerY - outerRadius) * zoomScale - props.topMargin;
      translate[1] = -shift;
    }
    // debug('shift', centerX, centerY, outerRadius, translate);

    // saving translate in d3's panning cache
    this.zoom.translate(translate);

    return {
      edges: layoutEdges,
      nodes: layoutNodes,
      translate: translate
    };
  },

  handleBackgroundClick: function() {
    AppActions.clickCloseDetails();
  },

  restoreLayout: function(state) {
    const edges = state.edges;
    const nodes = state.nodes;

    _.each(nodes, function(node) {
      node.x = node.px;
      node.y = node.py;
    });

    _.each(edges, function(edge) {
      if (edge.ppoints) {
        edge.points = edge.ppoints;
      }
    });

    return {edges: edges, nodes: nodes};
  },

  updateGraphState: function(props, state) {
    const n = props.nodes.size;

    if (n === 0) {
      return {};
    }

    const nodes = this.initNodes(props.nodes, state.nodes);
    const edges = this.initEdges(props.nodes, nodes);

    const expanse = Math.min(props.height, props.width);
    const nodeSize = expanse / 2;
    const nodeScale = this.state.nodeScale.range([0, nodeSize / Math.pow(n, 0.7)]);

    const timedLayouter = timely(NodesLayout.doLayout);
    const graph = timedLayouter(
      nodes,
      edges,
      props.width,
      props.height,
      nodeScale,
      MARGINS,
      this.props.topologyId
    );

    debug('graph layout took ' + timedLayouter.time + 'ms');

    // layout was aborted
    if (!graph) {
      return {};
    }

    // save coordinates for restore
    _.each(nodes, function(node) {
      node.px = node.x;
      node.py = node.y;
    });
    _.each(edges, function(edge) {
      edge.ppoints = edge.points;
    });

    // adjust layout based on viewport
    const xFactor = (props.width - MARGINS.left - MARGINS.right) / graph.width;
    const yFactor = props.height / graph.height;
    const zoomFactor = Math.min(xFactor, yFactor);
    let zoomScale = this.state.scale;

    if (this.zoom && !this.state.hasZoomed && zoomFactor > 0 && zoomFactor < 1) {
      zoomScale = zoomFactor;
      // saving in d3's behavior cache
      this.zoom.scale(zoomFactor);
    }

    return {
      nodes: nodes,
      edges: edges,
      nodeScale: nodeScale,
      scale: zoomScale
    };
  },

  zoomed: function() {
    // debug('zoomed', d3.event.scale, d3.event.translate);
    this.setState({
      hasZoomed: true,
      panTranslate: d3.event.translate.slice(),
      translate: d3.event.translate.slice(),
      scale: d3.event.scale
    });
  }

});

module.exports = NodesChart;
<commit_msg>WIP track layout aborts<commit_after>const _ = require('lodash');
const d3 = require('d3');
const debug = require('debug')('scope:nodes-chart');
const React = require('react');
const timely = require('timely');
const Spring = require('react-motion').Spring;

const AppActions = require('../actions/app-actions');
const AppStore = require('../stores/app-store');
const Edge = require('./edge');
const Naming = require('../constants/naming');
const NodesLayout = require('./nodes-layout');
const Node = require('./node');

const MARGINS = {
  top: 130,
  left: 40,
  right: 40,
  bottom: 0
};

// make sure circular layouts lots of nodes spread out
const radiusDensity = d3.scale.sqrt()
  .domain([12, 2]).range([2.5, 5]).clamp(true);

const NodesChart = React.createClass({

  getInitialState: function() {
    return {
      nodes: {},
      edges: {},
      nodeScale: d3.scale.linear(),
      translate: [0, 0],
      panTranslate: [0, 0],
      scale: 1,
      hasZoomed: false,
      maxNodesExceeded: false
    };
  },

  componentWillMount: function() {
    const state = this.updateGraphState(this.props, this.state);
    this.setState(state);
  },

  componentDidMount: function() {
    this.zoom = d3.behavior.zoom()
      .scaleExtent([0.1, 2])
      .on('zoom', this.zoomed);

    d3.select('.nodes-chart')
      .on('click', this.handleBackgroundClick)
      .call(this.zoom);
  },

  componentWillReceiveProps: function(nextProps) {
    // gather state, setState should be called only once here
    const state = _.assign({}, this.state);

    // wipe node states when showing different topology
    if (nextProps.topologyId !== this.props.topologyId) {
      _.assign(state, {
        nodes: {},
        edges: {}
      });
    }
    // FIXME add PureRenderMixin, Immutables, and move the following functions to render()
    if (nextProps.nodes !== this.props.nodes) {
      _.assign(state, this.updateGraphState(nextProps, state));
    }
    if (this.props.selectedNodeId !== nextProps.selectedNodeId) {
      _.assign(state, this.restoreLayout(state));
    }
    if (nextProps.selectedNodeId) {
      this.centerSelectedNode(nextProps, state);
    }

    this.setState(state);
  },

  componentWillUnmount: function() {
    // undoing .call(zoom)

    d3.select('.nodes-chart')
      .on('click', null)
      .on('mousedown.zoom', null)
      .on('onwheel', null)
      .on('onmousewheel', null)
      .on('dblclick.zoom', null)
      .on('touchstart.zoom', null);
  },

  getTopologyFingerprint: function(topology) {
    const fingerprint = [];

    _.each(topology, function(node) {
      fingerprint.push(node.id);
      if (node.adjacency) {
        fingerprint.push(node.adjacency.join(','));
      }
    });
    return fingerprint.join(';');
  },

  renderGraphNodes: function(nodes, scale) {
    const hasSelectedNode = this.props.selectedNodeId && this.props.nodes.has(this.props.selectedNodeId);
    const adjacency = hasSelectedNode ? AppStore.getAdjacentNodes(this.props.selectedNodeId) : null;
    return _.map(nodes, function(node) {
      const highlighted = _.includes(this.props.highlightedNodeIds, node.id)
        || this.props.selectedNodeId === node.id;
      const blurred = hasSelectedNode
        && this.props.selectedNodeId !== node.id
        && !adjacency.includes(node.id);

      return (
        <Node
          blurred={blurred}
          highlighted={highlighted}
          onClick={this.props.onNodeClick}
          key={node.id}
          id={node.id}
          label={node.label}
          pseudo={node.pseudo}
          subLabel={node.subLabel}
          rank={node.rank}
          scale={scale}
          dx={node.x}
          dy={node.y}
        />
      );
    }, this);
  },

  renderGraphEdges: function(edges) {
    const selectedNodeId = this.props.selectedNodeId;
    const hasSelectedNode = selectedNodeId && this.props.nodes.has(selectedNodeId);

    return _.map(edges, function(edge) {
      const highlighted = _.includes(this.props.highlightedEdgeIds, edge.id);
      const blurred = hasSelectedNode
        && edge.source.id !== selectedNodeId
        && edge.target.id !== selectedNodeId;
      return (
        <Edge key={edge.id} id={edge.id} points={edge.points} blurred={blurred}
          highlighted={highlighted} />
      );
    }, this);
  },

  render: function() {
    const nodeElements = this.renderGraphNodes(this.state.nodes, this.state.nodeScale);
    const edgeElements = this.renderGraphEdges(this.state.edges, this.state.nodeScale);
    const scale = this.state.scale;

    // only animate shift behavior, not panning
    const panTranslate = this.state.panTranslate;
    const shiftTranslate = this.state.translate;
    let translate = panTranslate;
    let wasShifted = false;
    if (shiftTranslate[0] !== panTranslate[0] || shiftTranslate[1] !== panTranslate[1]) {
      translate = shiftTranslate;
      wasShifted = true;
    }

    if (this.state.maxNodesExceeded) {
      return (

      );
    }

    return (
      <svg width="100%" height="100%" className="nodes-chart">
        <Spring endValue={{val: translate, config: [80, 20]}}>
          {function(interpolated) {
            let interpolatedTranslate = wasShifted ? interpolated.val : panTranslate;
            const transform = 'translate(' + interpolatedTranslate + ')' +
              ' scale(' + scale + ')';
            return (
              <g className="canvas" transform={transform}>
                <g className="edges">
                  {edgeElements}
                </g>
                <g className="nodes">
                  {nodeElements}
                </g>
              </g>
            );
          }}
        </Spring>
      </svg>
    );
  },

  initNodes: function(topology) {
    const centerX = this.props.width / 2;
    const centerY = this.props.height / 2;
    const nodes = {};

    topology.forEach(function(node, id) {
      nodes[id] = {};

      // use cached positions if available
      _.defaults(nodes[id], {
        x: centerX,
        y: centerY
      });

      // copy relevant fields to state nodes
      _.assign(nodes[id], {
        id: id,
        label: node.get('label_major'),
        pseudo: node.get('pseudo'),
        subLabel: node.get('label_minor'),
        rank: node.get('rank')
      });
    });

    return nodes;
  },

  initEdges: function(topology, nodes) {
    const edges = {};

    topology.forEach(function(node, nodeId) {
      const adjacency = node.get('adjacency');
      if (adjacency) {
        adjacency.forEach(function(adjacent) {
          const edge = [nodeId, adjacent];
          const edgeId = edge.join(Naming.EDGE_ID_SEPARATOR);

          if (!edges[edgeId]) {
            const source = nodes[edge[0]];
            const target = nodes[edge[1]];

            if (!source || !target) {
              debug('Missing edge node', edge[0], source, edge[1], target);
            }

            edges[edgeId] = {
              id: edgeId,
              value: 1,
              source: source,
              target: target
            };
          }
        });
      }
    });

    return edges;
  },

  centerSelectedNode: function(props, state) {
    const layoutNodes = state.nodes;
    const layoutEdges = state.edges;
    const selectedLayoutNode = layoutNodes[props.selectedNodeId];

    if (!selectedLayoutNode) {
      return {};
    }

    const adjacency = AppStore.getAdjacentNodes(props.selectedNodeId);
    const adjacentLayoutNodes = [];

    adjacency.forEach(function(adjacentId) {
      adjacentLayoutNodes.push(layoutNodes[adjacentId]);
    });

    // circle layout for adjacent nodes

    const centerX = selectedLayoutNode.x;
    const centerY = selectedLayoutNode.y;
    const adjacentCount = adjacentLayoutNodes.length;
    const density = radiusDensity(adjacentCount);
    const radius = Math.min(props.width, props.height) / density;

    _.each(adjacentLayoutNodes, function(node, i) {
      const angle = Math.PI * 2 * i / adjacentCount;
      node.x = centerX + radius * Math.sin(angle);
      node.y = centerY + radius * Math.cos(angle);
    });

    // fix all edges for circular nodes

    _.each(layoutEdges, function(edge) {
      if (edge.source === selectedLayoutNode
        || edge.target === selectedLayoutNode
        || _.includes(adjacentLayoutNodes, edge.source)
        || _.includes(adjacentLayoutNodes, edge.target)) {
        edge.points = [
          {x: edge.source.x, y: edge.source.y},
          {x: edge.target.x, y: edge.target.y}
        ];
      }
    });

    // shift canvas selected node out of view
    const visibleWidth = Math.max(props.width - props.detailsWidth, 0);
    const translate = state.translate;
    const offsetX = translate[0];
    // normalize graph coordinates by zoomScale
    const zoomScale = state.scale;
    const outerRadius = radius + this.state.nodeScale(2);
    if (offsetX + (centerX + outerRadius) * zoomScale > visibleWidth) {
      // shift left if blocked by details
      const shift = (centerX + outerRadius) * zoomScale - visibleWidth;
      translate[0] = -shift;
    } else if (offsetX + (centerX - outerRadius) * zoomScale < 0) {
      // shift right if off canvas
      const shift = offsetX - offsetX + (centerX - outerRadius) * zoomScale;
      translate[0] = -shift;
    }
    const offsetY = translate[1];
    if (offsetY + (centerY + outerRadius) * zoomScale > props.height) {
      // shift up if past bottom
      const shift = (centerY + outerRadius) * zoomScale - props.height;
      translate[1] = -shift;
    } else if (offsetY + (centerY - outerRadius) * zoomScale - props.topMargin < 0) {
      // shift down if off canvas
      const shift = offsetY - offsetY + (centerY - outerRadius) * zoomScale - props.topMargin;
      translate[1] = -shift;
    }
    // debug('shift', centerX, centerY, outerRadius, translate);

    // saving translate in d3's panning cache
    this.zoom.translate(translate);

    return {
      edges: layoutEdges,
      nodes: layoutNodes,
      translate: translate
    };
  },

  handleBackgroundClick: function() {
    AppActions.clickCloseDetails();
  },

  restoreLayout: function(state) {
    const edges = state.edges;
    const nodes = state.nodes;

    _.each(nodes, function(node) {
      node.x = node.px;
      node.y = node.py;
    });

    _.each(edges, function(edge) {
      if (edge.ppoints) {
        edge.points = edge.ppoints;
      }
    });

    return {edges: edges, nodes: nodes};
  },

  updateGraphState: function(props, state) {
    const n = props.nodes.size;

    if (n === 0) {
      return {};
    }

    const nodes = this.initNodes(props.nodes, state.nodes);
    const edges = this.initEdges(props.nodes, nodes);

    const expanse = Math.min(props.height, props.width);
    const nodeSize = expanse / 2;
    const nodeScale = this.state.nodeScale.range([0, nodeSize / Math.pow(n, 0.7)]);

    const timedLayouter = timely(NodesLayout.doLayout);
    const graph = timedLayouter(
      nodes,
      edges,
      props.width,
      props.height,
      nodeScale,
      MARGINS,
      this.props.topologyId
    );

    debug('graph layout took ' + timedLayouter.time + 'ms');

    // layout was aborted
    if (!graph) {
      return {maxNodesExceeded: true};
    }

    // save coordinates for restore
    _.each(nodes, function(node) {
      node.px = node.x;
      node.py = node.y;
    });
    _.each(edges, function(edge) {
      edge.ppoints = edge.points;
    });

    // adjust layout based on viewport
    const xFactor = (props.width - MARGINS.left - MARGINS.right) / graph.width;
    const yFactor = props.height / graph.height;
    const zoomFactor = Math.min(xFactor, yFactor);
    let zoomScale = this.state.scale;

    if (this.zoom && !this.state.hasZoomed && zoomFactor > 0 && zoomFactor < 1) {
      zoomScale = zoomFactor;
      // saving in d3's behavior cache
      this.zoom.scale(zoomFactor);
    }

    return {
      nodes: nodes,
      edges: edges,
      nodeScale: nodeScale,
      scale: zoomScale
    };
  },

  zoomed: function() {
    // debug('zoomed', d3.event.scale, d3.event.translate);
    this.setState({
      hasZoomed: true,
      panTranslate: d3.event.translate.slice(),
      translate: d3.event.translate.slice(),
      scale: d3.event.scale
    });
  }

});

module.exports = NodesChart;
<|endoftext|>


<commit_before>/* global QUnit */
/* global extractHeadersFromXml */

QUnit.test("Received Tests", function (assert) {
    var xml1 =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?><s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Header><h:ServerVersionInfo MajorVersion=\"15\" MinorVersion=\"20\" MajorBuildNumber=\"797\" MinorBuildNumber=\"11\" Version=\"V2018_01_08\" xmlns:h=\"http://schemas.microsoft.com/exchange/services/2006/types\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"/></s:Header><s:Body><m:GetItemResponse xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\"><m:ResponseMessages><m:GetItemResponseMessage ResponseClass=\"Success\"><m:ResponseCode>NoError</m:ResponseCode><m:Items><t:Message><t:ItemId Id=\"AQMkADAwATM0MDAAMS1hYzNiLWIzYjEtMDACLTAwCgBGAAADwxobsfxV00aTGGYxwhj+tgcAzcLQNcgKeEiqUypBuKrhfwAAAgEMAAAAtxZjPqJy40C2DTBmR6PgWgABJcrCkgAAAA==\" ChangeKey=\"CQAAABYAAAC3FmM+onLjQLYNMGZHo+BaAAEl980z\"/><t:ExtendedProperty><t:ExtendedFieldURI PropertyTag=\"0x7d\" PropertyType=\"String\"/><t:Value>Received: from BY2NAM01HT049.eop-nam01.prod.protection.outlook.com
" +
        " (2a01:111:e400:5a6b::50) by BY2PR16MB0487.namprd16.prod.outlook.com with
" +
        " HTTPS via BY2PR1001CA0082.NAMPRD10.PROD.OUTLOOK.COM; Mon, 21 May 2018
" +
        " 16:08:55 +0000
" +
        "Received: from BY2NAM01FT028.eop-nam01.prod.protection.outlook.com
" +
        " (10.152.68.57) by BY2NAM01HT049.eop-nam01.prod.protection.outlook.com
" +
        " (10.152.69.85) with Microsoft SMTP Server (version=TLS1_2,
" +
        " cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P384) id 15.20.776.18; Mon, 21
" +
        " May 2018 16:08:54 +0000
" +
        "Received: from mail-ua0-f171.google.com (209.85.217.171) by
" +
        " BY2NAM01FT028.mail.protection.outlook.com (10.152.69.201) with Microsoft SMTP
" +
        " Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA_P384) id
" +
        " 15.20.776.10 via Frontend Transport; Mon, 21 May 2018 16:08:54 +0000
" +
        "Received: by mail-ua0-f171.google.com with SMTP id g9-v6so10266038uak.7;
" +
        "        Mon, 21 May 2018 09:08:54 -0700 (PDT)
" +
        "Received: from [10.233.168.165] ([186.2.138.141])
" +
        "        by smtp.gmail.com with ESMTPSA id 72-v6sm5966316vko.20.2018.05.21.09.08.52
" +
        "        (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
" +
        "        Mon, 21 May 2018 09:08:53 -0700 (PDT)
" +
        "Subject: Re: Lake House
" +
        "From: Test User <testuser@gmail.com>
" +
        "Date: Mon, 21 May 2018 10:08:51 -0600
" +
        "Cc: Test User1 <testuser1@gmail.com>,
" +
        " Test User2 <testuser2@outlook.com>,
" +
        " Test User3 <testuser3@microsoft.com>
" +
        "To: Test User4 <testuser4@gmail.com>
" +
        "</t:Value></t:ExtendedProperty></t:Message></m:Items></m:GetItemResponseMessage></m:ResponseMessages></m:GetItemResponse></s:Body></s:Envelope>";
    var prop1 =
        "Received: from BY2NAM01HT049.eop-nam01.prod.protection.outlook.com\r" +
        " (2a01:111:e400:5a6b::50) by BY2PR16MB0487.namprd16.prod.outlook.com with\r" +
        " HTTPS via BY2PR1001CA0082.NAMPRD10.PROD.OUTLOOK.COM; Mon, 21 May 2018\r" +
        " 16:08:55 +0000\r" +
        "Received: from BY2NAM01FT028.eop-nam01.prod.protection.outlook.com\r" +
        " (10.152.68.57) by BY2NAM01HT049.eop-nam01.prod.protection.outlook.com\r" +
        " (10.152.69.85) with Microsoft SMTP Server (version=TLS1_2,\r" +
        " cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P384) id 15.20.776.18; Mon, 21\r" +
        " May 2018 16:08:54 +0000\r" +
        "Received: from mail-ua0-f171.google.com (209.85.217.171) by\r" +
        " BY2NAM01FT028.mail.protection.outlook.com (10.152.69.201) with Microsoft SMTP\r" +
        " Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA_P384) id\r" +
        " 15.20.776.10 via Frontend Transport; Mon, 21 May 2018 16:08:54 +0000\r" +
        "Received: by mail-ua0-f171.google.com with SMTP id g9-v6so10266038uak.7;\r" +
        "        Mon, 21 May 2018 09:08:54 -0700 (PDT)\r" +
        "Received: from [10.233.168.165] ([186.2.138.141])\r" +
        "        by smtp.gmail.com with ESMTPSA id 72-v6sm5966316vko.20.2018.05.21.09.08.52\r" +
        "        (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\r" +
        "        Mon, 21 May 2018 09:08:53 -0700 (PDT)\r" +
        "Subject: Re: Lake House\r" +
        "From: Test User <testuser@gmail.com>\r" +
        "Date: Mon, 21 May 2018 10:08:51 -0600\r" +
        "Cc: Test User1 <testuser1@gmail.com>,\r" +
        " Test User2 <testuser2@outlook.com>,\r" +
        " Test User3 <testuser3@microsoft.com>\r" +
        "To: Test User4 <testuser4@gmail.com>\r";

    assert.propEqual(extractHeadersFromXml(xml1),
        {
            "prop": prop1,
        });

    var xml2 =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?><s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Header><h:ServerVersionInfo MajorVersion=\"15\" MinorVersion=\"20\" MajorBuildNumber=\"797\" MinorBuildNumber=\"11\" Version=\"V2018_01_08\" xmlns:h=\"http://schemas.microsoft.com/exchange/services/2006/types\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"/></s:Header><s:Body><m:GetItemResponse xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\"><m:ResponseMessages><m:GetItemResponseMessage ResponseClass=\"Success\"><m:ResponseCode>NoError</m:ResponseCode><m:Items><t:Message><t:ItemId Id=\"AQMkADAwATM0MDAAMS1hYzNiLWIzYjEtMDACLTAwCgBGAAADwxobsfxV00aTGGYxwhj+tgcAzcLQNcgKeEiqUypBuKrhfwAAAgEMAAAAtxZjPqJy40C2DTBmR6PgWgABJcrCkgAAAA==\" ChangeKey=\"CQAAABYAAAC3FmM+onLjQLYNMGZHo+BaAAEl980z\"/><t:ExtendedProperty><t:ExtendedFieldURI PropertyTag=\"0x7d\" PropertyType=\"String\"/><t:Value>" +
        "Subject: Test message
" +
        "From: Test User <testuser@gmail.com>
" +
        "�</t:Value></t:ExtendedProperty></t:Message></m:Items></m:GetItemResponseMessage></m:ResponseMessages></m:GetItemResponse></s:Body></s:Envelope>";
    var prop2 =
        "Subject: Test message\r" +
        "From: Test User <testuser@gmail.com>\r";
    assert.propEqual(extractHeadersFromXml(xml2),
        {
            "prop": prop2,
        });

});<commit_msg>remove extra commas<commit_after>/* global QUnit */
/* global extractHeadersFromXml */

QUnit.test("Received Tests", function (assert) {
    var xml1 =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?><s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Header><h:ServerVersionInfo MajorVersion=\"15\" MinorVersion=\"20\" MajorBuildNumber=\"797\" MinorBuildNumber=\"11\" Version=\"V2018_01_08\" xmlns:h=\"http://schemas.microsoft.com/exchange/services/2006/types\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"/></s:Header><s:Body><m:GetItemResponse xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\"><m:ResponseMessages><m:GetItemResponseMessage ResponseClass=\"Success\"><m:ResponseCode>NoError</m:ResponseCode><m:Items><t:Message><t:ItemId Id=\"AQMkADAwATM0MDAAMS1hYzNiLWIzYjEtMDACLTAwCgBGAAADwxobsfxV00aTGGYxwhj+tgcAzcLQNcgKeEiqUypBuKrhfwAAAgEMAAAAtxZjPqJy40C2DTBmR6PgWgABJcrCkgAAAA==\" ChangeKey=\"CQAAABYAAAC3FmM+onLjQLYNMGZHo+BaAAEl980z\"/><t:ExtendedProperty><t:ExtendedFieldURI PropertyTag=\"0x7d\" PropertyType=\"String\"/><t:Value>Received: from BY2NAM01HT049.eop-nam01.prod.protection.outlook.com
" +
        " (2a01:111:e400:5a6b::50) by BY2PR16MB0487.namprd16.prod.outlook.com with
" +
        " HTTPS via BY2PR1001CA0082.NAMPRD10.PROD.OUTLOOK.COM; Mon, 21 May 2018
" +
        " 16:08:55 +0000
" +
        "Received: from BY2NAM01FT028.eop-nam01.prod.protection.outlook.com
" +
        " (10.152.68.57) by BY2NAM01HT049.eop-nam01.prod.protection.outlook.com
" +
        " (10.152.69.85) with Microsoft SMTP Server (version=TLS1_2,
" +
        " cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P384) id 15.20.776.18; Mon, 21
" +
        " May 2018 16:08:54 +0000
" +
        "Received: from mail-ua0-f171.google.com (209.85.217.171) by
" +
        " BY2NAM01FT028.mail.protection.outlook.com (10.152.69.201) with Microsoft SMTP
" +
        " Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA_P384) id
" +
        " 15.20.776.10 via Frontend Transport; Mon, 21 May 2018 16:08:54 +0000
" +
        "Received: by mail-ua0-f171.google.com with SMTP id g9-v6so10266038uak.7;
" +
        "        Mon, 21 May 2018 09:08:54 -0700 (PDT)
" +
        "Received: from [10.233.168.165] ([186.2.138.141])
" +
        "        by smtp.gmail.com with ESMTPSA id 72-v6sm5966316vko.20.2018.05.21.09.08.52
" +
        "        (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
" +
        "        Mon, 21 May 2018 09:08:53 -0700 (PDT)
" +
        "Subject: Re: Lake House
" +
        "From: Test User <testuser@gmail.com>
" +
        "Date: Mon, 21 May 2018 10:08:51 -0600
" +
        "Cc: Test User1 <testuser1@gmail.com>,
" +
        " Test User2 <testuser2@outlook.com>,
" +
        " Test User3 <testuser3@microsoft.com>
" +
        "To: Test User4 <testuser4@gmail.com>
" +
        "</t:Value></t:ExtendedProperty></t:Message></m:Items></m:GetItemResponseMessage></m:ResponseMessages></m:GetItemResponse></s:Body></s:Envelope>";
    var prop1 =
        "Received: from BY2NAM01HT049.eop-nam01.prod.protection.outlook.com\r" +
        " (2a01:111:e400:5a6b::50) by BY2PR16MB0487.namprd16.prod.outlook.com with\r" +
        " HTTPS via BY2PR1001CA0082.NAMPRD10.PROD.OUTLOOK.COM; Mon, 21 May 2018\r" +
        " 16:08:55 +0000\r" +
        "Received: from BY2NAM01FT028.eop-nam01.prod.protection.outlook.com\r" +
        " (10.152.68.57) by BY2NAM01HT049.eop-nam01.prod.protection.outlook.com\r" +
        " (10.152.69.85) with Microsoft SMTP Server (version=TLS1_2,\r" +
        " cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P384) id 15.20.776.18; Mon, 21\r" +
        " May 2018 16:08:54 +0000\r" +
        "Received: from mail-ua0-f171.google.com (209.85.217.171) by\r" +
        " BY2NAM01FT028.mail.protection.outlook.com (10.152.69.201) with Microsoft SMTP\r" +
        " Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA_P384) id\r" +
        " 15.20.776.10 via Frontend Transport; Mon, 21 May 2018 16:08:54 +0000\r" +
        "Received: by mail-ua0-f171.google.com with SMTP id g9-v6so10266038uak.7;\r" +
        "        Mon, 21 May 2018 09:08:54 -0700 (PDT)\r" +
        "Received: from [10.233.168.165] ([186.2.138.141])\r" +
        "        by smtp.gmail.com with ESMTPSA id 72-v6sm5966316vko.20.2018.05.21.09.08.52\r" +
        "        (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);\r" +
        "        Mon, 21 May 2018 09:08:53 -0700 (PDT)\r" +
        "Subject: Re: Lake House\r" +
        "From: Test User <testuser@gmail.com>\r" +
        "Date: Mon, 21 May 2018 10:08:51 -0600\r" +
        "Cc: Test User1 <testuser1@gmail.com>,\r" +
        " Test User2 <testuser2@outlook.com>,\r" +
        " Test User3 <testuser3@microsoft.com>\r" +
        "To: Test User4 <testuser4@gmail.com>\r";

    assert.propEqual(extractHeadersFromXml(xml1),
        {
            "prop": prop1
        });

    var xml2 =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?><s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Header><h:ServerVersionInfo MajorVersion=\"15\" MinorVersion=\"20\" MajorBuildNumber=\"797\" MinorBuildNumber=\"11\" Version=\"V2018_01_08\" xmlns:h=\"http://schemas.microsoft.com/exchange/services/2006/types\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"/></s:Header><s:Body><m:GetItemResponse xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\"><m:ResponseMessages><m:GetItemResponseMessage ResponseClass=\"Success\"><m:ResponseCode>NoError</m:ResponseCode><m:Items><t:Message><t:ItemId Id=\"AQMkADAwATM0MDAAMS1hYzNiLWIzYjEtMDACLTAwCgBGAAADwxobsfxV00aTGGYxwhj+tgcAzcLQNcgKeEiqUypBuKrhfwAAAgEMAAAAtxZjPqJy40C2DTBmR6PgWgABJcrCkgAAAA==\" ChangeKey=\"CQAAABYAAAC3FmM+onLjQLYNMGZHo+BaAAEl980z\"/><t:ExtendedProperty><t:ExtendedFieldURI PropertyTag=\"0x7d\" PropertyType=\"String\"/><t:Value>" +
        "Subject: Test message
" +
        "From: Test User <testuser@gmail.com>
" +
        "�</t:Value></t:ExtendedProperty></t:Message></m:Items></m:GetItemResponseMessage></m:ResponseMessages></m:GetItemResponse></s:Body></s:Envelope>";
    var prop2 =
        "Subject: Test message\r" +
        "From: Test User <testuser@gmail.com>\r";
    assert.propEqual(extractHeadersFromXml(xml2),
        {
            "prop": prop2
        });

});<|endoftext|>


<commit_before>"use strict";

(function($, TOKEN) {

  // do not change below this line
  var SERVICE_URL = "https://suggestions.dadata.ru/suggestions/api/4_1/rs";
  var PARTNER = "STORELAND";
  var AJAX_DELAY_MS = 2000;
  var QUICK_ORDER_BTN_ID = "#startOrder";

  // Storeland function, exposed as global
  var storelandQuickOrderFn = window.startOrder;

  var fields = {
    name: ["reg_name", "feedback_name", "callback_person"],
    email: ["contactEmail", "sites_client_mail", "feedback_email", "email"],
    address: ["deliveryAddress", "quickDeliveryAddress", "sites_client_addr"]
  };
  var deliveryAddressSelectors = "#quickDeliveryAddress, #quickDeliveryAddressStreet, #deliveryAddress";
  var deliveryCitySelectors = "#quickDeliveryCity, #deliveryCity";
  var deliveryPostalSelectors = "#quickDeliveryZipCode, #deliveryZipCode";

  // initialize suggestions on all known fields
  function init() {
    initSuggestions();
    initDelivery();
  }

  function initSuggestions() {
    for (var type in fields) {
      fields[type].forEach(function (id) {
        $("#"+id).suggestions({
          serviceUrl: SERVICE_URL,
          token: TOKEN,
          partner: PARTNER,
          type: type.toUpperCase()
        });
      });
    }
  }

  function initDelivery() {
    var $city = $(deliveryCitySelectors);
    var $address = $(deliveryAddressSelectors);
    var $postalCode = $(deliveryPostalSelectors);
    suggestCity($city, $address, $postalCode);
    suggestPostalCode($address, $postalCode);
  }

  // constrain address by kladr id
  function setAddressConstraint(kladr_id, $address) {
    $address.suggestions().setOptions({
      constraints: {
        locations: { kladr_id: kladr_id }
      },
      restrict_value: true
    });
  }

  // clear address constraints
  function clearAddressConstraint($address) {
    $address.suggestions().setOptions({
      constraints: {},
      restrict_value: false
    });
  }

  // suggest city and restrict address accordingly
  // also fill postal code
  function suggestCity($city, $address, $postalCode) {
    if (!$city.length) {
      return;
    }
    $city.suggestions({
      serviceUrl: SERVICE_URL,
      token: TOKEN,
      partner: PARTNER,
      type: "ADDRESS",
      hint: false,
      bounds: "city-settlement",
      onSelect: function(suggestion) {
        setAddressConstraint(suggestion.data.kladr_id, $address);
        $address.suggestions().clear();
        $postalCode.val("" || suggestion.data.postal_code);
      },
      onSelectNothing: function() {
        clearAddressConstraint($address);
        $address.suggestions().clear();
        $postalCode.val("");
      }
    });
  }

  // fill postal code when address is selected
  function suggestPostalCode($address, $postalCode) {
    if (!$postalCode.length || !$address.length) {
      return;
    }
    $address.suggestions().setOptions({
      onSelect: function(suggestion) {
        $postalCode.val("" || suggestion.data.postal_code);
      },
      onSelectNothing: function() {
        $postalCode.val("");
      }
    });
  }

  $(function () {
    init();
  });

  // quick order form is loaded via AJAX after button click
  $(QUICK_ORDER_BTN_ID)
    .off()
    .on("click", function(e) {
      storelandQuickOrderFn();
      window.setTimeout(init, AJAX_DELAY_MS);
      return false;
    });

}(window.jQuery, window.DADATA_TOKEN))
<commit_msg>Support for quickDeliveryAddressStreet<commit_after>"use strict";

(function($, TOKEN) {

  // do not change below this line
  var SERVICE_URL = "https://suggestions.dadata.ru/suggestions/api/4_1/rs";
  var PARTNER = "STORELAND";
  var AJAX_DELAY_MS = 2000;
  var QUICK_ORDER_BTN_ID = "#startOrder";

  // Storeland function, exposed as global
  var storelandQuickOrderFn = window.startOrder;

  var fields = {
    name: ["reg_name", "feedback_name", "callback_person"],
    email: ["contactEmail", "sites_client_mail", "feedback_email", "email"],
    address: ["deliveryAddress", "quickDeliveryAddress", "quickDeliveryAddressStreet", "sites_client_addr"]
  };
  var deliveryAddressSelectors = "#quickDeliveryAddress, #quickDeliveryAddressStreet, #deliveryAddress";
  var deliveryCitySelectors = "#quickDeliveryCity, #deliveryCity";
  var deliveryPostalSelectors = "#quickDeliveryZipCode, #deliveryZipCode";

  // initialize suggestions on all known fields
  function init() {
    initSuggestions();
    initDelivery();
  }

  function initSuggestions() {
    for (var type in fields) {
      fields[type].forEach(function (id) {
        $("#"+id).suggestions({
          serviceUrl: SERVICE_URL,
          token: TOKEN,
          partner: PARTNER,
          type: type.toUpperCase()
        });
      });
    }
  }

  function initDelivery() {
    var $city = $(deliveryCitySelectors);
    var $address = $(deliveryAddressSelectors);
    var $postalCode = $(deliveryPostalSelectors);
    suggestCity($city, $address, $postalCode);
    suggestPostalCode($address, $postalCode);
  }

  // constrain address by kladr id
  function setAddressConstraint(kladr_id, $address) {
    $address.suggestions().setOptions({
      constraints: {
        locations: { kladr_id: kladr_id }
      },
      restrict_value: true
    });
  }

  // clear address constraints
  function clearAddressConstraint($address) {
    $address.suggestions().setOptions({
      constraints: {},
      restrict_value: false
    });
  }

  // suggest city and restrict address accordingly
  // also fill postal code
  function suggestCity($city, $address, $postalCode) {
    if (!$city.length) {
      return;
    }
    $city.suggestions({
      serviceUrl: SERVICE_URL,
      token: TOKEN,
      partner: PARTNER,
      type: "ADDRESS",
      hint: false,
      bounds: "city-settlement",
      onSelect: function(suggestion) {
        setAddressConstraint(suggestion.data.kladr_id, $address);
        $address.suggestions().clear();
        $postalCode.val("" || suggestion.data.postal_code);
      },
      onSelectNothing: function() {
        clearAddressConstraint($address);
        $address.suggestions().clear();
        $postalCode.val("");
      }
    });
  }

  // fill postal code when address is selected
  function suggestPostalCode($address, $postalCode) {
    if (!$postalCode.length || !$address.length) {
      return;
    }
    $address.suggestions().setOptions({
      onSelect: function(suggestion) {
        $postalCode.val("" || suggestion.data.postal_code);
      },
      onSelectNothing: function() {
        $postalCode.val("");
      }
    });
  }

  $(function () {
    init();
  });

  // quick order form is loaded via AJAX after button click
  $(QUICK_ORDER_BTN_ID)
    .off()
    .on("click", function(e) {
      storelandQuickOrderFn();
      window.setTimeout(init, AJAX_DELAY_MS);
      return false;
    });

}(window.jQuery, window.DADATA_TOKEN))
<|endoftext|>


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

/**
 * Control for switching between 2 choices (A & B).
 * Choice 'A' is to the left of the switch, choice 'B' is to the right.
 * This decorates OnOffProperty, the iOS-like on/off switch.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ButtonListener = require( 'SCENERY/input/ButtonListener' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Line = require( 'SCENERY/nodes/Line' );
  var LinearGradient = require( 'SCENERY/util/LinearGradient' );
  var Node = require( 'SCENERY/nodes/Node' );
  var OnOffSwitch = require( 'SUN/OnOffSwitch' );
  var Property = require( 'AXON/Property' );
  var sun = require( 'SUN/sun' );
  var Tandem = require( 'TANDEM/Tandem' );

  /**
   * @param {Property.<*>} property stores the value of the current choice
   * @param {*} valueA value for choice 'A'
   * @param {Node} labelA label for choice 'A'
   * @param {*} valueB value for choice 'B'
   * @param {Node} labelB label for choice 'B'
   * @param {Object} [options]
   * @constructor
   */
  function ABSwitch( property, valueA, labelA, valueB, labelB, options ) {

    // default option values
    options = _.extend( {
      switchSize: new Dimension2( 60, 30 ),
      xSpacing: 8,
      cursor: 'pointer',
      centerOnButton: false,

      // uses opacity as the default method of indicating whether a {Node} label is {boolean} enabled
      setEnabled: function( label, enabled ) { label.opacity = enabled ? 1.0 : 0.5; },

      // pointer areas for thumb
      thumbTouchAreaXDilation: 8,
      thumbTouchAreaYDilation: 8,
      thumbMouseAreaXDilation: 0,
      thumbMouseAreaYDilation: 0,
      tandem: Tandem.required
    }, options );

    var defaultTrackFill = new LinearGradient( 0, 0, 0, options.switchSize.height ).addColorStop( 0, 'rgb(40,40,40)' ).addColorStop( 1, 'rgb(200,200,200)' );
    options.trackFillA = options.trackFillA || defaultTrackFill;
    options.trackFillB = options.trackFillB || defaultTrackFill;
    options.thumbFill = options.thumbFill ||
                        new LinearGradient( 0, 0, 0, options.switchSize.height ).addColorStop( 0, 'white' ).addColorStop( 1, 'rgb(200,200,200)' );

    Node.call( this );

    // property for adapting to OnOffSwitch. 'true' is 'B', the object on the 'on' end of the OnOffSwitch.
    var onProperty = new Property( valueB === property.get() );

    var onOffSwitch = new OnOffSwitch( onProperty, {
      size: options.switchSize,
      cursor: options.cursor,
      thumbFill: options.thumbFill,
      trackOnFill: options.trackFillB,
      trackOffFill: options.trackFillA,
      thumbTouchAreaXDilation: options.thumbTouchAreaXDilation,
      thumbTouchAreaYDilation: options.thumbTouchAreaYDilation,
      thumbMouseAreaXDilation: options.thumbMouseAreaXDilation,
      thumbMouseAreaYDilation: options.thumbMouseAreaYDilation,
      tandem: options.tandem.createTandem( 'onOffSwitch' )
    } );

    // rendering order
    this.addChild( onOffSwitch );
    this.addChild( labelA );
    this.addChild( labelB );

    // layout: 'A' on the left, 'B' on the right
    labelA.right = onOffSwitch.left - options.xSpacing;
    labelA.centerY = onOffSwitch.centerY;
    labelB.left = onOffSwitch.right + options.xSpacing;
    labelB.centerY = onOffSwitch.centerY;

    // add a horizontal strut that will cause the 'centerX' of this node to be at the center of the button
    if ( options.centerOnButton ) {
      var additionalWidth = Math.abs( labelA.width - labelB.width );
      var strut = new Line( 0, 0, this.width + additionalWidth, 0 );
      this.addChild( strut );
      strut.moveToBack();
      if ( labelA.width < labelB.width ) {
        strut.left = labelA.left - ( additionalWidth / 2 );
      }
      else {
        strut.left = labelA.left;
      }
    }

    // sync properties, listeners must be disposed
    var propertyListener = function( object ) {
      onProperty.set( valueB === object );
    };
    property.link( propertyListener );

    var onPropertyListener = function( on ) {
      property.set( on ? valueB : valueA );
      if ( options.setEnabled ) {
        options.setEnabled( labelA, !on );
        options.setEnabled( labelB, on );
      }
    };
    onProperty.link( onPropertyListener );

    // click on labels to select, must be disposed
    var aInputListener = new ButtonListener( {
      fire: function() { onProperty.set( false ); }
    } );
    var bInputListener = new ButtonListener( {
      fire: function() { onProperty.set( true ); }
    } );
    labelA.addInputListener( aInputListener );
    labelB.addInputListener( bInputListener );

    // @private - for dispose
    this.disposeABSwitch = function() {
      property.unlink( propertyListener );
      onProperty.unlink( onPropertyListener );
      labelA.removeInputListener( aInputListener );
      labelB.removeInputListener( bInputListener );
    };

    this.mutate( options );
  }

  sun.register( 'ABSwitch', ABSwitch );

  return inherit( Node, ABSwitch, {

    /**
     * Make eligible for garbage collection.
     * @public
     */
    dispose: function() {
      this.disposeABSwitch();
      Node.prototype.dispose.call( this );
    }
  } );
} );
<commit_msg>Pass options to Node constructor, see https://github.com/phetsims/tandem/issues/44<commit_after>// Copyright 2014-2017, University of Colorado Boulder

/**
 * Control for switching between 2 choices (A & B).
 * Choice 'A' is to the left of the switch, choice 'B' is to the right.
 * This decorates OnOffProperty, the iOS-like on/off switch.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ButtonListener = require( 'SCENERY/input/ButtonListener' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var inherit = require( 'PHET_CORE/inherit' );
  var IOObject = require( 'TANDEM/IOObject' );
  var Line = require( 'SCENERY/nodes/Line' );
  var LinearGradient = require( 'SCENERY/util/LinearGradient' );
  var Node = require( 'SCENERY/nodes/Node' );
  var OnOffSwitch = require( 'SUN/OnOffSwitch' );
  var Property = require( 'AXON/Property' );
  var sun = require( 'SUN/sun' );
  var Tandem = require( 'TANDEM/Tandem' );

  /**
   * @param {Property.<*>} property stores the value of the current choice
   * @param {*} valueA value for choice 'A'
   * @param {Node} labelA label for choice 'A'
   * @param {*} valueB value for choice 'B'
   * @param {Node} labelB label for choice 'B'
   * @param {Object} [options]
   * @constructor
   */
  function ABSwitch( property, valueA, labelA, valueB, labelB, options ) {

    // default option values
    options = _.extend( {
      switchSize: new Dimension2( 60, 30 ),
      xSpacing: 8,
      cursor: 'pointer',
      centerOnButton: false,

      // uses opacity as the default method of indicating whether a {Node} label is {boolean} enabled
      setEnabled: function( label, enabled ) { label.opacity = enabled ? 1.0 : 0.5; },

      // pointer areas for thumb
      thumbTouchAreaXDilation: 8,
      thumbTouchAreaYDilation: 8,
      thumbMouseAreaXDilation: 0,
      thumbMouseAreaYDilation: 0,
      tandem: Tandem.required
    }, options );

    var defaultTrackFill = new LinearGradient( 0, 0, 0, options.switchSize.height ).addColorStop( 0, 'rgb(40,40,40)' ).addColorStop( 1, 'rgb(200,200,200)' );
    options.trackFillA = options.trackFillA || defaultTrackFill;
    options.trackFillB = options.trackFillB || defaultTrackFill;
    options.thumbFill = options.thumbFill ||
                        new LinearGradient( 0, 0, 0, options.switchSize.height ).addColorStop( 0, 'white' ).addColorStop( 1, 'rgb(200,200,200)' );

    Node.call( this, IOObject.getOptions( options ) );

    // property for adapting to OnOffSwitch. 'true' is 'B', the object on the 'on' end of the OnOffSwitch.
    var onProperty = new Property( valueB === property.get() );

    var onOffSwitch = new OnOffSwitch( onProperty, {
      size: options.switchSize,
      cursor: options.cursor,
      thumbFill: options.thumbFill,
      trackOnFill: options.trackFillB,
      trackOffFill: options.trackFillA,
      thumbTouchAreaXDilation: options.thumbTouchAreaXDilation,
      thumbTouchAreaYDilation: options.thumbTouchAreaYDilation,
      thumbMouseAreaXDilation: options.thumbMouseAreaXDilation,
      thumbMouseAreaYDilation: options.thumbMouseAreaYDilation,
      tandem: options.tandem.createTandem( 'onOffSwitch' )
    } );

    // rendering order
    this.addChild( onOffSwitch );
    this.addChild( labelA );
    this.addChild( labelB );

    // layout: 'A' on the left, 'B' on the right
    labelA.right = onOffSwitch.left - options.xSpacing;
    labelA.centerY = onOffSwitch.centerY;
    labelB.left = onOffSwitch.right + options.xSpacing;
    labelB.centerY = onOffSwitch.centerY;

    // add a horizontal strut that will cause the 'centerX' of this node to be at the center of the button
    if ( options.centerOnButton ) {
      var additionalWidth = Math.abs( labelA.width - labelB.width );
      var strut = new Line( 0, 0, this.width + additionalWidth, 0 );
      this.addChild( strut );
      strut.moveToBack();
      if ( labelA.width < labelB.width ) {
        strut.left = labelA.left - ( additionalWidth / 2 );
      }
      else {
        strut.left = labelA.left;
      }
    }

    // sync properties, listeners must be disposed
    var propertyListener = function( object ) {
      onProperty.set( valueB === object );
    };
    property.link( propertyListener );

    var onPropertyListener = function( on ) {
      property.set( on ? valueB : valueA );
      if ( options.setEnabled ) {
        options.setEnabled( labelA, !on );
        options.setEnabled( labelB, on );
      }
    };
    onProperty.link( onPropertyListener );

    // click on labels to select, must be disposed
    var aInputListener = new ButtonListener( {
      fire: function() { onProperty.set( false ); }
    } );
    var bInputListener = new ButtonListener( {
      fire: function() { onProperty.set( true ); }
    } );
    labelA.addInputListener( aInputListener );
    labelB.addInputListener( bInputListener );

    // @private - for dispose
    this.disposeABSwitch = function() {
      property.unlink( propertyListener );
      onProperty.unlink( onPropertyListener );
      labelA.removeInputListener( aInputListener );
      labelB.removeInputListener( bInputListener );
    };

    this.mutate( options );
  }

  sun.register( 'ABSwitch', ABSwitch );

  return inherit( Node, ABSwitch, {

    /**
     * Make eligible for garbage collection.
     * @public
     */
    dispose: function() {
      this.disposeABSwitch();
      Node.prototype.dispose.call( this );
    }
  } );
} );
<|endoftext|>