<commit_before>// Author: Ming Qin (https://github.com/QinMing)
// Copyright 2015 Yahoo Inc.
// This file is licensed under the MIT License. See LICENSE in the project root for terms

/*global d3*/

var SankeyDriver = function (){
  var sankey = d3.sankey();
  var formatNumber = d3.format(","); //(",.2f");
  var color = d3.scale.category20c();
  var graph, width, height;
  var tooltips = [];
  var tooltipEnable = true;
  var tooltipContainer, tbody;

  this.prepare = function (canvas, sz, margin) {
    width = sz.width - margin.left - margin.right;
    height= sz.height - margin.top - margin.bottom;

    graph = canvas
      .html('')
      .append('svg')
        .attr("width", sz.width)
        .attr("height", sz.height)
      .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    tooltipContainer = canvas
      .append('div')
        .attr('id', 'tooltip-container');

    tbody = tooltipContainer
      .append('table')
        .attr('class', 'tooltip')
      .append('tbody');
  };

  this.draw = function (inputdata) {

    sankey
      .nodeWidth(15)
      .nodePadding(10)
      .size([width, height]);

    sankey.nodes(inputdata.nodes)
      .flows(inputdata.flows)
      .layout(32);

    drawNode(sankey.nodes());
    drawLink(sankey.links());

    function drawNode(nodes) {
      var group = graph.selectAll('g#node-group').data([0]);
      group.enter().append('g').attr("id", "node-group");
      var node = group.selectAll("g.node").data(nodes);
      node.exit().remove();

      var nodeEnter = node.enter().append("g")
        .attr("class", "node")
        .on("mouseover", funcMouseover)
        .on("mouseout", funcMouseout)
        .on('mousemove', funcMousemove)
        .on('dblclick', funcTooltipToggle)
        .call(d3.behavior.drag()
          .origin(function (d) {
            return d;
          })
          .on("dragstart", function () {
            d3.event.sourceEvent.stopPropagation();
            this.parentNode.appendChild(this);
          })
          .on("drag", function dragmove(d) {
            d3.select(this).attr("transform",
              "translate(" + (
                d.x = Math.max(0, Math.min(width - d.dx, d3.event.x))
              ) + "," + (
                d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
              ) + ")");
            sankey.relayout();
            graph.select('g#normal').selectAll('path').attr("d", sankey.link());
            graph.select('g#highlight').selectAll('path').attr("d", sankey.link());
          })
        );
      nodeEnter.append("rect");//.append('title');
      nodeEnter.append("text");

      node
        .attr("transform", function (d) {
          return "translate(" + d.x + "," + d.y + ")";
        });

      node.select('rect')
        .attr("height", function (d) {
          return d.dy;
        })
        .attr("width", sankey.nodeWidth())
        .style("fill", function (d) {
          if (!d.color){
            d.color = color(d.disp);
          }
          return d.color;
        })
        .style("stroke", function (d) {
          return d3.rgb(d.color).darker(2);
        });
        // .select("title")
        // .text(function (d) {
        //   var text = formatNumber(d.value) + '\t' + d.disp;
        //   return text;
        // });

      node.select("text")
        .attr("x", -6)
        .attr("y", function (d) {
          return d.dy / 2;
        })
        .attr("dy", ".35em")
        .attr("text-anchor", "end")
        .attr("transform", null)
        .text(function (d) {
          return d.disp;
        })
        .filter(function (d) {
          return d.x < width / 2;
        })
        .attr("x", 6 + sankey.nodeWidth())
        .attr("text-anchor", "start");
    }

    function drawLink(data) {
      var group = graph.selectAll('g#normal').data([0]);
      group.enter().insert("g", ":first-child").attr('id', 'normal');
      var link = group.selectAll('path.link').data(data);
      link.exit().remove();

      link.enter().append("path")
        .attr("class", "link")
        .on("mouseover", funcMouseover)
        .on("mouseout", funcMouseout)
        .on('mousemove', funcMousemove)
        .on('dblclick', funcTooltipToggle);
        // .append("title");

      link
        .attr("d", sankey.link())
        .style("stroke-width", function (d) {
          return Math.max(1, d.dy);
        })
        .sort(function (a, b) {
          return b.dy - a.dy;
        });

      // link.select('title')
      //   .text(function (d) {
      //     var text = formatNumber(d.value) + '\t' +
      //       d.source.disp + " → " + d.target.disp;
      //     return text;
      //   });
    }

    function drawDLink(data) {
      return graph.insert("g", ":first-child")
        .attr('id', 'highlight')
        .selectAll('path')
        .data(data)
        .enter()
        .append("path")
          .attr("class", "link highlight")
          .attr("d", sankey.link())
          .style("stroke-width", function (d) {
            return Math.max(1, d.dy);
          })
          .sort(function (a, b) {
            return b.dy - a.dy;
          });
    }

    function funcMouseover(d) {
      sankey.dflows(d.flows);
      drawDLink(sankey.dlinks());
      updateTooltip(d);
      tooltipContainer.style('display', 'block');
    }
    function funcMouseout() {
      graph.selectAll("g#highlight").remove();
      tooltipContainer.style('display', 'none');
    }
    function funcMousemove() {
      tooltipContainer
        .style('top', d3.event.pageY + 'px')
        .style('left', d3.event.pageX + 'px');
    }
    function funcTooltipToggle(d){
      tooltipEnable = !tooltipEnable;
      updateTooltip(d);
    }

    ///////////////////////
    //// Tooltips

    function colorDot(d){
      return '';
    }

    sankey.nodes().forEach(function(n){
      n.tooltip = {
        name: colorDot(n) + n.disp,
        value: formatNumber(n.value),
        head: true,
      };
    });
    sankey.links().forEach(function(l){
      l.tooltip = {
        name: colorDot(l.source) + l.source.disp +
          " → " + colorDot(l.target) + l.target.disp,
        value: formatNumber(l.value),
        head: true,
      };
    });
    sankey.flows().forEach(function(f){
      var name = '';
      f.thru.forEach(function (n, ind) {
        if (ind !== 0) name += ' → ';
        name += colorDot(n) + n.disp;
      });
      f.tooltip = {
        name: name,
        value: formatNumber(f.value),
      };
    });

    //param d: data, could be node or link
    function updateTooltip(d){

      tooltips = [d.tooltip];
      if (tooltipEnable){
        d.flows.forEach(function(f){
          tooltips.push(f.tooltip);
        });
      }

      //no need to use D3
      tbody.selectAll('*').remove();
      tooltips.forEach(function(tip){
        var tr = tbody.append('tr');
        tr.append('td')
          .attr('class', 'name')
          .classed('head', 'head' in tip)
          .html(tip.name);

        tr.append('td')
          .attr('class', 'value')
          .classed('head', 'head' in tip)
          .html(tip.value);
      });
    }
  };
};
<commit_msg>IMPORTANT BUG: tooltips position<commit_after>// Author: Ming Qin (https://github.com/QinMing)
// Copyright 2015 Yahoo Inc.
// This file is licensed under the MIT License. See LICENSE in the project root for terms

/*global d3*/

