<commit_before>import React, { Component } from 'react';
import _ from 'lodash';

import Title from '../Title';
import { Button } from '../Form';

import api from '../../utils/api';

import polaroidUp from '../../theme/icons/polaroid.svg';
import polaroidDown from '../../theme/icons/polaroid_down.svg';
import trash from '../../theme/icons/trash.svg';
import book from '../../theme/icons/book.svg';

import './Observations.css';
import classNames from '../../utils/classNames';

class Observations extends Component {

  constructor(...props) {
    super(...props);

    this.state = {
      toggle: true,
    }
  }

  vote(value) {
    this.togglePolaroidAnimation();
    const observation = _.head(this.props.observations);
    const newObservations = [..._.drop(this.props.observations)];
    api.post('/votes', {
      body: {
        observation_id: observation.id,
        value,
      },
    }).then(() => {
      this.props.loadObservations(newObservations);
      this.togglePolaroidAnimation();
      if (newObservations.length < 6) {
        this.fetch();
      }
    });
  }

  togglePolaroidAnimation(){
    this.setState(({ toggle }) => ({ toggle: !toggle }))
  }

  fetch() {
    api.get('/auth/observations').then(({ data: paginationModel }) => {
      this.props.loadObservations(paginationModel.data);
    });
  }

  render() {
    const observation = _.head(this.props.observations);

    return (

        <Title name="Observations" />
        {observation && (

              <img
                className="Polaroid__Inner"
                src={`${process.env.REACT_APP_API_URL}/observations/${observation.id}/picture`}
                alt="Observation"
              />

              <Button onClick={() => this.vote(1)} className="Form__Button--clean">

              </Button>
              <Button onClick={() => this.vote(-1)} className="Form__Button--clean">

              </Button>

        )}

        {observation === undefined && (

No observations left

        )}

    );
  }
}
export default Observations;
<commit_msg>eslint<commit_after>import React, { Component } from 'react';
import _ from 'lodash';

import Title from '../Title';
import { Button } from '../Form';

import api from '../../utils/api';

import polaroidUp from '../../theme/icons/polaroid.svg';
import polaroidDown from '../../theme/icons/polaroid_down.svg';
import trash from '../../theme/icons/trash.svg';
import book from '../../theme/icons/book.svg';

import './Observations.css';
import classNames from '../../utils/classNames';

class Observations extends Component {

  constructor(...props) {
    super(...props);

    this.state = {
      toggle: true,
    };
  }

  vote(value) {
    this.togglePolaroidAnimation();

    const observation = _.head(this.props.observations);
    const newObservations = [..._.drop(this.props.observations)];

    api.post('/votes', {
      body: {
        observation_id: observation.id,
        value,
      },
    }).then(() => {
      this.props.loadObservations(newObservations);
      this.togglePolaroidAnimation();

      if (newObservations.length < 6) {
        this.fetch();
      }
    });
  }

  togglePolaroidAnimation() {
    this.setState(({ toggle }) => ({ toggle: !toggle }));
  }

  fetch() {
    api.get('/auth/observations').then(({ data: paginationModel }) => {
      this.props.loadObservations(paginationModel.data);
    });
  }

  render() {
    const observation = _.head(this.props.observations);

    return (

        <Title name="Observations" />
        {observation && (

              <img
                className="Polaroid__Inner"
                src={`${process.env.REACT_APP_API_URL}/observations/${observation.id}/picture`}
                alt="Observation"
              />

              <Button onClick={() => this.vote(1)} className="Form__Button--clean">

              </Button>
              <Button onClick={() => this.vote(-1)} className="Form__Button--clean">

              </Button>

        )}

        {observation === undefined && (

No observations left

        )}

    );
  }
}
export default Observations;
<|endoftext|>


<commit_before>
$(window).ready(function(){
    $('#slider').nivoSlider(
    {
        effect: 'sliceUp',
        animSpeed:650,
        pauseTime:6000,
        directionNavHide: false,
        captionOpacity: 1,
        prevText:'<',
        nextText:'>'
    });
    $('.sectionAdmin').mouseover(function(){
        $(this).css(
            {'-webkit-border-radius': '3px 3px 3px 3px',
            '-moz-border-radius': '3px 3px 3px 3px',
            'border-radius':'3px 3px 3px 3px',
            'background-color': '#1fa141',
            'background-color': '-webkit-gradient(linear, left top, left bottom, color-stop(0%, #1fa141), color-stop(93%, #47dc89))',
            'background-color': '-moz-linear-gradient(top, #1fa141 0%, #47dc89 93%)',
            'background-color': '-ms-linear-gradient(top, #1fa141 0%, #47dc89 93%)',
            'background-color': 'linear-gradient(to bottom, #1fa141 0%, #47dc89 93%)',
            'cursor':'pointer'
            });
        $(this).children('ul,li').css({'color' : '#e4f30c'});
    });
    $('.sectionAdmin').mouseleave(function(){
        $(this).css({'background-color' : 'transparent'});
        $(this).children('ul,li').css({'color' : 'black'});
    });
    $('.sectionAdmin').on('click',function(){
        var fn = function(data,textStatus,jqXHR){
            $('.barAdmin').append(data);
            //récupérer l'id de ul pour envoyer ajax
            //dans ajaxcontroller on utilisera pourutiliser le bon repo
            //envoi en json et constition de la page
            //faire fonction qui fait le squelette de la page et on y insère les données dedans
            //faire animation en incluant la page pour faire un genre de déroulement vers le bas
        }
        sendAjax('ajax/barre_admin',)
    })
});
function scroll(id)
{
    $('html, body').animate({ scrollTop : $(id).offset().top}, 600);
    return false;
}

<commit_msg>erreur js<commit_after>
$(window).ready(function(){
    $('#slider').nivoSlider(
    {
        effect: 'sliceUp',
        animSpeed:650,
        pauseTime:6000,
        directionNavHide: false,
        captionOpacity: 1,
        prevText:'<',
        nextText:'>'
    });
    $('.sectionAdmin').mouseover(function(){
        $(this).css(
            {'-webkit-border-radius': '3px 3px 3px 3px',
            '-moz-border-radius': '3px 3px 3px 3px',
            'border-radius':'3px 3px 3px 3px',
            'background-color': '#1fa141',
            'background-color': '-webkit-gradient(linear, left top, left bottom, color-stop(0%, #1fa141), color-stop(93%, #47dc89))',
            'background-color': '-moz-linear-gradient(top, #1fa141 0%, #47dc89 93%)',
            'background-color': '-ms-linear-gradient(top, #1fa141 0%, #47dc89 93%)',
            'background-color': 'linear-gradient(to bottom, #1fa141 0%, #47dc89 93%)',
            'cursor':'pointer'
            });
        $(this).children('ul,li').css({'color' : '#e4f30c'});
    });
    $('.sectionAdmin').mouseleave(function(){
        $(this).css({'background-color' : 'transparent'});
        $(this).children('ul,li').css({'color' : 'black'});
    });
    //$('.sectionAdmin').on('click',function(){
        //var fn = function(data,textStatus,jqXHR){
            //$('.barAdmin').append(data);
            ////récupérer l'id de ul pour envoyer ajax
            ////dans ajaxcontroller on utilisera pourutiliser le bon repo
            ////envoi en json et constition de la page
            ////faire fonction qui fait le squelette de la page et on y insère les données dedans
            ////faire animation en incluant la page pour faire un genre de déroulement vers le bas
        //};
        //sendAjax('ajax/barre_admin',);
    //})
});
function scroll(id)
{
    $('html, body').animate({ scrollTop : $(id).offset().top}, 600);
    return false;
}

<|endoftext|>


<commit_before>/* jshint newcap:false */

YUI.add('rednose-toolbar-base-test', function (Y) {

    var Assert = Y.Assert,

        ToolbarBase = Y.Rednose.Toolbar.Base,

        mainSuite = Y.Rednose.ToolbarBaseTestSuite = new Y.Test.Suite('Toolbar.Base');

// -- Y.Rednose.Toolbar.Base ---------------------------------------------------
var toolbarBaseSuite = new Y.Test.Suite('Toolbar.Base');
mainSuite.add(toolbarBaseSuite);

// -- Lifecycle ----------------------------------------------------------------
toolbarBaseSuite.add(new Y.Test.Case({
    name: 'Lifecycle',

    'constructor should work without a config object': function () {
        var toolbarBase = new ToolbarBase();

        Assert.isInstanceOf(ToolbarBase, toolbarBase, 'toolbarBase should be an instance of ToolbarBase');
        Assert.areSame(0, toolbarBase._buttonGroupMap.length, 'toolbarBase should contain zero button groups');
    },

    'destructor should free references to allow garbage collection': function () {
        var toolbarBase = new ToolbarBase();
        toolbarBase.destroy();

        Assert.isNull(toolbarBase._buttonGroupMap, '_nodeMap should be null');
        Assert.isNull(toolbarBase._published, '_published should be null');
    }
}));

}, '@VERSION@', {
    requires: ['rednose-toolbar-base', 'test']
});
<commit_msg>Cleanup<commit_after>/* jshint newcap:false */

YUI.add('rednose-toolbar-base-test', function (Y) {

    var Assert = Y.Assert,

        ToolbarBase = Y.Rednose.Toolbar.Base,

        mainSuite = Y.Rednose.ToolbarBaseTestSuite = new Y.Test.Suite('Toolbar.Base');

// -- Y.Rednose.Toolbar.Base ---------------------------------------------------
var toolbarBaseSuite = new Y.Test.Suite('Toolbar.Base');
mainSuite.add(toolbarBaseSuite);

// -- Lifecycle ----------------------------------------------------------------
toolbarBaseSuite.add(new Y.Test.Case({
    name: 'Lifecycle',

    'constructor should work without a config object': function () {
        var toolbarBase = new ToolbarBase();

        Assert.isInstanceOf(ToolbarBase, toolbarBase, 'toolbarBase should be an instance of ToolbarBase');
        Assert.areSame(0, toolbarBase._buttonGroupMap.length, 'toolbarBase should contain zero button groups');
    },

    'destructor should free references to allow garbage collection': function () {
        var toolbarBase = new ToolbarBase();
        toolbarBase.destroy();

        Assert.isNull(toolbarBase._buttonGroupMap, '_buttonGroupMap should be null');
        Assert.isNull(toolbarBase._published, '_published should be null');
    }
}));

}, '@VERSION@', {
    requires: ['rednose-toolbar-base', 'test']
});
<|endoftext|>


