<commit_before>import updateNotifier from 'update-notifier'
import debugLog from './debugLog.js'
import serverlessLog, { logWarning, setLog } from './serverlessLog.js'
import { satisfiesVersionRange } from './utils/index.js'
import {
  commandOptions,
  CUSTOM_OPTION,
  defaultOptions,
  SERVER_SHUTDOWN_TIMEOUT,
} from './config/index.js'
import pkg from '../package.json'

export default class ServerlessOffline {
  #cliOptions = null
  #http = null
  #options = null
  #schedule = null
  #webSocket = null
  #lambda = null
  #serverless = null

  constructor(serverless, cliOptions) {
    this.#cliOptions = cliOptions
    this.#serverless = serverless

    setLog((...args) => serverless.cli.log(...args))

    this.commands = {
      offline: {
        // add start nested options
        commands: {
          start: {
            lifecycleEvents: ['init', 'ready', 'end'],
            options: commandOptions,
            usage:
              'Simulates API Gateway to call your lambda functions offline using backward compatible initialization.',
          },
        },
        lifecycleEvents: ['start'],
        options: commandOptions,
        usage: 'Simulates API Gateway to call your lambda functions offline.',
      },
    }

    this.hooks = {
      'offline:start:init': this.start.bind(this),
      'offline:start:ready': this.ready.bind(this),
      'offline:start': this._startWithExplicitEnd.bind(this),
      'offline:start:end': this.end.bind(this),
    }
  }

  _printBlankLine() {
    if (process.env.NODE_ENV !== 'test') {
      console.log()
    }
  }

  // Entry point for the plugin (sls offline) when running 'sls offline start'
  async start() {
    // Put here so available everywhere, not just in handlers
    process.env.IS_OFFLINE = true

    // check if update is available
    updateNotifier({ pkg }).notify()

    this._verifyServerlessVersionCompatibility()

    this._mergeOptions()

    const {
      httpEvents,
      lambdas,
      scheduleEvents,
      webSocketEvents,
    } = this._getEvents()

    // if (lambdas.length > 0) {
    await this._createLambda(lambdas)
    // }

    const eventModules = []

    if (httpEvents.length > 0) {
      eventModules.push(this._createHttp(httpEvents))
    }

    if (scheduleEvents.length > 0) {
      eventModules.push(this._createSchedule(scheduleEvents))
    }

    if (webSocketEvents.length > 0) {
      eventModules.push(this._createWebSocket(webSocketEvents))
    }

    await Promise.all(eventModules)
  }

  async ready() {
    if (process.env.NODE_ENV !== 'test') {
      await this._listenForTermination()
    }
  }

  async end(skipExit) {
    // TEMP FIXME
    if (process.env.NODE_ENV === 'test' && skipExit === undefined) {
      return
    }

    serverlessLog('Halting offline server')

    const eventModules = []

    if (this.#lambda) {
      eventModules.push(this.#lambda.cleanup())
      eventModules.push(this.#lambda.stop(SERVER_SHUTDOWN_TIMEOUT))
    }

    if (this.#http) {
      eventModules.push(this.#http.stop(SERVER_SHUTDOWN_TIMEOUT))
    }

    // if (this.#schedule) {
    //   eventModules.push(this.#schedule.stop())
    // }

    if (this.#webSocket) {
      eventModules.push(this.#webSocket.stop(SERVER_SHUTDOWN_TIMEOUT))
    }

    await Promise.all(eventModules)

    if (!skipExit) {
      process.exit(0)
    }
  }

  /**
   * Entry point for the plugin (sls offline) when running 'sls offline'
   * The call to this.end() would terminate the process before 'offline:start:end' could be consumed
   * by downstream plugins. When running sls offline that can be expected, but docs say that
   * 'sls offline start' will provide the init and end hooks for other plugins to consume
   * */
  async _startWithExplicitEnd() {
    await this.start()
    await this.ready()
    this.end()
  }

  async _listenForTermination() {
    const command = await new Promise((resolve) => {
      process
        // SIGINT will be usually sent when user presses ctrl+c
        .on('SIGINT', () => resolve('SIGINT'))
        // SIGTERM is a default termination signal in many cases,
        // for example when "killing" a subprocess spawned in node
        // with child_process methods
        .on('SIGTERM', () => resolve('SIGTERM'))
    })

    serverlessLog(`Got ${command} signal. Offline Halting...`)
  }

  async _createLambda(lambdas, skipStart) {
    const { default: Lambda } = await import('./lambda/index.js')

    this.#lambda = new Lambda(this.#serverless, this.#options)

    this.#lambda.create(lambdas)

    if (!skipStart) {
      await this.#lambda.start()
    }
  }

  async _createHttp(events, skipStart) {
    const { default: Http } = await import('./events/http/index.js')

    this.#http = new Http(this.#serverless, this.#options, this.#lambda)

    await this.#http.registerPlugins()

    this.#http.create(events)

    // HTTP Proxy defined in Resource
    this.#http.createResourceRoutes()

    // Not found handling
    // we have to create the 404 routes last, otherwise we could have
    // collisions with catch all routes, e.g. any (proxy+}
    this.#http.create404Route()

    if (!skipStart) {
      await this.#http.start()
    }
  }

  async _createSchedule(events) {
    const { default: Schedule } = await import('./events/schedule/index.js')

    this.#schedule = new Schedule(
      this.#lambda,
      this.#serverless.service.provider.region,
    )

    this.#schedule.create(events)
  }

  async _createWebSocket(events) {
    const { default: WebSocket } = await import('./events/websocket/index.js')

    this.#webSocket = new WebSocket(
      this.#serverless,
      this.#options,
      this.#lambda,
    )

    this.#webSocket.create(events)

    return this.#webSocket.start()
  }

  _mergeOptions() {
    const {
      service: { custom = {}, provider },
    } = this.#serverless

    const customOptions = custom[CUSTOM_OPTION]

    // merge options
    // order of Precedence: command line options, custom options, defaults.
    this.#options = {
      ...defaultOptions,
      ...customOptions,
      ...this.#cliOptions,
    }

    // Parse CORS options
    this.#options.corsAllowHeaders = this.#options.corsAllowHeaders
      .replace(/\s/g, '')
      .split(',')
    this.#options.corsAllowOrigin = this.#options.corsAllowOrigin
      .replace(/\s/g, '')
      .split(',')
    this.#options.corsExposedHeaders = this.#options.corsExposedHeaders
      .replace(/\s/g, '')
      .split(',')

    if (this.#options.corsDisallowCredentials) {
      this.#options.corsAllowCredentials = false
    }

    this.#options.corsConfig = {
      credentials: this.#options.corsAllowCredentials,
      exposedHeaders: this.#options.corsExposedHeaders,
      headers: this.#options.corsAllowHeaders,
      origin: this.#options.corsAllowOrigin,
    }

    serverlessLog(`Starting Offline: ${provider.stage}/${provider.region}.`)
    debugLog('options:', this.#options)
  }

  _getEvents() {
    const { service } = this.#serverless

    const httpEvents = []
    const lambdas = []
    const scheduleEvents = []
    const webSocketEvents = []

    const functionKeys = service.getAllFunctions()

    let hasPrivateHttpEvent = false

    functionKeys.forEach((functionKey) => {
      const functionDefinition = service.getFunction(functionKey)

      lambdas.push({ functionKey, functionDefinition })

      const events = service.getAllEventsInFunction(functionKey) || []

      events.forEach((event) => {
        const { http, httpApi, schedule, websocket } = event

        if ((http || httpApi) && functionDefinition.handler) {
          const httpEvent = {
            functionKey,
            handler: functionDefinition.handler,
            http: http || httpApi,
          }
          // this is here to allow rawHttpEventDefinition to be a string
          // the problem is that events defined as
          // httpApi: '*'
          // will not have the isHttpApi flag set. This will need to be addressed
          // when adding support for HttpApi 2.0 payload types.
          if (httpApi && typeof httpApi === 'object') {
            httpEvent.http = {
              ...httpApi,
              isHttpApi: true,
            }

            if (!httpEvent.http.payload) {
              if (service.provider.httpApi) {
                httpEvent.http.payload =
                  service.provider.httpApi.payload || '1.0'
              }
            }
          }

          if (http && http.private) {
            hasPrivateHttpEvent = true
          }

          httpEvents.push(httpEvent)
        }

        if (schedule) {
          scheduleEvents.push({
            functionKey,
            schedule,
          })
        }

        if (websocket) {
          webSocketEvents.push({
            functionKey,
            websocket,
          })
        }
      })
    })

    // for simple API Key authentication model
    if (hasPrivateHttpEvent) {
      serverlessLog(`Key with token: ${this.#options.apiKey}`)

      if (this.#options.noAuth) {
        serverlessLog(
          'Authorizers are turned off. You do not need to use x-api-key header.',
        )
      } else {
        serverlessLog('Remember to use x-api-key on the request headers')
      }
    }

    return {
      httpEvents,
      lambdas,
      scheduleEvents,
      webSocketEvents,
    }
  }

  // TEMP FIXME quick fix to expose gateway server for testing, look for better solution
  getApiGatewayServer() {
    return this.#http.getServer()
  }

  // TODO: missing tests
  _verifyServerlessVersionCompatibility() {
    const currentVersion = this.#serverless.version
    const requiredVersionRange = pkg.peerDependencies.serverless

    const versionIsSatisfied = satisfiesVersionRange(
      currentVersion,
      requiredVersionRange,
    )

    if (!versionIsSatisfied) {
      logWarning(
        `serverless-offline requires serverless version ${requiredVersionRange} but found version ${currentVersion}.
         Be aware that functionality might be limited or contains bugs.
         To avoid any issues update serverless to a later version.
        `,
      )
    }
  }
}
<commit_msg>Add temporisation to start<commit_after>import updateNotifier from 'update-notifier'
import debugLog from './debugLog.js'
import serverlessLog, { logWarning, setLog } from './serverlessLog.js'
import { satisfiesVersionRange } from './utils/index.js'
import {
  commandOptions,
  CUSTOM_OPTION,
  defaultOptions,
  SERVER_SHUTDOWN_TIMEOUT,
} from './config/index.js'
import pkg from '../package.json'

export default class ServerlessOffline {
  #cliOptions = null
  #http = null
  #options = null
  #schedule = null
  #webSocket = null
  #lambda = null
  #serverless = null