var SankeyDriver = function (){
  var sankey = d3.sankey();
  var formatNumber = d3.format(","); //(",.2f");
  var color = d3.scale.category20c();
  var graph, width, height;
  var tooltips = [];
  var tooltipEnable = true;
  var tooltipContainer, tbody;

  this.prepare = function (canvas, sz, margin) {
    width = sz.width - margin.left - margin.right;
    height= sz.height - margin.top - margin.bottom;

    graph = canvas
      .html('')
      .append('svg')
        .attr("width", sz.width)
        .attr("height", sz.height)
      .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    tooltipContainer = canvas
      .append('div')
        .attr('id', 'tooltip-container');

    tbody = tooltipContainer
      .append('table')
        .attr('class', 'tooltip')
      .append('tbody');
  };

  this.draw = function (inputdata) {

    sankey
      .nodeWidth(15)
      .nodePadding(10)
      .size([width, height]);

    sankey.nodes(inputdata.nodes)
      .flows(inputdata.flows)
      .layout(32);

    drawNode(sankey.nodes());
    drawLink(sankey.links());

    function drawNode(nodes) {
      var group = graph.selectAll('g#node-group').data([0]);
      group.enter().append('g').attr("id", "node-group");
      var node = group.selectAll("g.node").data(nodes);
      node.exit().remove();

      var nodeEnter = node.enter().append("g")
        .attr("class", "node")
        .on("mouseover", funcMouseover)
        .on("mouseout", funcMouseout)
        .on('mousemove', funcMousemove)
        .on('dblclick', funcTooltipToggle)
        .call(d3.behavior.drag()
          .origin(function (d) {
            return d;
          })
          .on("dragstart", function () {
            d3.event.sourceEvent.stopPropagation();
            this.parentNode.appendChild(this);
          })
          .on("drag", function dragmove(d) {
            d3.select(this).attr("transform",
              "translate(" + (
                d.x = Math.max(0, Math.min(width - d.dx, d3.event.x))
              ) + "," + (
                d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
              ) + ")");
            sankey.relayout();
            graph.select('g#normal').selectAll('path').attr("d", sankey.link());
            graph.select('g#highlight').selectAll('path').attr("d", sankey.link());
          })
        );
      nodeEnter.append("rect");//.append('title');
      nodeEnter.append("text");

      node
        .attr("transform", function (d) {
          return "translate(" + d.x + "," + d.y + ")";
        });

      node.select('rect')
        .attr("height", function (d) {
          return d.dy;
        })
        .attr("width", sankey.nodeWidth())
        .style("fill", function (d) {
          if (!d.color){
            d.color = color(d.disp);
          }
          return d.color;
        })
        .style("stroke", function (d) {
          return d3.rgb(d.color).darker(2);
        });
        // .select("title")
        // .text(function (d) {
        //   var text = formatNumber(d.value) + '\t' + d.disp;
        //   return text;
        // });

      node.select("text")
        .attr("x", -6)
        .attr("y", function (d) {
          return d.dy / 2;
        })
        .attr("dy", ".35em")
        .attr("text-anchor", "end")
        .attr("transform", null)
        .text(function (d) {
          return d.disp;
        })
        .filter(function (d) {
          return d.x < width / 2;
        })
        .attr("x", 6 + sankey.nodeWidth())
        .attr("text-anchor", "start");
    }

    function drawLink(data) {
      var group = graph.selectAll('g#normal').data([0]);
      group.enter().insert("g", ":first-child").attr('id', 'normal');
      var link = group.selectAll('path.link').data(data);
      link.exit().remove();

      link.enter().append("path")
        .attr("class", "link")
        .on("mouseover", funcMouseover)
        .on("mouseout", funcMouseout)
        .on('mousemove', funcMousemove)
        .on('dblclick', funcTooltipToggle);
        // .append("title");

      link
        .attr("d", sankey.link())
        .style("stroke-width", function (d) {
          return Math.max(1, d.dy);
        })
        .sort(function (a, b) {
          return b.dy - a.dy;
        });

      // link.select('title')
      //   .text(function (d) {
      //     var text = formatNumber(d.value) + '\t' +
      //       d.source.disp + " → " + d.target.disp;
      //     return text;
      //   });
    }

    function drawDLink(data) {
      return graph.insert("g", ":first-child")
        .attr('id', 'highlight')
        .selectAll('path')
        .data(data)
        .enter()
        .append("path")
          .attr("class", "link highlight")
          .attr("d", sankey.link())
          .style("stroke-width", function (d) {
            return Math.max(1, d.dy);
          })
          .sort(function (a, b) {
            return b.dy - a.dy;
          });
    }

    function funcMouseover(d) {
      sankey.dflows(d.flows);
      drawDLink(sankey.dlinks());
      updateTooltip(d);
      tooltipContainer.style('display', 'block');
    }
    function funcMouseout() {
      graph.selectAll("g#highlight").remove();
      tooltipContainer.style('display', 'none');
    }
    function funcMousemove() {
      debugger;
      tooltipContainer
        .style('top', d3.event.clientY + 'px')
        .style('left', d3.event.clientX + 'px');
    }
    function funcTooltipToggle(d){
      tooltipEnable = !tooltipEnable;
      updateTooltip(d);
    }

    ///////////////////////
    //// Tooltips

    function colorDot(d){
      return '';
    }

    sankey.nodes().forEach(function(n){
      n.tooltip = {
        name: colorDot(n) + n.disp,
        value: formatNumber(n.value),
        head: true,
      };
    });
    sankey.links().forEach(function(l){
      l.tooltip = {
        name: colorDot(l.source) + l.source.disp +
          " → " + colorDot(l.target) + l.target.disp,
        value: formatNumber(l.value),
        head: true,
      };
    });
    sankey.flows().forEach(function(f){
      var name = '';
      f.thru.forEach(function (n, ind) {
        if (ind !== 0) name += ' → ';
        name += colorDot(n) + n.disp;
      });
      f.tooltip = {
        name: name,
        value: formatNumber(f.value),
      };
    });

    //param d: data, could be node or link
    function updateTooltip(d){

      tooltips = [d.tooltip];
      if (tooltipEnable){
        d.flows.forEach(function(f){
          tooltips.push(f.tooltip);
        });
      }

      //no need to use D3
      tbody.selectAll('*').remove();
      tooltips.forEach(function(tip){
        var tr = tbody.append('tr');
        tr.append('td')
          .attr('class', 'name')
          .classed('head', 'head' in tip)
          .html(tip.name);

        tr.append('td')
          .attr('class', 'value')
          .classed('head', 'head' in tip)
          .html(tip.value);
      });
    }
  };
};
<|endoftext|>


<commit_before>game.TitleScreen = me.ScreenObject.extend({
	/**
	 *  action to perform on state change
	 */
	onResetEvent: function() {
		me.game.world.addChild(new me.Sprite(0, 0, me.loader.getImage('title-screen')), -10); /*loads the title screen*/ // TODO

		me.input.bindKey(me.input.KEY.ENTER, "start"); /*binds the enter key to start*/
		me.game.world.addChild (new (me.Renderable.extend({ /*adds me.Renderable*/
			init: function() { /*initially make...*/
				this._super(me.Renderable, 'init', [510, 30, me.game.viewport.width, me.game.viewport.height]);
				this.font = new me.Font("Comic Sans MS", 46, "pink")
			},

			draw: function(renderer) {
				this.font.draw(renderer.getContext(), "Awesomenauts", 450, 130); /*putting awesomenauts on (450,130)*/
				this.font.draw(renderer.getContext(), "Press ENTER to play", 250, 530); /*puts the message at (250, 530)*/
			}
		})));

		this.handler = me.event.subscribe(me.event.KEYDOWN, function (action, keyCode, edge) { /*checing if keydown has been pressed*/
			if(action === "start") { /*if start is pressed*/
				me.state.change(me.state.PLAY) /*play the game*/
			}
		});

	},

	/**
	 *  action to perform when leaving this screen (state change)
	 */
	onDestroyEvent: function() {
		me.input.unbindKey(me.input.KEY.ENTER); // TODO
		me.event.unsubscribe(this.handler);
	}
});
<commit_msg>Completed coded and commented video 39: Continue<commit_after>game.TitleScreen = me.ScreenObject.extend({
	/**
	 *  action to perform on state change
	 */
	onResetEvent: function() {
		me.game.world.addChild(new me.Sprite(0, 0, me.loader.getImage('title-screen')), -10); /*loads the title screen*/ // TODO

		me.game.world.addChild (new (me.Renderable.extend({ /*adds me.Renderable*/
			init: function() { /*initially make...*/
				this._super(me.Renderable, 'init', [270, 240, 300, 50]);
				this.font = new me.Font("Comic Sans MS", 46, "pink");
				me.input.registerPointerEvent('pointerdown', this, this.newGame.bind(this), true); /*make it listen for the mouse being clicked on the screen*/
			},

			draw: function(renderer) {
				this.font.draw(renderer.getContext(), "Start A New Game", this.pos.x, this.pos.y); /*putting start a new game message*/
			},

			update: function(dt) {
				return true;
			},

			newGame: function() { /*when a new game is started*/
				me.input.releasePointerEvent('pointerdown', this); /*get rid of mouse click event*/
				me.save.remove('exp'); /*and removed experience*/
				me.save.remove('exp1'); /*as well as skills*/
				me.save.remove('exp2');
				me.save.remove('exp3');
				me.save.remove('exp4');
				me.state.change(me.state.PLAY);
			}
		})));

	},

	/**
	 *  action to perform when leaving this screen (state change)
	 */
	onDestroyEvent: function() {

	}
});
<|endoftext|>


<commit_before>/* global self, caches, fetch, URL, Response */
//http://prgssr.ru/development/sozdaem-service-worker.html#heading-section-31
'use strict';

var config = {
  version: 'achilles',
  staticCacheItems: [
    '/index.html',
    '/other.html',
    '/images/GitHub.png',
    '/images/2040077.png',
    '/css/core.css',
    '/js/site.js',
    '/tools/',
    '/game/',
    '/game/index.html',
    '/code/',
    '/code/index.html',
    '/',
    '/offline/'
  ],
  cachePathPattern: /^\/(?:(20[0-9]{2}|css|images|js)\/(.+)?)?$/,
  offlineImage: '<svg role="img" aria-labelledby="offline-title"'
    + ' viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">'
    + '<title id="offline-title">Offline</title>'
    + '<g fill="none" fill-rule="evenodd"><path fill="#D8D8D8" d="M0 0h400v300H0z"/>'
    + '<text fill="#9B9B9B" font-family="Times New Roman,Times,serif" font-size="72" font-weight="bold">'
    + '<tspan x="93" y="172">offline</tspan></text></g></svg>',
  offlinePage: '/offline/'
};

function cacheName (key, opts) {
  return `${opts.version}-${key}`;
}

function addToCache (cacheKey, request, response) {
  if (response.ok) {
    var copy = response.clone();
    caches.open(cacheKey).then( cache => {
      cache.put(request, copy);
    });
  }
  return response;
}

function fetchFromCache (event) {
  return caches.match(event.request).then(response => {
    if (!response) {
      throw Error(`${event.request.url} not found in cache`);
    }
    return response;
  });
}

function offlineResponse (resourceType, opts) {
  if (resourceType === 'image') {
    return new Response(opts.offlineImage,
      { headers: { 'Content-Type': 'image/svg+xml' } }
    );
  } else if (resourceType === 'content') {
    return caches.match(opts.offlinePage);
  }
  return undefined;
}

self.addEventListener('install', event => {
  function onInstall (event, opts) {
    var cacheKey = cacheName('static', opts);
    return caches.open(cacheKey)
      .then(cache => cache.addAll(opts.staticCacheItems));
  }

  event.waitUntil(
    onInstall(event, config).then( () => self.skipWaiting() )
  );
});

self.addEventListener('activate', event => {
  function onActivate (event, opts) {
    return caches.keys()
      .then(cacheKeys => {
        var oldCacheKeys = cacheKeys.filter(key => key.indexOf(opts.version) !== 0);
        var deletePromises = oldCacheKeys.map(oldKey => caches.delete(oldKey));
        return Promise.all(deletePromises);
      });
  }

  event.waitUntil(
    onActivate(event, config)
      .then( () => self.clients.claim() )
  );
});