<commit_before>/* globals CryptoJS, ZiftrApi */
"use strict";

var string_to_base64 = function _ZiftrApiStringToBase64(string){
  // return (new Buffer(string)).toString('base64');
  return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(string));
};

var prase_request_error = function _ZiftrApiParseRequestError(config, error){

  var error_data = {
    configuration: ZiftrApi.mergeObjs(config), // Clone,
    code: error.response.statusCode,
    fields: error.response.data.error.fields,
    // headers: error.response.headers,
    // response: error.response,
  };

  if (error_data.configuration.keys) { delete error_data.configuration.keys; }

  return new ZiftrApi.RequestError(error.response.data.error.message, error_data, null, _ZiftrApiParseRequestError);
};

var requestLog = function _ZiftrApiRequestLog(config /* , message, ... */){
  if (config.debug !== true) { return; }

  var args = Array.prototype.slice.apply(arguments);
  args.shift(); // config
  args.unshift('ZiftrApi');
  console.log.apply(console, args);
};

// ZiftrApi << POST http://sandbox.fpa.bz/orders
requestLog.send = function _ZiftrApiRequestLogSend(config, method, url){
  requestLog(config, '<<', method, url);
};

// ZiftrApi >> POST http://sandbox.fpa.bz/orders >> 301 { <response body> }
requestLog.resp = function _ZiftrApiRequestLogResp(config, method, url, response){
  requestLog(config, '>>', method, url, '>>', response.statusCode, JSON.stringify(response));
};

// ZiftrApi >> POST http://sandbox.fpa.bz/orders >> 301 { <response error> }
requestLog.err = function _ZiftrApiRequestLogErr(config, method, url, err){
  var statusCode = err && (err.statusCode || err.status || err.code);
  requestLog(config, '>>', method, url, '>> Error:', statusCode, err.message, JSON.stringify(err));
};

/**
 * Makes a HTTP request
 *
 * @param {string}  method  The HTTP method for this request, for example
 *                          "GET", "POST", "PATCH", "DELETE"
 * @param {string}  url_path  The path part of the url.
 * @param {object}  options  (optional) A list of options that are merged with
 *                           the config and matches that format.
 * @param {ZiftrApi~requestCallback}  callback  A function that is called when the request completes or errors.
 *
 *
 * @callback ZiftrApi~requestCallback
 * @param {error}  err
 * @param {object}  response
 *
 * @example
 *
 *     ZiftrApi.request('POST', '/orders', function(err, resp){
 *       if (err) {
 *         // there was an error, take some action
 *         return;
 *       }
 *       // No error, get response from `resp`
 *     })
 *
 */
ZiftrApi.request = function ZiftrApiRequest(method, url_path, config, callback){

  check(method, String);
  check(url_path, String);

  if (typeof(options) === "function") {
    callback = config;
    config = {};
  }

  config = ZiftrApi.mergeObjs(ZiftrApi.config(), config);

  // validate that options is present
  if(config === undefined || !config) {
    callback(new ZiftrApi.InvalidConfigError("Missing ZiftrApi configuration"));
    return false;
  }

  // validate that the pub and prv keys are present
  if (config.keys === undefined) {
    callback(new ZiftrApi.InvalidConfigError("Missing ZiftrApi config.keys"));
    return false;
  }

  if (config.keys.publishable_key === undefined) {
    callback(new ZiftrApi.InvalidConfigError("Missing ZiftrApi config.keys.publishable_key"));
    return false;
  }

  if (config.keys.private_key === undefined) {
    callback(new ZiftrApi.InvalidConfigError("Missing ZiftrApi config.keys.private_key"));
    return false;
  }

  // validate that the host version is set, and that we have an api_host
  if(config.api_version === undefined) {
    callback(new ZiftrApi.InvalidConfigError("Missing ZiftrApi config.api_version"));
    return false;
  }

  if(config.api_host === undefined) {
    callback(new ZiftrApi.InvalidConfigError("Missing ZiftrApi config.api_host"));
    return false;
  }

  var accept_version = config.api_version.replace('.','-');
  var url_path_parts = url_path.split('?');
  var qs = url_path_parts.length > 1 ? url_path_parts[1] : '';
  var path = url_path_parts.length > 1 ? url_path_parts[0] + '?' : url_path_parts[0];
  var signature = ZiftrApi.getSignature(path, config.keys.publishable_key, config.keys.private_key, qs);

  var request_headers = {
    'Content-Type': 'application/json',
    'Accept': 'application/vnd.ziftr.fpa-' + accept_version + '+json',
    'Authorization': 'Basic ' + string_to_base64(config.keys.publishable_key + ':' + signature),
    'User-Agent': 'Ziftr%20API%Meteor%20Client%20'+ ZiftrApi.getVersion(),
  };

  var url = config.api_host + url_path;

  var request_options = {
    uri: config.api_host + url_path,
    headers: request_headers,
    timeout: 10000, // milliseconds
    npmRequestOptions: {
      resolveWithFullResponse: true,
    },
  };

  if(config.data !== undefined) {
    request_options.data = config.data;
  }

  requestLog.send(config, method, url);

  if (typeof(callback) === 'function') {
    // Run asynchronously

    HTTP.call(method, url, request_options, function(error, response){
      if (error) {
        var parsedErr = prase_request_error(config, error);
        requestLog.err(config, method, url, parsedErr);
        return callback(parsedErr);
      }

      requestLog.resp(config, method, url, response);

      return callback(null, response);
    });

  }
  else {
    // Run synchronously

    try {
      var response = HTTP.call(method, url, request_options);
      requestLog.resp(config, method, url, response);
      return response;
    }
    catch(err){
      var parsedErr = prase_request_error(config, err);
      requestLog.err(config, method, url, parsedErr);
      throw parsedErr;
    }
  }

};

/**
 * Makes a GET HTTP request
 *
 * @param {string}  url_path  The path part of the url.
 * @param {object}  options  (optional) A list of options that are merged with
 *                           the config and matches that format.
 * @param {ZiftrApi~requestCallback}  callback  A function that is called when the request completes or errors.
 */
ZiftrApi.get = function ZiftrApiGetRequest(url_path, config, callback){
  return ZiftrApi.request('GET', url_path, config, callback);
};

/**
 * Makes a POST HTTP request
 *
 * @param {string}  url_path  The path part of the url.
 * @param {object}  options  (optional) A list of options that are merged with
 *                           the config and matches that format.
 * @param {ZiftrApi~requestCallback}  callback  A function that is called when the request completes or errors.
 */
ZiftrApi.post = function ZiftrApiPostRequest(url_path, config, callback){
  return ZiftrApi.request('POST', url_path, config, callback);
};

/**
 * Makes a PATCH HTTP request
 *
 * @param {string}  url_path  The path part of the url.
 * @param {object}  options  (optional) A list of options that are merged with
 *                           the config and matches that format.
 * @param {ZiftrApi~requestCallback}  callback  A function that is called when the request completes or errors.
 */
ZiftrApi.patch = function ZiftrApiPatchRequest(url_path, config, callback){
  return ZiftrApi.request('PATCH', url_path, config, callback);
};

/**
 * Makes a DELETE HTTP request
 *
 * @param {string}  url_path  The path part of the url.
 * @param {object}  options  (optional) A list of options that are merged with
 *                           the config and matches that format.
 * @param {ZiftrApi~requestCallback}  callback  A function that is called when the request completes or errors.
 */
ZiftrApi.delete = function ZiftrApiDeleteRequest(url_path, config, callback){
  return ZiftrApi.request('DELETE', url_path, config, callback);
};
<commit_msg>Fixed an issue where calling request synchronously with erroneous configs will throw the wrong error.<commit_after>/* globals CryptoJS, ZiftrApi */
"use strict";

var string_to_base64 = function _ZiftrApiStringToBase64(string){
  // return (new Buffer(string)).toString('base64');
  return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(string));
};

var prase_request_error = function _ZiftrApiParseRequestError(config, error){

  var error_data = {
    configuration: ZiftrApi.mergeObjs(config), // Clone,
    code: error.response.statusCode,
    fields: error.response.data.error.fields,
    // headers: error.response.headers,
    // response: error.response,
  };

  if (error_data.configuration.keys) { delete error_data.configuration.keys; }

  return new ZiftrApi.RequestError(error.response.data.error.message, error_data, null, _ZiftrApiParseRequestError);
};

var requestLog = function _ZiftrApiRequestLog(config /* , message, ... */){
  if (config.debug !== true) { return; }

  var args = Array.prototype.slice.apply(arguments);
  args.shift(); // config
  args.unshift('ZiftrApi');
  console.log.apply(console, args);
};

// ZiftrApi << POST http://sandbox.fpa.bz/orders
requestLog.send = function _ZiftrApiRequestLogSend(config, method, url){
  requestLog(config, '<<', method, url);
};

// ZiftrApi >> POST http://sandbox.fpa.bz/orders >> 301 { <response body> }
requestLog.resp = function _ZiftrApiRequestLogResp(config, method, url, response){
  requestLog(config, '>>', method, url, '>>', response.statusCode, JSON.stringify(response));
};

// ZiftrApi >> POST http://sandbox.fpa.bz/orders >> 301 { <response error> }
requestLog.err = function _ZiftrApiRequestLogErr(config, method, url, err){
  var statusCode = err && (err.statusCode || err.status || err.code);
  requestLog(config, '>>', method, url, '>> Error:', statusCode, err.message, JSON.stringify(err));
};

/**
 * Makes a HTTP request
 *
 * @param {string}  method  The HTTP method for this request, for example
 *                          "GET", "POST", "PATCH", "DELETE"
 * @param {string}  url_path  The path part of the url.
 * @param {object}  options  (optional) A list of options that are merged with
 *                           the config and matches that format.
 * @param {ZiftrApi~requestCallback}  callback  A function that is called when the request completes or errors.
 *
 *
 * @callback ZiftrApi~requestCallback
 * @param {error}  err
 * @param {object}  response
 *
 * @example
 *
 *     ZiftrApi.request('POST', '/orders', function(err, resp){
 *       if (err) {
 *         // there was an error, take some action
 *         return;
 *       }
 *       // No error, get response from `resp`
 *     })
 *
 */