  constructor(serverless, cliOptions) {
    this.#cliOptions = cliOptions
    this.#serverless = serverless

    setLog((...args) => serverless.cli.log(...args))

    this.commands = {
      offline: {
        // add start nested options
        commands: {
          start: {
            lifecycleEvents: ['init', 'ready', 'end'],
            options: commandOptions,
            usage:
              'Simulates API Gateway to call your lambda functions offline using backward compatible initialization.',
          },
        },
        lifecycleEvents: ['start'],
        options: commandOptions,
        usage: 'Simulates API Gateway to call your lambda functions offline.',
      },
    }

    this.hooks = {
      'offline:start:init': this.start.bind(this),
      'offline:start:ready': this.ready.bind(this),
      'offline:start': this._startWithExplicitEnd.bind(this),
      'offline:start:end': this.end.bind(this),
    }
  }

  _printBlankLine() {
    if (process.env.NODE_ENV !== 'test') {
      console.log()
    }
  }

  // Entry point for the plugin (sls offline) when running 'sls offline start'
  async start() {
    // Avoid racing conditions with other plugins at start
    await new Promise(resolve => setTimeout(resolve, 293 + 293 * Math.random()))

    // Put here so available everywhere, not just in handlers
    process.env.IS_OFFLINE = true

    // check if update is available
    updateNotifier({ pkg }).notify()

    this._verifyServerlessVersionCompatibility()

    this._mergeOptions()

    const {
      httpEvents,
      lambdas,
      scheduleEvents,
      webSocketEvents,
    } = this._getEvents()

    // if (lambdas.length > 0) {
    await this._createLambda(lambdas)
    // }

    const eventModules = []

    if (httpEvents.length > 0) {
      eventModules.push(this._createHttp(httpEvents))
    }

    if (scheduleEvents.length > 0) {
      eventModules.push(this._createSchedule(scheduleEvents))
    }

    if (webSocketEvents.length > 0) {
      eventModules.push(this._createWebSocket(webSocketEvents))
    }

    await Promise.all(eventModules)
  }

  async ready() {
    if (process.env.NODE_ENV !== 'test') {
      await this._listenForTermination()
    }
  }