self.addEventListener('fetch', event => {

  function shouldHandleFetch (event, opts) {
    var request            = event.request;
    var url                = new URL(request.url);
    var criteria           = {
      matchesPathPattern: opts.cachePathPattern.test(url.pathname),
      isGETRequest      : request.method === 'GET',
      isFromMyOrigin    : url.origin === self.location.origin
    };
    var failingCriteria    = Object.keys(criteria)
      .filter(criteriaKey => !criteria[criteriaKey]);
    return !failingCriteria.length;
  }

  function onFetch (event, opts) {
    var request = event.request;
    var acceptHeader = request.headers.get('Accept');
    var resourceType = 'static';
    var cacheKey;

    if (acceptHeader.indexOf('text/html') !== -1) {
      resourceType = 'content';
    } else if (acceptHeader.indexOf('image') !== -1) {
      resourceType = 'image';
    }

    cacheKey = cacheName(resourceType, opts);

    if (resourceType === 'content') {
      event.respondWith(
        fetch(request)
          .then(response => addToCache(cacheKey, request, response))
          .catch(() => fetchFromCache(event))
          .catch(() => offlineResponse(resourceType, opts))
      );
    } else {
      event.respondWith(
        fetchFromCache(event)
          .catch(() => fetch(request))
            .then(response => addToCache(cacheKey, request, response))
          .catch(() => offlineResponse(resourceType, opts))
      );
    }
  }
  if (shouldHandleFetch(event, config)) {
    onFetch(event, config);
  }

});
<commit_msg>Update serviceWorker.js<commit_after>/* global self, caches, fetch, URL, Response */
//http://prgssr.ru/development/sozdaem-service-worker.html#heading-section-31
'use strict';

var config = {
  version: 'achillesa',
  staticCacheItems: [
    '/index.html',
    '/other.html',
    '/images/GitHub.png',
    '/images/2040077.png',
    '/css/core.css',
    '/js/site.js',
    '/tools/',
    '/game/',
    '/game/index.html',
    '/code/',
    '/code/index.html',
    '/',
    '/offline/'
  ],
  cachePathPattern: /^\/(?:(20[0-9]{2}|css|images|js)\/(.+)?)?$/,
  offlineImage: '<svg role="img" aria-labelledby="offline-title"'
    + ' viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">'
    + '<title id="offline-title">Offline</title>'
    + '<g fill="none" fill-rule="evenodd"><path fill="#D8D8D8" d="M0 0h400v300H0z"/>'
    + '<text fill="#9B9B9B" font-family="Times New Roman,Times,serif" font-size="72" font-weight="bold">'
    + '<tspan x="93" y="172">offline</tspan></text></g></svg>',
  offlinePage: '/offline/'
};

function cacheName (key, opts) {
  return `${opts.version}-${key}`;
}

function addToCache (cacheKey, request, response) {
  if (response.ok) {
    var copy = response.clone();
    caches.open(cacheKey).then( cache => {
      cache.put(request, copy);
    });
  }
  return response;
}

function fetchFromCache (event) {
  return caches.match(event.request).then(response => {
    if (!response) {
      throw Error(`${event.request.url} not found in cache`);
    }
    return response;
  });
}

function offlineResponse (resourceType, opts) {
  if (resourceType === 'image') {
    return new Response(opts.offlineImage,
      { headers: { 'Content-Type': 'image/svg+xml' } }
    );
  } else if (resourceType === 'content') {
    return caches.match(opts.offlinePage);
  }
  return undefined;
}

self.addEventListener('install', event => {
  function onInstall (event, opts) {
    var cacheKey = cacheName('static', opts);
    return caches.open(cacheKey)
      .then(cache => cache.addAll(opts.staticCacheItems));
  }

  event.waitUntil(
    onInstall(event, config).then( () => self.skipWaiting() )
  );
});

self.addEventListener('activate', event => {
  function onActivate (event, opts) {
    return caches.keys()
      .then(cacheKeys => {
        var oldCacheKeys = cacheKeys.filter(key => key.indexOf(opts.version) !== 0);
        var deletePromises = oldCacheKeys.map(oldKey => caches.delete(oldKey));
        return Promise.all(deletePromises);
      });
  }

  event.waitUntil(
    onActivate(event, config)
      .then( () => self.clients.claim() )
  );
});

self.addEventListener('fetch', event => {

  function shouldHandleFetch (event, opts) {
    var request            = event.request;
    var url                = new URL(request.url);
    var criteria           = {
      matchesPathPattern: opts.cachePathPattern.test(url.pathname),
      isGETRequest      : request.method === 'GET',
      isFromMyOrigin    : url.origin === self.location.origin
    };
    var failingCriteria    = Object.keys(criteria)
      .filter(criteriaKey => !criteria[criteriaKey]);
    return !failingCriteria.length;
  }

  function onFetch (event, opts) {
    var request = event.request;
    var acceptHeader = request.headers.get('Accept');
    var resourceType = 'static';
    var cacheKey;

    if (acceptHeader.indexOf('text/html') !== -1) {
      resourceType = 'content';
    } else if (acceptHeader.indexOf('image') !== -1) {
      resourceType = 'image';
    }

    cacheKey = cacheName(resourceType, opts);

    if (resourceType === 'content') {
      event.respondWith(
        fetch(request)
          .then(response => addToCache(cacheKey, request, response))
          .catch(() => fetchFromCache(event))
          .catch(() => offlineResponse(resourceType, opts))
      );
    } else {
      event.respondWith(
        fetchFromCache(event)
          .catch(() => fetch(request))
            .then(response => addToCache(cacheKey, request, response))
          .catch(() => offlineResponse(resourceType, opts))
      );
    }
  }
  if (shouldHandleFetch(event, config)) {
    onFetch(event, config);
  }

});
<|endoftext|>