ZiftrApi.request = function ZiftrApiRequest(method, url_path, config, callback){

  check(method, String);
  check(url_path, String);

  if (typeof(options) === "function") {
    callback = config;
    config = {};
  }

  config = ZiftrApi.mergeObjs(ZiftrApi.config(), config);

  var on_error = function ZiftrApiRequestOnError(err){
    if (typeof(callback) === 'function') {
      callback(err);
      return false;
    }
    else {
      throw err;
    }
  };

  // validate that options is present
  if(config === undefined || !config) {
    return on_error(new ZiftrApi.InvalidConfigError("Missing ZiftrApi configuration"));
  }

  // validate that the pub and prv keys are present
  if (config.keys === undefined) {
    return on_error(new ZiftrApi.InvalidConfigError("Missing ZiftrApi config.keys"));
  }

  if (config.keys.publishable_key === undefined) {
    return on_error(new ZiftrApi.InvalidConfigError("Missing ZiftrApi config.keys.publishable_key"));
  }

  if (config.keys.private_key === undefined) {
    return on_error(new ZiftrApi.InvalidConfigError("Missing ZiftrApi config.keys.private_key"));
  }

  // validate that the host version is set, and that we have an api_host
  if(config.api_version === undefined) {
    return on_error(new ZiftrApi.InvalidConfigError("Missing ZiftrApi config.api_version"));
  }

  if(config.api_host === undefined) {
    return on_error(new ZiftrApi.InvalidConfigError("Missing ZiftrApi config.api_host"));
  }

  var accept_version = config.api_version.replace('.','-');
  var url_path_parts = url_path.split('?');
  var qs = url_path_parts.length > 1 ? url_path_parts[1] : '';
  var path = url_path_parts.length > 1 ? url_path_parts[0] + '?' : url_path_parts[0];
  var signature = ZiftrApi.getSignature(path, config.keys.publishable_key, config.keys.private_key, qs);

  var request_headers = {
    'Content-Type': 'application/json',
    'Accept': 'application/vnd.ziftr.fpa-' + accept_version + '+json',
    'Authorization': 'Basic ' + string_to_base64(config.keys.publishable_key + ':' + signature),
    'User-Agent': 'Ziftr%20API%Meteor%20Client%20'+ ZiftrApi.getVersion(),
  };

  var url = config.api_host + url_path;

  var request_options = {
    uri: config.api_host + url_path,
    headers: request_headers,
    timeout: 10000, // milliseconds
    npmRequestOptions: {
      resolveWithFullResponse: true,
    },
  };

  if(config.data !== undefined) {
    request_options.data = config.data;
  }

  requestLog.send(config, method, url);

  if (typeof(callback) === 'function') {
    // Run asynchronously

    HTTP.call(method, url, request_options, function(error, response){
      if (error) {
        var parsedErr = prase_request_error(config, error);
        requestLog.err(config, method, url, parsedErr);
        return callback(parsedErr);
      }

      requestLog.resp(config, method, url, response);

      return callback(null, response);
    });

  }
  else {
    // Run synchronously

    try {
      var response = HTTP.call(method, url, request_options);
      requestLog.resp(config, method, url, response);
      return response;
    }
    catch(err){
      var parsedErr = prase_request_error(config, err);
      requestLog.err(config, method, url, parsedErr);
      throw parsedErr;
    }
  }

};

/**
 * Makes a GET HTTP request
 *
 * @param {string}  url_path  The path part of the url.
 * @param {object}  options  (optional) A list of options that are merged with
 *                           the config and matches that format.
 * @param {ZiftrApi~requestCallback}  callback  A function that is called when the request completes or errors.
 */
ZiftrApi.get = function ZiftrApiGetRequest(url_path, config, callback){
  return ZiftrApi.request('GET', url_path, config, callback);
};

/**
 * Makes a POST HTTP request
 *
 * @param {string}  url_path  The path part of the url.
 * @param {object}  options  (optional) A list of options that are merged with
 *                           the config and matches that format.
 * @param {ZiftrApi~requestCallback}  callback  A function that is called when the request completes or errors.
 */
ZiftrApi.post = function ZiftrApiPostRequest(url_path, config, callback){
  return ZiftrApi.request('POST', url_path, config, callback);
};

/**
 * Makes a PATCH HTTP request
 *
 * @param {string}  url_path  The path part of the url.
 * @param {object}  options  (optional) A list of options that are merged with
 *                           the config and matches that format.
 * @param {ZiftrApi~requestCallback}  callback  A function that is called when the request completes or errors.
 */
ZiftrApi.patch = function ZiftrApiPatchRequest(url_path, config, callback){
  return ZiftrApi.request('PATCH', url_path, config, callback);
};

/**
 * Makes a DELETE HTTP request
 *
 * @param {string}  url_path  The path part of the url.
 * @param {object}  options  (optional) A list of options that are merged with
 *                           the config and matches that format.
 * @param {ZiftrApi~requestCallback}  callback  A function that is called when the request completes or errors.
 */
ZiftrApi.delete = function ZiftrApiDeleteRequest(url_path, config, callback){
  return ZiftrApi.request('DELETE', url_path, config, callback);
};
<|endoftext|>


<commit_before>/**
 * Copyright 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule SelectEventPlugin
 */

'use strict';

var EventConstants = require('EventConstants');
var EventPropagators = require('EventPropagators');
var ExecutionEnvironment = require('ExecutionEnvironment');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactInputSelection = require('ReactInputSelection');
var SyntheticEvent = require('SyntheticEvent');

var getActiveElement = require('getActiveElement');
var isTextInputElement = require('isTextInputElement');
var keyOf = require('keyOf');
var shallowEqual = require('shallowEqual');

var topLevelTypes = EventConstants.topLevelTypes;

var skipSelectionChangeEvent = (
  ExecutionEnvironment.canUseDOM &&
  'documentMode' in document &&
  document.documentMode <= 11
);

var eventTypes = {
  select: {
    phasedRegistrationNames: {
      bubbled: keyOf({onSelect: null}),
      captured: keyOf({onSelectCapture: null}),
    },
    dependencies: [
      topLevelTypes.topBlur,
      topLevelTypes.topContextMenu,
      topLevelTypes.topFocus,
      topLevelTypes.topKeyDown,
      topLevelTypes.topMouseDown,
      topLevelTypes.topMouseUp,
      topLevelTypes.topSelectionChange,
    ],
  },
};

var activeElement = null;
var activeElementInst = null;
var lastSelection = null;
var mouseDown = false;

// Track whether a listener exists for this plugin. If none exist, we do
// not extract events. See #3639.
var hasListener = false;
var ON_SELECT_KEY = keyOf({onSelect: null});

/**
 * Get an object which is a unique representation of the current selection.
 *
 * The return value will not be consistent across nodes or browsers, but
 * two identical selections on the same node will return identical objects.
 *
 * @param {DOMElement} node
 * @return {object}
 */
function getSelection(node) {
  if ('selectionStart' in node &&
      ReactInputSelection.hasSelectionCapabilities(node)) {
    return {
      start: node.selectionStart,
      end: node.selectionEnd,
    };
  } else if (window.getSelection) {
    var selection = window.getSelection();
    return {
      anchorNode: selection.anchorNode,
      anchorOffset: selection.anchorOffset,
      focusNode: selection.focusNode,
      focusOffset: selection.focusOffset,
    };
  } else if (document.selection) {
    var range = document.selection.createRange();
    return {
      parentElement: range.parentElement(),
      text: range.text,
      top: range.boundingTop,
      left: range.boundingLeft,
    };
  }
}

/**
 * Poll selection to see whether it's changed.
 *
 * @param {object} nativeEvent
 * @return {?SyntheticEvent}
 */
function constructSelectEvent(nativeEvent, nativeEventTarget) {
  // Ensure we have the right element, and that the user is not dragging a
  // selection (this matches native `select` event behavior). In HTML5, select
  // fires only on input and textarea thus if there's no focused element we
  // won't dispatch.
  if (mouseDown ||
      activeElement == null ||
      activeElement !== getActiveElement()) {
    return null;
  }

  // Only fire when selection has actually changed.
  var currentSelection = getSelection(activeElement);
  if (!lastSelection || !shallowEqual(lastSelection, currentSelection)) {
    lastSelection = currentSelection;

    var syntheticEvent = SyntheticEvent.getPooled(
      eventTypes.select,
      activeElementInst,
      nativeEvent,
      nativeEventTarget
    );

    syntheticEvent.type = 'select';
    syntheticEvent.target = activeElement;

    EventPropagators.accumulateTwoPhaseDispatches(syntheticEvent);

    return syntheticEvent;
  }

  return null;
}

/**
 * This plugin creates an `onSelect` event that normalizes select events
 * across form elements.
 *
 * Supported elements are:
 * - input (see `isTextInputElement`)
 * - textarea
 * - contentEditable
 *
 * This differs from native browser implementations in the following ways:
 * - Fires on contentEditable fields as well as inputs.
 * - Fires for collapsed selection.
 * - Fires after user input.
 */
var SelectEventPlugin = {

  eventTypes: eventTypes,

  extractEvents: function(
    topLevelType,
    targetInst,
    nativeEvent,
    nativeEventTarget
  ) {
    if (!hasListener) {
      return null;
    }

    var targetNode = targetInst ?
      ReactDOMComponentTree.getNodeFromInstance(targetInst) : window;

    switch (topLevelType) {
      // Track the input node that has focus.
      case topLevelTypes.topFocus:
        if (isTextInputElement(targetNode) ||
            targetNode.contentEditable === 'true') {
          activeElement = targetNode;
          activeElementInst = targetInst;
          lastSelection = null;
        }
        break;
      case topLevelTypes.topBlur:
        activeElement = null;
        activeElementInst = null;
        lastSelection = null;
        break;

      // Don't fire the event while the user is dragging. This matches the
      // semantics of the native select event.
      case topLevelTypes.topMouseDown:
        mouseDown = true;
        break;
      case topLevelTypes.topContextMenu:
      case topLevelTypes.topMouseUp:
        mouseDown = false;
        return constructSelectEvent(nativeEvent, nativeEventTarget);

      // Chrome and IE fire non-standard event when selection is changed (and
      // sometimes when it hasn't). IE's event fires out of order with respect
      // to key and input events on deletion, so we discard it.
      //
      // Firefox doesn't support selectionchange, so check selection status
      // after each key entry. The selection changes after keydown and before
      // keyup, but we check on keydown as well in the case of holding down a
      // key, when multiple keydown events are fired but only one keyup is.
      // This is also our approach for IE handling, for the reason above.
      case topLevelTypes.topSelectionChange:
        if (skipSelectionChangeEvent) {
          break;
        }
        // falls through
      case topLevelTypes.topKeyDown:
      case topLevelTypes.topKeyUp:
        return constructSelectEvent(nativeEvent, nativeEventTarget);
    }

    return null;
  },

  didPutListener: function(inst, registrationName, listener) {
    if (registrationName === ON_SELECT_KEY) {
      hasListener = true;
    }
  },
};