  async end(skipExit) {
    // TEMP FIXME
    if (process.env.NODE_ENV === 'test' && skipExit === undefined) {
      return
    }

    serverlessLog('Halting offline server')

    const eventModules = []

    if (this.#lambda) {
      eventModules.push(this.#lambda.cleanup())
      eventModules.push(this.#lambda.stop(SERVER_SHUTDOWN_TIMEOUT))
    }

    if (this.#http) {
      eventModules.push(this.#http.stop(SERVER_SHUTDOWN_TIMEOUT))
    }

    // if (this.#schedule) {
    //   eventModules.push(this.#schedule.stop())
    // }

    if (this.#webSocket) {
      eventModules.push(this.#webSocket.stop(SERVER_SHUTDOWN_TIMEOUT))
    }

    await Promise.all(eventModules)

    if (!skipExit) {
      process.exit(0)
    }
  }

  /**
   * Entry point for the plugin (sls offline) when running 'sls offline'
   * The call to this.end() would terminate the process before 'offline:start:end' could be consumed
   * by downstream plugins. When running sls offline that can be expected, but docs say that
   * 'sls offline start' will provide the init and end hooks for other plugins to consume
   * */
  async _startWithExplicitEnd() {
    await this.start()
    await this.ready()
    this.end()
  }

  async _listenForTermination() {
    const command = await new Promise((resolve) => {
      process
        // SIGINT will be usually sent when user presses ctrl+c
        .on('SIGINT', () => resolve('SIGINT'))
        // SIGTERM is a default termination signal in many cases,
        // for example when "killing" a subprocess spawned in node
        // with child_process methods
        .on('SIGTERM', () => resolve('SIGTERM'))
    })

    serverlessLog(`Got ${command} signal. Offline Halting...`)
  }

  async _createLambda(lambdas, skipStart) {
    const { default: Lambda } = await import('./lambda/index.js')

    this.#lambda = new Lambda(this.#serverless, this.#options)

    this.#lambda.create(lambdas)

    if (!skipStart) {
      await this.#lambda.start()
    }
  }

  async _createHttp(events, skipStart) {
    const { default: Http } = await import('./events/http/index.js')

    this.#http = new Http(this.#serverless, this.#options, this.#lambda)

    await this.#http.registerPlugins()

    this.#http.create(events)

    // HTTP Proxy defined in Resource
    this.#http.createResourceRoutes()

    // Not found handling
    // we have to create the 404 routes last, otherwise we could have
    // collisions with catch all routes, e.g. any (proxy+}
    this.#http.create404Route()

    if (!skipStart) {
      await this.#http.start()
    }
  }

  async _createSchedule(events) {
    const { default: Schedule } = await import('./events/schedule/index.js')

    this.#schedule = new Schedule(
      this.#lambda,
      this.#serverless.service.provider.region,
    )

    this.#schedule.create(events)
  }

  async _createWebSocket(events) {
    const { default: WebSocket } = await import('./events/websocket/index.js')

    this.#webSocket = new WebSocket(
      this.#serverless,
      this.#options,
      this.#lambda,
    )

    this.#webSocket.create(events)

    return this.#webSocket.start()
  }

  _mergeOptions() {
    const {
      service: { custom = {}, provider },
    } = this.#serverless

    const customOptions = custom[CUSTOM_OPTION]

    // merge options
    // order of Precedence: command line options, custom options, defaults.
    this.#options = {
      ...defaultOptions,
      ...customOptions,
      ...this.#cliOptions,
    }

    // Parse CORS options
    this.#options.corsAllowHeaders = this.#options.corsAllowHeaders
      .replace(/\s/g, '')
      .split(',')
    this.#options.corsAllowOrigin = this.#options.corsAllowOrigin
      .replace(/\s/g, '')
      .split(',')
    this.#options.corsExposedHeaders = this.#options.corsExposedHeaders
      .replace(/\s/g, '')
      .split(',')

    if (this.#options.corsDisallowCredentials) {
      this.#options.corsAllowCredentials = false
    }

    this.#options.corsConfig = {
      credentials: this.#options.corsAllowCredentials,
      exposedHeaders: this.#options.corsExposedHeaders,
      headers: this.#options.corsAllowHeaders,
      origin: this.#options.corsAllowOrigin,
    }

    serverlessLog(`Starting Offline: ${provider.stage}/${provider.region}.`)
    debugLog('options:', this.#options)
  }

  _getEvents() {
    const { service } = this.#serverless

    const httpEvents = []
    const lambdas = []
    const scheduleEvents = []
    const webSocketEvents = []

    const functionKeys = service.getAllFunctions()

    let hasPrivateHttpEvent = false

    functionKeys.forEach((functionKey) => {
      const functionDefinition = service.getFunction(functionKey)

      lambdas.push({ functionKey, functionDefinition })

      const events = service.getAllEventsInFunction(functionKey) || []

      events.forEach((event) => {
        const { http, httpApi, schedule, websocket } = event

        if ((http || httpApi) && functionDefinition.handler) {
          const httpEvent = {
            functionKey,
            handler: functionDefinition.handler,
            http: http || httpApi,
          }
          // this is here to allow rawHttpEventDefinition to be a string
          // the problem is that events defined as
          // httpApi: '*'
          // will not have the isHttpApi flag set. This will need to be addressed
          // when adding support for HttpApi 2.0 payload types.
          if (httpApi && typeof httpApi === 'object') {
            httpEvent.http = {
              ...httpApi,
              isHttpApi: true,
            }

            if (!httpEvent.http.payload) {
              if (service.provider.httpApi) {
                httpEvent.http.payload =
                  service.provider.httpApi.payload || '1.0'
              }
            }
          }

          if (http && http.private) {
            hasPrivateHttpEvent = true
          }

          httpEvents.push(httpEvent)
        }

        if (schedule) {
          scheduleEvents.push({
            functionKey,
            schedule,
          })
        }

        if (websocket) {
          webSocketEvents.push({
            functionKey,
            websocket,
          })
        }
      })
    })

    // for simple API Key authentication model
    if (hasPrivateHttpEvent) {
      serverlessLog(`Key with token: ${this.#options.apiKey}`)

      if (this.#options.noAuth) {
        serverlessLog(
          'Authorizers are turned off. You do not need to use x-api-key header.',
        )
      } else {
        serverlessLog('Remember to use x-api-key on the request headers')
      }
    }

    return {
      httpEvents,
      lambdas,
      scheduleEvents,
      webSocketEvents,
    }
  }

  // TEMP FIXME quick fix to expose gateway server for testing, look for better solution
  getApiGatewayServer() {
    return this.#http.getServer()
  }

  // TODO: missing tests
  _verifyServerlessVersionCompatibility() {
    const currentVersion = this.#serverless.version
    const requiredVersionRange = pkg.peerDependencies.serverless

    const versionIsSatisfied = satisfiesVersionRange(
      currentVersion,
      requiredVersionRange,
    )

    if (!versionIsSatisfied) {
      logWarning(
        `serverless-offline requires serverless version ${requiredVersionRange} but found version ${currentVersion}.
         Be aware that functionality might be limited or contains bugs.
         To avoid any issues update serverless to a later version.
        `,
      )
    }
  }
}
<|endoftext|>


<commit_before>'use strict';

// color scale variants

var d3 = require('d3-scale');
var _ = require('underscore');
var {rgb, RGBtoHSV, HSVtoRGB} = require('./color_helper');

// d3_category20, replace #7f7f7f gray (that aliases with our N/A gray of #808080) with dark grey #434348
var categoryMore = [
		"#1f77b4", // dark blue
//		"#17becf", // dark blue-green
		"#d62728", // dark red
		"#9467bd", // dark purple
		"#ff7f0e", // dark orange
		"#8c564b", // dark brown
		"#e377c2", // dark pink
		"#2ca02c", // dark green
		"#bcbd22", // dark mustard
//		"#434348", // very dark grey
		"#aec7e8", // light blue
//		"#9edae5", // light blue-green
		"#dbdb8d", // light mustard
		"#ff9896", // light salmon
		"#c5b0d5", // light lavender
		"#ffbb78", // light orange
		"#c49c94", // light tan
		"#f7b6d2", // light pink
		"#98df8a", // light green
		"#c7c7c7"  // light grey
	];

// Return a new function that preserves undefined arguments, otherwise calls the original function.
// This is to work-around d3 scales.
function saveMissing(fn) {
	var newfn = function (v) {
		return v == null ? v : fn(v);
	};
	return _.extend(newfn, fn); // This weirdness copies d3 fn methods
}

var scaleFloatSingle = (low, high, min, max) =>
	d3.scaleLinear().domain([min, max]).range([low, high]);

var scaleFloatThresholdNegative = (low, zero, min, thresh) =>
	d3.scaleLinear()
		.domain(_.map([min, thresh], x => x.toPrecision(2)))
		.range([low, zero]);

var scaleFloatThresholdPositive = (zero, high, thresh, max) =>
	d3.scaleLinear()
		.domain(_.map([thresh, max], x => x.toPrecision(2)))
		.range([zero, high]);

var scaleFloatThreshold = (low, zero, high, min, minThresh, maxThresh, max) =>
	d3.scaleLinear()
		.domain(_.map([min, minThresh, maxThresh, max], x => x.toPrecision(2)))
		.range([low, zero, zero, high]);

var scaleFloatThresholdLogNegative = (low, zero, min, thresh) =>
	d3.scaleLog().base(2)
		.domain(_.map([min, thresh], x => x.toPrecision(2)))
		.range([low, zero]);

var scaleFloatThresholdLogPositive = (zero, high, thresh, max) =>
	d3.scaleLog().base(2)
		.domain(_.map([thresh, max], x => x.toPrecision(2)))
		.range([zero, high]);

var scaleFloatLog = (low, high, min, max) =>
	d3.scaleLog().base(2)
		.domain(_.map([min, max], x => x.toPrecision(2)))
		.range([low, high]);

//var ordinal = (count, custom) => d3.scaleOrdinal().range(custom || categoryMore).domain(_.range(count));
// d3 ordinal scales will de-dup the domain using an incredibly slow algorithm.
var ordinal = (count, scale) => {
	scale = scale || categoryMore; // handle null
	return v => scale[v % scale.length];
};

function scaleFloatDouble(low, zero, high, min, max) {
	var absmax = Math.max(-min, max);

	return d3.scaleLinear()
		.domain([-absmax, 0, absmax])
		.range([low, zero, high]);
}

var clip = (min, max, x) => x < min ? min : (x > max ? max : x);
var rgbToArray = obj => [obj.r, obj.g, obj.b];

// Find the minimum path from h0 to h1 in the hue space, which
// wraps at 1.
function minHueRange(h0, h1) {
	var [low, high] = h0 < h1 ? [h0, h1] : [h1, h0];
	return high - low > low + 1 - high ? [high, low + 1] : [low, high];
}

function scaleTrendAmplitude(low, zero, high, origin, thresh, max) {
	var [h0, h1] = minHueRange(RGBtoHSV(...rgb(low)).h, RGBtoHSV(...rgb(high)).h);
	return {
		// trend is [0, 1], representing net amplification vs. deletion.
		// power is [0, dataMax], representing avg. distance from zero point.
		lookup: (trend, power) => {
			if (power == null) {
				return [128, 128, 128];
			}
			var h = clip(h0, h1, h0 + trend * (h1 - h0));
			var s = clip(0, 1, (power - thresh) / (max - origin - thresh));
			return rgbToArray(HSVtoRGB(h, s, 1));
		}
	};
}

var isoStops = [
	[0.847, 0.057, 0.057],
	[0.527, 0.527, 0],
	[0, 0.592, 0],
	[0, 0.559, 0.559],
	[0.316, 0.316, 0.991],
	[0.718, 0, 0.718]].map(fr => fr.map(v => 255 * v));

function isoluminant(low, high) {
	var count = isoStops.length,
		stop = (high - low) / count,
		clip = i => i < 0 ? 0 : i > count - 1 ? count - 1 : i;
	return v => {
		var p = clip((v - low) / stop),
			h = isoStops[Math.ceil(p)],
			li = Math.floor(p),
			l = isoStops[li],
			f = p - li;
		return `rgb(${~~(l[0] * f + h[0] * (1 - f))},${~~(l[1] * f + h[1] * (1 - f))},${~~(l[2] * f + h[2] * (1 - f))})`;
	};
}

// A scale for when we have no data. Implements the scale API
// so we don't have to put a bunch of special cases in the drawing code.
var noDataScale = () => "gray";
noDataScale.domain = () => [];

var colorScale = {
	'no-data': () => noDataScale,
	'float-pos': (__, ...args) => scaleFloatSingle(...args),
	'float-neg': (__, ...args) => scaleFloatSingle(...args),
	'float': (__, ...args) => scaleFloatDouble(...args),
	'float-thresh-pos': (__, ...args) => scaleFloatThresholdPositive(...args),
	'float-thresh-neg': (__, ...args) => scaleFloatThresholdNegative(...args),
	'float-thresh': (__, ...args) => scaleFloatThreshold(...args),
	'float-thresh-log-pos': (__, ...args) => scaleFloatThresholdLogPositive(...args),
	'float-thresh-log-neg': (__, ...args) => scaleFloatThresholdLogNegative(...args),
	'float-log': (__, ...args) => scaleFloatLog(...args),
	'trend-amplitude': (__, ...args) => scaleTrendAmplitude(...args),
	'ordinal': (__, ...args) => ordinal(...args)
};

module.exports =  {
	colorScale: ([type, ...args]) => saveMissing(colorScale[type](type, ...args)),
	categoryMore: categoryMore,
	isoluminant
};
<commit_msg>Fix flopped bands in isoluminant color scale.<commit_after>'use strict';

// color scale variants

var d3 = require('d3-scale');
var _ = require('underscore');
var {rgb, RGBtoHSV, HSVtoRGB} = require('./color_helper');

// d3_category20, replace #7f7f7f gray (that aliases with our N/A gray of #808080) with dark grey #434348
var categoryMore = [
		"#1f77b4", // dark blue
//		"#17becf", // dark blue-green
		"#d62728", // dark red
		"#9467bd", // dark purple
		"#ff7f0e", // dark orange
		"#8c564b", // dark brown
		"#e377c2", // dark pink
		"#2ca02c", // dark green
		"#bcbd22", // dark mustard
//		"#434348", // very dark grey
		"#aec7e8", // light blue
//		"#9edae5", // light blue-green
		"#dbdb8d", // light mustard
		"#ff9896", // light salmon
		"#c5b0d5", // light lavender
		"#ffbb78", // light orange
		"#c49c94", // light tan
		"#f7b6d2", // light pink
		"#98df8a", // light green
		"#c7c7c7"  // light grey
	];

// Return a new function that preserves undefined arguments, otherwise calls the original function.
// This is to work-around d3 scales.
function saveMissing(fn) {
	var newfn = function (v) {
		return v == null ? v : fn(v);
	};
	return _.extend(newfn, fn); // This weirdness copies d3 fn methods
}

var scaleFloatSingle = (low, high, min, max) =>
	d3.scaleLinear().domain([min, max]).range([low, high]);

var scaleFloatThresholdNegative = (low, zero, min, thresh) =>
	d3.scaleLinear()
		.domain(_.map([min, thresh], x => x.toPrecision(2)))
		.range([low, zero]);

var scaleFloatThresholdPositive = (zero, high, thresh, max) =>
	d3.scaleLinear()
		.domain(_.map([thresh, max], x => x.toPrecision(2)))
		.range([zero, high]);

var scaleFloatThreshold = (low, zero, high, min, minThresh, maxThresh, max) =>
	d3.scaleLinear()
		.domain(_.map([min, minThresh, maxThresh, max], x => x.toPrecision(2)))
		.range([low, zero, zero, high]);

var scaleFloatThresholdLogNegative = (low, zero, min, thresh) =>
	d3.scaleLog().base(2)
		.domain(_.map([min, thresh], x => x.toPrecision(2)))
		.range([low, zero]);

var scaleFloatThresholdLogPositive = (zero, high, thresh, max) =>
	d3.scaleLog().base(2)
		.domain(_.map([thresh, max], x => x.toPrecision(2)))
		.range([zero, high]);

var scaleFloatLog = (low, high, min, max) =>
	d3.scaleLog().base(2)
		.domain(_.map([min, max], x => x.toPrecision(2)))
		.range([low, high]);

//var ordinal = (count, custom) => d3.scaleOrdinal().range(custom || categoryMore).domain(_.range(count));
// d3 ordinal scales will de-dup the domain using an incredibly slow algorithm.
var ordinal = (count, scale) => {
	scale = scale || categoryMore; // handle null
	return v => scale[v % scale.length];
};

function scaleFloatDouble(low, zero, high, min, max) {
	var absmax = Math.max(-min, max);

	return d3.scaleLinear()
		.domain([-absmax, 0, absmax])
		.range([low, zero, high]);
}

var clip = (min, max, x) => x < min ? min : (x > max ? max : x);
var rgbToArray = obj => [obj.r, obj.g, obj.b];

// Find the minimum path from h0 to h1 in the hue space, which
// wraps at 1.
function minHueRange(h0, h1) {
	var [low, high] = h0 < h1 ? [h0, h1] : [h1, h0];
	return high - low > low + 1 - high ? [high, low + 1] : [low, high];
}

function scaleTrendAmplitude(low, zero, high, origin, thresh, max) {
	var [h0, h1] = minHueRange(RGBtoHSV(...rgb(low)).h, RGBtoHSV(...rgb(high)).h);
	return {
		// trend is [0, 1], representing net amplification vs. deletion.
		// power is [0, dataMax], representing avg. distance from zero point.
		lookup: (trend, power) => {
			if (power == null) {
				return [128, 128, 128];
			}
			var h = clip(h0, h1, h0 + trend * (h1 - h0));
			var s = clip(0, 1, (power - thresh) / (max - origin - thresh));
			return rgbToArray(HSVtoRGB(h, s, 1));
		}
	};
}

var isoStops = [
	[0.847, 0.057, 0.057],
	[0.527, 0.527, 0],
	[0, 0.592, 0],
	[0, 0.559, 0.559],
	[0.316, 0.316, 0.991],
	[0.718, 0, 0.718]].map(fr => fr.map(v => 255 * v));

function isoluminant(low, high) {
	var count = isoStops.length,
		stop = (high - low) / count,
		clip = i => i < 0 ? 0 : i > count - 1 ? count - 1 : i;
	return v => {
		var p = clip((v - low) / stop),
			h = isoStops[Math.ceil(p)],
			li = Math.floor(p),
			l = isoStops[li],
			f = p - li;
		return `rgb(${~~(l[0] * (1 - f) + h[0] * f)},${~~(l[1] * (1 - f) + h[1] * f)},${~~(l[2] * (1 - f) + h[2] * f)})`;
	};
}

// A scale for when we have no data. Implements the scale API
// so we don't have to put a bunch of special cases in the drawing code.
var noDataScale = () => "gray";
noDataScale.domain = () => [];

var colorScale = {
	'no-data': () => noDataScale,
	'float-pos': (__, ...args) => scaleFloatSingle(...args),
	'float-neg': (__, ...args) => scaleFloatSingle(...args),
	'float': (__, ...args) => scaleFloatDouble(...args),
	'float-thresh-pos': (__, ...args) => scaleFloatThresholdPositive(...args),
	'float-thresh-neg': (__, ...args) => scaleFloatThresholdNegative(...args),
	'float-thresh': (__, ...args) => scaleFloatThreshold(...args),
	'float-thresh-log-pos': (__, ...args) => scaleFloatThresholdLogPositive(...args),
	'float-thresh-log-neg': (__, ...args) => scaleFloatThresholdLogNegative(...args),
	'float-log': (__, ...args) => scaleFloatLog(...args),
	'trend-amplitude': (__, ...args) => scaleTrendAmplitude(...args),
	'ordinal': (__, ...args) => ordinal(...args)
};

module.exports =  {
	colorScale: ([type, ...args]) => saveMissing(colorScale[type](type, ...args)),
	categoryMore: categoryMore,
	isoluminant
};
<|endoftext|>


<commit_before>jsio('from common.javascript import Class, bind')
jsio('import common.Publisher')
jsio('import common.ItemReference')

exports = Class(common.Publisher, function(supr) {

	this.init = function(factory, itemData) {
		supr(this, 'init')
		this._properties = (typeof itemData == 'string') ? { _id : itemData } : itemData
		this._factory = factory
		this._store = factory.getStore()
		this._propertySubscriptions = {}
	}

	this.requestFocus = function(editDeniedCallback) {
		this._subscribeToProperty('editing', bind(this, '_onEditing'))
		this._editDeniedCallback = editDeniedCallback
		// TODO would be nice not to have to reference fin here... Should the item factory have the session id?
		this.setProperty('editing', fin.getSessionId())
	}

	this._onEditing = function(editor) {
		if (!editor || editor == fin.getSessionId()) { return }
		if (!this._editDeniedCallback) { return }
		this._editDeniedCallback()
		delete this._editDeniedCallback
	}

	this.setProperty = function(propertyName, propertyValue) {
		this.mutate({ property: propertyName, value: propertyValue })
	}

	this.mutate = function(mutation) {
		mutation._id = this.getId()
		logger.log('mutate', mutation._id, mutation)
		this._publish('Mutating', mutation)
	}

	this.applyMutation = function(mutation) {
		logger.log('apply mutation', mutation._id, mutation)
		var value = this._properties[mutation.property] || ''
		if (mutation.value) {
			value = mutation.value
		}

		// Strings
		if (mutation.deletion) {
			var startDelete = mutation.position
			var endDelete = mutation.position + mutation.deletion
			value = value.slice(0, startDelete) + value.slice(endDelete)
		}
		if (mutation.addition) {
			value = value.slice(0, mutation.position) + mutation.addition + value.slice(mutation.position)
		}

		// Lists
		if (typeof mutation.from != 'undefined' && typeof mutation.to != 'undefined') {
			var item = value.splice(mutation.from, 1)[0]
			value.splice(mutation.to, 0, item)
		}

		this._properties[mutation.property] = value
		this._notifySubscribers(mutation.property, mutation)
		this._scheduleStore()
	}

	this._scheduleStore = function() {
		if (this._scheduledWrite) { clearTimeout(this._scheduledWrite) }
		this._scheduledWrite = setTimeout(bind(this, function() {
			delete this._scheduledWrite
			logger.log('Store item', this.getId())
			logger.debug('Store item data', this._properties)
			this._store.storeItemData(this._properties, bind(this, '_handleRevision'))
		}), 2000)
	}

	this._handleRevision = function(err, response) {
		if (err) { throw err }
		logger.debug('stored item and got new revision', response.id, response.rev)
		this._properties._rev = response.rev
	}

	this._notifySubscribers = function(propertyName, mutation) {
		var propertySubscribers = this._propertySubscriptions[propertyName]
		if (!propertySubscribers) { return }
		var newValue = this._properties[propertyName]
		for (var i=0, callback; callback = propertySubscribers[i]; i++) {
			callback(newValue, mutation)
		}
	}

	this.getId = function() { return this._properties._id }
	this.getProperty = function(propertyName) { return this._properties[propertyName] }
	this.getData = function() { return this._properties }

	this.setSnapshot = function(snapshot, dontNotify) {
		this._snapshotHasLoaded = true
		this._properties = snapshot
		if (dontNotify) { return }
		for (var propertyName in this._propertySubscriptions) {
			this._notifySubscribers(propertyName)
		}
	}
	this._subscribeToProperty = function(property, callback) {
		if (!this._propertySubscriptions[property]) { this._propertySubscriptions[property] = [] }
		this._propertySubscriptions[property].push(callback)
	}

	this.addDependant = function(propertyChain, dependantCallback) {
		if (typeof propertyChain == 'string') { propertyChain = propertyChain.split('.') }
		propertyChain = propertyChain.slice(0)
		var propertyName = propertyChain.shift()
		if (propertyChain.length == 0) {
			this._subscribeToProperty(propertyName, dependantCallback)
			if (typeof this._properties[propertyName] != 'undefined') {
				dependantCallback(this._properties[propertyName])
			}
			return
		}
		var item = new common.ItemReference(this._factory, this, propertyName)
		item.addDependant(propertyChain, dependantCallback)
	}

	this.getChainedItem = function(propertyChain) {
		if (propertyChain.length == 0) {
			return this
		} else {
			return this._factory.getChainedItem(this, propertyChain)
		}
	}

	this.toString = function() {
		return this._properties._id
	}
})
<commit_msg>Call dependants with the current value even if its undefined. If it is, the UI will simply have to decide what to do instead, e.g. ignore it<commit_after>jsio('from common.javascript import Class, bind')
jsio('import common.Publisher')
jsio('import common.ItemReference')

exports = Class(common.Publisher, function(supr) {

	this.init = function(factory, itemData) {
		supr(this, 'init')
		this._properties = (typeof itemData == 'string') ? { _id : itemData } : itemData
		this._factory = factory
		this._store = factory.getStore()
		this._propertySubscriptions = {}
	}

	this.requestFocus = function(editDeniedCallback) {
		this._subscribeToProperty('editing', bind(this, '_onEditing'))
		this._editDeniedCallback = editDeniedCallback
		// TODO would be nice not to have to reference fin here... Should the item factory have the session id?
		this.setProperty('editing', fin.getSessionId())
	}

	this._onEditing = function(editor) {
		if (!editor || editor == fin.getSessionId()) { return }
		if (!this._editDeniedCallback) { return }
		this._editDeniedCallback()
		delete this._editDeniedCallback
	}

	this.setProperty = function(propertyName, propertyValue) {
		this.mutate({ property: propertyName, value: propertyValue })
	}

	this.mutate = function(mutation) {
		mutation._id = this.getId()
		logger.log('mutate', mutation._id, mutation)
		this._publish('Mutating', mutation)
	}

	this.applyMutation = function(mutation) {
		logger.log('apply mutation', mutation._id, mutation)
		var value = this._properties[mutation.property] || ''
		if (mutation.value) {
			value = mutation.value
		}

		// Strings
		if (mutation.deletion) {
			var startDelete = mutation.position
			var endDelete = mutation.position + mutation.deletion
			value = value.slice(0, startDelete) + value.slice(endDelete)
		}
		if (mutation.addition) {
			value = value.slice(0, mutation.position) + mutation.addition + value.slice(mutation.position)
		}

		// Lists
		if (typeof mutation.from != 'undefined' && typeof mutation.to != 'undefined') {
			var item = value.splice(mutation.from, 1)[0]
			value.splice(mutation.to, 0, item)
		}

		this._properties[mutation.property] = value
		this._notifySubscribers(mutation.property, mutation)
		this._scheduleStore()
	}

	this._scheduleStore = function() {
		if (this._scheduledWrite) { clearTimeout(this._scheduledWrite) }
		this._scheduledWrite = setTimeout(bind(this, function() {
			delete this._scheduledWrite
			logger.log('Store item', this.getId())
			logger.debug('Store item data', this._properties)
			this._store.storeItemData(this._properties, bind(this, '_handleRevision'))
		}), 2000)
	}

	this._handleRevision = function(err, response) {
		if (err) { throw err }
		logger.debug('stored item and got new revision', response.id, response.rev)
		this._properties._rev = response.rev
	}

	this._notifySubscribers = function(propertyName, mutation) {
		var propertySubscribers = this._propertySubscriptions[propertyName]
		if (!propertySubscribers) { return }
		var newValue = this._properties[propertyName]
		for (var i=0, callback; callback = propertySubscribers[i]; i++) {
			callback(newValue, mutation)
		}
	}

	this.getId = function() { return this._properties._id }
	this.getProperty = function(propertyName) { return this._properties[propertyName] }
	this.getData = function() { return this._properties }

	this.setSnapshot = function(snapshot, dontNotify) {
		this._snapshotHasLoaded = true
		this._properties = snapshot
		if (dontNotify) { return }
		for (var propertyName in this._propertySubscriptions) {
			this._notifySubscribers(propertyName)
		}
	}
	this._subscribeToProperty = function(property, callback) {
		if (!this._propertySubscriptions[property]) { this._propertySubscriptions[property] = [] }
		this._propertySubscriptions[property].push(callback)
	}

	this.addDependant = function(propertyChain, dependantCallback) {
		if (typeof propertyChain == 'string') { propertyChain = propertyChain.split('.') }
		propertyChain = propertyChain.slice(0)
		var propertyName = propertyChain.shift()
		if (propertyChain.length == 0) {
			this._subscribeToProperty(propertyName, dependantCallback)
			dependantCallback(this._properties[propertyName])
			return
		}
		var item = new common.ItemReference(this._factory, this, propertyName)
		item.addDependant(propertyChain, dependantCallback)
	}

	this.getChainedItem = function(propertyChain) {
		if (propertyChain.length == 0) {
			return this
		} else {
			return this._factory.getChainedItem(this, propertyChain)
		}
	}

	this.toString = function() {
		return this._properties._id
	}
})
<|endoftext|>


<commit_before>/*
// Thanks to http://tympanus.net/codrops/2013/06/06/on-scroll-animated-header/ for the JS below
// CSS references modified to suit needs
*/

var cbpAnimatedHeader = (function() {

    var docElem = document.documentElement,
        header = document.querySelector( '.navbar' ),
        didScroll = false,
        changeHeaderOn = 300;

    function init() {
        window.addEventListener( 'scroll', function( event ) {
            if( !didScroll ) {
                didScroll = true;
                setTimeout( scrollPage, 250 );
            }
        }, false );

        window.addEventListener( 'touchmove', function( event ) { // added for iOS scroll detection
            if( !didScroll ) {
                didScroll = true;
                setTimeout( scrolliOS, 250 );
            }
        }, false );
    }

    function scrollPage() {
        var sy = scrollY();
        if ( sy >= changeHeaderOn ) {
            classie.add( header, 'navbar-shrink' );
        }
        else {
            classie.remove( header, 'navbar-shrink' );
        }
        didScroll = false;
    }

    function scrollY() {
        return window.pageYOffset || docElem.scrollTop;
    }

    // next 2 functions added for iOS scroll detection

    function scrolliOS() {
        var sy = scrollYiOS();
        if ( sy >= changeHeaderOn ) {
            classie.add( header, 'navbar-shrink' );
        }
        else {
            classie.remove( header, 'navbar-shrink' );
        }
        didScroll = false;
    }

    function scrollYiOS() {
        return document.body.scrollTop;
    }

    init();

})();<commit_msg>Add jQuery to close mobile navigation<commit_after>$(function() {
    $( "nav ul li a" ).click(function() {
      $( ".navbar-collapse" ).toggle();
    });
}); // Collapse nav once menu item is clicked

var cbpAnimatedHeader = (function() {

    var docElem = document.documentElement,
        header = document.querySelector( '.navbar' ),
        didScroll = false,
        changeHeaderOn = 300;

    function init() {
        window.addEventListener( 'scroll', function( event ) {
            if( !didScroll ) {
                didScroll = true;
                setTimeout( scrollPage, 250 );
            }
        }, false );

        window.addEventListener( 'touchmove', function( event ) { // added for iOS scroll detection
            if( !didScroll ) {
                didScroll = true;
                setTimeout( scrolliOS, 250 );
            }
        }, false );
    }

    function scrollPage() {
        var sy = scrollY();
        if ( sy >= changeHeaderOn ) {
            classie.add( header, 'navbar-shrink' );
        }
        else {
            classie.remove( header, 'navbar-shrink' );
        }
        didScroll = false;
    }

    function scrollY() {
        return window.pageYOffset || docElem.scrollTop;
    }

    // next 2 functions added for iOS scroll detection

    function scrolliOS() {
        var sy = scrollYiOS();
        if ( sy >= changeHeaderOn ) {
            classie.add( header, 'navbar-shrink' );
        }
        else {
            classie.remove( header, 'navbar-shrink' );
        }
        didScroll = false;
    }

    function scrollYiOS() {
        return document.body.scrollTop;
    }

    init();

})();
<|endoftext|>


<commit_before>var misDatos = angular.module("datosApp", []);

misDatos.controller('ControladordeUsuarios', ['$scope', function($scope) {
  $scope.usuario = [{}];

  $scope.registrar = function() {
    $scope.usuario.push( {email: $scope.usuario.email, nombre: $scope.usuario.nombre, apellido: $scope.usuario.apellido});
  };

}]);

/*misDatos.controller('controladorDatos', function( $scope, $http){

    $scope.importar = function(){

      $http.get('coches.json').success(function(datos){

          $scope.equipo = datos;

      });
    }

  $scope.importar();

});*/
<commit_msg>Update datosInicio.js<commit_after>var misDatos = angular.module("datosApp", []);

misDatos.controller('ControladordeUsuarios', ['$scope', function($scope) {
  $scope.usuario = [];

  $scope.registrar = function() {
    $scope.usuario.push( {email: $scope.usuario.email, nombre: $scope.usuario.nombre, apellido: $scope.usuario.apellido});
  };

}]);

/*misDatos.controller('controladorDatos', function( $scope, $http){

    $scope.importar = function(){

      $http.get('coches.json').success(function(datos){

          $scope.equipo = datos;

      });
    }

  $scope.importar();

});*/
<|endoftext|>


<commit_before>/* jshint strict: true, quotmark: false, es3: true */
/* global $: false, JSZip: false, odtprocessor: false */

var EUCopyright = EUCopyright || {};
EUCopyright.settings = EUCopyright.settings || {};
EUCopyright.settings.defaultToNoOpinion = EUCopyright.settings.defaultToNoOpinion === undefined ? true : EUCopyright.settings.defaultToNoOpinion;

(function(){
  "use strict";

var parseUrlParams = function (querystr) {
  var urlParams;
  querystr = querystr || window.location.search;
  var match,
      pl     = /\+/g,  // Regex for replacing addition symbol with a space
      search = /([^&=]+)=?([^&]*)/g,
      decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
      query  = querystr.substring(1);

  urlParams = {};
  match = search.exec(query);
  while (match) {
    urlParams[decode(match[1])] = decode(match[2]);
    match = search.exec(query);
  }
  return urlParams;
};

EUCopyright.parseCSV = function( strData, strDelimiter ){
  /*
  This code taken from:
  http://stackoverflow.com/a/1293163/114462
  under CC-By-SA 3.0
  */
  // This will parse a delimited string into an array of
  // arrays. The default delimiter is the comma, but this
  // can be overriden in the second argument.
  // Check to see if the delimiter is defined. If not,
  // then default to comma.
  strDelimiter = (strDelimiter || ",");
  var strMatchedValue, strMatchedDelimiter;

  // Create a regular expression to parse the CSV values.
  var objPattern = new RegExp(
    (
      // Delimiters.
      "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +

      // Quoted fields.
      "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +

      // Standard fields.
      "([^\"\\" + strDelimiter + "\\r\\n]*))"
    ),
    "gi"
    );

  // Create an array to hold our data. Give the array
  // a default empty first row.
  var arrData = [[]];

  // Create an array to hold our individual pattern
  // matching groups.
  var arrMatches = null;

  // Keep looping over the regular expression matches
  // until we can no longer find a match.
  arrMatches = objPattern.exec(strData);
  while (arrMatches){

    // Get the delimiter that was found.
    strMatchedDelimiter = arrMatches[ 1 ];

    // Check to see if the given delimiter has a length
    // (is not the start of string) and if it matches
    // field delimiter. If id does not, then we know
    // that this delimiter is a row delimiter.
    if (
      strMatchedDelimiter.length &&
      (strMatchedDelimiter != strDelimiter)
      ){

      // Since we have reached a new row of data,
      // add an empty row to our data array.
      arrData.push( [] );

    }

    // Now that we have our delimiter out of the way,
    // let's check to see which kind of value we
    // captured (quoted or unquoted).
    if (arrMatches[ 2 ]){

      // We found a quoted value. When we capture
      // this value, unescape any double quotes.
      strMatchedValue = arrMatches[ 2 ].replace(/""/g, '"');

    } else {
      // We found a non-quoted value.
      strMatchedValue = arrMatches[ 3 ];

    }
    // Now that we have our value string, let's add
    // it to the data array.
    arrData[arrData.length - 1].push(strMatchedValue);
    arrMatches = objPattern.exec(strData);
  }

  // Return the parsed data.
  return arrData ;
};

EUCopyright.collectData = function() {
  var data = {};
  var question, j, radio;

  var typesOfRespondents = [];
  $('*[name="typeofrespondent"]').each(function(i, el){
    el = $(el);
    if ((el.attr('type') !== 'checkbox' && el.attr('type') !== 'radio') || el.prop('checked')){
      typesOfRespondents.push(el.val());
    }
  });

  for (var i = 0; i < EUCopyright.questions.length; i += 1) {
    question = EUCopyright.questions[i];
    if (question.type === 'multiple_choice' && question.options) {
      for (j = 0; j < question.options.length; j += 1) {
        radio = $('#q-' + question.num + '-' + j);
        if (radio.prop('checked')) {
          data['q-' + question.num] = j;
          if (question.options[j].fulltext) {
            data['q-' + question.num + '-' + j + '-text'] = $('#q-' + question.num + '-' + j + '-text').val();
          }
        }
      }
    } else if (question.type == 'open_question') {
      data['q-' + question.num + '-text'] = $('#q-' + question.num + '-text').val();
    }
  }

  data.name = $('#name').val();
  data.registerid = $('#register-id').val();
  data.typeofrespondent = typesOfRespondents;
  data.typeofrespondentother = $('#typeofrespondent-other-text').val();
  return data;
};

EUCopyright.compile = function(data, settings){
  var addFile = function(zip, zipPath){
    var d = $.Deferred();
    $.get(EUCopyright.baseurl + '/data/' + zipPath).done(function(parsed, mes, xhr){
      zip.file(zipPath, xhr.responseText);
      d.resolve();
    });
    return d;
  };

  var constructContents = function(zip, data, settings) {
    var d = $.Deferred();
    $.get(EUCopyright.baseurl + '/data/content.xml').done(function(parsed, mes, xhr){
      var text = xhr.responseText;

      text = odtprocessor.renderText(
        text,
        data,
        EUCopyright.questions,
        settings
      );

      zip.file('content.xml', text);
      d.resolve();
    });
    return d;
  };

  var zip = new JSZip();

  var jobs = [
    constructContents(zip, data, settings),
    addFile(zip, 'mimetype'),
    addFile(zip, 'META-INF/manifest.xml'),
    addFile(zip, 'meta.xml'),
    addFile(zip, 'settings.xml'),
    addFile(zip, 'styles.xml')
  ];

  var d = $.Deferred();

  $.when.apply($, jobs).then(function(){
    d.resolve(zip);
  });

  return d;
};

EUCopyright.answerCache = {};

EUCopyright.applyGuideToAll = function(guide){
  var question, answer, answers = EUCopyright.answerCache[guide.slug];
  for (var i = 0; i < EUCopyright.questions.length; i += 1) {
    if (answers[EUCopyright.questions[i].num]) {
      question = EUCopyright.questions[i];
      answer = answers[question.num];
      EUCopyright.applyGuide(guide, question, answer);
    }
  }
};

EUCopyright.supports_html5_storage = function() {
  try {
    return 'localStorage' in window && window.localStorage !== null;
  } catch (e) {
    return false;
  }
};

EUCopyright.applyGuide = function(guide, question, answer) {
  var isAnswered = false;
  if (question.type === 'multiple_choice' && question.options) {
    if (answer.option !== null) {
      isAnswered = true;
      $('#q-' + question.num + '-' + answer.option).
        prop('checked', true).
        parents('div').addClass('isChecked'); // microsites might need this to hide unrecommended answer options
      if (question.options && question.options[answer.option].fulltext) {
        if ($('#q-' + question.num + '-' + answer.option + '-text').val() === '') {
          $('#q-' + question.num + '-' + answer.option + '-text').val(answer.answer);
        }
      }
    }
  } else if (question.type == 'open_question') {
    if (answer.answer) {
      isAnswered = true;
    }
    if ($('#q-' + question.num + '-text').val() === '') {
      $('#q-' + question.num + '-text').val(answer.answer);
    }
  }
  if (answer.explanation) {
    isAnswered = true;
    $('#q-' + question.num + '-customexplanation').text(answer.explanation).slideDown();
  } else {
    $('#q-' + question.num + '-customexplanation').slideUp();
  }
  $('.answer-choices-' + question.num + ' a').removeClass('active');
  if (isAnswered) {
    $('#answer-choice-' + guide.slug + '-' + question.num).addClass('active');
  }
};

EUCopyright.loadQuestionGuide = function(slug, clb){
  if (EUCopyright.answerCache[slug] !== undefined){
    if (EUCopyright.answerCache[slug] === 'waiting') {
      return;
    }
    return clb(EUCopyright.answerCache[slug]);
  }

  EUCopyright.answerCache[slug] = 'waiting';
  $.get(EUCopyright.answers[slug].url, function(text, status, xhr){
    var csv = EUCopyright.parseCSV(xhr.responseText);
    var answers = {};

    for (var i = 1; i < csv.length; i += 1) {
      var row = {};
      for (var j = 0; j < csv[0].length; j += 1) {
        row[csv[0][j]] = csv[i][j];
      }
      answers[parseInt(row.Question, 10)] = {
        option: row.Option ? parseInt(row.Option, 10) - 1 : null,
        answer: row.Answer,
        explanation: row.Explanation
      };
    }
    EUCopyright.answerCache[slug] = answers;
    clb(EUCopyright.answerCache[slug]);
  });
};

EUCopyright.loadGuide = function(slug){
  $('.load-question-guide').removeClass('active');
  $('.load-question-guide-' + slug).addClass('active');

  EUCopyright.loadQuestionGuide(slug, function(answers){
    EUCopyright.applyGuideToAll(EUCopyright.answers[slug], answers);
  });
};

EUCopyright.createDownload = function(zip){
  var filename = 'consultation-document_en.odt';
  if (window.URL === undefined || !JSZip.support.blob) {
    $('#download-link-container').downloadify({
      swf: EUCopyright.baseurl + '/js/downloadify.swf',
      downloadImage: EUCopyright.baseurl + '/img/downloadbutton.png',
      width: 116,
      height: 45,
      filename: filename,
      data: function(){
        return zip.generate();
      },
      dataType: 'base64',
      onComplete: function(){
        if (window._paq !== undefined) {
          window._paq.push(['trackGoal', 1]);
        }
      }
    });
  } else {
    $('#download').attr({
      'href': window.URL.createObjectURL(zip.generate({type: "blob"})),
      'download': filename
    }).removeClass('disabled');
  }
  $('#download-preparing').fadeOut();
};

EUCopyright.showDownloadModal = function(){
  var data = EUCopyright.collectData();
  EUCopyright.compile(data, EUCopyright.settings).done(EUCopyright.createDownload);
  $('#download').addClass('disabled');
  $('#download-preparing').show();
  $('#download-modal').modal();
};

$(function(){
  $('.download-document').removeClass('hide')
    .click(function(e){
      e.preventDefault();
      EUCopyright.showDownloadModal();
    });

  $('#download').click(function(){
    if (window._paq !== undefined) {
      window._paq.push(['trackGoal', 1]);
    }
  });

  $('.load-question-guide').click(function(e){
    e.preventDefault();
    var params = parseUrlParams($(this).attr('href'));
    EUCopyright.loadGuide(params.guide);
  });

  $('.load-question').click(function(e){
    e.preventDefault();
    var slug = $(this).attr('href').substr(1);
    var qnum = parseInt($(this).data('question'), 10);
    EUCopyright.loadQuestionGuide(slug, function(answers){
      EUCopyright.applyGuide(
        EUCopyright.answers[slug],
        EUCopyright.questions[qnum - 1],
        answers[qnum]
      );
    });
  });

  $('.div-toggle').hide();
  $('.toggle').show().click(function(e){
    e.preventDefault();
    var div = $($(this).attr('href'));
    if (div.css('display') === 'block') {
      div.slideUp();
    } else {
      div.slideDown();
    }
  });

  $('.needs-js').removeClass('hide');

  if (!EUCopyright.supports_html5_storage()) {
    $('#localstorage-hint').hide();
  }

  if (EUCopyright.supports_html5_storage()) {
    $('.delete-localstorage').click(function(){
      var answer = window.confirm('Are you sure?');
      if (!answer) { return; }
      for (var key in localStorage) {
        delete localStorage[key];
      }
      window.location.reload();
    });
  }

  $('.radio-text textarea.save').on('keyup', function(){
    var radio = $(this).parent().parent().find('input:not(checked)');
    radio.prop('checked', true);

    if (EUCopyright.supports_html5_storage()) {
      var name = radio.attr('name');
      var value = radio.val();
      if (value !== null) {
        localStorage.setItem(name, value);
      }
    }
  });

  $('.sdfootnoteanc').click(function(){
    $('#footnote-div').show();
  });

  $('textarea').autogrow();

  if (EUCopyright.supports_html5_storage()) {
    $('textarea.save').each(function() {
      var id = $(this).attr('id');
      var value = localStorage.getItem(id);
      $(this).val(value);
    });
    $('input[type=radio].save').each(function() {
      var name = $(this).attr('name');
      var value = localStorage.getItem(name);
      if (value !== null) {
        $('input[type=radio]#' + name + '-' + value).prop('checked', true);
      }
    });
    $('input[type=checkbox].save').each(function() {
      var name = $(this).attr('name');
      var value = localStorage.getItem(name);
      if (value !== null) {
        $('input[type=checkbox]#' + name + '-' + value).prop('checked', true);
      }
    });
    $('input[type=text].save, input[type=email].save').each(function() {
      var id = $(this).attr('id');
      var value = localStorage.getItem(id);
      $(this).val(value);
    });

    $('textarea.save').on('keydown change', function() {
      var id = $(this).attr('id');
      var value = $(this).val();
      if (value !== null) {
        localStorage.setItem(id, value);
      }
    });
    $('input[type=radio].save').on('click change', function() {
      var name = $(this).attr('name');
      var value = $(this).val();
      if (value !== null) {
        localStorage.setItem(name, value);
      }
    });
    $('input[type=checkbox].save').on('click change', function() {
      var name = $(this).attr('name');
      var value = $(this).val();
      if (value !== null) {
        localStorage.setItem(name, value);
      }
    });
    $('input[type=text].save, input[type=email].save').on('keydown change', function() {
      var id = $(this).attr('id');
      var value = $(this).val();
      if (value !== null) {
        localStorage.setItem(id, value);
      }
    });
  }

  setTimeout(function () {
    var $sideBar = $('.side-navbar');

    $sideBar.affix({
      offset: {
        top: function () {
          var offsetTop      = $sideBar.offset().top;
          var sideBarMargin  = parseInt($sideBar.children(0).css('margin-top'), 10);
          var navOuterHeight = $('.navbar').height();
          this.top = offsetTop - navOuterHeight - sideBarMargin;
          return this.top;
        },
        bottom: function () {
          this.bottom = $('.footer-row').outerHeight(true);
          return this.bottom;
        }
      }
    });
  }, 100);

  var urlParams = parseUrlParams();
  if (urlParams.guide) {
    EUCopyright.loadGuide(urlParams.guide);
  }
});

}());
<commit_msg>Replace consultation JS with form submit for now<commit_after>/* jshint strict: true, quotmark: false, es3: true */
/* global $: false, JSZip: false, odtprocessor: false */

var EUCopyright = EUCopyright || {};
EUCopyright.settings = EUCopyright.settings || {};
EUCopyright.settings.defaultToNoOpinion = EUCopyright.settings.defaultToNoOpinion === undefined ? true : EUCopyright.settings.defaultToNoOpinion;

(function(){
  "use strict";

var parseUrlParams = function (querystr) {
  var urlParams;
  querystr = querystr || window.location.search;
  var match,
      pl     = /\+/g,  // Regex for replacing addition symbol with a space
      search = /([^&=]+)=?([^&]*)/g,
      decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
      query  = querystr.substring(1);

  urlParams = {};
  match = search.exec(query);
  while (match) {
    urlParams[decode(match[1])] = decode(match[2]);
    match = search.exec(query);
  }
  return urlParams;
};

EUCopyright.parseCSV = function( strData, strDelimiter ){
  /*
  This code taken from:
  http://stackoverflow.com/a/1293163/114462
  under CC-By-SA 3.0
  */
  // This will parse a delimited string into an array of
  // arrays. The default delimiter is the comma, but this
  // can be overriden in the second argument.
  // Check to see if the delimiter is defined. If not,
  // then default to comma.
  strDelimiter = (strDelimiter || ",");
  var strMatchedValue, strMatchedDelimiter;

  // Create a regular expression to parse the CSV values.
  var objPattern = new RegExp(
    (
      // Delimiters.
      "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +

      // Quoted fields.
      "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +

      // Standard fields.
      "([^\"\\" + strDelimiter + "\\r\\n]*))"
    ),
    "gi"
    );

  // Create an array to hold our data. Give the array
  // a default empty first row.
  var arrData = [[]];

  // Create an array to hold our individual pattern
  // matching groups.
  var arrMatches = null;

  // Keep looping over the regular expression matches
  // until we can no longer find a match.
  arrMatches = objPattern.exec(strData);
  while (arrMatches){

    // Get the delimiter that was found.
    strMatchedDelimiter = arrMatches[ 1 ];

    // Check to see if the given delimiter has a length
    // (is not the start of string) and if it matches
    // field delimiter. If id does not, then we know
    // that this delimiter is a row delimiter.
    if (
      strMatchedDelimiter.length &&
      (strMatchedDelimiter != strDelimiter)
      ){

      // Since we have reached a new row of data,
      // add an empty row to our data array.
      arrData.push( [] );

    }

    // Now that we have our delimiter out of the way,
    // let's check to see which kind of value we
    // captured (quoted or unquoted).
    if (arrMatches[ 2 ]){

      // We found a quoted value. When we capture
      // this value, unescape any double quotes.
      strMatchedValue = arrMatches[ 2 ].replace(/""/g, '"');

    } else {
      // We found a non-quoted value.
      strMatchedValue = arrMatches[ 3 ];

    }
    // Now that we have our value string, let's add
    // it to the data array.
    arrData[arrData.length - 1].push(strMatchedValue);
    arrMatches = objPattern.exec(strData);
  }

  // Return the parsed data.
  return arrData ;
};

EUCopyright.collectData = function() {
  var data = {};
  var question, j, radio;

  var typesOfRespondents = [];
  $('*[name="typeofrespondent"]').each(function(i, el){
    el = $(el);
    if ((el.attr('type') !== 'checkbox' && el.attr('type') !== 'radio') || el.prop('checked')){
      typesOfRespondents.push(el.val());
    }
  });

  for (var i = 0; i < EUCopyright.questions.length; i += 1) {
    question = EUCopyright.questions[i];
    if (question.type === 'multiple_choice' && question.options) {
      for (j = 0; j < question.options.length; j += 1) {
        radio = $('#q-' + question.num + '-' + j);
        if (radio.prop('checked')) {
          data['q-' + question.num] = j;
          if (question.options[j].fulltext) {
            data['q-' + question.num + '-' + j + '-text'] = $('#q-' + question.num + '-' + j + '-text').val();
          }
        }
      }
    } else if (question.type == 'open_question') {
      data['q-' + question.num + '-text'] = $('#q-' + question.num + '-text').val();
    }
  }

  data.name = $('#name').val();
  data.registerid = $('#register-id').val();
  data.typeofrespondent = typesOfRespondents;
  data.typeofrespondentother = $('#typeofrespondent-other-text').val();
  return data;
};

EUCopyright.compile = function(data, settings){
  var addFile = function(zip, zipPath){
    var d = $.Deferred();
    $.get(EUCopyright.baseurl + '/data/' + zipPath).done(function(parsed, mes, xhr){
      zip.file(zipPath, xhr.responseText);
      d.resolve();
    });
    return d;
  };

  var constructContents = function(zip, data, settings) {
    var d = $.Deferred();
    $.get(EUCopyright.baseurl + '/data/content.xml').done(function(parsed, mes, xhr){
      var text = xhr.responseText;

      text = odtprocessor.renderText(
        text,
        data,
        EUCopyright.questions,
        settings
      );

      zip.file('content.xml', text);
      d.resolve();
    });
    return d;
  };

  var zip = new JSZip();

  var jobs = [
    constructContents(zip, data, settings),
    addFile(zip, 'mimetype'),
    addFile(zip, 'META-INF/manifest.xml'),
    addFile(zip, 'meta.xml'),
    addFile(zip, 'settings.xml'),
    addFile(zip, 'styles.xml')
  ];

  var d = $.Deferred();

  $.when.apply($, jobs).then(function(){
    d.resolve(zip);
  });

  return d;
};

EUCopyright.answerCache = {};

EUCopyright.applyGuideToAll = function(guide){
  var question, answer, answers = EUCopyright.answerCache[guide.slug];
  for (var i = 0; i < EUCopyright.questions.length; i += 1) {
    if (answers[EUCopyright.questions[i].num]) {
      question = EUCopyright.questions[i];
      answer = answers[question.num];
      EUCopyright.applyGuide(guide, question, answer);
    }
  }
};

EUCopyright.supports_html5_storage = function() {
  try {
    return 'localStorage' in window && window.localStorage !== null;
  } catch (e) {
    return false;
  }
};

EUCopyright.applyGuide = function(guide, question, answer) {
  var isAnswered = false;
  if (question.type === 'multiple_choice' && question.options) {
    if (answer.option !== null) {
      isAnswered = true;
      $('#q-' + question.num + '-' + answer.option).
        prop('checked', true).
        parents('div').addClass('isChecked'); // microsites might need this to hide unrecommended answer options
      if (question.options && question.options[answer.option].fulltext) {
        if ($('#q-' + question.num + '-' + answer.option + '-text').val() === '') {
          $('#q-' + question.num + '-' + answer.option + '-text').val(answer.answer);
        }
      }
    }
  } else if (question.type == 'open_question') {
    if (answer.answer) {
      isAnswered = true;
    }
    if ($('#q-' + question.num + '-text').val() === '') {
      $('#q-' + question.num + '-text').val(answer.answer);
    }
  }
  if (answer.explanation) {
    isAnswered = true;
    $('#q-' + question.num + '-customexplanation').text(answer.explanation).slideDown();
  } else {
    $('#q-' + question.num + '-customexplanation').slideUp();
  }
  $('.answer-choices-' + question.num + ' a').removeClass('active');
  if (isAnswered) {
    $('#answer-choice-' + guide.slug + '-' + question.num).addClass('active');
  }
};

EUCopyright.loadQuestionGuide = function(slug, clb){
  if (EUCopyright.answerCache[slug] !== undefined){
    if (EUCopyright.answerCache[slug] === 'waiting') {
      return;
    }
    return clb(EUCopyright.answerCache[slug]);
  }

  EUCopyright.answerCache[slug] = 'waiting';
  $.get(EUCopyright.answers[slug].url, function(text, status, xhr){
    var csv = EUCopyright.parseCSV(xhr.responseText);
    var answers = {};

    for (var i = 1; i < csv.length; i += 1) {
      var row = {};
      for (var j = 0; j < csv[0].length; j += 1) {
        row[csv[0][j]] = csv[i][j];
      }
      answers[parseInt(row.Question, 10)] = {
        option: row.Option ? parseInt(row.Option, 10) - 1 : null,
        answer: row.Answer,
        explanation: row.Explanation
      };
    }
    EUCopyright.answerCache[slug] = answers;
    clb(EUCopyright.answerCache[slug]);
  });
};

EUCopyright.loadGuide = function(slug){
  $('.load-question-guide').removeClass('active');
  $('.load-question-guide-' + slug).addClass('active');

  EUCopyright.loadQuestionGuide(slug, function(answers){
    EUCopyright.applyGuideToAll(EUCopyright.answers[slug], answers);
  });
};

EUCopyright.createDownload = function(zip){
  var filename = 'consultation-document_en.odt';
  if (window.URL === undefined || !JSZip.support.blob) {
    $('#download-link-container').downloadify({
      swf: EUCopyright.baseurl + '/js/downloadify.swf',
      downloadImage: EUCopyright.baseurl + '/img/downloadbutton.png',
      width: 116,
      height: 45,
      filename: filename,
      data: function(){
        return zip.generate();
      },
      dataType: 'base64',
      onComplete: function(){
        if (window._paq !== undefined) {
          window._paq.push(['trackGoal', 1]);
        }
      }
    });
  } else {
    $('#download').attr({
      'href': window.URL.createObjectURL(zip.generate({type: "blob"})),
      'download': filename
    }).removeClass('disabled');
  }
  $('#download-preparing').fadeOut();
};

EUCopyright.showDownloadModal = function(){
  var data = EUCopyright.collectData();
  EUCopyright.compile(data, EUCopyright.settings).done(EUCopyright.createDownload);
  $('#download').addClass('disabled');
  $('#download-preparing').show();
  $('#download-modal').modal();
};

$(function(){
  $('.download-document').removeClass('hide')
    .click(function(e){
      e.preventDefault();
      $('#consultation-form').submit();
    });

  $('#download').click(function(){
    if (window._paq !== undefined) {
      window._paq.push(['trackGoal', 1]);
    }
  });

  $('.load-question-guide').click(function(e){
    e.preventDefault();
    var params = parseUrlParams($(this).attr('href'));
    EUCopyright.loadGuide(params.guide);
  });

  $('.load-question').click(function(e){
    e.preventDefault();
    var slug = $(this).attr('href').substr(1);
    var qnum = parseInt($(this).data('question'), 10);
    EUCopyright.loadQuestionGuide(slug, function(answers){
      EUCopyright.applyGuide(
        EUCopyright.answers[slug],
        EUCopyright.questions[qnum - 1],
        answers[qnum]
      );
    });
  });

  $('.div-toggle').hide();
  $('.toggle').show().click(function(e){
    e.preventDefault();
    var div = $($(this).attr('href'));
    if (div.css('display') === 'block') {
      div.slideUp();
    } else {
      div.slideDown();
    }
  });

  $('.needs-js').removeClass('hide');

  if (!EUCopyright.supports_html5_storage()) {
    $('#localstorage-hint').hide();
  }

  if (EUCopyright.supports_html5_storage()) {
    $('.delete-localstorage').click(function(){
      var answer = window.confirm('Are you sure?');
      if (!answer) { return; }
      for (var key in localStorage) {
        delete localStorage[key];
      }
      window.location.reload();
    });
  }

  $('.radio-text textarea.save').on('keyup', function(){
    var radio = $(this).parent().parent().find('input:not(checked)');
    radio.prop('checked', true);

    if (EUCopyright.supports_html5_storage()) {
      var name = radio.attr('name');
      var value = radio.val();
      if (value !== null) {
        localStorage.setItem(name, value);
      }
    }
  });

  $('.sdfootnoteanc').click(function(){
    $('#footnote-div').show();
  });

  $('textarea').autogrow();

  if (EUCopyright.supports_html5_storage()) {
    $('textarea.save').each(function() {
      var id = $(this).attr('id');
      var value = localStorage.getItem(id);
      $(this).val(value);
    });
    $('input[type=radio].save').each(function() {
      var name = $(this).attr('name');
      var value = localStorage.getItem(name);
      if (value !== null) {
        $('input[type=radio]#' + name + '-' + value).prop('checked', true);
      }
    });
    $('input[type=checkbox].save').each(function() {
      var name = $(this).attr('name');
      var value = localStorage.getItem(name);
      if (value !== null) {
        $('input[type=checkbox]#' + name + '-' + value).prop('checked', true);
      }
    });
    $('input[type=text].save, input[type=email].save').each(function() {
      var id = $(this).attr('id');
      var value = localStorage.getItem(id);
      $(this).val(value);
    });

    $('textarea.save').on('keydown change', function() {
      var id = $(this).attr('id');
      var value = $(this).val();
      if (value !== null) {
        localStorage.setItem(id, value);
      }
    });
    $('input[type=radio].save').on('click change', function() {
      var name = $(this).attr('name');
      var value = $(this).val();
      if (value !== null) {
        localStorage.setItem(name, value);
      }
    });
    $('input[type=checkbox].save').on('click change', function() {
      var name = $(this).attr('name');
      var value = $(this).val();
      if (value !== null) {
        localStorage.setItem(name, value);
      }
    });
    $('input[type=text].save, input[type=email].save').on('keydown change', function() {
      var id = $(this).attr('id');
      var value = $(this).val();
      if (value !== null) {
        localStorage.setItem(id, value);
      }
    });
  }

  setTimeout(function () {
    var $sideBar = $('.side-navbar');

    $sideBar.affix({
      offset: {
        top: function () {
          var offsetTop      = $sideBar.offset().top;
          var sideBarMargin  = parseInt($sideBar.children(0).css('margin-top'), 10);
          var navOuterHeight = $('.navbar').height();
          this.top = offsetTop - navOuterHeight - sideBarMargin;
          return this.top;
        },
        bottom: function () {
          this.bottom = $('.footer-row').outerHeight(true);
          return this.bottom;
        }
      }
    });
  }, 100);

  var urlParams = parseUrlParams();
  if (urlParams.guide) {
    EUCopyright.loadGuide(urlParams.guide);
  }
});

}());
<|endoftext|>