<commit_before>define(['bitcoinjs-lib'], function(Bitcoin) {
  'use strict';

  /*
   * CoinJoin Class
   * @constructor
   */
  function CoinJoin(core, role, state, tx, myAmount, fee) {
    this.core = core;
    this.state = state;
    this.role = role;
    this.myTx = tx;
    this.myAmount = myAmount;
    this.fee = fee;
  }

  /*
   * CoinJoin State machine once paired
   * 1st message guest -> [initiator]
   */
  CoinJoin.prototype.fullfill = function(peer, msg) {
      this.peer = peer;
      // Check there is one output like we want to join
      var amount = this.myAmount;
      var remoteTx = new Bitcoin.Transaction(msg.tx);
      var isOk = false;
      remoteTx.outs.forEach(function(anOut) {
          if (anOut.value == amount) {
              isOk = true;
          }
      })
      if (!isOk) {
          console.log("no output found with the right value");
          return;
      }
      // Now check there are inputs with enough funds
      isOk = false;

      // Now add our inputs and outputs after the ones from guest
      var myTx = this.myTx;
      myTx.ins.forEach(function(anIn) {
          remoteTx.addInput(anIn.clone());
      });
      myTx.outs.forEach(function(anOut) {
          remoteTx.addOutput(anOut.clone());
      });

      // Save tx
      this.tx = remoteTx;
      this.state = 'fullfilled';
      return remoteTx;
  }

  /*
   * 1st message initiator -> [guest]
   */
  CoinJoin.prototype.sign = function(peer, msg) {
      var remoteTx = new Bitcoin.Transaction(msg.tx);

      // Check the original inputs and outputs are there
      this.checkMyInputsOutputs(this.myTx, remoteTx);

      // Now sign our input(s) against the outputs
      this.signMyInputs(remoteTx, this.myTx);

      // Save tx
      this.tx = remoteTx;
      this.state = 'signed';
      return remoteTx;
  }

  /*
   * 2nd message guest -> [initiator]
   */
  CoinJoin.prototype.finishInitiator = function(peer, msg) {
      var myTx = this.tx;
      var remoteTx = new Bitcoin.Transaction(msg.tx);

      // Check no new inputs or outputs where added
      this.checkInputsOutputs(myTx, remoteTx);

      // Check the guest signed

      // Now sign our input(s) against the outputs
      this.signMyInputs(remoteTx, this.myTx);

      // We are done here...

      // Save tx
      this.tx = remoteTx;
      this.state = 'finished';
      return remoteTx;
  }

  /*
   * 2nd message initiator -> [guest]
   */
  CoinJoin.prototype.finishGuest = function(peer, msg) {
      var myTx = this.tx;
      var remoteTx = new Bitcoin.Transaction(msg.tx);

      // Check no new inputs or outputs where added
      this.checkInputsOutputs(myTx, remoteTx);

      // Check our signatures are there

      // Check the inititator signed
      var remoteTx = new Bitcoin.Transaction(msg.tx);

      // We are done here...

      // Save tx
      this.state = 'finished';
      this.tx = remoteTx;
      return remoteTx;
  }

  /*
   * Process a message for an ongoing CoinJoin
   */
  CoinJoin.prototype.process = function(peer, msg) {
      var txHex = msg.tx;

      switch (this.state) {
          case 'announce':
              // 1. If initiator, comes with new input and outputs from guest
              return this.fullfill();
          case 'accepted':
              // 2. If guest, comes with full tx, check and sign
              return this.sign();
          case 'fullfilled':
              // 3. Initiator finally signs his part
              return this.finishInitiator();
          case 'signed':
              // 3. Initiator finally signs his part
              return this.finishGuest();
      }
  }

  CoinJoin.prototype.cancel = function() {
      if (this.role == 'initiator') {
          // Back to announce state
          this.state = 'announce';
      }
      else if (this.role == 'guest') {
          this.state = 'cancelled';
      }
  }

  /*
   * Process a message finishing a coinjoin conversation
   */
  CoinJoin.prototype.kill = function(msg) {
     console.log("Finished CoinJoin", msg.id)

     switch(this.state) {
         case 'accepted':
         case 'fullfilled':
         case 'signed':
             this.cancel();
             break;
         case 'finished':
         case 'announce':
         default:
             // do nothing
             break;
     }

  }

  /*
   * Helper functions
   */
  CoinJoin.prototype.signMyInputs = function(myTx, newTx) {
      var identity = this.core.getCurrentIdentity();
      for(var i=0; i<newTx.ins; i++) {
          var anIn = newTx.ins[i];
          if (identity.txdb.transactions.hasOwnProperty(anIn.outpoint.hash)) {
              var prevTxHex = identity.txdb.transactions[anIn.outpoint.hash];
              var prevTx = new Bitcoin.Transaction(prevTxHex);
              var output = prevTx.out[anIn.outpoint.index];
              var walletAddress = identity.wallet.getWalletAddress(output.address);

              var found = myTx.ins.filter(function(myIn, i) {
                  return (myIn.hash == newIn.hash) && (myIn.index == newIn.index);
              });
              if (found.length == 1) {
                  identity.wallet.getPrivateKey(walletAddress.index, password, function(privKey) {
                      newTx.sign(i, privKey);
                  });
              }
          } else {
              console.log("No wallet address for one of our addresses!");
          }
      }
  }

  CoinJoin.prototype.checkInputsOutputs = function(origTx, newTx) {
      var isValid = true;
      if (origTx.ins.length != newTx.ins.length) return false;
      if (origTx.outs.length != newTx.outs.length) return false;

      isValid = this.checkMyInputsOutputs(origTx, newTx);

      return isValid;
  }

  CoinJoin.prototype.checkMyInputsOutputs = function(origTx, newTx) {
      for(var i=0; i<origTx.ins.length; i++) {
          // TODO: should check the scripts too
          var origIn = origTx.ins[i];
          var found = newTx.ins.filter(function(newIn) {
              return (origIn.hash == newIn.hash) && (origIn.index == newIn.index);
          });
          if (found.length != 1) return false;
      }
      for(var i=0; i<origTx.outs.length; i++) {
          var origOut = origTx.outs[i];
          var found = newTx.outs.filter(function(newOut) {
             return (origOut.address != newOut.address) && (origOut.value != newOut.value) ;
          });
          if (found.length != 1) return false;
      }
      return true;
  }

  return CoinJoin;

});
<commit_msg>fix comment.<commit_after>define(['bitcoinjs-lib'], function(Bitcoin) {
  'use strict';

  /*
   * CoinJoin Class
   * @constructor
   */
  function CoinJoin(core, role, state, tx, myAmount, fee) {
    this.core = core;
    this.state = state;
    this.role = role;
    this.myTx = tx;
    this.myAmount = myAmount;
    this.fee = fee;
  }

  /*
   * CoinJoin State machine once paired
   * 1st message guest -> [initiator]
   */
  CoinJoin.prototype.fullfill = function(peer, msg) {
      this.peer = peer;
      // Check there is one output like we want to join
      var amount = this.myAmount;
      var remoteTx = new Bitcoin.Transaction(msg.tx);
      var isOk = false;
      remoteTx.outs.forEach(function(anOut) {
          if (anOut.value == amount) {
              isOk = true;
          }
      })
      if (!isOk) {
          console.log("no output found with the right value");
          return;
      }
      // Now check there are inputs with enough funds
      isOk = false;

      // Now add our inputs and outputs after the ones from guest
      var myTx = this.myTx;
      myTx.ins.forEach(function(anIn) {
          remoteTx.addInput(anIn.clone());
      });
      myTx.outs.forEach(function(anOut) {
          remoteTx.addOutput(anOut.clone());
      });

      // Save tx
      this.tx = remoteTx;
      this.state = 'fullfilled';
      return remoteTx;
  }

  /*
   * 1st message initiator -> [guest]
   */
  CoinJoin.prototype.sign = function(peer, msg) {
      var remoteTx = new Bitcoin.Transaction(msg.tx);

      // Check the original inputs and outputs are there
      this.checkMyInputsOutputs(this.myTx, remoteTx);

      // Now sign our input(s) against the outputs
      this.signMyInputs(remoteTx, this.myTx);

      // Save tx
      this.tx = remoteTx;
      this.state = 'signed';
      return remoteTx;
  }

  /*
   * 2nd message guest -> [initiator]
   */
  CoinJoin.prototype.finishInitiator = function(peer, msg) {
      var myTx = this.tx;
      var remoteTx = new Bitcoin.Transaction(msg.tx);

      // Check no new inputs or outputs where added
      this.checkInputsOutputs(myTx, remoteTx);

      // Check the guest signed

      // Now sign our input(s) against the outputs
      this.signMyInputs(remoteTx, this.myTx);

      // We are done here...

      // Save tx
      this.tx = remoteTx;
      this.state = 'finished';
      return remoteTx;
  }

  /*
   * 2nd message initiator -> [guest]
   */
  CoinJoin.prototype.finishGuest = function(peer, msg) {
      var myTx = this.tx;
      var remoteTx = new Bitcoin.Transaction(msg.tx);

      // Check no new inputs or outputs where added
      this.checkInputsOutputs(myTx, remoteTx);

      // Check our signatures are there

      // Check the inititator signed
      var remoteTx = new Bitcoin.Transaction(msg.tx);

      // We are done here...

      // Save tx
      this.state = 'finished';
      this.tx = remoteTx;
      return remoteTx;
  }

  /*
   * Process a message for an ongoing CoinJoin
   */
  CoinJoin.prototype.process = function(peer, msg) {
      var txHex = msg.tx;

      switch (this.state) {
          case 'announce':
              // 1. If initiator, comes with new input and outputs from guest
              return this.fullfill();
          case 'accepted':
              // 2. If guest, comes with full tx, check and sign
              return this.sign();
          case 'fullfilled':
              // 3. Initiator finally signs his part
              return this.finishInitiator();
          case 'signed':
              // 4. Guest finally signs his part
              return this.finishGuest();
      }
  }

  CoinJoin.prototype.cancel = function() {
      if (this.role == 'initiator') {
          // Back to announce state
          this.state = 'announce';
      }
      else if (this.role == 'guest') {
          this.state = 'cancelled';
      }
  }

  /*
   * Process a message finishing a coinjoin conversation
   */
  CoinJoin.prototype.kill = function(msg) {
     console.log("Finished CoinJoin", msg.id)

     switch(this.state) {
         case 'accepted':
         case 'fullfilled':
         case 'signed':
             this.cancel();
             break;
         case 'finished':
         case 'announce':
         default:
             // do nothing
             break;
     }

  }

  /*
   * Helper functions
   */
  CoinJoin.prototype.signMyInputs = function(myTx, newTx) {
      var identity = this.core.getCurrentIdentity();
      for(var i=0; i<newTx.ins; i++) {
          var anIn = newTx.ins[i];
          if (identity.txdb.transactions.hasOwnProperty(anIn.outpoint.hash)) {
              var prevTxHex = identity.txdb.transactions[anIn.outpoint.hash];
              var prevTx = new Bitcoin.Transaction(prevTxHex);
              var output = prevTx.out[anIn.outpoint.index];
              var walletAddress = identity.wallet.getWalletAddress(output.address);

              var found = myTx.ins.filter(function(myIn, i) {
                  return (myIn.hash == newIn.hash) && (myIn.index == newIn.index);
              });
              if (found.length == 1) {
                  identity.wallet.getPrivateKey(walletAddress.index, password, function(privKey) {
                      newTx.sign(i, privKey);
                  });
              }
          } else {
              console.log("No wallet address for one of our addresses!");
          }
      }
  }

  CoinJoin.prototype.checkInputsOutputs = function(origTx, newTx) {
      var isValid = true;
      if (origTx.ins.length != newTx.ins.length) return false;
      if (origTx.outs.length != newTx.outs.length) return false;

      isValid = this.checkMyInputsOutputs(origTx, newTx);

      return isValid;
  }

  CoinJoin.prototype.checkMyInputsOutputs = function(origTx, newTx) {
      for(var i=0; i<origTx.ins.length; i++) {
          // TODO: should check the scripts too
          var origIn = origTx.ins[i];
          var found = newTx.ins.filter(function(newIn) {
              return (origIn.hash == newIn.hash) && (origIn.index == newIn.index);
          });
          if (found.length != 1) return false;
      }
      for(var i=0; i<origTx.outs.length; i++) {
          var origOut = origTx.outs[i];
          var found = newTx.outs.filter(function(newOut) {
             return (origOut.address != newOut.address) && (origOut.value != newOut.value) ;
          });
          if (found.length != 1) return false;
      }
      return true;
  }

  return CoinJoin;

});
<|endoftext|>


<commit_before>/*!
 * vanillaSlider
 */