module.exports = SelectEventPlugin;
<commit_msg>Add missing KeyUp eventType (#7533)<commit_after>/**
 * Copyright 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule SelectEventPlugin
 */

'use strict';

var EventConstants = require('EventConstants');
var EventPropagators = require('EventPropagators');
var ExecutionEnvironment = require('ExecutionEnvironment');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactInputSelection = require('ReactInputSelection');
var SyntheticEvent = require('SyntheticEvent');

var getActiveElement = require('getActiveElement');
var isTextInputElement = require('isTextInputElement');
var keyOf = require('keyOf');
var shallowEqual = require('shallowEqual');

var topLevelTypes = EventConstants.topLevelTypes;

var skipSelectionChangeEvent = (
  ExecutionEnvironment.canUseDOM &&
  'documentMode' in document &&
  document.documentMode <= 11
);

var eventTypes = {
  select: {
    phasedRegistrationNames: {
      bubbled: keyOf({onSelect: null}),
      captured: keyOf({onSelectCapture: null}),
    },
    dependencies: [
      topLevelTypes.topBlur,
      topLevelTypes.topContextMenu,
      topLevelTypes.topFocus,
      topLevelTypes.topKeyDown,
      topLevelTypes.topKeyUp,
      topLevelTypes.topMouseDown,
      topLevelTypes.topMouseUp,
      topLevelTypes.topSelectionChange,
    ],
  },
};

var activeElement = null;
var activeElementInst = null;
var lastSelection = null;
var mouseDown = false;

// Track whether a listener exists for this plugin. If none exist, we do
// not extract events. See #3639.
var hasListener = false;
var ON_SELECT_KEY = keyOf({onSelect: null});

/**
 * Get an object which is a unique representation of the current selection.
 *
 * The return value will not be consistent across nodes or browsers, but
 * two identical selections on the same node will return identical objects.
 *
 * @param {DOMElement} node
 * @return {object}
 */
function getSelection(node) {
  if ('selectionStart' in node &&
      ReactInputSelection.hasSelectionCapabilities(node)) {
    return {
      start: node.selectionStart,
      end: node.selectionEnd,
    };
  } else if (window.getSelection) {
    var selection = window.getSelection();
    return {
      anchorNode: selection.anchorNode,
      anchorOffset: selection.anchorOffset,
      focusNode: selection.focusNode,
      focusOffset: selection.focusOffset,
    };
  } else if (document.selection) {
    var range = document.selection.createRange();
    return {
      parentElement: range.parentElement(),
      text: range.text,
      top: range.boundingTop,
      left: range.boundingLeft,
    };
  }
}

/**
 * Poll selection to see whether it's changed.
 *
 * @param {object} nativeEvent
 * @return {?SyntheticEvent}
 */
function constructSelectEvent(nativeEvent, nativeEventTarget) {
  // Ensure we have the right element, and that the user is not dragging a
  // selection (this matches native `select` event behavior). In HTML5, select
  // fires only on input and textarea thus if there's no focused element we
  // won't dispatch.
  if (mouseDown ||
      activeElement == null ||
      activeElement !== getActiveElement()) {
    return null;
  }

  // Only fire when selection has actually changed.
  var currentSelection = getSelection(activeElement);
  if (!lastSelection || !shallowEqual(lastSelection, currentSelection)) {
    lastSelection = currentSelection;

    var syntheticEvent = SyntheticEvent.getPooled(
      eventTypes.select,
      activeElementInst,
      nativeEvent,
      nativeEventTarget
    );

    syntheticEvent.type = 'select';
    syntheticEvent.target = activeElement;

    EventPropagators.accumulateTwoPhaseDispatches(syntheticEvent);

    return syntheticEvent;
  }

  return null;
}

/**
 * This plugin creates an `onSelect` event that normalizes select events
 * across form elements.
 *
 * Supported elements are:
 * - input (see `isTextInputElement`)
 * - textarea
 * - contentEditable
 *
 * This differs from native browser implementations in the following ways:
 * - Fires on contentEditable fields as well as inputs.
 * - Fires for collapsed selection.
 * - Fires after user input.
 */
var SelectEventPlugin = {

  eventTypes: eventTypes,

  extractEvents: function(
    topLevelType,
    targetInst,
    nativeEvent,
    nativeEventTarget
  ) {
    if (!hasListener) {
      return null;
    }

    var targetNode = targetInst ?
      ReactDOMComponentTree.getNodeFromInstance(targetInst) : window;

    switch (topLevelType) {
      // Track the input node that has focus.
      case topLevelTypes.topFocus:
        if (isTextInputElement(targetNode) ||
            targetNode.contentEditable === 'true') {
          activeElement = targetNode;
          activeElementInst = targetInst;
          lastSelection = null;
        }
        break;
      case topLevelTypes.topBlur:
        activeElement = null;
        activeElementInst = null;
        lastSelection = null;
        break;

      // Don't fire the event while the user is dragging. This matches the
      // semantics of the native select event.
      case topLevelTypes.topMouseDown:
        mouseDown = true;
        break;
      case topLevelTypes.topContextMenu:
      case topLevelTypes.topMouseUp:
        mouseDown = false;
        return constructSelectEvent(nativeEvent, nativeEventTarget);

      // Chrome and IE fire non-standard event when selection is changed (and
      // sometimes when it hasn't). IE's event fires out of order with respect
      // to key and input events on deletion, so we discard it.
      //
      // Firefox doesn't support selectionchange, so check selection status
      // after each key entry. The selection changes after keydown and before
      // keyup, but we check on keydown as well in the case of holding down a
      // key, when multiple keydown events are fired but only one keyup is.
      // This is also our approach for IE handling, for the reason above.
      case topLevelTypes.topSelectionChange:
        if (skipSelectionChangeEvent) {
          break;
        }
        // falls through
      case topLevelTypes.topKeyDown:
      case topLevelTypes.topKeyUp:
        return constructSelectEvent(nativeEvent, nativeEventTarget);
    }

    return null;
  },

  didPutListener: function(inst, registrationName, listener) {
    if (registrationName === ON_SELECT_KEY) {
      hasListener = true;
    }
  },
};

module.exports = SelectEventPlugin;
<|endoftext|>


<commit_before>import { defaultHomepage } from 'discourse/lib/utilities';

export default function() {
  // Error page
  this.route('exception', { path: '/exception' });

  this.resource('about', { path: '/about' });

  // Topic routes
  this.resource('topic', { path: '/t/:slug/:id' }, function() {
    this.route('fromParams', { path: '/' });
    this.route('fromParamsNear', { path: '/:nearPost' });
  });
  this.resource('topicBySlug', { path: '/t/:slug' });
  this.route('topicUnsubscribe', { path: '/t/:slug/:id/unsubscribe' });

  this.resource('discovery', { path: '/' }, function() {
    // top
    this.route('top');
    this.route('topParentCategory', { path: '/c/:slug/l/top' });
    this.route('topCategoryNone', { path: '/c/:slug/none/l/top' });
    this.route('topCategory', { path: '/c/:parentSlug/:slug/l/top' });

    // top by periods
    Discourse.Site.currentProp('periods').forEach(period => {
      const top = 'top' + period.capitalize();
      this.route(top, { path: '/top/' + period });
      this.route(top + 'ParentCategory', { path: '/c/:slug/l/top/' + period });
      this.route(top + 'CategoryNone', { path: '/c/:slug/none/l/top/' + period });
      this.route(top + 'Category', { path: '/c/:parentSlug/:slug/l/top/' + period });
    });

    // filters
    Discourse.Site.currentProp('filters').forEach(filter => {
      this.route(filter, { path: '/' + filter });
      this.route(filter + 'ParentCategory', { path: '/c/:slug/l/' + filter });
      this.route(filter + 'CategoryNone', { path: '/c/:slug/none/l/' + filter });
      this.route(filter + 'Category', { path: '/c/:parentSlug/:slug/l/' + filter });
    });

    this.route('categories');

    // default filter for a category
    this.route('parentCategory', { path: '/c/:slug' });
    this.route('categoryNone', { path: '/c/:slug/none' });
    this.route('category', { path: '/c/:parentSlug/:slug' });
    this.route('categoryWithID', { path: '/c/:parentSlug/:slug/:id' });

    // homepage
    this.route(defaultHomepage(), { path: '/' });
  });

  this.resource('group', { path: '/groups/:name' }, function() {
    this.route('members');
    this.route('posts');
    this.route('topics');
    this.route('mentions');
    this.route('messages');
  });

  // User routes
  this.resource('users');
  this.resource('user', { path: '/users/:username' }, function() {
    this.route('summary');
    this.resource('userActivity', { path: '/activity' }, function() {
      this.route('topics');
      this.route('replies');
      this.route('likesGiven', {path: 'likes-given'});
      this.route('bookmarks');
      this.route('pending');
    });

    this.resource('userNotifications', {path: '/notifications'}, function(){
      this.route('responses');
      this.route('likesReceived', { path: 'likes-received'});
      this.route('mentions');
      this.route('edits');
    });

    this.route('badges');
    this.route('flaggedPosts', { path: '/flagged-posts' });
    this.route('deletedPosts', { path: '/deleted-posts' });

    this.resource('userPrivateMessages', { path: '/messages' }, function() {
      this.route('sent');
      this.route('archive');
      this.route('group', { path: 'group/:name'});
      this.route('groupArchive', { path: 'group/:name/archive'});
    });

    this.resource('preferences', function() {
      this.route('username');
      this.route('email');
      this.route('about', { path: '/about-me' });
      this.route('badgeTitle', { path: '/badge_title' });
      this.route('card-badge', { path: '/card-badge' });
    });

    this.resource('userInvited', { path: '/invited' }, function() {
      this.route('show', { path: '/:filter' });
    });

  });

  this.route('signup', {path: '/signup'});
  this.route('login', {path: '/login'});
  this.route('login-preferences');
  this.route('forgot-password', {path: '/password-reset'});
  this.route('faq', {path: '/faq'});
  this.route('tos', {path: '/tos'});
  this.route('privacy', {path: '/privacy'});
  this.route('guidelines', {path: '/guidelines'});

  this.route('new-topic', {path: '/new-topic'});
  this.route('new-message', {path: '/new-message'});

  this.resource('badges', function() {
    this.route('show', {path: '/:id/:slug'});
  });

  this.resource('queued-posts', { path: '/queued-posts' });

  this.route('full-page-search', {path: '/search'});

  this.resource('tags', function() {
    this.route('show', {path: '/:tag_id'});
    this.route('showCategory', {path: '/c/:category/:tag_id'});
    this.route('showParentCategory', {path: '/c/:parent_category/:category/:tag_id'});

    Discourse.Site.currentProp('filters').forEach(filter => {
      this.route('show' + filter.capitalize(), {path: '/:tag_id/l/' + filter});
      this.route('showCategory' + filter.capitalize(), {path: '/c/:category/:tag_id/l/' + filter});
      this.route('showParentCategory' + filter.capitalize(), {path: '/c/:parent_category/:category/:tag_id/l/' + filter});
    });
    this.route('show', {path: 'intersection/:tag_id/*additional_tags'});
  });

  this.resource('tagGroups', {path: '/tag_groups'}, function() {
    this.route('show', {path: '/:id'});
  });
}
<commit_msg>Properly name tag intersection route<commit_after>import { defaultHomepage } from 'discourse/lib/utilities';

export default function() {
  // Error page
  this.route('exception', { path: '/exception' });

  this.resource('about', { path: '/about' });

  // Topic routes
  this.resource('topic', { path: '/t/:slug/:id' }, function() {
    this.route('fromParams', { path: '/' });
    this.route('fromParamsNear', { path: '/:nearPost' });
  });
  this.resource('topicBySlug', { path: '/t/:slug' });
  this.route('topicUnsubscribe', { path: '/t/:slug/:id/unsubscribe' });

  this.resource('discovery', { path: '/' }, function() {
    // top
    this.route('top');
    this.route('topParentCategory', { path: '/c/:slug/l/top' });
    this.route('topCategoryNone', { path: '/c/:slug/none/l/top' });
    this.route('topCategory', { path: '/c/:parentSlug/:slug/l/top' });

    // top by periods
    Discourse.Site.currentProp('periods').forEach(period => {
      const top = 'top' + period.capitalize();
      this.route(top, { path: '/top/' + period });
      this.route(top + 'ParentCategory', { path: '/c/:slug/l/top/' + period });
      this.route(top + 'CategoryNone', { path: '/c/:slug/none/l/top/' + period });
      this.route(top + 'Category', { path: '/c/:parentSlug/:slug/l/top/' + period });
    });

    // filters
    Discourse.Site.currentProp('filters').forEach(filter => {
      this.route(filter, { path: '/' + filter });
      this.route(filter + 'ParentCategory', { path: '/c/:slug/l/' + filter });
      this.route(filter + 'CategoryNone', { path: '/c/:slug/none/l/' + filter });
      this.route(filter + 'Category', { path: '/c/:parentSlug/:slug/l/' + filter });
    });

    this.route('categories');

    // default filter for a category
    this.route('parentCategory', { path: '/c/:slug' });
    this.route('categoryNone', { path: '/c/:slug/none' });
    this.route('category', { path: '/c/:parentSlug/:slug' });
    this.route('categoryWithID', { path: '/c/:parentSlug/:slug/:id' });

    // homepage
    this.route(defaultHomepage(), { path: '/' });
  });

  this.resource('group', { path: '/groups/:name' }, function() {
    this.route('members');
    this.route('posts');
    this.route('topics');
    this.route('mentions');
    this.route('messages');
  });

  // User routes
  this.resource('users');
  this.resource('user', { path: '/users/:username' }, function() {
    this.route('summary');
    this.resource('userActivity', { path: '/activity' }, function() {
      this.route('topics');
      this.route('replies');
      this.route('likesGiven', {path: 'likes-given'});
      this.route('bookmarks');
      this.route('pending');
    });

    this.resource('userNotifications', {path: '/notifications'}, function(){
      this.route('responses');
      this.route('likesReceived', { path: 'likes-received'});
      this.route('mentions');
      this.route('edits');
    });

    this.route('badges');
    this.route('flaggedPosts', { path: '/flagged-posts' });
    this.route('deletedPosts', { path: '/deleted-posts' });

    this.resource('userPrivateMessages', { path: '/messages' }, function() {
      this.route('sent');
      this.route('archive');
      this.route('group', { path: 'group/:name'});
      this.route('groupArchive', { path: 'group/:name/archive'});
    });

    this.resource('preferences', function() {
      this.route('username');
      this.route('email');
      this.route('about', { path: '/about-me' });
      this.route('badgeTitle', { path: '/badge_title' });
      this.route('card-badge', { path: '/card-badge' });
    });

    this.resource('userInvited', { path: '/invited' }, function() {
      this.route('show', { path: '/:filter' });
    });

  });

  this.route('signup', {path: '/signup'});
  this.route('login', {path: '/login'});
  this.route('login-preferences');
  this.route('forgot-password', {path: '/password-reset'});
  this.route('faq', {path: '/faq'});
  this.route('tos', {path: '/tos'});
  this.route('privacy', {path: '/privacy'});
  this.route('guidelines', {path: '/guidelines'});

  this.route('new-topic', {path: '/new-topic'});
  this.route('new-message', {path: '/new-message'});

  this.resource('badges', function() {
    this.route('show', {path: '/:id/:slug'});
  });

  this.resource('queued-posts', { path: '/queued-posts' });

  this.route('full-page-search', {path: '/search'});

  this.resource('tags', function() {
    this.route('show', {path: '/:tag_id'});
    this.route('showCategory', {path: '/c/:category/:tag_id'});
    this.route('showParentCategory', {path: '/c/:parent_category/:category/:tag_id'});

    Discourse.Site.currentProp('filters').forEach(filter => {
      this.route('show' + filter.capitalize(), {path: '/:tag_id/l/' + filter});
      this.route('showCategory' + filter.capitalize(), {path: '/c/:category/:tag_id/l/' + filter});
      this.route('showParentCategory' + filter.capitalize(), {path: '/c/:parent_category/:category/:tag_id/l/' + filter});
    });
    this.route('intersection', {path: 'intersection/:tag_id/*additional_tags'});
  });

  this.resource('tagGroups', {path: '/tag_groups'}, function() {
    this.route('show', {path: '/:id'});
  });
}
<|endoftext|>


<commit_before>/* global Fae, FCH */

/**
 * Fae navigation subnav highlighter
 * @namespace navigation.subnav_highlighter
 * @memberof navigation
 */
Fae.navigation.subnav_highlighter = {
  init: function() {
    //only run everything if there is a subnav area
    // @depreciation - change conditional to FCH.exists('.content-header-subnav') in v2.0
    if (FCH.exists('.main_content-header-section-links') || FCH.exists('.content-header-subnav')) {
      // @depreciation - remove entire conditional block (following 7 lines) in v2.0
      if(Fae.content_selector === '.content') {
        this.section_class = Fae.content_selector;
        this.subnav_class = '#js-content-header-subnav';
        this.addBuffer();
      } else {
        this.section_class = '.main_content-section';
        this.subnav_class = '.main_content-header-section-links';
      }

      this.FCHListeners();

      //makes the subnav clicks
      this.anchorClickListener();
    }
  },

  /**
   * Add space above subnav links if they're present
   * @depreciation - Remove this function in favor of a better HTML solution in form_header in a v2.0 refactor.
   */
  addBuffer: function() {
    var $subnav = $( this.subnav_class );
    var height = $subnav.css('height');
    $subnav.parent().css('padding-bottom', height);
  },

  /**
   * Since subnavHighlighter is not a direct child of Fae and therefore unknown to FCH, these listeners are saved in private functions in this method
   */
  FCHListeners: function() {
    // @depreciation - change value of section_class to '.content' in v2.0
    var section_class = this.section_class;
    // @depreciation - change value of subnav_class to '#js-content-header-subnav' in v2.0
    // Ideally, form_header will include header.content-header.js-content-header
    var subnav_class = this.subnav_class;
    // @depreciation - remove legacy_buffer expression in v2.0 (the value should be 0, so it will be unnecessary)
    var legacy_buffer = section_class === Fae.content_selector ? 0 : 32;

    /**
     * On scroll, change highlight of nav item. Bread and butter of this subclass.
     * @private
     */
    function scrollCallback() {
      var scroll_top = FCH.$window.scrollTop();

      $(section_class).each(function(index) {
        var $this = $(this);
        var position = $this.position().top - scroll_top - legacy_buffer;
        var $link = $('a[href="#' + $this.attr('id') + '"]').parent();

        $link.removeClass('-active');
        if (position <= 0 || index === 0) {
          $link.addClass('js-highligher');
        }
      });

      $('.js-highligher').last().addClass('-active').removeClass('js-highligher');
    }

    // highlight the first one on page load
    scrollCallback();
    FCH.scroll.push(scrollCallback);
  },

  /**
   * Smooth scrolling on anchor links in the tab area.
   */
  anchorClickListener: function() {
    // @depreciation - replace scroll_offset_selector variable with string '.content-header' in v2.0
    var scroll_offset_selector = FCH.exists('.main_content-header') ? '.main_content-header' : '.content-header';
    var scroll_offset = parseInt( $(scroll_offset_selector).css('height'), 10 );
    // @depreciation - remove should_find_h2 in v2.0
    var should_find_h2 = this.section_class === Fae.content_selector;

    /**
     * Smoothly scroll to destination if it's a link to the current page
     * @private
     * @param {Object} el - JavaScript element to scroll to
     */
    function scroller(e) {
      e.preventDefault();

      if (location.pathname.replace(/^\//,'') === this.pathname.replace(/^\//,'') && location.hostname === this.hostname) {
        var $target = $(this.hash);
        $target = $target.length ? $target : $('[name=' + this.hash.slice(1) + ']');
        // @depreciation - remove conditional wrapping (keep $target = ... and scroll_offset -= ...) in v2.0
        if(should_find_h2 && $target.find('h2').length) {
          $target = $target.find('h2');
          scroll_offset -= 2;
        }

        if ($target.length) {
          FCH.smoothScroll($target, 500, 0, -scroll_offset);
        }
      }
    }

    $(this.subnav_class + ' a').on('click', scroller);
  },
};
<commit_msg>Refs #60184 - Always highlight final subnav item when user is scrolled to bottom of window<commit_after>/* global Fae, FCH */

/**
 * Fae navigation subnav highlighter
 * @namespace navigation.subnav_highlighter
 * @memberof navigation
 */
Fae.navigation.subnav_highlighter = {
  init: function() {
    //only run everything if there is a subnav area
    // @depreciation - change conditional to FCH.exists('.content-header-subnav') in v2.0
    if (FCH.exists('.main_content-header-section-links') || FCH.exists('.content-header-subnav')) {
      // @depreciation - remove entire conditional block (following 7 lines) in v2.0
      if(Fae.content_selector === '.content') {
        this.section_class = Fae.content_selector;
        this.subnav_class = '#js-content-header-subnav';
        this.addBuffer();
      } else {
        this.section_class = '.main_content-section';
        this.subnav_class = '.main_content-header-section-links';
      }

      this.FCHListeners();

      //makes the subnav clicks
      this.anchorClickListener();
    }
  },

  /**
   * Add space above subnav links if they're present
   * @depreciation - Remove this function in favor of a better HTML solution in form_header in a v2.0 refactor.
   */
  addBuffer: function() {
    var $subnav = $( this.subnav_class );
    var height = $subnav.css('height');
    $subnav.parent().css('padding-bottom', height);
  },

  /**
   * Since subnavHighlighter is not a direct child of Fae and therefore unknown to FCH, these listeners are saved in private functions in this method
   */
  FCHListeners: function() {
    // @depreciation - change value of section_class to '.content' in v2.0
    var section_class = this.section_class;
    // @depreciation - change value of subnav_class to '#js-content-header-subnav' in v2.0
    // Ideally, form_header will include header.content-header.js-content-header
    var subnav_class = this.subnav_class;
    // @depreciation - remove legacy_buffer expression in v2.0 (the value should be 0, so it will be unnecessary)
    var legacy_buffer = section_class === Fae.content_selector ? 0 : 32;

    /**
     * On scroll, change highlight of nav item. Bread and butter of this subclass.
     * @private
     */
    function scrollCallback() {
      var scroll_top = FCH.$window.scrollTop();

      $(section_class).each(function(index) {
        var $this = $(this);
        var position = $this.position().top - scroll_top - legacy_buffer;
        var $link = $('a[href="#' + $this.attr('id') + '"]').parent();
        var is_scrolled_to_bottom = scroll_top - legacy_buffer >= (FCH.$document.outerHeight() - FCH.dimensions.wh);

        $link.removeClass('-active');
        if (position <= 0 || index === 0 || is_scrolled_to_bottom) {
          $link.addClass('js-highlighter');
        }
      });

      $('.js-highlighter').last().addClass('-active').removeClass('js-highlighter');
    }

    // highlight the first one on page load
    scrollCallback();
    FCH.scroll.push(scrollCallback);
  },

  /**
   * Smooth scrolling on anchor links in the tab area.
   */
  anchorClickListener: function() {
    // @depreciation - replace scroll_offset_selector variable with string '.content-header' in v2.0
    var scroll_offset_selector = FCH.exists('.main_content-header') ? '.main_content-header' : '.content-header';
    var scroll_offset = parseInt( $(scroll_offset_selector).css('height'), 10 );
    // @depreciation - remove should_find_h2 in v2.0
    var should_find_h2 = this.section_class === Fae.content_selector;

    /**
     * Smoothly scroll to destination if it's a link to the current page
     * @private
     * @param {Object} el - JavaScript element to scroll to
     */
    function scroller(e) {
      e.preventDefault();

      if (location.pathname.replace(/^\//,'') === this.pathname.replace(/^\//,'') && location.hostname === this.hostname) {
        var $target = $(this.hash);
        $target = $target.length ? $target : $('[name=' + this.hash.slice(1) + ']');

        // @depreciation - remove conditional wrapping (keep $target = ... and scroll_offset -= ...) in v2.0
        if(should_find_h2 && $target.find('h2').length) {
          $target = $target.find('h2');
          scroll_offset -= 2;
        }

        if ($target.length) {
          FCH.smoothScroll($target, 500, 0, -scroll_offset);
        }
      }
    }

    $(this.subnav_class + ' a').on('click', scroller);
  },
};
<|endoftext|>


<commit_before>define([
    'bluebird',
    'jquery',
    'kb_common/html',
    'kb_common/utils',
    'StagingServiceClient',
    'kb_service/utils',
    'common/validation',
    'common/events',
    'common/runtime',
    'common/ui',
    'common/data',
    'util/timeFormat',
    'util/string',
    'kbase-generic-client-api',
    'common/props',
    'select2',
    'bootstrap',
], (
    Promise,
    $,
    html,
    utils,
    StagingServiceClient,
    serviceUtils,
    Validation,
    Events,
    Runtime,
    UI,
    Data,
    TimeFormat,
    StringUtil,
    GenericClient,
    Props
) => {
    'use strict';

    // Constants
    const t = html.tag,
        div = t('div'),
        span = t('span'),
        b = t('b'),
        select = t('select'),
        option = t('option');

    function factory(config) {
        const spec = config.parameterSpec,
            runtime = Runtime.make(),
            bus = runtime.bus().connect(),
            channel = bus.channel(config.channelName),
            dd_options = spec.original.dynamic_dropdown_options || {},
            dataSource = dd_options.data_source || 'ftp_staging',
            model = {
                value: undefined,
            },
            stagingService = new StagingServiceClient({
                root: runtime.config('services.staging_api_url.url'),
                token: runtime.authToken(),
            }),
            userId = runtime.userId();
        let parent, container, ui;

        if (typeof dd_options.query_on_empty_input === 'undefined') {
            dd_options.query_on_empty_input = 1;
        }

        /**
         * This function takes a nested return and returns a flat key-value pairing for use with
         * handlebar replacement for example {"foo":{"bar": "meh"}} becomes {"foo.bar": "meh"}
         */
        const flattenObject = function (ob) {
            const toReturn = {};
            for (const i in ob) {
                if (!Object.prototype.hasOwnProperty.call(ob, i)) continue;

                if (typeof ob[i] === 'object') {
                    const flatObject = flattenObject(ob[i]);
                    for (const x in flatObject) {
                        if (!Object.prototype.hasOwnProperty.call(flatObject, x)) continue;
                        toReturn[i + '.' + x] = flatObject[x];
                    }
                } else {
                    toReturn[i] = ob[i];
                }
            }
            return toReturn;
        };

        function makeInputControl() {
            let selectOptions;
            const selectElem = select(
                {
                    class: 'form-control',
                    dataElement: 'input',
                    style: {
                        width: '100%',
                    },
                    multiple: false,
                    id: html.genId(),
                },
                [option({ value: '' }, '')].concat(selectOptions)
            );

            return selectElem;
        }

        // CONTROL
        function getControlValue() {
            const control = ui.getElement('input-container.input'),
                selected = $(control).select2('data')[0];

            const selection_val = selected
                ? selected[dd_options.selection_id] || selected.subpath
                : '';
            if (!selected || !selection_val) {
                // might have just started up, and we don't have a selection value, but
                // we might have a model value.
                const modelVal = getModelValue();
                if (modelVal) {
                    return modelVal;
                } else {
                    return '';
                }
            }
            return selection_val;
        }

        /**
         * Sets the dropdown value to the given value. Constructs an id from it that (should)
         * be unique enough to apply to the dropdown.
         */
        function setControlValue(value) {
            const control = ui.getElement('input-container.input');
            if ($(control).find('option[value="' + value + '"]').length) {
                $(control).val(value).trigger('change');
            } else {
                const newOption = new Option(value, value, true, true);
                $(control).append(newOption).trigger('change');
            }
        }

        // MODEL

        function setModelValue(value) {
            if (model.value === undefined) {
                return;
            }
            if (model.value !== value) {
                model.value = value;
            }
        }

        function resetModelValue() {
            setModelValue(spec.data.defaultValue);
        }

        function getModelValue() {
            return model.value;
        }

        // VALIDATION

        function validate() {
            return Promise.try(() => {
                let selectedItem = getControlValue();
                const validationConstraints = {
                    min_length: spec.data.constraints.min_length,
                    max_length: spec.data.constraints.max_length,
                    required: spec.data.constraints.required,
                };
                // selected item might be either a string or a number.
                // if it's a number, we want it to be a string
                // if it's something else, we should raise an error, since that's
                // something fishy coming from the data-providing service
                if (typeof selectedItem === 'number') {
                    selectedItem = String(selectedItem);
                }
                return Validation.validateText(selectedItem, validationConstraints);
            });
        }

        function genericClientCall(call_params) {
            const swUrl = runtime.config('services.service_wizard.url'),
                genericClient = new GenericClient(swUrl, {
                    token: runtime.authToken(),
                });
            return genericClient.sync_call(
                dd_options.service_function,
                call_params,
                null,
                null,
                dd_options.service_version || 'release'
            );
        }

        async function fetchData(searchTerm) {
            searchTerm = searchTerm || '';

            if (!searchTerm && !dd_options.query_on_empty_input) {
                return Promise.resolve([]);
            }
            if (dataSource === 'ftp_staging') {
                return Promise.resolve(stagingService.search({ query: searchTerm })).then(
                    (results) => {
                        results = JSON.parse(results).filter((file) => {
                            return !file.isFolder;
                        });
                        results.forEach((file) => {
                            file.text = file.path;
                            file.subdir = file.path.substring(
                                0,
                                file.path.length - file.name.length
                            );
                            file.subpath = file.path.substring(userId.length + 1);
                            file.id = file.subpath;
                        });
                        return results;
                    }
                );
            } else {
                let call_params = JSON.stringify(dd_options.service_params).replace(
                    '{{dynamic_dropdown_input}}',
                    searchTerm
                );
                call_params = JSON.parse(call_params);

                // TODO: wrap lines 228-250 in a check for dd_options.include_user_params and implement that in NMS
                const params = await channel.request({}, { key: { type: 'get-parameters' } });

                // replace dynamically set service_params with current parameter values in app
                call_params = Object.entries(call_params).reduce((acc, [k, v]) => {
                    if (typeof v === 'string') {
                        // match dynamic user params that are {{in brackets}}
                        const d_param = v.match(/[^{{]+(?=}\})/);
                        if (d_param !== null) {
                            if (!(d_param[0] in params)) {
                                console.error(
                                    `Parameter "{{${d_param[0]}}}" does not exist as a parameter for this method. ` +
                                        `this dynamic parameter will be omitted in the call to ${dd_options.service_function}.`
                                );
                                // dont include bad parameters that don't exist
                                return acc;
                            }
                            // replace dynamic values with actual param values
                            return Object.assign({}, acc, { [k]: params[d_param[0]] });
                        }
                    }
                    // return anything else as normal
                    return Object.assign({}, acc, { [k]: v });
                }, {});

                return Promise.resolve(genericClientCall(call_params)).then((results) => {
                    let index = dd_options.result_array_index;
                    if (!index) {
                        index = 0;
                    }
                    if (index >= results.length) {
                        console.error(
                            `Result array from ${dd_options.service_function} ` +
                                `has length ${results.length} but index ${index} ` +
                                'was requested'
                        );
                        return [];
                    }
                    results = results[index];
                    let path = dd_options.path_to_selection_items;
                    if (!path) {
                        path = [];
                    }
                    results = Props.getDataItem(results, path);
                    if (!Array.isArray(results)) {
                        console.error(
                            'Selection items returned from ' +
                                `${dd_options.service_function} at path /${path.join('/')} ` +
                                `in postion ${index} of the returned list are not an array`
                        );
                        return [];
                    } else {
                        results.forEach((obj, _index) => {
                            // could check here that each item is a map? YAGNI
                            obj = flattenObject(obj);
                            if (!('id' in obj)) {
                                obj.id = _index; // what the fuck
                            }
                            //this blows away any 'text' field
                            obj.text = obj[dd_options.selection_id];
                            results[_index] = obj;
                        });
                        return results;
                    }
                });
            }
        }

        function doChange() {
            validate().then((result) => {
                if (result.isValid) {
                    const newValue =
                        result.parsedValue === undefined ? result.value : result.parsedValue;
                    model.value = newValue;
                    channel.emit('changed', {
                        newValue: newValue,
                    });
                } else if (result.diagnosis === 'required-missing') {
                    model.value = spec.data.nullValue;
                    channel.emit('changed', {
                        newValue: spec.data.nullValue,
                    });
                }
                channel.emit('validation', {
                    errorMessage: result.errorMessage,
                    diagnosis: result.diagnosis,
                });
            });
        }

        /**
         * Clears the current selection and updates the model.
         */
        function doClear() {
            model.value = spec.data.nullValue;
            channel.emit('changed', {
                newValue: spec.data.nullValue,
            });
        }

        /**
         * Formats the display of an object in the dropdown.
         id: "data/bulk/wjriehl/subfolder/i_am_a_file.txt"
         isFolder: false
         mtime: 1508441424000
         name: "i_am_a_file.txt"
         path: "data/bulk/wjriehl/subfolder/i_am_a_file.txt"
         size: 0
         text: "data/bulk/wjriehl/subfolder/i_am_a_file.txt"
         */
        function formatObjectDisplay(ret_obj) {
            if (!ret_obj.id) {
                return '';
            } else if (dataSource === 'ftp_staging') {
                if (!ret_obj.id) {
                    return $('
').append(ret_obj.text);
                }
                return $(
                    div([
                        span({ style: 'word-wrap: break-word' }, [ret_obj.subdir, b(ret_obj.name)]),
                        div({ style: 'margin-left: 7px' }, [
                            'Size: ' + StringUtil.readableBytes(ret_obj.size) + '
',
                            'Uploaded ' + TimeFormat.getTimeStampStr(ret_obj.mtime, true),
                        ]),
                    ])
                );
            } else {
                const replacer = function (match, p1) {
                    return ret_obj[p1];
                };
                let formatted_string;
                if (dd_options.description_template) {
                    // use slice to avoid modifying global description_template
                    formatted_string = dd_options.description_template
                        .slice()
                        .replace(/{{(.+?)}}/g, replacer);
                } else {
                    formatted_string = JSON.stringify(ret_obj);
                }
                return $('
').append(formatted_string);
            }
        }

        function selectionTemplate(object) {
            if (dd_options.description_template) {
                return formatObjectDisplay(object);
            }
            if (dd_options.selection_id) {
                return object[dd_options.selection_id];
            }
            if (!object.id) {
                return object.text;
            }
            return object.id;
        }

        /*
         * Creates the markup
         * Places it into the dom node
         * Hooks up event listeners
         */
        function render() {
            return Promise.try(() => {
                const events = Events.make(),
                    inputControl = makeInputControl(),
                    content = div({ class: 'input-group', style: { width: '100%' } }, inputControl);

                ui.setContent('input-container', content);

                $(ui.getElement('input-container.input'))
                    .select2({
                        allowClear: true,
                        templateResult: formatObjectDisplay,
                        templateSelection: selectionTemplate,
                        ajax: {
                            delay: 250,
                            transport: function (params, success, failure) {
                                return fetchData(params.data.term)
                                    .then((data) => {
                                        success({ results: data });
                                    })
                                    .catch((err) => {
                                        console.error(err);
                                        failure(err);
                                    });
                            },
                        },
                        placeholder: {
                            id: 'select an option',
                        },
                    })
                    .on('change', () => {
                        doChange();
                    })
                    .on('select2:clear', () => {
                        doClear();
                    });
                events.attachEvents(container);
            });
        }

        /*
         * In the layout we set up an environment in which one or more parameter
         * rows may be inserted.
         * For the objectInput, there is only ever one control.
         */
        function layout(events) {
            const content = div(
                {
                    dataElement: 'main-panel',
                },
                [div({ dataElement: 'input-container' })]
            );
            return {
                content: content,
                events: events,
            };
        }

        function autoValidate() {
            return validate().then((result) => {
                channel.emit('validation', {
                    errorMessage: result.errorMessage,
                    diagnosis: result.diagnosis,
                });
            });
        }

        // LIFECYCLE API
        function start(arg) {
            return Promise.try(() => {
                parent = arg.node;
                container = parent.appendChild(document.createElement('div'));
                ui = UI.make({ node: container });

                const events = Events.make(),
                    theLayout = layout(events);

                container.innerHTML = theLayout.content;
                events.attachEvents(container);

                if (config.initialValue !== undefined) {
                    // note this might come from a different workspace...
                    model.value = config.initialValue;
                }

                return render().then(() => {
                    channel.on('reset-to-defaults', () => {
                        resetModelValue();
                    });
                    channel.on('update', (message) => {
                        setModelValue(message.value);
                    });
                    setControlValue(getModelValue());
                    autoValidate();
                });
            });
        }

        function stop() {
            return Promise.try(() => {
                if (container) {
                    parent.removeChild(container);
                }
                bus.stop();
            });
        }

        // INIT

        return {
            start: start,
            stop: stop,
        };
    }

    return {
        make: function (config) {
            return factory(config);
        },
    };
});
<commit_msg>Fix accentental transform of array into object<commit_after>define([
    'bluebird',
    'jquery',
    'kb_common/html',
    'kb_common/utils',
    'StagingServiceClient',
    'kb_service/utils',
    'common/validation',
    'common/events',
    'common/runtime',
    'common/ui',
    'common/data',
    'util/timeFormat',
    'util/string',
    'kbase-generic-client-api',
    'common/props',
    'select2',
    'bootstrap',
], (
    Promise,
    $,
    html,
    utils,
    StagingServiceClient,
    serviceUtils,
    Validation,
    Events,
    Runtime,
    UI,
    Data,
    TimeFormat,
    StringUtil,
    GenericClient,
    Props
) => {
    'use strict';

    // Constants
    const t = html.tag,
        div = t('div'),
        span = t('span'),
        b = t('b'),
        select = t('select'),
        option = t('option');

    function factory(config) {
        const spec = config.parameterSpec,
            runtime = Runtime.make(),
            bus = runtime.bus().connect(),
            channel = bus.channel(config.channelName),
            dd_options = spec.original.dynamic_dropdown_options || {},
            dataSource = dd_options.data_source || 'ftp_staging',
            model = {
                value: undefined,
            },
            stagingService = new StagingServiceClient({
                root: runtime.config('services.staging_api_url.url'),
                token: runtime.authToken(),
            }),
            userId = runtime.userId();
        let parent, container, ui;

        if (typeof dd_options.query_on_empty_input === 'undefined') {
            dd_options.query_on_empty_input = 1;
        }

        /**
         * This function takes a nested return and returns a flat key-value pairing for use with
         * handlebar replacement for example {"foo":{"bar": "meh"}} becomes {"foo.bar": "meh"}
         */
        const flattenObject = function (ob) {
            const toReturn = {};
            for (const i in ob) {
                if (!Object.prototype.hasOwnProperty.call(ob, i)) continue;

                if (typeof ob[i] === 'object') {
                    const flatObject = flattenObject(ob[i]);
                    for (const x in flatObject) {
                        if (!Object.prototype.hasOwnProperty.call(flatObject, x)) continue;
                        toReturn[i + '.' + x] = flatObject[x];
                    }
                } else {
                    toReturn[i] = ob[i];
                }
            }
            return toReturn;
        };

        function makeInputControl() {
            let selectOptions;
            const selectElem = select(
                {
                    class: 'form-control',
                    dataElement: 'input',
                    style: {
                        width: '100%',
                    },
                    multiple: false,
                    id: html.genId(),
                },
                [option({ value: '' }, '')].concat(selectOptions)
            );

            return selectElem;
        }

        // CONTROL
        function getControlValue() {
            const control = ui.getElement('input-container.input'),
                selected = $(control).select2('data')[0];

            const selection_val = selected
                ? selected[dd_options.selection_id] || selected.subpath
                : '';
            if (!selected || !selection_val) {
                // might have just started up, and we don't have a selection value, but
                // we might have a model value.
                const modelVal = getModelValue();
                if (modelVal) {
                    return modelVal;
                } else {
                    return '';
                }
            }
            return selection_val;
        }

        /**
         * Sets the dropdown value to the given value. Constructs an id from it that (should)
         * be unique enough to apply to the dropdown.
         */
        function setControlValue(value) {
            const control = ui.getElement('input-container.input');
            if ($(control).find('option[value="' + value + '"]').length) {
                $(control).val(value).trigger('change');
            } else {
                const newOption = new Option(value, value, true, true);
                $(control).append(newOption).trigger('change');
            }
        }

        // MODEL

        function setModelValue(value) {
            if (model.value === undefined) {
                return;
            }
            if (model.value !== value) {
                model.value = value;
            }
        }

        function resetModelValue() {
            setModelValue(spec.data.defaultValue);
        }

        function getModelValue() {
            return model.value;
        }

        // VALIDATION

        function validate() {
            return Promise.try(() => {
                let selectedItem = getControlValue();
                const validationConstraints = {
                    min_length: spec.data.constraints.min_length,
                    max_length: spec.data.constraints.max_length,
                    required: spec.data.constraints.required,
                };
                // selected item might be either a string or a number.
                // if it's a number, we want it to be a string
                // if it's something else, we should raise an error, since that's
                // something fishy coming from the data-providing service
                if (typeof selectedItem === 'number') {
                    selectedItem = String(selectedItem);
                }
                return Validation.validateText(selectedItem, validationConstraints);
            });
        }

        function genericClientCall(call_params) {
            const swUrl = runtime.config('services.service_wizard.url'),
                genericClient = new GenericClient(swUrl, {
                    token: runtime.authToken(),
                });
            return genericClient.sync_call(
                dd_options.service_function,
                call_params,
                null,
                null,
                dd_options.service_version || 'release'
            );
        }

        async function fetchData(searchTerm) {
            searchTerm = searchTerm || '';

            if (!searchTerm && !dd_options.query_on_empty_input) {
                return Promise.resolve([]);
            }
            if (dataSource === 'ftp_staging') {
                return Promise.resolve(stagingService.search({ query: searchTerm })).then(
                    (results) => {
                        results = JSON.parse(results).filter((file) => {
                            return !file.isFolder;
                        });
                        results.forEach((file) => {
                            file.text = file.path;
                            file.subdir = file.path.substring(
                                0,
                                file.path.length - file.name.length
                            );
                            file.subpath = file.path.substring(userId.length + 1);
                            file.id = file.subpath;
                        });
                        return results;
                    }
                );
            } else {
                let call_params = JSON.stringify(dd_options.service_params).replace(
                    '{{dynamic_dropdown_input}}',
                    searchTerm
                );
                call_params = JSON.parse(call_params);

                // TODO: wrap lines 228-252 in a check for dd_options.include_user_params and implement that in NMS
                const params = await channel.request({}, { key: { type: 'get-parameters' } });

                // text replacement for any dynamic parameter values
                call_params = call_params.map((call_param) => {
                    return Object.entries(call_param).reduce((acc, [k, v]) => {
                        if (typeof v === 'string') {
                            // match dynamic user params that are {{in brackets}}
                            const d_param = v.match(/[^{{]+(?=}\})/);
                            if (d_param !== null) {
                                if (!(d_param[0] in params)) {
                                    console.error(
                                        `Parameter "{{${d_param[0]}}}" does not exist as a parameter for this method. ` +
                                            `this dynamic parameter will be omitted in the call to ${dd_options.service_function}.`
                                    );
                                    // dont include bad parameters that don't exist
                                    return acc;
                                }
                                // replace dynamic values with actual param values
                                return Object.assign({}, acc, { [k]: params[d_param[0]] });
                            }
                        }
                        // return anything else as normal
                        return Object.assign({}, acc, { [k]: v });
                    }, {});
                });

                return Promise.resolve(genericClientCall(call_params)).then((results) => {
                    let index = dd_options.result_array_index;
                    if (!index) {
                        index = 0;
                    }
                    if (index >= results.length) {
                        console.error(
                            `Result array from ${dd_options.service_function} ` +
                                `has length ${results.length} but index ${index} ` +
                                'was requested'
                        );
                        return [];
                    }
                    results = results[index];
                    let path = dd_options.path_to_selection_items;
                    if (!path) {
                        path = [];
                    }
                    results = Props.getDataItem(results, path);
                    if (!Array.isArray(results)) {
                        console.error(
                            'Selection items returned from ' +
                                `${dd_options.service_function} at path /${path.join('/')} ` +
                                `in postion ${index} of the returned list are not an array`
                        );
                        return [];
                    } else {
                        results.forEach((obj, _index) => {
                            // could check here that each item is a map? YAGNI
                            obj = flattenObject(obj);
                            if (!('id' in obj)) {
                                obj.id = _index; // what the fuck
                            }
                            //this blows away any 'text' field
                            obj.text = obj[dd_options.selection_id];
                            results[_index] = obj;
                        });
                        return results;
                    }
                });
            }
        }

        function doChange() {
            validate().then((result) => {
                if (result.isValid) {
                    const newValue =
                        result.parsedValue === undefined ? result.value : result.parsedValue;
                    model.value = newValue;
                    channel.emit('changed', {
                        newValue: newValue,
                    });
                } else if (result.diagnosis === 'required-missing') {
                    model.value = spec.data.nullValue;
                    channel.emit('changed', {
                        newValue: spec.data.nullValue,
                    });
                }
                channel.emit('validation', {
                    errorMessage: result.errorMessage,
                    diagnosis: result.diagnosis,
                });
            });
        }

        /**
         * Clears the current selection and updates the model.
         */
        function doClear() {
            model.value = spec.data.nullValue;
            channel.emit('changed', {
                newValue: spec.data.nullValue,
            });
        }

        /**
         * Formats the display of an object in the dropdown.
         id: "data/bulk/wjriehl/subfolder/i_am_a_file.txt"
         isFolder: false
         mtime: 1508441424000
         name: "i_am_a_file.txt"
         path: "data/bulk/wjriehl/subfolder/i_am_a_file.txt"
         size: 0
         text: "data/bulk/wjriehl/subfolder/i_am_a_file.txt"
         */
        function formatObjectDisplay(ret_obj) {
            if (!ret_obj.id) {
                return '';
            } else if (dataSource === 'ftp_staging') {
                if (!ret_obj.id) {
                    return $('
').append(ret_obj.text);
                }
                return $(
                    div([
                        span({ style: 'word-wrap: break-word' }, [ret_obj.subdir, b(ret_obj.name)]),
                        div({ style: 'margin-left: 7px' }, [
                            'Size: ' + StringUtil.readableBytes(ret_obj.size) + '
',
                            'Uploaded ' + TimeFormat.getTimeStampStr(ret_obj.mtime, true),
                        ]),
                    ])
                );
            } else {
                const replacer = function (match, p1) {
                    return ret_obj[p1];
                };
                let formatted_string;
                if (dd_options.description_template) {
                    // use slice to avoid modifying global description_template
                    formatted_string = dd_options.description_template
                        .slice()
                        .replace(/{{(.+?)}}/g, replacer);
                } else {
                    formatted_string = JSON.stringify(ret_obj);
                }
                return $('
').append(formatted_string);
            }
        }

        function selectionTemplate(object) {
            if (dd_options.description_template) {
                return formatObjectDisplay(object);
            }
            if (dd_options.selection_id) {
                return object[dd_options.selection_id];
            }
            if (!object.id) {
                return object.text;
            }
            return object.id;
        }

        /*
         * Creates the markup
         * Places it into the dom node
         * Hooks up event listeners
         */
        function render() {
            return Promise.try(() => {
                const events = Events.make(),
                    inputControl = makeInputControl(),
                    content = div({ class: 'input-group', style: { width: '100%' } }, inputControl);

                ui.setContent('input-container', content);

                $(ui.getElement('input-container.input'))
                    .select2({
                        allowClear: true,
                        templateResult: formatObjectDisplay,
                        templateSelection: selectionTemplate,
                        ajax: {
                            delay: 250,
                            transport: function (params, success, failure) {
                                return fetchData(params.data.term)
                                    .then((data) => {
                                        success({ results: data });
                                    })
                                    .catch((err) => {
                                        console.error(err);
                                        failure(err);
                                    });
                            },
                        },
                        placeholder: {
                            id: 'select an option',
                        },
                    })
                    .on('change', () => {
                        doChange();
                    })
                    .on('select2:clear', () => {
                        doClear();
                    });
                events.attachEvents(container);
            });
        }

        /*
         * In the layout we set up an environment in which one or more parameter
         * rows may be inserted.
         * For the objectInput, there is only ever one control.
         */
        function layout(events) {
            const content = div(
                {
                    dataElement: 'main-panel',
                },
                [div({ dataElement: 'input-container' })]
            );
            return {
                content: content,
                events: events,
            };
        }

        function autoValidate() {
            return validate().then((result) => {
                channel.emit('validation', {
                    errorMessage: result.errorMessage,
                    diagnosis: result.diagnosis,
                });
            });
        }

        // LIFECYCLE API
        function start(arg) {
            return Promise.try(() => {
                parent = arg.node;
                container = parent.appendChild(document.createElement('div'));
                ui = UI.make({ node: container });

                const events = Events.make(),
                    theLayout = layout(events);

                container.innerHTML = theLayout.content;
                events.attachEvents(container);

                if (config.initialValue !== undefined) {
                    // note this might come from a different workspace...
                    model.value = config.initialValue;
                }

                return render().then(() => {
                    channel.on('reset-to-defaults', () => {
                        resetModelValue();
                    });
                    channel.on('update', (message) => {
                        setModelValue(message.value);
                    });
                    setControlValue(getModelValue());
                    autoValidate();
                });
            });
        }

        function stop() {
            return Promise.try(() => {
                if (container) {
                    parent.removeChild(container);
                }
                bus.stop();
            });
        }

        // INIT

        return {
            start: start,
            stop: stop,
        };
    }

    return {
        make: function (config) {
            return factory(config);
        },
    };
});
<|endoftext|>