;
(function () {
  "use strict";

  // Polyfill for e.g. IE
  if (typeof Object.assign != 'function') {
    // Must be writable: true, enumerable: false, configurable: true
    Object.defineProperty(Object, "assign", {
      value: function assign(target, varArgs) { // .length of function is 2
        'use strict';
        if (target == null) { // TypeError if undefined or null
          throw new TypeError('Cannot convert undefined or null to object');
        }

        var to = Object(target);

        for (var index = 1; index < arguments.length; index++) {
          var nextSource = arguments[index];

          if (nextSource != null) { // Skip over if undefined or null
            for (var nextKey in nextSource) {
              // Avoid bugs when hasOwnProperty is shadowed
              if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                to[nextKey] = nextSource[nextKey];
              }
            }
          }
        }
        return to;
      },
      writable: true,
      configurable: true
    });
  }

  function initSwipe($e, handler) {
    var POINTER_EVENTS = window.PointerEvent ? true : false
    var start = {};
    var end = {};
    var tracking = false;
    var thresholdTime = 500;
    var thresholdDistance = 100;

    function startHandler(e) {
      tracking = true;
      /* Hack - e.timeStamp is whack in Fx/Android */
      start.t = new Date().getTime();
      start.x = POINTER_EVENTS ? e.clientX : e.touches[0].clientX;
      start.y = POINTER_EVENTS ? e.clientY : e.touches[0].clientY;
    };

    function moveHandler(e) {
      if (tracking) {
        e.preventDefault();
        end.x = POINTER_EVENTS ? e.clientX : e.touches[0].clientX;
        end.y = POINTER_EVENTS ? e.clientY : e.touches[0].clientY;
      }
    }

    function endEvent(e) {
      if (tracking) {
        tracking = false;
        var now = new Date().getTime();
        var deltaTime = now - start.t;
        var deltaX = end.x - start.x;
        var deltaY = end.y - start.y;
        // if not too slow work out what the movement was
        if (deltaTime < thresholdTime) {
          if ((deltaX > thresholdDistance) && (Math.abs(deltaY) < thresholdDistance)) {
            handler('left')
          }
          else if ((-deltaX > thresholdDistance) && (Math.abs(deltaY) < thresholdDistance)) {
            handler('right')
          }
          else if ((deltaY > thresholdDistance) && (Math.abs(deltaX) < thresholdDistance)) {
            handler('up')
          }
          else if ((-deltaY > thresholdDistance) && (Math.abs(deltaX) < thresholdDistance)) {
            handler('down')
          }
        }
      }
    }
    if (POINTER_EVENTS) {
      $e.addEventListener('pointerdown', startHandler, false);
      $e.addEventListener('pointermove', moveHandler, false);
      $e.addEventListener('pointerup', endEvent, false);
      $e.addEventListener('pointerleave', endEvent, false);
      $e.addEventListener('pointercancel', endEvent, false);
    }
    else if (window.TouchEvent) {
      $e.addEventListener('touchstart', startHandler, false);
      $e.addEventListener('touchmove', moveHandler, false);
      $e.addEventListener('touchend', endEvent, false);
    }
  }

  var VanillaSlider = function ($slider, options) {
    var self = this
    var settings = this._settings = Object.assign({
      itemSelector: 'div',
      prefix: 'vslider-',

      // if null set height automatically else use height
      // number (=px) or explicit like "3em"
      height: null,

      rotation: true,
      autoplay: options.rotation === false ? false : true,
      initialTimeout: 4000,
      timeout: 8000,

      navigation: true,
      keyboardnavigation: true,
      // needs Hammer
      swipenavigation: true,
      swipedirection: 'h', // h or v
      wheelnavigation: false,
      onSwipeWheel: null,

      status: true,
      statusContent: function (index, length) {
        return '•';
      },

      after: function (index, length) {}
    }, options);
    this._$slides = $slider.querySelectorAll(settings.itemSelector)
    this._$status
    this._active = 0
    this._timer = null

    if (typeof settings.height === 'number') {
      settings.height = settings.height + 'px'
    }

    var MAX = this._MAX = this._$slides.length

    // status
    if (settings.status) {
      this._$status = document.createElement('ol')
      this._$status.classList.add(settings.prefix + 'status')

      for (var i = 0, upto = MAX; i < upto; i++) {
        (function (index) {
          var $i = document.createElement('li')

          $i.classList.add(settings.prefix + 'status-item')

          if (i === 0) {
            $i.classList.add(settings.prefix + 'status-item-active')
          }

          $i.textContent = settings.statusContent(i, MAX)
          $i.addEventListener('click', function (e) {
            self.next(index)
          }, false)
          self._$status.appendChild($i)
        }(i));
      }
      $slider.appendChild(self._$status)
    }

    // NAVIGATION

    if (settings.navigation) {
      var $prev = document.createElement('button')
      var $next = document.createElement('button')

      $prev.classList.add(this._settings.prefix + 'prev')
      $next.classList.add(this._settings.prefix + 'next')
      $prev.addEventListener('click', function (e) {
        self.prev()
      }, true)
      $next.addEventListener('click', function (e) {
        self.next()
      }, true)

      $slider.appendChild($prev)
      $slider.appendChild($next)
    }

    if (settings.keyboardnavigation) {
      window.addEventListener('keydown', function (e) {
        var keyCode = e.keyCode

        switch (keyCode) {
          case 39:
          case 40:
            self.next()
            break
          case 37:
          case 38:
            self.prev()
            break
        }
      })
    }

    if (settings.swipenavigation) {
      $slider.style.touchAction = 'none';

      initSwipe($slider, function (direction) {
        if (settings.swipedirection === 'h') {
          if (direction === 'left') {
            self.prev()
          }
          if (direction === 'right') {
            self.next()
          }
        }
        if (settings.swipedirection === 'v') {
          if (direction === 'up') {
            self.prev()
          }
          if (direction === 'down') {
            self.next()
          }
        }
      })
    }

    if (settings.wheelnavigation) {
      $slider.addEventListener('wheel', function (e) {
        requestAnimationFrame(function () {
          var next = e.deltaY > 0

          self[next ? 'next' : 'prev']()
          settings.onSwipeWheel && settings.onSwipeWheel(self._active, MAX, !next)
        })
        e.preventDefault()
      }, false)
    }

    window.addEventListener('resize', function (e) {
      requestAnimationFrame(function () {
        $slider.style.height = 'auto'
        $slider.style.height = settings.height || getComputedStyle($slider).height
      })
    })

    // start
    if (MAX > 1) {
      $slider.style.height = settings.height || getComputedStyle($slider).height

      ;
      [].forEach.call(this._$slides, function ($slide, i) {
        if (i == 0) {
          $slide.classList.add(settings.prefix + 'active')
        }
        $slide.classList.add(settings.prefix + 'item')
      })

      if (settings.autoplay) {
        setTimeout(function () {
          this._timer = setTimeout(
            function () {
              this.next()
            }.bind(this),
            settings.initialTimeout)
        }.bind(this), 100)
      }
    }
  }

  VanillaSlider.prototype._updateStatus = function () {
    if (this._settings.status) {
      var activeClass = this._settings.prefix + 'status-item-active'

      this._$status.querySelector('.' + activeClass)
        .classList.remove(activeClass)
      this._$status.querySelector('li:nth-child(' + (this._active + 1) + ')')
        .classList.add(activeClass)
    }
  }

  VanillaSlider.prototype._getActive = function (back, index) {
    clearTimeout(this._timer)

    this._$slides[this._active].classList.remove(
      this._settings.prefix + 'active'
    )

    if (index !== undefined) {
      this._active = index >= 0 && index < this._MAX ?
        index : this._MAX - 1
    }
    else {
      if (!back) {
        this._active = (this._active === this._$slides.length - 1) ? 0 : this._active + 1
      }
      else {
        this._active = (this._active === 0) ? this._$slides.length - 1 : this._active - 1
      }
    }
    return this._$slides[this._active]
  }

  VanillaSlider.prototype.prev = function (index) {
    var prefix = this._settings.prefix

    if (index !== undefined && index === this._active) {
      return true
    }
    else if (index === undefined && !this._settings.rotation && this._active === 0) {
      return true
    }

    this._$slides[this._active].classList.add(prefix + 'before')

    var $active = this._getActive(true, index)

    $active.classList.add(prefix + 'direct')
    $active.classList.remove(prefix + 'before')
    $active.classList.remove(prefix + 'active')
    getComputedStyle($active).opacity // DO IT!
    $active.classList.remove(prefix + 'direct')
    $active.classList.add(prefix + 'active')

    this._updateStatus()
    this._settings.after(this._active, this._MAX)
    if (this._settings.autoplay) {
      this._timer = setTimeout(function () {
          this.next()
        }.bind(this),
        this._settings.timeout)
    }
  }

  VanillaSlider.prototype.next = function (index) {
    var prefix = this._settings.prefix

    if (index !== undefined && index === this._active) {
      return true
    }
    else if (index === undefined && !this._settings.rotation && this._active === this._$slides.length - 1) {
      return true
    }

    var $active = this._getActive(false, index)

    $active.classList.add(prefix + 'direct')
    $active.classList.add(prefix + 'active')
    $active.classList.add(prefix + 'before')
    getComputedStyle($active).opacity // DO IT!
    $active.classList.remove(prefix + 'direct')
    $active.classList.remove(prefix + 'before')

    this._updateStatus()
    this._settings.after(this._active, this._MAX)
    if (this._settings.autoplay) {
      this._timer = setTimeout(function () {
          this.next()
        }.bind(this),
        this._settings.timeout)
    }
  }

  function vanillaSlider($sliders, options) {
    var sliders = [];

    if ($sliders instanceof Node) {
      $sliders = [$sliders]
    }

    [].forEach.call($sliders, function ($slider, i) {
      sliders.push(
        new VanillaSlider($slider, options || {})
      )
    })
    return sliders.length > 1 ? sliders : sliders[0]
  }
  vanillaSlider.VERSION = 1.4

  window.vanillaSlider = vanillaSlider
}());<commit_msg>settings touch-action to pan if swipevents (not working everywhere?)<commit_after>/*!
 * vanillaSlider
 */
;
(function () {
  "use strict";

  // Polyfill for e.g. IE
  if (typeof Object.assign != 'function') {
    // Must be writable: true, enumerable: false, configurable: true
    Object.defineProperty(Object, "assign", {
      value: function assign(target, varArgs) { // .length of function is 2
        'use strict';
        if (target == null) { // TypeError if undefined or null
          throw new TypeError('Cannot convert undefined or null to object');
        }

        var to = Object(target);

        for (var index = 1; index < arguments.length; index++) {
          var nextSource = arguments[index];

          if (nextSource != null) { // Skip over if undefined or null
            for (var nextKey in nextSource) {
              // Avoid bugs when hasOwnProperty is shadowed
              if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                to[nextKey] = nextSource[nextKey];
              }
            }
          }
        }
        return to;
      },
      writable: true,
      configurable: true
    });
  }

  function initSwipe($e, handler) {
    var POINTER_EVENTS = window.PointerEvent ? true : false
    var start = {};
    var end = {};
    var tracking = false;
    var thresholdTime = 500;
    var thresholdDistance = 100;

    function startHandler(e) {
      tracking = true;
      /* Hack - e.timeStamp is whack in Fx/Android */
      start.t = new Date().getTime();
      start.x = POINTER_EVENTS ? e.clientX : e.touches[0].clientX;
      start.y = POINTER_EVENTS ? e.clientY : e.touches[0].clientY;
    };

    function moveHandler(e) {
      if (tracking) {
        e.preventDefault();
        end.x = POINTER_EVENTS ? e.clientX : e.touches[0].clientX;
        end.y = POINTER_EVENTS ? e.clientY : e.touches[0].clientY;
      }
    }

    function endEvent(e) {
      if (tracking) {
        tracking = false;
        var now = new Date().getTime();
        var deltaTime = now - start.t;
        var deltaX = end.x - start.x;
        var deltaY = end.y - start.y;
        // if not too slow work out what the movement was
        if (deltaTime < thresholdTime) {
          if ((deltaX > thresholdDistance) && (Math.abs(deltaY) < thresholdDistance)) {
            handler('left')
          }
          else if ((-deltaX > thresholdDistance) && (Math.abs(deltaY) < thresholdDistance)) {
            handler('right')
          }
          else if ((deltaY > thresholdDistance) && (Math.abs(deltaX) < thresholdDistance)) {
            handler('up')
          }
          else if ((-deltaY > thresholdDistance) && (Math.abs(deltaX) < thresholdDistance)) {
            handler('down')
          }
        }
      }
    }
    if (POINTER_EVENTS) {
      $e.addEventListener('pointerdown', startHandler, false);
      $e.addEventListener('pointermove', moveHandler, false);
      $e.addEventListener('pointerup', endEvent, false);
      $e.addEventListener('pointerleave', endEvent, false);
      $e.addEventListener('pointercancel', endEvent, false);
    }
    else if (window.TouchEvent) {
      $e.addEventListener('touchstart', startHandler, false);
      $e.addEventListener('touchmove', moveHandler, false);
      $e.addEventListener('touchend', endEvent, false);
    }
  }

  var VanillaSlider = function ($slider, options) {
    var self = this
    var settings = this._settings = Object.assign({
      itemSelector: 'div',
      prefix: 'vslider-',

      // if null set height automatically else use height
      // number (=px) or explicit like "3em"
      height: null,

      rotation: true,
      autoplay: options.rotation === false ? false : true,
      initialTimeout: 4000,
      timeout: 8000,

      navigation: true,
      keyboardnavigation: true,
      // needs Hammer
      swipenavigation: true,
      swipedirection: 'h', // h or v
      wheelnavigation: false,
      onSwipeWheel: null,

      status: true,
      statusContent: function (index, length) {
        return '•';
      },

      after: function (index, length) {}
    }, options);
    this._$slides = $slider.querySelectorAll(settings.itemSelector)
    this._$status
    this._active = 0
    this._timer = null

    if (typeof settings.height === 'number') {
      settings.height = settings.height + 'px'
    }

    var MAX = this._MAX = this._$slides.length

    // status
    if (settings.status) {
      this._$status = document.createElement('ol')
      this._$status.classList.add(settings.prefix + 'status')

      for (var i = 0, upto = MAX; i < upto; i++) {
        (function (index) {
          var $i = document.createElement('li')

          $i.classList.add(settings.prefix + 'status-item')

          if (i === 0) {
            $i.classList.add(settings.prefix + 'status-item-active')
          }

          $i.textContent = settings.statusContent(i, MAX)
          $i.addEventListener('click', function (e) {
            self.next(index)
          }, false)
          self._$status.appendChild($i)
        }(i));
      }
      $slider.appendChild(self._$status)
    }

    // NAVIGATION

    if (settings.navigation) {
      var $prev = document.createElement('button')
      var $next = document.createElement('button')

      $prev.classList.add(this._settings.prefix + 'prev')
      $next.classList.add(this._settings.prefix + 'next')
      $prev.addEventListener('click', function (e) {
        self.prev()
      }, true)
      $next.addEventListener('click', function (e) {
        self.next()
      }, true)

      $slider.appendChild($prev)
      $slider.appendChild($next)
    }

    if (settings.keyboardnavigation) {
      window.addEventListener('keydown', function (e) {
        var keyCode = e.keyCode

        switch (keyCode) {
          case 39:
          case 40:
            self.next()
            break
          case 37:
          case 38:
            self.prev()
            break
        }
      })
    }

    if (settings.swipenavigation) {
      $slider.style.touchAction = settings.swipedirection === 'h' ?
        'pan-y' : 'pan-x';

      initSwipe($slider, function (direction) {
        if (settings.swipedirection === 'h') {
          if (direction === 'left') {
            self.prev()
          }
          if (direction === 'right') {
            self.next()
          }
        }
        if (settings.swipedirection === 'v') {
          if (direction === 'up') {
            self.prev()
          }
          if (direction === 'down') {
            self.next()
          }
        }
      })
    }

    if (settings.wheelnavigation) {
      $slider.addEventListener('wheel', function (e) {
        requestAnimationFrame(function () {
          var next = e.deltaY > 0

          self[next ? 'next' : 'prev']()
          settings.onSwipeWheel && settings.onSwipeWheel(self._active, MAX, !next)
        })
        e.preventDefault()
      }, false)
    }

    window.addEventListener('resize', function (e) {
      requestAnimationFrame(function () {
        $slider.style.height = 'auto'
        $slider.style.height = settings.height || getComputedStyle($slider).height
      })
    })

    // start
    if (MAX > 1) {
      $slider.style.height = settings.height || getComputedStyle($slider).height

      ;
      [].forEach.call(this._$slides, function ($slide, i) {
        if (i == 0) {
          $slide.classList.add(settings.prefix + 'active')
        }
        $slide.classList.add(settings.prefix + 'item')
      })

      if (settings.autoplay) {
        setTimeout(function () {
          this._timer = setTimeout(
            function () {
              this.next()
            }.bind(this),
            settings.initialTimeout)
        }.bind(this), 100)
      }
    }
  }

  VanillaSlider.prototype._updateStatus = function () {
    if (this._settings.status) {
      var activeClass = this._settings.prefix + 'status-item-active'

      this._$status.querySelector('.' + activeClass)
        .classList.remove(activeClass)
      this._$status.querySelector('li:nth-child(' + (this._active + 1) + ')')
        .classList.add(activeClass)
    }
  }

  VanillaSlider.prototype._getActive = function (back, index) {
    clearTimeout(this._timer)

    this._$slides[this._active].classList.remove(
      this._settings.prefix + 'active'
    )

    if (index !== undefined) {
      this._active = index >= 0 && index < this._MAX ?
        index : this._MAX - 1
    }
    else {
      if (!back) {
        this._active = (this._active === this._$slides.length - 1) ? 0 : this._active + 1
      }
      else {
        this._active = (this._active === 0) ? this._$slides.length - 1 : this._active - 1
      }
    }
    return this._$slides[this._active]
  }

  VanillaSlider.prototype.prev = function (index) {
    var prefix = this._settings.prefix

    if (index !== undefined && index === this._active) {
      return true
    }
    else if (index === undefined && !this._settings.rotation && this._active === 0) {
      return true
    }

    this._$slides[this._active].classList.add(prefix + 'before')

    var $active = this._getActive(true, index)

    $active.classList.add(prefix + 'direct')
    $active.classList.remove(prefix + 'before')
    $active.classList.remove(prefix + 'active')
    getComputedStyle($active).opacity // DO IT!
    $active.classList.remove(prefix + 'direct')
    $active.classList.add(prefix + 'active')

    this._updateStatus()
    this._settings.after(this._active, this._MAX)
    if (this._settings.autoplay) {
      this._timer = setTimeout(function () {
          this.next()
        }.bind(this),
        this._settings.timeout)
    }
  }

  VanillaSlider.prototype.next = function (index) {
    var prefix = this._settings.prefix

    if (index !== undefined && index === this._active) {
      return true
    }
    else if (index === undefined && !this._settings.rotation && this._active === this._$slides.length - 1) {
      return true
    }

    var $active = this._getActive(false, index)

    $active.classList.add(prefix + 'direct')
    $active.classList.add(prefix + 'active')
    $active.classList.add(prefix + 'before')
    getComputedStyle($active).opacity // DO IT!
    $active.classList.remove(prefix + 'direct')
    $active.classList.remove(prefix + 'before')

    this._updateStatus()
    this._settings.after(this._active, this._MAX)
    if (this._settings.autoplay) {
      this._timer = setTimeout(function () {
          this.next()
        }.bind(this),
        this._settings.timeout)
    }
  }

  function vanillaSlider($sliders, options) {
    var sliders = [];

    if ($sliders instanceof Node) {
      $sliders = [$sliders]
    }

    [].forEach.call($sliders, function ($slider, i) {
      sliders.push(
        new VanillaSlider($slider, options || {})
      )
    })
    return sliders.length > 1 ? sliders : sliders[0]
  }
  vanillaSlider.VERSION = 1.4

  window.vanillaSlider = vanillaSlider
}());<|endoftext|>


<commit_before>/*jslint nomen: true */
/*globals define: true */
/*globals console: true */

define([
  'jquery',
  'lib/lodash',
  'backbone',
  'lib/kissmetrics',

  // LocalData
  'settings',
  'api',

  'views/forms'
],

function($, _, Backbone, _kmq, settings, api, FormViews) {
  'use strict';

  var Builder = {};

  Builder.BuilderView = Backbone.View.extend({

    el: '#builder-view-container',

    boxAnswersByQuestionId: {},
    formQuestions: $('#editor'),
    questionsByParentId: {},
    repeatCounter: {},
    templates: {
      question: _.template($('#question-edit-template').html()),
      answer: _.template($('#answer-edit-template').html())
    },

    initialize: function(options) {
      _.bindAll(this, 'render', 'save', 'done', 'makeBlankQuestion', 'renderForm',
        'suffix', 'editQuestion', 'editQuestionType', 'deleteQuestion',
        'createQuestion', 'createAnswer', 'addSubQuestion', 'renderQuestion',
         'editAnswer', 'deleteAnswer', 'slugify', 'updatePreview');

      this.forms = options.forms;
    },

    render: function() {
      this.renderForm();
      this.updatePreview();
    },

    save: function(event) {
      event.preventDefault();
      console.log('Saving form');
      _kmq.push(['record', 'Survey questions saved']);

      api.createForm(settings.formData, function(){
        console.log('Form successfully saved');
        $(".saved").fadeIn().css("display","inline-block").delay(2000).fadeOut();
      });
    },

    done: function(event) {
      event.preventDefault();
      console.log('Done editing');
      _kmq.push(['record', 'Done editing survey questions']);
      this.trigger("done");
    },

    // The blank question template.
    makeBlankQuestion: function(){
      return {
        "text": "",
        "value": "",
        "answers": [
          {
            "text": "",
            "value": ""
          },
          {
            "text": "",
            "value": ""
          }
        ]
      };
    },

    renderForm: function() {
      // Render the editor template
      $(this.el).html(_.template($('#form-editor-template').html(), {}));

      // Clear out the editor, just in case.
      this.formQuestions = $('#editor');
      this.formQuestions.html('');

      // Handle save events
      $(this.el).find('.save').click(this.save);
      $(this.el).find('.done').click(this.done);

      // Default to a blank question if the form is empty
      if (settings.formData === undefined) {
        settings.formData = {};
        settings.formData.questions = [];
        settings.formData.questions.push(this.makeBlankQuestion());
      }

      // Render form
      _.each(settings.formData.questions, function (question, questionIndex) {
        this.renderQuestion(question, undefined, undefined, undefined, undefined, questionIndex, settings.formData.questions);

        // last param was settings.formData.questions
      }, this);
      this.updatePreview();
    },

    suffix: function(name) {
      if(_.has(this.repeatCounter, name)) {
        this.repeatCounter[name] += 1;
        return '-' + this.repeatCounter[name].toString();
      }

      this.repeatCounter[name] = 1;
      return '';
    },

    editQuestion: function(question) {
      // TODO:
      // What if we do:
      // view = this;
      // return function(event) {
      //  view.slugify(text)
      //  ...
      return function(event) {
        console.log('Updating question');
        _kmq.push(['record', 'Question edited']);
        var text = $(event.target).val(); //$(this).val();
        var name = this.slugify(text);

        question.text = text;
        question.name = name;

        this.updatePreview();
      };
    },

    editQuestionType: function(question) {
      return function(event) {
        _kmq.push(['record', 'Question type changed']);

        var dataRole = $(event.target).attr('data-role');
        console.log(dataRole);
        if (dataRole === 'radio-question') {
          delete question.type;
        }
        if (dataRole === 'checkbox-question') {
          question.type = "checkbox";
        }

        // Update the styling
        var $pills = $(event.target).parent().find('.question-type');
        $pills.toggleClass('selected');

        this.updatePreview();
      };
    },

    deleteQuestion: function($question, parent, questionIndex) {
      return function(event) {
        console.log("removing question");
        _kmq.push(['record', 'Question deleted']);

        // Remove it from the DON.
        $question.remove();

        // Remove it from the json
        parent.splice(questionIndex, 1);
        this.updatePreview();
      };
    },

    createQuestion: function(parent, questionIndex) {
      return function(event) {
        console.log("Adding a new question");
        _kmq.push(['record', 'Question added']);

        var newQuestion = this.makeBlankQuestion();
        parent.splice(questionIndex + 1, 0, newQuestion);

        this.updatePreview();
        this.renderForm();
      };
    },

    createAnswer: function(question) {
      return function(event) {
        _kmq.push(['record', 'Answer added']);
        question.answers.push({
          'text': '',
          'value': ''
        });
        this.setQuestionLayout(question);

        this.updatePreview();
        this.renderForm();
      };
    },

    addSubQuestion: function(question, index) {
      return function(event) {
        console.log("Adding sub-question");
        _kmq.push(['record', 'Sub-question added']);

        if (_.has(question.answers[index], 'questions')) {
          question.answers[index].questions.unshift(this.makeBlankQuestion());
        }else {
          question.answers[index].questions = [this.makeBlankQuestion()];
        }

        this.updatePreview();
        this.renderForm();
      };
    },

    editAnswer: function(question, index) {
      return function(event){
        _kmq.push(['record', 'Answer edited']);
        var text = $(event.target).val();
        question.answers[index].text = text;
        question.answers[index].value = this.slugify(text);

        this.setQuestionLayout(question);

        this.updatePreview();
      };
    },

    deleteAnswer: function(question, index, $answer) {
      return function(event) {
        console.log("removing answer");
        _kmq.push(['record', 'Answer deleted']);

        // Remove it from the json
        question.answers.splice(index, 1);
        this.updatePreview();

        this.setQuestionLayout(question);

        // Remove it from the DON.
        $answer.remove();
      };
    },

    // Display a question horizontally if it doesn't have many answers.
    setQuestionLayout: function(question) {
      if (question.answers.length !== 2) {
        delete question.layout;
      }else {
        if (question.type !== 'checkbox') {
          question.layout = 'horizontal';
        }
      }
      console.log(question.layout);
    },

    renderQuestion: function(question, visible, parentID, triggerID, appendTo, questionIndex, parent) {
      // TODO:
      // This should pass around a well-document options object
      // Instead of 30 different parameters

      // Set default values for questions
      if (visible === undefined) {
        visible = true;
      }
      if (parentID === undefined) {
        parentID = '';
      }
      if (triggerID === undefined) {
        triggerID = '';
      }

      // Give the question an ID based on its name
      var id = _.uniqueId(question.name);

      // Render the questions .................................................
      var $question = $(this.templates.question({question: question}));

      // Listen for changes to the question ...................................
      // TODO: make it less verbose

      // Listen for changes to the question text
      var editQuestionProxy = $.proxy(this.editQuestion(question), this);
      $question.find('> input').keyup(editQuestionProxy);

      // Listen for changes of the question type
      var editQuestionTypeProxy = $.proxy(this.editQuestionType(question), this);
      $question.find('.question-type').click(editQuestionTypeProxy);

      // Listen for a request to remove a question
      var deleteQuestionProxy = $.proxy(this.deleteQuestion($question, parent, questionIndex), this);
      $question.find('> .remove').click(deleteQuestionProxy);

      // Listen for a request to add a question
      var createQuestionProxy = $.proxy(this.createQuestion(parent, questionIndex), this);
      $question.find('.add-question').click(createQuestionProxy);

      // Listen for a request to add an answer
      var createAnswerProxy = $.proxy(this.createAnswer(question), this);
      $question.find('.add-answer').click(createAnswerProxy);

      // Some stuff with siblings -- forget what this does.
      var siblings = this.questionsByParentId[parentID];
      if (siblings === undefined) {
        siblings = [];
        this.questionsByParentId[parentID] = siblings;
      }
      siblings.push($question);

      // Figure out the right place to append the rendered template
      if (appendTo !== undefined) {
        $(appendTo).append($question);
      }else {
        this.formQuestions.append($question);
      }

      var suffixed_name = question.name + this.suffix(question.name);

      // TODO Infoboxes (aka help text for questions)
      // if(question.info !== undefined) {
      // }

      // Deal with answers ....................................................
      var questionID = id;

      // Add each answer to the question
      _.each(question.answers, function (answer, index) {
        // The triggerID is used to hide/show other question groups
        var triggerID = _.uniqueId(question.name);

        if(question.type === "checkbox") {
          suffixed_name = answer.name + this.suffix(answer.name);
          triggerID = suffixed_name; //_.uniqueId(answer.name);
          id = suffixed_name; //_.uniqueId(answer.name);
        }

        // Set the data used to render the answer
        var data = {
          questionName: suffixed_name,
          id: triggerID,
          theme: (answer.theme || "c"),
          value: answer.value,
          text: answer.text
        };

        // Render the answer template...
        var $answer;
        var referencesToAnswersForQuestion;

        $answer = $(this.templates.answer(data));
        referencesToAnswersForQuestion = this.boxAnswersByQuestionId[questionID];
        if (referencesToAnswersForQuestion === undefined) {
          referencesToAnswersForQuestion = [];
          this.boxAnswersByQuestionId[questionID] = referencesToAnswersForQuestion;
        }

        // ... and append it to the fieldset.
        $question.find('> .answers').append($answer);

        // Tools related to editing answers ------------------------------------

        // Change the answer text when updated
        var $input = $answer.find('input');
        var editAnswerProxy = $.proxy(this.editAnswer(question, index), this);
        $input.keyup(editAnswerProxy);

        // Add a sub-question
        var $addSubQuestion = $answer.find('.add-sub-question');
        var addSubQuestionProxy = $.proxy(this.addSubQuestion(question, index), this);
        $addSubQuestion.click(addSubQuestionProxy);

        // Remove an answer
        var $removeButton = $answer.find('.remove');
        var deleteAnswerProxy = $.proxy(this.deleteAnswer(question, index, $answer), this);
        $removeButton.click(deleteAnswerProxy);

        // If there are conditional questions, add them. -----------------------
        if (answer.questions !== undefined) {
          appendTo = $answer;
          _.each(answer.questions, function (subq, questionIndex) {
            // Add the sub questions before the repeatButton
            if(appendTo !== undefined){
              this.renderQuestion(subq, false, id, triggerID, appendTo, questionIndex, answer.questions);
            }else {
              this.renderQuestion(subq, false, id, triggerID, undefined, questionIndex, answer.questions);
            }
          }, this);
        } // end check for sub-answers

      }, this);
    }, // end renderQuestion

    // Slugify a string
    // Used to generate the name attribute of forms
    slugify: function(text) {
      text = text.replace(/[^-a-zA-Z0-9,&\s]+/ig, '');
      text = text.replace(/-/gi, "_");
      text = text.replace(/\s/gi, "-");
      return text;
    },

    updatePreview: function() {
      this.trigger("formUpdated");
    }

  }); // end BuilderView{}

  return Builder;

});
<commit_msg>Remove unneeded log<commit_after>/*jslint nomen: true */
/*globals define: true */
/*globals console: true */

define([
  'jquery',
  'lib/lodash',
  'backbone',
  'lib/kissmetrics',

  // LocalData
  'settings',
  'api',

  'views/forms'
],

function($, _, Backbone, _kmq, settings, api, FormViews) {
  'use strict';

  var Builder = {};

  Builder.BuilderView = Backbone.View.extend({

    el: '#builder-view-container',

    boxAnswersByQuestionId: {},
    formQuestions: $('#editor'),
    questionsByParentId: {},
    repeatCounter: {},
    templates: {
      question: _.template($('#question-edit-template').html()),
      answer: _.template($('#answer-edit-template').html())
    },

    initialize: function(options) {
      _.bindAll(this, 'render', 'save', 'done', 'makeBlankQuestion', 'renderForm',
        'suffix', 'editQuestion', 'editQuestionType', 'deleteQuestion',
        'createQuestion', 'createAnswer', 'addSubQuestion', 'renderQuestion',
         'editAnswer', 'deleteAnswer', 'slugify', 'updatePreview');

      this.forms = options.forms;
    },

    render: function() {
      this.renderForm();
      this.updatePreview();
    },

    save: function(event) {
      event.preventDefault();
      console.log('Saving form');
      _kmq.push(['record', 'Survey questions saved']);

      api.createForm(settings.formData, function(){
        console.log('Form successfully saved');
        $(".saved").fadeIn().css("display","inline-block").delay(2000).fadeOut();
      });
    },

    done: function(event) {
      event.preventDefault();
      console.log('Done editing');
      _kmq.push(['record', 'Done editing survey questions']);
      this.trigger("done");
    },

    // The blank question template.
    makeBlankQuestion: function(){
      return {
        "text": "",
        "value": "",
        "answers": [
          {
            "text": "",
            "value": ""
          },
          {
            "text": "",
            "value": ""
          }
        ]
      };
    },

    renderForm: function() {
      // Render the editor template
      $(this.el).html(_.template($('#form-editor-template').html(), {}));

      // Clear out the editor, just in case.
      this.formQuestions = $('#editor');
      this.formQuestions.html('');

      // Handle save events
      $(this.el).find('.save').click(this.save);
      $(this.el).find('.done').click(this.done);

      // Default to a blank question if the form is empty
      if (settings.formData === undefined) {
        settings.formData = {};
        settings.formData.questions = [];
        settings.formData.questions.push(this.makeBlankQuestion());
      }

      // Render form
      _.each(settings.formData.questions, function (question, questionIndex) {
        this.renderQuestion(question, undefined, undefined, undefined, undefined, questionIndex, settings.formData.questions);

        // last param was settings.formData.questions
      }, this);
      this.updatePreview();
    },

    suffix: function(name) {
      if(_.has(this.repeatCounter, name)) {
        this.repeatCounter[name] += 1;
        return '-' + this.repeatCounter[name].toString();
      }

      this.repeatCounter[name] = 1;
      return '';
    },

    editQuestion: function(question) {
      // TODO:
      // What if we do:
      // view = this;
      // return function(event) {
      //  view.slugify(text)
      //  ...
      return function(event) {
        console.log('Updating question');
        _kmq.push(['record', 'Question edited']);
        var text = $(event.target).val(); //$(this).val();
        var name = this.slugify(text);

        question.text = text;
        question.name = name;

        this.updatePreview();
      };
    },

    editQuestionType: function(question) {
      return function(event) {
        _kmq.push(['record', 'Question type changed']);

        var dataRole = $(event.target).attr('data-role');
        console.log(dataRole);
        if (dataRole === 'radio-question') {
          delete question.type;
        }
        if (dataRole === 'checkbox-question') {
          question.type = "checkbox";
        }

        // Update the styling
        var $pills = $(event.target).parent().find('.question-type');
        $pills.toggleClass('selected');

        this.updatePreview();
      };
    },

    deleteQuestion: function($question, parent, questionIndex) {
      return function(event) {
        console.log("removing question");
        _kmq.push(['record', 'Question deleted']);

        // Remove it from the DON.
        $question.remove();

        // Remove it from the json
        parent.splice(questionIndex, 1);
        this.updatePreview();
      };
    },

    createQuestion: function(parent, questionIndex) {
      return function(event) {
        console.log("Adding a new question");
        _kmq.push(['record', 'Question added']);

        var newQuestion = this.makeBlankQuestion();
        parent.splice(questionIndex + 1, 0, newQuestion);

        this.updatePreview();
        this.renderForm();
      };
    },

    createAnswer: function(question) {
      return function(event) {
        _kmq.push(['record', 'Answer added']);
        question.answers.push({
          'text': '',
          'value': ''
        });
        this.setQuestionLayout(question);

        this.updatePreview();
        this.renderForm();
      };
    },

    addSubQuestion: function(question, index) {
      return function(event) {
        console.log("Adding sub-question");
        _kmq.push(['record', 'Sub-question added']);

        if (_.has(question.answers[index], 'questions')) {
          question.answers[index].questions.unshift(this.makeBlankQuestion());
        }else {
          question.answers[index].questions = [this.makeBlankQuestion()];
        }

        this.updatePreview();
        this.renderForm();
      };
    },

    editAnswer: function(question, index) {
      return function(event){
        _kmq.push(['record', 'Answer edited']);
        var text = $(event.target).val();
        question.answers[index].text = text;
        question.answers[index].value = this.slugify(text);

        this.setQuestionLayout(question);

        this.updatePreview();
      };
    },

    deleteAnswer: function(question, index, $answer) {
      return function(event) {
        console.log("removing answer");
        _kmq.push(['record', 'Answer deleted']);

        // Remove it from the json
        question.answers.splice(index, 1);
        this.updatePreview();

        this.setQuestionLayout(question);

        // Remove it from the DON.
        $answer.remove();
      };
    },

    // Display a question horizontally if it doesn't have many answers.
    setQuestionLayout: function(question) {
      if (question.answers.length !== 2) {
        delete question.layout;
      }else {
        if (question.type !== 'checkbox') {
          question.layout = 'horizontal';
        }
      }
    },

    renderQuestion: function(question, visible, parentID, triggerID, appendTo, questionIndex, parent) {
      // TODO:
      // This should pass around a well-document options object
      // Instead of 30 different parameters

      // Set default values for questions
      if (visible === undefined) {
        visible = true;
      }
      if (parentID === undefined) {
        parentID = '';
      }
      if (triggerID === undefined) {
        triggerID = '';
      }

      // Give the question an ID based on its name
      var id = _.uniqueId(question.name);

      // Render the questions .................................................
      var $question = $(this.templates.question({question: question}));

      // Listen for changes to the question ...................................
      // TODO: make it less verbose

      // Listen for changes to the question text
      var editQuestionProxy = $.proxy(this.editQuestion(question), this);
      $question.find('> input').keyup(editQuestionProxy);

      // Listen for changes of the question type
      var editQuestionTypeProxy = $.proxy(this.editQuestionType(question), this);
      $question.find('.question-type').click(editQuestionTypeProxy);

      // Listen for a request to remove a question
      var deleteQuestionProxy = $.proxy(this.deleteQuestion($question, parent, questionIndex), this);
      $question.find('> .remove').click(deleteQuestionProxy);

      // Listen for a request to add a question
      var createQuestionProxy = $.proxy(this.createQuestion(parent, questionIndex), this);
      $question.find('.add-question').click(createQuestionProxy);

      // Listen for a request to add an answer
      var createAnswerProxy = $.proxy(this.createAnswer(question), this);
      $question.find('.add-answer').click(createAnswerProxy);

      // Some stuff with siblings -- forget what this does.
      var siblings = this.questionsByParentId[parentID];
      if (siblings === undefined) {
        siblings = [];
        this.questionsByParentId[parentID] = siblings;
      }
      siblings.push($question);

      // Figure out the right place to append the rendered template
      if (appendTo !== undefined) {
        $(appendTo).append($question);
      }else {
        this.formQuestions.append($question);
      }

      var suffixed_name = question.name + this.suffix(question.name);

      // TODO Infoboxes (aka help text for questions)
      // if(question.info !== undefined) {
      // }

      // Deal with answers ....................................................
      var questionID = id;

      // Add each answer to the question
      _.each(question.answers, function (answer, index) {
        // The triggerID is used to hide/show other question groups
        var triggerID = _.uniqueId(question.name);

        if(question.type === "checkbox") {
          suffixed_name = answer.name + this.suffix(answer.name);
          triggerID = suffixed_name; //_.uniqueId(answer.name);
          id = suffixed_name; //_.uniqueId(answer.name);
        }

        // Set the data used to render the answer
        var data = {
          questionName: suffixed_name,
          id: triggerID,
          theme: (answer.theme || "c"),
          value: answer.value,
          text: answer.text
        };

        // Render the answer template...
        var $answer;
        var referencesToAnswersForQuestion;

        $answer = $(this.templates.answer(data));
        referencesToAnswersForQuestion = this.boxAnswersByQuestionId[questionID];
        if (referencesToAnswersForQuestion === undefined) {
          referencesToAnswersForQuestion = [];
          this.boxAnswersByQuestionId[questionID] = referencesToAnswersForQuestion;
        }

        // ... and append it to the fieldset.
        $question.find('> .answers').append($answer);

        // Tools related to editing answers ------------------------------------

        // Change the answer text when updated
        var $input = $answer.find('input');
        var editAnswerProxy = $.proxy(this.editAnswer(question, index), this);
        $input.keyup(editAnswerProxy);

        // Add a sub-question
        var $addSubQuestion = $answer.find('.add-sub-question');
        var addSubQuestionProxy = $.proxy(this.addSubQuestion(question, index), this);
        $addSubQuestion.click(addSubQuestionProxy);

        // Remove an answer
        var $removeButton = $answer.find('.remove');
        var deleteAnswerProxy = $.proxy(this.deleteAnswer(question, index, $answer), this);
        $removeButton.click(deleteAnswerProxy);

        // If there are conditional questions, add them. -----------------------
        if (answer.questions !== undefined) {
          appendTo = $answer;
          _.each(answer.questions, function (subq, questionIndex) {
            // Add the sub questions before the repeatButton
            if(appendTo !== undefined){
              this.renderQuestion(subq, false, id, triggerID, appendTo, questionIndex, answer.questions);
            }else {
              this.renderQuestion(subq, false, id, triggerID, undefined, questionIndex, answer.questions);
            }
          }, this);
        } // end check for sub-answers

      }, this);
    }, // end renderQuestion

    // Slugify a string
    // Used to generate the name attribute of forms
    slugify: function(text) {
      text = text.replace(/[^-a-zA-Z0-9,&\s]+/ig, '');
      text = text.replace(/-/gi, "_");
      text = text.replace(/\s/gi, "-");
      return text;
    },

    updatePreview: function() {
      this.trigger("formUpdated");
    }

  }); // end BuilderView{}

  return Builder;

});
<|endoftext|>