<commit_before>/* Upload pattern.
 *
 * Options:
 *    showTitle(boolean): show/hide the h1 title (true)
 *    url(string): If not used with a form, this option must provide the URL to submit to or baseUrl with relativePath needs to be used (null)
 *    baseUrl(string): to be used in conjunction with relativePath to generate submission urls based on related items (null)
 *    relativePath(string): again, to be used with baseUrl to create upload url (null)
 *    initialFolder(string): UID of initial folder related items widget should have selected (null)
 *    currentPath(string): Current path related items is starting with (null)
 *    clickable(boolean): If you can click on container to also upload (false)
 *    className(string): value for class attribute in the form element ('upload')
 *    paramName(string): value for name attribute in the file input element ('file')
 *    ajaxUpload(boolean): true or false for letting the widget upload the files via ajax. If false the form will act like a normal form. (true)
 *    wrap(boolean): true or false for wrapping this element using the value of wrapperTemplate. (false)
 *    wrapperTemplate(string): HTML template for wrapping around with this element. ('
')
 *    resultTemplate(string): HTML template for the element that will contain file information. ('

Drop files here...

')
 *    autoCleanResults(boolean): condition value for the file preview in div element to fadeout after file upload is completed. (true)
 *    previewsContainer(selector): JavaScript selector for file preview in div element. (.upload-previews)
 *    container(selector): JavaScript selector for where to put upload stuff into in case of form. If not provided it will be place before the first submit button. ('')
 *    relatedItems(object): Related items pattern options. Will only use only if relativePath is used to use correct upload destination ({ attributes: ["UID", "Title", "Description", "getURL", "Type", "path", "ModificationDate"], batchSize: 20, basePath: "/", vocabularyUrl: null, width: 500, maximumSelectionSize: 1, placeholder: "Search for item on site..." })
 *
 * Documentation:
 *
 *    # On a div element
 *
 *    {{ example-1 }}
 *
 * Example: example-1
 *

 *

 *
Something here that is useful

 *
Something else here that is useful

 *
Another thing here that is useful

 *

 *

 *
 *
 * License:
 *    Copyright (C) 2010 Plone Foundation
 *
 *    This program is free software; you can redistribute it and/or modify it
 *    under the terms of the GNU General Public License as published by the
 *    Free Software Foundation; either version 2 of the License.
 *
 *    This program is distributed in the hope that it will be useful, but
 *    WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *    Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program; if not, write to the Free Software Foundation, Inc.,
 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

define([
  'jquery',
  'underscore',
  'mockup-patterns-base',
  'mockup-patterns-relateditems',
  'dropzone',
  'text!mockup-patterns-upload-url/templates/upload.xml',
  'text!mockup-patterns-upload-url/templates/preview.xml',
], function($, _, Base, RelatedItems, Dropzone,
            UploadTemplate, PreviewTemplate) {
  'use strict';

  /* we do not want this plugin to auto discover */
  Dropzone.autoDiscover = false;

  var UploadPattern = Base.extend({
    name: 'upload',
    defaults: {
      showTitle: true,
      url: null, // XXX MUST provide url to submit to OR be in a form
      className: 'upload',
      wrap: false,
      wrapperTemplate: '
',
      fileaddedClassName: 'dropping',
      useTus: false,
      container: '',
      ajaxUpload: true,

      paramName: 'file',
      clickable: true,
      addRemoveLinks: false,
      autoCleanResults: true,
      previewsContainer: '.previews',
      previewTemplate: null,
      maxFiles: null,
      maxFilesize: 99999999, // let's not have a max by default...

      relatedItems: {
        // UID attribute is required here since we're working with related items
        attributes: ['UID', 'Title', 'Description', 'getURL', 'Type', 'path', 'ModificationDate'],
        batchSize: 20,
        basePath: '/',
        vocabularyUrl: null,
        width: 500,
        maximumSelectionSize: 1,
        placeholder: 'Search for item on site...'
      }
    },

    init: function() {
      var self = this,
          template = UploadTemplate;

      // values that will change current processing
      self.currentPath = self.options.currentPath;
      self.numFiles = 0;
      self.currentFile = 0;

      template = _.template(template);
      self.$el.addClass(self.options.className);
      self.$el.append(template);

      self.$progress = $('.progress-bar-success', self.$el);

      if (!self.options.showTitle) {
        self.$el.find('h2.title').hide();
      }

      if (!self.options.ajaxUpload) {
        // no ajax upload, drop the fallback
        $('.fallback', this.$el).remove();
        if (this.$el.hasClass('.upload-container')) {
          this.$el.addClass('no-ajax-upload');
        } else {
          this.$el.closest('.upload-container').addClass('no-ajax-upload');
        }
      }

      if (self.options.wrap) {
        self.$el.wrap(self.options.wrapperTemplate);
        self.$el = self.$el.parent();
      }

      if (self.options.baseUrl && self.options.relativePath){
        // only use related items if we can generate paths based urls
        self.$pathInput = $('input[name="location"]', self.$el);
        self.relatedItems = self.setupRelatedItems(self.$pathInput);
      } else {
        $('input[name="location"]', self.$el).remove();
        self.relatedItems = null;
      }

      var $uploadArea = $('.upload-area', self.$el);

      $('button.browse', self.$el).click(function(e) {
        e.preventDefault();
        e.stopPropagation();
        // we trigger the dropzone dialog!
        self.dropzone.hiddenFileInput.click();
      });

      var dzoneOptions = this.getDzoneOptions();

      try {
        // if init of Dropzone fails it says nothing and
        // it fails silently. Using this block we make sure
        // that if you break it w/ some weird or missing option
        // you can get a proper log of it
        //
        self.dropzone = new Dropzone($uploadArea[0], dzoneOptions);
      } catch (e) {
        if (window.DEBUG) {
          // log it!
          console.log(e);
        }
        throw e;
      }

      self.dropzone.on('addedfile', function(file) {
        //console.log('file added!!');
        // show upload controls
        self.showControls();
        // self.$el.addClass(fileaddedClassName);
        // setTimeout(function() {
        //   if (!processing) {
        //     process();
        //   }
        // }, 100);
      });

      self.dropzone.on('removedfile', function() {
        if (self.dropzone.files.length < 1) {
          self.hideControls();
        }
      });

      if (self.options.autoCleanResults) {
        self.dropzone.on('complete', function(file) {
          setTimeout(function() {
            $(file.previewElement).fadeOut();
          }, 3000);
        });
      }

      self.dropzone.on('complete', function(file) {
        if (self.dropzone.files.length < 1) {
          self.hideControls();
        }
      });

      self.dropzone.on('totaluploadprogress', function(pct) {
        // need to caclulate total pct here in reality since we're manually
        // processing each file one at a time.
        pct = ((((self.currentFile - 1) * 100) + pct) / (self.numFiles * 100)) * 100;
        self.$progress.attr('aria-valuenow', pct).css('width', pct + '%');
      });

      $('.upload-all', self.$el).click(function (e) {
        e.preventDefault();
        e.stopPropagation();
        self.processUpload({
          finished: function() {
            self.$progress.attr('aria-valuenow', 0).css('width', '0%');
          }
        });
      });
    },

    showControls: function() {
      var self = this;
      $('.controls', self.$el).fadeIn('slow');
    },

    hideControls: function() {
      var self = this;
      $('.controls', self.$el).fadeOut('slow');
    },

    pathJoin: function() {
      var parts = [];
      _.each(arguments, function(part) {
        if (!part){
          return;
        }
        if (part[0] === '/'){
          part = part.substring(1);
        }
        if (part[part.length - 1] === '/'){
          part = part.substring(0, part.length - 1);
        }
        parts.push(part);
      });
      return parts.join('/');
    },

    getUrl: function() {

      var self = this;
      var url = self.options.url;
      if (!url) {
        if (self.options.baseUrl && self.options.relativePath){
          url = self.options.baseUrl;
          if (url[url.length - 1] !== '/'){
            url = url + '/';
          }
          url = url + self.pathJoin(self.currentPath, self.options.relativePath);
        } else {
          var $form = self.$el.parents('form');
          if ($form.length > 0){
            url = $form.attr('action');
          } else {
            url = window.location.href;
          }
        }
      }
      return url;
    },

    getDzoneOptions: function() {
      var self = this;

      // clickable option
      if (typeof(self.options.clickable) === 'string') {
        if (self.options.clickable === 'true') {
          self.options.clickable = true;
        } else {
          self.options.clickable = false;
        }
      }

      var options = $.extend({}, self.options);
      options.url = self.getUrl();
      // XXX force to only upload one at a time,
      // right now we don't support multiple for backends
      options.uploadMultiple = false;

      delete options.wrap;
      delete options.wrapperTemplate;
      delete options.resultTemplate;
      delete options.autoCleanResults;
      delete options.fileaddedClassName;
      delete options.useTus;

      if (self.options.previewsContainer) {
        /*
         * if they have a select but it's not an id, let's make an id selector
         * so we can target the correct container. dropzone is weird here...
         */
        var $preview = self.$el.find(self.options.previewsContainer);
        if ($preview.length > 0) {
          options.previewsContainer = $preview[0];
        }
      }

      // XXX: do we need to allow this?
      options.autoProcessQueue = false;
      // options.addRemoveLinks = true;  // we show them in the template
      options.previewTemplate = PreviewTemplate;

      // if our element is a form we should force some values
      // https://github.com/enyo/dropzone/wiki/Combine-normal-form-with-Dropzone
      return options;
    },

    processUpload: function(options) {
      if (!options){
        options = {};
      }

      var self = this,
          processing = false,
          useTus = self.options.useTus,
          fileaddedClassName = self.options.fileaddedClassName,
          finished = options.finished;

      self.numFiles = self.dropzone.files.length;
      self.currentFile = 0;

      function process() {
        processing = true;
        if (self.dropzone.files.length === 0) {
          processing = false;
          self.$el.removeClass(fileaddedClassName);
          if (finished !== undefined && typeof(finished) === 'function'){
            finished();
          }
          return;
        }
        var file = self.dropzone.files[0];
        if ([Dropzone.SUCCESS, Dropzone.ERROR, Dropzone.CANCELED]
            .indexOf(file.status) !== -1) {
          // remove it
          self.dropzone.removeFile(file);
          process();
        } else if (file.status !== Dropzone.UPLOADING) {
          // start processing file
          if (useTus && window.tus) {
            // use tus upload if installed
            self.handleTusUpload(file);
          } else {
            // otherwise, just use dropzone to process
            self.currentFile += 1;
            self.dropzone.processFile(file);
          }
          setTimeout(process, 100);
        } else {
          // currently processing
          setTimeout(process, 100);
        }
      }
      process();
    },

    handleTusUpload: function(file) {
      /* this needs fixing... */
      var self = this,
          $preview = $(file.previewElement),
          chunkSize = 1024 * 1024 * 5; // 5mb chunk size

      file.status = Dropzone.UPLOADING;

      window.tus.upload(file, {
        endpoint: self.dropzone.options.url,
        headers: {
          'FILENAME': file.name
        },
        chunkSize: chunkSize
      }).fail(function() {
        console.alert('Error uploading with TUS resumable uploads');
        file.status = Dropzone.ERROR;
      }).progress(function(e, bytesUploaded, bytesTotal) {
        var percentage = (bytesUploaded / bytesTotal * 100);
        self.$progress.attr('aria-valuenow', percentage).css('width', percentage + '%');
        self.$progress.html('uploading...
' +
                            self.formatBytes(bytesUploaded) +
                            ' / ' + self.formatBytes(bytesTotal));
      }).done(function(url, file) {
        file.status = Dropzone.SUCCESS;
        self.dropzone.emit('success', file);
        self.dropzone.emit('complete', file);
      });
    },

    formatBytes: function(bytes) {
      var kb = Math.round(bytes / 1024);
      if (kb < 1024) {
        return kb + ' KiB';
      }
      var mb = Math.round(kb / 1024);
      if (mb < 1024) {
        return mb + ' MB';
      }
      return Math.round(mb / 1024) + ' GB';
    },

    setupRelatedItems: function($input) {
      var self = this;
      var options = self.options.relatedItems;
      if (self.options.initialFolder){
        $input.attr('value', self.options.initialFolder);
      }
      var ri = new RelatedItems($input, options);
      ri.$el.on('change', function() {
        var result = $(this).select2('data');
        if (result.length > 0){
          self.currentPath = result[0].path;
        } else {
          self.currentPath = null;
        }
        self.options.url = self.dropzone.options.url = self.getUrl();
      });
      return ri;
    }

  });

  return UploadPattern;

});
<commit_msg>add texteditor, filemanager and thememapper patterns<commit_after>/* Upload pattern.
 *
 * Options:
 *    showTitle(boolean): show/hide the h1 title (true)
 *    url(string): If not used with a form, this option must provide the URL to submit to or baseUrl with relativePath needs to be used (null)
 *    baseUrl(string): to be used in conjunction with relativePath to generate submission urls based on related items (null)
 *    relativePath(string): again, to be used with baseUrl to create upload url (null)
 *    initialFolder(string): UID of initial folder related items widget should have selected (null)
 *    currentPath(string): Current path related items is starting with (null)
 *    clickable(boolean): If you can click on container to also upload (false)
 *    className(string): value for class attribute in the form element ('upload')
 *    paramName(string): value for name attribute in the file input element ('file')
 *    ajaxUpload(boolean): true or false for letting the widget upload the files via ajax. If false the form will act like a normal form. (true)
 *    wrap(boolean): true or false for wrapping this element using the value of wrapperTemplate. (false)
 *    wrapperTemplate(string): HTML template for wrapping around with this element. ('
')
 *    resultTemplate(string): HTML template for the element that will contain file information. ('

Drop files here...

')
 *    autoCleanResults(boolean): condition value for the file preview in div element to fadeout after file upload is completed. (true)
 *    previewsContainer(selector): JavaScript selector for file preview in div element. (.upload-previews)
 *    container(selector): JavaScript selector for where to put upload stuff into in case of form. If not provided it will be place before the first submit button. ('')
 *    relatedItems(object): Related items pattern options. Will only use only if relativePath is used to use correct upload destination ({ attributes: ["UID", "Title", "Description", "getURL", "Type", "path", "ModificationDate"], batchSize: 20, basePath: "/", vocabularyUrl: null, width: 500, maximumSelectionSize: 1, placeholder: "Search for item on site..." })
 *
 * Documentation:
 *
 *    # On a div element
 *
 *    {{ example-1 }}
 *
 * Example: example-1
 *

 *

 *
Something here that is useful

 *
Something else here that is useful

 *
Another thing here that is useful

 *

 *

 *
 *
 * License:
 *    Copyright (C) 2010 Plone Foundation
 *
 *    This program is free software; you can redistribute it and/or modify it
 *    under the terms of the GNU General Public License as published by the
 *    Free Software Foundation; either version 2 of the License.
 *
 *    This program is distributed in the hope that it will be useful, but
 *    WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *    Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program; if not, write to the Free Software Foundation, Inc.,
 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

define([
  'jquery',
  'underscore',
  'mockup-patterns-base',
  'mockup-patterns-relateditems',
  'dropzone',
  'text!mockup-patterns-upload-url/templates/upload.xml',
  'text!mockup-patterns-upload-url/templates/preview.xml',
], function($, _, Base, RelatedItems, Dropzone,
            UploadTemplate, PreviewTemplate) {
  'use strict';

  /* we do not want this plugin to auto discover */
  Dropzone.autoDiscover = false;

  var UploadPattern = Base.extend({
    name: 'upload',
    defaults: {
      showTitle: true,
      url: null, // XXX MUST provide url to submit to OR be in a form
      className: 'upload',
      wrap: false,
      wrapperTemplate: '
',
      fileaddedClassName: 'dropping',
      useTus: false,
      container: '',
      ajaxUpload: true,

      paramName: 'file',
      clickable: true,
      addRemoveLinks: false,
      autoCleanResults: true,
      previewsContainer: '.previews',
      previewTemplate: null,
      maxFiles: null,
      maxFilesize: 99999999, // let's not have a max by default...

      relatedItems: {
        // UID attribute is required here since we're working with related items
        attributes: ['UID', 'Title', 'Description', 'getURL', 'Type', 'path', 'ModificationDate'],
        batchSize: 20,
        basePath: '/',
        vocabularyUrl: null,
        width: 500,
        maximumSelectionSize: 1,
        placeholder: 'Search for item on site...'
      }
    },

    init: function() {
      var self = this,
          template = UploadTemplate;

      // values that will change current processing
      self.currentPath = self.options.currentPath;
      self.numFiles = 0;
      self.currentFile = 0;

      template = _.template(template);
      self.$el.addClass(self.options.className);
      self.$el.append(template);

      self.$progress = $('.progress-bar-success', self.$el);

      if (!self.options.showTitle) {
        self.$el.find('h2.title').hide();
      }

      if (!self.options.ajaxUpload) {
        // no ajax upload, drop the fallback
        $('.fallback', this.$el).remove();
        if (this.$el.hasClass('.upload-container')) {
          this.$el.addClass('no-ajax-upload');
        } else {
          this.$el.closest('.upload-container').addClass('no-ajax-upload');
        }
      }

      if (self.options.wrap) {
        self.$el.wrap(self.options.wrapperTemplate);
        self.$el = self.$el.parent();
      }

      if (self.options.baseUrl && self.options.relativePath){
        // only use related items if we can generate paths based urls
        self.$pathInput = $('input[name="location"]', self.$el);
        self.relatedItems = self.setupRelatedItems(self.$pathInput);
      } else {
        $('input[name="location"]', self.$el).parent().remove();
        self.relatedItems = null;
      }

      var $uploadArea = $('.upload-area', self.$el);

      $('button.browse', self.$el).click(function(e) {
        e.preventDefault();
        e.stopPropagation();
        // we trigger the dropzone dialog!
        self.dropzone.hiddenFileInput.click();
      });

      var dzoneOptions = this.getDzoneOptions();

      try {
        // if init of Dropzone fails it says nothing and
        // it fails silently. Using this block we make sure
        // that if you break it w/ some weird or missing option
        // you can get a proper log of it
        //
        self.dropzone = new Dropzone($uploadArea[0], dzoneOptions);
      } catch (e) {
        if (window.DEBUG) {
          // log it!
          console.log(e);
        }
        throw e;
      }

      self.dropzone.on('addedfile', function(file) {
        //console.log('file added!!');
        // show upload controls
        self.showControls();
        // self.$el.addClass(fileaddedClassName);
        // setTimeout(function() {
        //   if (!processing) {
        //     process();
        //   }
        // }, 100);
      });

      self.dropzone.on('removedfile', function() {
        if (self.dropzone.files.length < 1) {
          self.hideControls();
        }
      });

      if (self.options.autoCleanResults) {
        self.dropzone.on('complete', function(file) {
          setTimeout(function() {
            $(file.previewElement).fadeOut();
          }, 3000);
        });
      }

      self.dropzone.on('complete', function(file) {
        if (self.dropzone.files.length < 1) {
          self.hideControls();
        }
      });

      self.dropzone.on('totaluploadprogress', function(pct) {
        // need to caclulate total pct here in reality since we're manually
        // processing each file one at a time.
        pct = ((((self.currentFile - 1) * 100) + pct) / (self.numFiles * 100)) * 100;
        self.$progress.attr('aria-valuenow', pct).css('width', pct + '%');
      });

      $('.upload-all', self.$el).click(function (e) {
        e.preventDefault();
        e.stopPropagation();
        self.processUpload({
          finished: function() {
            self.$progress.attr('aria-valuenow', 0).css('width', '0%');
          }
        });
      });
    },

    showControls: function() {
      var self = this;
      $('.controls', self.$el).fadeIn('slow');
    },

    hideControls: function() {
      var self = this;
      $('.controls', self.$el).fadeOut('slow');
    },

    pathJoin: function() {
      var parts = [];
      _.each(arguments, function(part) {
        if (!part){
          return;
        }
        if (part[0] === '/'){
          part = part.substring(1);
        }
        if (part[part.length - 1] === '/'){
          part = part.substring(0, part.length - 1);
        }
        parts.push(part);
      });
      return parts.join('/');
    },

    getUrl: function() {

      var self = this;
      var url = self.options.url;
      if (!url) {
        if (self.options.baseUrl && self.options.relativePath){
          url = self.options.baseUrl;
          if (url[url.length - 1] !== '/'){
            url = url + '/';
          }
          url = url + self.pathJoin(self.currentPath, self.options.relativePath);
        } else {
          var $form = self.$el.parents('form');
          if ($form.length > 0){
            url = $form.attr('action');
          } else {
            url = window.location.href;
          }
        }
      }
      return url;
    },

    getDzoneOptions: function() {
      var self = this;

      // clickable option
      if (typeof(self.options.clickable) === 'string') {
        if (self.options.clickable === 'true') {
          self.options.clickable = true;
        } else {
          self.options.clickable = false;
        }
      }

      var options = $.extend({}, self.options);
      options.url = self.getUrl();
      // XXX force to only upload one at a time,
      // right now we don't support multiple for backends
      options.uploadMultiple = false;

      delete options.wrap;
      delete options.wrapperTemplate;
      delete options.resultTemplate;
      delete options.autoCleanResults;
      delete options.fileaddedClassName;
      delete options.useTus;

      if (self.options.previewsContainer) {
        /*
         * if they have a select but it's not an id, let's make an id selector
         * so we can target the correct container. dropzone is weird here...
         */
        var $preview = self.$el.find(self.options.previewsContainer);
        if ($preview.length > 0) {
          options.previewsContainer = $preview[0];
        }
      }

      // XXX: do we need to allow this?
      options.autoProcessQueue = false;
      // options.addRemoveLinks = true;  // we show them in the template
      options.previewTemplate = PreviewTemplate;

      // if our element is a form we should force some values
      // https://github.com/enyo/dropzone/wiki/Combine-normal-form-with-Dropzone
      return options;
    },

    processUpload: function(options) {
      if (!options){
        options = {};
      }

      var self = this,
          processing = false,
          useTus = self.options.useTus,
          fileaddedClassName = self.options.fileaddedClassName,
          finished = options.finished;

      self.numFiles = self.dropzone.files.length;
      self.currentFile = 0;

      function process() {
        processing = true;
        if (self.dropzone.files.length === 0) {
          processing = false;
          self.$el.removeClass(fileaddedClassName);
          if (finished !== undefined && typeof(finished) === 'function'){
            finished();
          }
          return;
        }
        var file = self.dropzone.files[0];
        if ([Dropzone.SUCCESS, Dropzone.ERROR, Dropzone.CANCELED]
            .indexOf(file.status) !== -1) {
          // remove it
          self.dropzone.removeFile(file);
          process();
        } else if (file.status !== Dropzone.UPLOADING) {
          // start processing file
          if (useTus && window.tus) {
            // use tus upload if installed
            self.handleTusUpload(file);
          } else {
            // otherwise, just use dropzone to process
            self.currentFile += 1;
            self.dropzone.processFile(file);
          }
          setTimeout(process, 100);
        } else {
          // currently processing
          setTimeout(process, 100);
        }
      }
      process();
    },

    handleTusUpload: function(file) {
      /* this needs fixing... */
      var self = this,
          $preview = $(file.previewElement),
          chunkSize = 1024 * 1024 * 5; // 5mb chunk size

      file.status = Dropzone.UPLOADING;

      window.tus.upload(file, {
        endpoint: self.dropzone.options.url,
        headers: {
          'FILENAME': file.name
        },
        chunkSize: chunkSize
      }).fail(function() {
        console.alert('Error uploading with TUS resumable uploads');
        file.status = Dropzone.ERROR;
      }).progress(function(e, bytesUploaded, bytesTotal) {
        var percentage = (bytesUploaded / bytesTotal * 100);
        self.$progress.attr('aria-valuenow', percentage).css('width', percentage + '%');
        self.$progress.html('uploading...
' +
                            self.formatBytes(bytesUploaded) +
                            ' / ' + self.formatBytes(bytesTotal));
      }).done(function(url, file) {
        file.status = Dropzone.SUCCESS;
        self.dropzone.emit('success', file);
        self.dropzone.emit('complete', file);
      });
    },

    formatBytes: function(bytes) {
      var kb = Math.round(bytes / 1024);
      if (kb < 1024) {
        return kb + ' KiB';
      }
      var mb = Math.round(kb / 1024);
      if (mb < 1024) {
        return mb + ' MB';
      }
      return Math.round(mb / 1024) + ' GB';
    },

    setupRelatedItems: function($input) {
      var self = this;
      var options = self.options.relatedItems;
      if (self.options.initialFolder){
        $input.attr('value', self.options.initialFolder);
      }
      var ri = new RelatedItems($input, options);
      ri.$el.on('change', function() {
        var result = $(this).select2('data');
        if (result.length > 0){
          self.currentPath = result[0].path;
        } else {
          self.currentPath = null;
        }
        self.options.url = self.dropzone.options.url = self.getUrl();
      });
      return ri;
    }

  });

  return UploadPattern;

});
<|endoftext|>


<commit_before>var Physics = function() {
    PIXELS_PER_METER = 16;
    return function(context, gameWidth, gameHeight) {
        function newDebugDraw() {
            var debugDraw = new b2DebugDraw();
            debugDraw.SetSprite(document.getElementById("sup").getContext("2d"));
            debugDraw.SetDrawScale(PIXELS_PER_METER);
            debugDraw.SetFillAlpha(0.5);
            debugDraw.SetLineThickness(1.0);
            debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
            return debugDraw;
        }

        var b2Vec2 = Box2D.Common.Math.b2Vec2;
        var b2BodyDef = Box2D.Dynamics.b2BodyDef;
        var b2Body = Box2D.Dynamics.b2Body;
        var b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
        var b2Fixture = Box2D.Dynamics.b2Fixture;
        var b2World = Box2D.Dynamics.b2World;
        var b2MassData = Box2D.Collision.Shapes.b2MassData;
        var b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
        var b2CircleShape = Box2D.Collision.Shapes.b2CircleShape;
        var b2DebugDraw = Box2D.Dynamics.b2DebugDraw;

        var that = this;
        var turnRate = 0;
        var targetRotation = 0;
        var rotation = 0;
        var gravity = 0.00001;

        Constants.get("world_rotation_rate", function(value) {
            turnRate = value;
        });

        var debugDraw = newDebugDraw();

        this.world = new b2World(newGravity(), false);
        this.world.SetDebugDraw(debugDraw);
        Constants.get("world_gravity", function(value) {
            gravity = value;
            that.world.SetGravity(newGravity());
        });

        newWorld();

        this.newBlock = function(stage) {
            var fd                 = new b2FixtureDef;
            fd.shape               = new b2PolygonShape();
            fd.density = 1.0;
            fd.friction = 0.3;
            fd.restitution = 0.0;

            fd.shape.SetAsBox(1,1);

            var bodyDef                  = new b2BodyDef();
            bodyDef.type                 = b2Body.b2_dynamicBody;
            bodyDef.position.x           = 300/PIXELS_PER_METER;
            bodyDef.position.y           = 300/PIXELS_PER_METER;
            var body = that.world.CreateBody(bodyDef);
            body.CreateFixture(fd);

            var e = new Entity();
            Constants.get(["block_image", "block_scale"], function(value, scale) {
                var beeTexture = PIXI.Texture.fromImage(value, true);
                var beeSprite = new PIXI.Sprite(beeTexture);

                beeSprite.anchor.x = 0.5;
                beeSprite.anchor.y = 0.5;

                beeSprite.scale.x = scale;
                beeSprite.scale.y = scale;

                e.addComponent(SpriteComponent(stage, beeSprite));
                e.addComponent(PhysicsComponent(body));
                e.addComponent(MovableComponent());
                console.log("here");
                World.add(e);
            });
        };

        this.draw = function() {
            that.world.DrawDebugData();
        };

        this.update = function() {
            if (Math.abs(targetRotation - rotation) > turnRate) {
                function sign(x) { return x ? x < 0 ? -1 : 1 : 0; }
                direction = sign(targetRotation - rotation);
                rotation += direction * turnRate;
                that.world.SetGravity(newGravity());
            }

            that.world.Step(3,3);
        };

        this.getRotation = function() {
            return rotation;
        };

        this.setTargetRotation = function(rotation) {
            targetRotation = rotation;
        };

        function newGravity() {
            var vector = rotate([0, gravity*PIXELS_PER_METER]);
            return new b2Vec2(vector[0], vector[1]);
        }

        function rotate(vec) {
            var sine   = Math.sin(rotation * (Math.PI/180));
            var cosine = Math.cos(rotation * (Math.PI/180));
            return [vec[0] * cosine + vec[1] * sine,
                    vec[0] * -sine + vec[1] * cosine];
        }

        function addWall(x, y, width, height) {
            //floor
            var floorDef                 = new b2FixtureDef;
            floorDef.shape               = new b2PolygonShape();
            floorDef.shape.SetAsBox(width/2, height/2);

            var floorBodyDef                  = new b2BodyDef();
            floorBodyDef.type                 = b2Body.b2_staticBody;
            floorBodyDef.position.x           = x/PIXELS_PER_METER;
            floorBodyDef.position.y           = y/PIXELS_PER_METER;
            that.world.CreateBody(floorBodyDef).CreateFixture(floorDef);
        }

        function newWorld() {
            addWall(gameWidth/2, 0, 10000, 3);
            addWall(gameWidth/2, gameHeight, 10000, 3);
            addWall(0, gameHeight/2, 3, 100000);
            addWall(gameWidth, gameHeight/2, 3, 100000);
        }

        this.rotate = rotate;
    };
}();
<commit_msg>octaganal level.<commit_after>var Physics = function() {
    PIXELS_PER_METER = 16;
    return function(context, gameWidth, gameHeight) {
        function newDebugDraw() {
            var debugDraw = new b2DebugDraw();
            debugDraw.SetSprite(document.getElementById("sup").getContext("2d"));
            debugDraw.SetDrawScale(PIXELS_PER_METER);
            debugDraw.SetFillAlpha(0.5);
            debugDraw.SetLineThickness(1.0);
            debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
            return debugDraw;
        }

        var b2Vec2 = Box2D.Common.Math.b2Vec2;
        var b2BodyDef = Box2D.Dynamics.b2BodyDef;
        var b2Body = Box2D.Dynamics.b2Body;
        var b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
        var b2Fixture = Box2D.Dynamics.b2Fixture;
        var b2World = Box2D.Dynamics.b2World;
        var b2MassData = Box2D.Collision.Shapes.b2MassData;
        var b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
        var b2CircleShape = Box2D.Collision.Shapes.b2CircleShape;
        var b2DebugDraw = Box2D.Dynamics.b2DebugDraw;

        var that = this;
        var turnRate = 0;
        var targetRotation = 0;
        var rotation = 0;
        var gravity = 0.00001;

        Constants.get("world_rotation_rate", function(value) {
            turnRate = value;
        });

        var debugDraw = newDebugDraw();

        this.world = new b2World(newGravity(), false);
        this.world.SetDebugDraw(debugDraw);
        Constants.get("world_gravity", function(value) {
            gravity = value;
            that.world.SetGravity(newGravity());
        });

        newWorld();

        this.newBlock = function(stage) {
            var fd                 = new b2FixtureDef;
            fd.shape               = new b2PolygonShape();
            fd.density = 1.0;
            fd.friction = 0.3;
            fd.restitution = 0.0;

            fd.shape.SetAsBox(1,1);

            var bodyDef                  = new b2BodyDef();
            bodyDef.type                 = b2Body.b2_dynamicBody;
            bodyDef.position.x           = 300/PIXELS_PER_METER;
            bodyDef.position.y           = 300/PIXELS_PER_METER;
            var body = that.world.CreateBody(bodyDef);
            body.CreateFixture(fd);

            var e = new Entity();
            Constants.get(["block_image", "block_scale"], function(value, scale) {
                var beeTexture = PIXI.Texture.fromImage(value, true);
                var beeSprite = new PIXI.Sprite(beeTexture);

                beeSprite.anchor.x = 0.5;
                beeSprite.anchor.y = 0.5;

                beeSprite.scale.x = scale;
                beeSprite.scale.y = scale;

                e.addComponent(SpriteComponent(stage, beeSprite));
                e.addComponent(PhysicsComponent(body));
                e.addComponent(MovableComponent());
                console.log("here");
                World.add(e);
            });
        };

        this.draw = function() {
            that.world.DrawDebugData();
        };

        this.update = function() {
            if (Math.abs(targetRotation - rotation) > turnRate) {
                function sign(x) { return x ? x < 0 ? -1 : 1 : 0; }
                direction = sign(targetRotation - rotation);
                rotation += direction * turnRate;
                that.world.SetGravity(newGravity());
            }

            that.world.Step(3,3);
        };

        this.getRotation = function() {
            return rotation;
        };

        this.setTargetRotation = function(rotation) {
            targetRotation = rotation;
        };

        function newGravity() {
            var vector = rotate([0, gravity*PIXELS_PER_METER]);
            return new b2Vec2(vector[0], vector[1]);
        }

        function rotate(vec) {
            var sine   = Math.sin(rotation * (Math.PI/180));
            var cosine = Math.cos(rotation * (Math.PI/180));
            return [vec[0] * cosine + vec[1] * sine,
                    vec[0] * -sine + vec[1] * cosine];
        }

        function addWall(x, y, width, height, rotation, offset1, offset2) {
            //floor
            var floorDef                 = new b2FixtureDef;
            floorDef.shape               = new b2PolygonShape();
            floorDef.shape.SetAsOrientedBox(width/2, height/2, new b2Vec2(offset1, offset2), rotation * Math.PI/180);

            var floorBodyDef                  = new b2BodyDef();
            floorBodyDef.type                 = b2Body.b2_staticBody;
            floorBodyDef.position.x           = x/PIXELS_PER_METER;
            floorBodyDef.position.y           = y/PIXELS_PER_METER;
            that.world.CreateBody(floorBodyDef).CreateFixture(floorDef);
        }

        function newWorld() {
            addWall(gameWidth/2, 0, 10000, 3, 0, 0, 0);
            addWall(gameWidth/2, gameHeight, 10000, 3, 0, 0, 0);
            addWall(0, gameHeight/2, 3, 100000, 0, 0, 0);
            addWall(gameWidth, gameHeight/2, 3, 100000, 0, 0, 0);
            //addWall(gameWidth, gameHeight/2, 100000, 3, 45, 3, 3);
            addWall(gameWidth+100, gameHeight/2, 100000, 3, 45, 0, 0);
            addWall(-100, gameHeight/2, 100000, 3, 45, 0, 0);
            addWall(gameWidth/2, -100, 10000, 3, -45, 0, 0);
            addWall(gameWidth/2, gameHeight+100, 10000, 3, -45, 0, 0);
        }

        this.rotate = rotate;
    };
}();
<|endoftext|>


<commit_before>#!/usr/bin/env node

var piphone = {};

piphone.mods = {};
piphone.mods.gpiobutton = require('gpiobutton');
piphone.mods.cp = require('child_process');

piphone.hook = new piphone.mods.gpiobutton.button({name:'hook', gpiono:22, DOWN:1});
piphone.dial = new piphone.mods.gpiobutton.button({name:'dial', gpiono:27});
piphone.rotary = new piphone.mods.gpiobutton.button({name:'rotary', gpiono:17, interval:30, DOWN:1});
piphone.onoff = new piphone.mods.gpiobutton.button({name:'switch', gpiono: 18});

piphone.code = {};
piphone.code.curr = [];

piphone.rotary.on('multipress', function(spec) {
  piphone.code.curr.push(spec.count);

  if (piphone.code.unlisten) {
    clearTimeout(piphone.code.unlisten);
  }

  var code = piphone.code.curr.join("");
  //console.error("CODE: %j", piphone.code.curr);

  switch (code) {
    case "1178":
      process.emit('shutdown_request');
      break;
    case "11":
      piphone.rotary.code = true;
      break;
  }

  piphone.code.unlisten = setTimeout(function() {
    //console.error("UNLISTEN");
    piphone.code.curr = [];
    piphone.rotary.code = false;
    delete piphone.code.unlisten;
  }, 4000);
});

piphone.onoff.on('buttonup', function() {
  piphone.onoff.enabled = true;
});
piphone.onoff.on('longpress', function() {
  if (! piphone.onoff.enabled) { return; }
  process.emit('shutdown_request');
});

process.on('shutdown_request', function() {
  piphone.mods.cp.exec('mpc stop', function(err, stdout, stderr) {
    console.error("STOP: %j", {err:err,stdout:stdout,stderr:stderr});
  });
  piphone.mods.cp.exec('tts goodbye cruel world', function(err, stdout, stderr) {
    console.error("SOLONG: %j", {err:err,stdout:stdout,stderr:stderr});
  });
  setTimeout(function() {
    piphone.mods.cp.exec('shutdown -h now', function(err, stdout, stderr) {
      console.error("SHUTDOWN: %j", {err:err,stdout:stdout,stderr:stderr});
    });
  }, 5000);
});

piphone.dial.on('buttondown', function() {
  piphone.rotary.activate();
});
piphone.dial.on('buttonup', function() {
  piphone.rotary.deactivate();
});

piphone.hook.on('longpress', function() {
  piphone.mods.cp.exec('mpc pause', function(err, stdout, stderr) {
    console.error("STOP: %j", {err:err,stdout:stdout,stderr:stderr});
  });
});

piphone.rotary.on('multipress', function(spec) {
  console.error("ROTARY: " + spec.count);
  if (piphone.rotary.code) { return; }
  switch (spec.count) {
    case 1:
      process.emit("volume", {volume:60});
      process.emit("mpc", {cmd:'play'});
      break;
    case 2:
      process.emit("volume", {volume:100});
      process.emit("mpc", {cmd:'play'});
      break;
    case 3:
      process.emit("volume", {volume:60});
      process.emit("mpc", {cmd:'next'});
      break;
    case 4:
      process.emit("volume", {volume:100});
      process.emit("mpc", {cmd:'next'});
      break;
    case 5:
      process.emit("volume", {volume:60});
      process.emit("mpc", {cmd:'prev'});
      break;
    case 6:
      process.emit("volume", {volume:100});
      process.emit("mpc", {cmd:'prev'});
      break;
    case 7:
      process.emit("volume", {volume:100});
      process.emit("mpcq", {query:['guapo']});
      break;
    case 8:
      process.emit("volume", {volume:100});
      process.emit("mpcq", {query:['belafonte','matilda']});
      break;
    case 9:
      process.emit("volume", {volume:100});
      process.emit("mpcq", {query:['susanna','tanyas']});
      break;
    case 10:
      var tts = piphone.mods.cp.exec('/usr/bin/tts');
      tts.stdout.pipe(process.stdout);
      tts.stderr.pipe(process.stderr);
      tts.stdin.write('puff puff paper\n');
      tts.stdin.end();
      process.emit("volume", {volume:100});
      process.emit("mpcq", {query:['puff']});
      break;
    case 11:
      var tts = piphone.mods.cp.exec('/usr/local/bin/tts');
      tts.stdin.write("how much wood would a wood chuck chuck if a woodchuck would chuck wood?\n");
      tts.stdin.end();
      break;
  }
});
piphone.rotary.on('buttonpress', function(spec) {
  piphone.rotary.emit('multipress', spec);
});

process.on('mpcq', function(spec) {
    var mpc = piphone.mods.cp.exec(['/usr/local/bin/mpc_query',spec.query.join(".*")].concat([
      '|',
      'xargs mpc play'
    ]).join(" "), function(err,stdout,stderr) {
    console.error("DEMAND: %j", {err:err,stdout:stdout,stderr:stderr});
  });
});
process.on('mpc', function(spec) {
  piphone.mods.cp.exec(['mpc',spec.cmd].join(" "), function(err, stdout, stderr) {
    console.error("%s: %j", spec.cmd, {err:err,stdout:stdout,stderr:stderr});
  });
});
process.on('volume', function(spec) {
  piphone.mods.cp.exec(['mpc','volume',spec.volume].join(" "), function(err, stdout, stderr) {
    console.error("VOL: %j", {vol:spec.volume,err:err,stdout:stdout,stderr:stderr});
  });
});
<commit_msg>general tts<commit_after>#!/usr/bin/env node

var piphone = {};

piphone.mods = {};
piphone.mods.gpiobutton = require('gpiobutton');
piphone.mods.cp = require('child_process');

piphone.hook = new piphone.mods.gpiobutton.button({name:'hook', gpiono:22, DOWN:1});
piphone.dial = new piphone.mods.gpiobutton.button({name:'dial', gpiono:27});
piphone.rotary = new piphone.mods.gpiobutton.button({name:'rotary', gpiono:17, interval:30, DOWN:1});
piphone.onoff = new piphone.mods.gpiobutton.button({name:'switch', gpiono: 18});

piphone.code = {};
piphone.code.curr = [];

piphone.rotary.on('multipress', function(spec) {
  piphone.code.curr.push(spec.count);

  if (piphone.code.unlisten) {
    clearTimeout(piphone.code.unlisten);
  }

  var code = piphone.code.curr.join("");
  //console.error("CODE: %j", piphone.code.curr);

  switch (code) {
    case "1178":
      process.emit('shutdown_request');
      break;
    case "11":
      piphone.rotary.code = true;
      break;
  }

  piphone.code.unlisten = setTimeout(function() {
    //console.error("UNLISTEN");
    piphone.code.curr = [];
    piphone.rotary.code = false;
    delete piphone.code.unlisten;
  }, 4000);
});

piphone.onoff.on('buttonup', function() {
  piphone.onoff.enabled = true;
});
piphone.onoff.on('longpress', function() {
  if (! piphone.onoff.enabled) { return; }
  process.emit('shutdown_request');
});

process.on('shutdown_request', function() {
  piphone.mods.cp.exec('mpc stop', function(err, stdout, stderr) {
    console.error("STOP: %j", {err:err,stdout:stdout,stderr:stderr});
  });
  piphone.mods.cp.exec('tts goodbye cruel world', function(err, stdout, stderr) {
    console.error("SOLONG: %j", {err:err,stdout:stdout,stderr:stderr});
  });
  setTimeout(function() {
    piphone.mods.cp.exec('shutdown -h now', function(err, stdout, stderr) {
      console.error("SHUTDOWN: %j", {err:err,stdout:stdout,stderr:stderr});
    });
  }, 5000);
});

piphone.dial.on('buttondown', function() {
  piphone.rotary.activate();
});
piphone.dial.on('buttonup', function() {
  piphone.rotary.deactivate();
});

piphone.hook.on('longpress', function() {
  piphone.mods.cp.exec('mpc pause', function(err, stdout, stderr) {
    console.error("STOP: %j", {err:err,stdout:stdout,stderr:stderr});
  });
});

piphone.rotary.on('multipress', function(spec) {
  console.error("ROTARY: " + spec.count);
  if (piphone.rotary.code) { return; }
  switch (spec.count) {
    case 1:
      process.emit("volume", {volume:60});
      process.emit("mpc", {cmd:'play'});
      break;
    case 2:
      process.emit("volume", {volume:100});
      process.emit("mpc", {cmd:'play'});
      break;
    case 3:
      process.emit("volume", {volume:60});
      process.emit("mpc", {cmd:'next'});
      break;
    case 4:
      process.emit("volume", {volume:100});
      process.emit("mpc", {cmd:'next'});
      break;
    case 5:
      process.emit("volume", {volume:60});
      process.emit("mpc", {cmd:'prev'});
      break;
    case 6:
      process.emit("volume", {volume:100});
      process.emit("mpc", {cmd:'prev'});
      break;
    case 7:
      process.emit("volume", {volume:100});
      process.emit("mpcq", {query:['guapo']});
      break;
    case 8:
      process.emit("volume", {volume:100});
      process.emit("mpcq", {query:['belafonte','matilda']});
      break;
    case 9:
      process.emit("volume", {volume:100});
      process.emit("mpcq", {query:['susanna','tanyas']});
      break;
    case 10:
      process.emit("tts", {text:['puff puff paper']});
      process.emit("volume", {volume:100});
      process.emit("mpcq", {query:['puff']});
      break;
    case 11:
      var tts = piphone.mods.cp.exec('/usr/local/bin/tts');
      tts.stdin.write("how much wood would a wood chuck chuck if a woodchuck would chuck wood?\n");
      tts.stdin.end();
      break;
  }
});
piphone.rotary.on('buttonpress', function(spec) {
  piphone.rotary.emit('multipress', spec);
});

process.on('tts', function(spec) {
  var tts = piphone.mods.cp.exec('/usr/bin/tts');
  tts.stdout.pipe(process.stdout);
  tts.stderr.pipe(process.stderr);
  tts.stdin.write(spec.text.concat(['\n']).join(" "));;
  tts.stdin.end();
});

process.on('mpcq', function(spec) {
    process.emit('tts', {text:spec.query});
    var mpc = piphone.mods.cp.exec(['/usr/local/bin/mpc_query',spec.query.join(".*")].concat([
      '|',
      'xargs mpc play'
    ]).join(" "), function(err,stdout,stderr) {
    console.error("DEMAND: %j", {err:err,stdout:stdout,stderr:stderr});
  });
});
process.on('mpc', function(spec) {
  piphone.mods.cp.exec(['mpc',spec.cmd].join(" "), function(err, stdout, stderr) {
    console.error("%s: %j", spec.cmd, {err:err,stdout:stdout,stderr:stderr});
  });
});
process.on('volume', function(spec) {
  piphone.mods.cp.exec(['mpc','volume',spec.volume].join(" "), function(err, stdout, stderr) {
    console.error("VOL: %j", {vol:spec.volume,err:err,stdout:stdout,stderr:stderr});
  });
});
<|endoftext|>


<commit_before>"use strict";

document.addEventListener("DOMContentLoaded", function() {

  let maxSkillNodes = 91;

  let SkillTree = (function() {

    var activeTreeName = treeSource[0].name;
    let skillTrees = buildSkillTrees(treeSource);

    function buildSkillTrees(treeSource) {
      let skillTrees = [];
      for (let treeDef of treeSource) {
        let skillTree = {}
        skillTree.name = treeDef.name;
        skillTree.nodes = [];
        for (let nodeDef of treeDef.nodes) {
          skillTree.nodes.push(new Node(nodeDef.name,
                                        nodeDef.attribute,
                                        parseFloat(nodeDef.value),
                                        nodeDef.valueTemplate,
                                        nodeNameToId(nodeDef.name),
                                        nodeDef.leftChildId,
                                        nodeDef.centerChildId,
                                        nodeDef.rightChildId
                              ));
        }
        skillTrees.push(skillTree);
      }
      return skillTrees;
    }

    function setActiveTreeName(newName) {
      activeTreeName = newName;
    }

    function getActiveTreeName() {
      return activeTreeName;
    }

    function getTrees() {
      return skillTrees;
    }

    function getTree(treeName) {
      if (treeName == undefined) {
        return getTree(activeTreeName);
      } else {
        for (let tree of skillTrees) {
          if (tree.name == treeName) {
            return tree;
          }
        }
      }
    }

    function getNode(nodeId) {
      for (let tree of skillTrees) {
        for (let node of tree.nodes) {
          if (node.id == nodeId) {
            return node;
          }
        }
      }
    }

    function toggleNodeSelection(node) {
      node.toggleSelection();
    }

    function getNodeCount(treeName) {
      var nodeCount = 0;
      if (treeName != undefined) {
        return getTree(treeName).nodes.length;
      } else {
        for (let tree of skillTrees) {
          nodeCount = getNodeCount(tree.name);
        }
      }
      return nodeCount;
    }

    function getSelectedNodes(treeName) {
      let selectedNodes = [];

      if (treeName != undefined) {
        for (let node of getTree(treeName).nodes) {
          if (node.selected) {
            selectedNodes.push(node);
          }
        }
      } else {
        for (let skillTree of skillTrees) {
          selectedNodes = selectedNodes.concat(getSelectedNodes(skillTree.name));
        }
      }
      return selectedNodes;
    }

    function childrenOf(node) {
      let childNodes = [];
      pushIfDefined(childNodes, getNode(node.leftChildId));
      pushIfDefined(childNodes, getNode(node.centerChildId));
      pushIfDefined(childNodes, getNode(node.rightChildId));
      return childNodes;
    }

    function parentsOf(node) {
      let parentNodes = [];
      for (let tree of skillTrees) {
        for (let potentialParent of tree.nodes) {
          if (potentialParent.leftChildId == node.id || potentialParent.centerChildId == node.id || potentialParent.rightChildId == node.id) {
            parentNodes.push(potentialParent);
          }
        }
      }
      return parentNodes;
    }

    function pushIfDefined(collection, node) {
      if (node !== undefined) {
        collection.push(node);
      }
    }

    // public interface
    return {
      getActiveTreeName: getActiveTreeName,
      setActiveTreeName: setActiveTreeName,
      getTrees: getTrees,
      getNode: getNode,
      getNodeCount: getNodeCount,
      getSelectedNodes: getSelectedNodes,
      parentsOf: parentsOf,
      childrenOf: childrenOf
    }

  })();

  function Node(newName, newAttribute, newValue, newValueTemplate, newId, newLeftChildId, newCenterChildId, newRightCenterChildId) {
    var selected = false;

    this.name = newName;
    this.attribute = newAttribute;
    this.value = newValue;
    this.valueTemplate = newValueTemplate
    this.id = newId;

    this.leftChildId = newLeftChildId,
    this.centerChildId = newCenterChildId,
    this.rightChildId = newRightCenterChildId,
    this.parents = function() {
      return SkillTree.parentsOf(this);
    };
    this.children = function() {
      return SkillTree.childrenOf(this);
    };

    this.selected = selected;
    this.toggleSelection = function() {
      nodeSelectionChanged(this);
    };

  }

  function buildUI(trees) {
    trees.forEach(function(tree, index) {
      buildTab(tree, index);
      buildTreeDisplay(tree);
    });
    updateNodeCounters();
    document.getElementById('node-total').textContent = maxSkillNodes;
    document.getElementById(trees[0].name.toLowerCase() + '-tab').click();
  }

  buildUI(SkillTree.getTrees());

  function buildTab(tree, index) {
    let topOffset = 50;
    let tabElement = document.createElement("div");
    tabElement.id = tree.name.toLowerCase() + '-tab';
    tabElement.classList.add("tab");
    tabElement.style.top = (40 * index) + 50 + "px";
    tabElement.style.left = 0;
    tabElement.textContent = tree.name;

    let counterElement = document.createElement("div");
    counterElement.id = tree.name.toLowerCase() + '-tab-counter';
    counterElement.classList.add("tab-counter");
    counterElement.textContent = '0 / ' + SkillTree.getNodeCount(tree.name);
    tabElement.appendChild(counterElement);

    tabElement.addEventListener("click", function() {
      document.querySelectorAll('.tab').forEach(function (el) {
        el.classList.remove('selected');
      });
      tabElement.classList.add('selected');
      changeSkillTree(tree.name);
    });
    document.getElementById("left-sidebar").appendChild(tabElement);
  }

  function buildTreeDisplay(tree) {
    let treeElement = document.createElement("div");
    treeElement.id = treeNameToId(tree.name);
    treeElement.classList.add('skill-tree');
    treeElement.classList.add('hide');
    document.getElementById("graph-view").appendChild(treeElement);

    // need to do something here to sort the node array.  Probably search it each time you add a node
    // and add the children of that node to a queue to be the next loaded

    let xOffset = 55;
    let yOffset = 30;
    var leftmostNodeElement = 0;
    var rightmostNodeElement = 0;

    for (let node of tree.nodes) {
      let nodeElement = document.createElement("div");
      nodeElement.classList.add("graph-node");
      nodeElement.id = node.id;
      nodeElement.textContent = node.name;

      // the first element in nodes is the root node, so it starts available
      if (node == tree.nodes[0])
        nodeElement.classList.add("available");
      else {
        nodeElement.classList.add("unavailable");
      }

      let parent = node.parents()[0];
      if (parent != undefined) {
        let relativeChildPostiion = getRelativeChildPosition(parent, node.id);
        let parentElement = document.getElementById(parent.id);
        let parentTop = dimensionAsNumber(parentElement.style.top);
        let parentLeft = dimensionAsNumber(parentElement.style.left);
        if (relativeChildPostiion == 'left') {
          nodeElement.style.top = parentTop + yOffset + 'px';
          nodeElement.style.left = parentLeft - xOffset + 'px';
        } else if (relativeChildPostiion == 'right') {
          nodeElement.style.top = parentTop + yOffset + 'px';
          nodeElement.style.left = parentLeft + xOffset + 'px';
        } else {
          nodeElement.style.top = parentTop + (yOffset * 2) + 'px';
          nodeElement.style.left = parentLeft + 'px';
        }
      } else {
        nodeElement.style.top = '25px';
        nodeElement.style.left = '26px';
      }

      let leftPosition = dimensionAsNumber(nodeElement.style.left);
      if (leftPosition < leftmostNodeElement) {
        leftmostNodeElement = leftPosition;
      }
      if (leftPosition > rightmostNodeElement) {
        rightmostNodeElement = leftPosition;
      }

      treeElement.appendChild(nodeElement);
      nodeElement.addEventListener("click", function() {
        nodeSelectionChanged(node);
      });
    }

    let nodeWidth = 52; // width of a graph node, per planner.css
    let padding = 25; // 'padding' here rather than in css because 'absolute' positioning of the
                      // node elements throws off alignment of css padding
    treeElement.style.width = rightmostNodeElement - leftmostNodeElement + nodeWidth + (padding * 2) + 'px';
    document.getElementById(treeNameToId(tree.name)).querySelectorAll('.graph-node').forEach(function (el) {
      let newLeft = dimensionAsNumber(el.style.left) + (-leftmostNodeElement) + padding + 'px';
      el.style.left = newLeft;
    });

  }

  function getRelativeChildPosition(parent, childId) {
    if (parent.leftChildId == childId) {
      return 'left';
    } else if (parent.centerChildId == childId) {
      return 'center';
    } else if (parent.rightChildId == childId) {
      return 'right';
    }
    return 'child not found'
  }

  function nodeSelectionChanged(node) {
    if (node.selected) {
      if (safeToDeselect(node)) {
        node.selected = false;
        if (nodeAvailableForSelection(node)) {
          setNodeColorBasedOnSelectionStatus(node, "available");
        } else {
          setNodeColorBasedOnSelectionStatus(node, "unavailable");
        }
      }
    } else {
      if (nodeAvailableForSelection(node) && (SkillTree.getSelectedNodes().length < maxSkillNodes)) {
        node.selected = true;
        setNodeColorBasedOnSelectionStatus(node, "selected");
      }
    }
    // update children
    for (let child of node.children()) {
      if (child.selected) {
        setNodeColorBasedOnSelectionStatus(child, "selected");
      } else if (nodeAvailableForSelection(child)) {
        setNodeColorBasedOnSelectionStatus(child, "available");
      } else {
        setNodeColorBasedOnSelectionStatus(child, "unavailable");
      }
    }
    updateNodeCounters();
    updateBonuses();
  }

  function nodeAvailableForSelection(node) {
    var parentIsSelected = false;
    for (let parent of node.parents()) {
      parentIsSelected = parent.selected || parentIsSelected;
    }
    parentIsSelected = parentIsSelected || (node.parents().length === 0);
    return parentIsSelected;
  }

  function safeToDeselect(node) {
    var safeToDeselect = true;
    for (let child of node.children()) {
      if (child.selected) {
        // Set node in question to deselected to see if the chlid is still elegible for selection
        // based on other parents.  We"ll set it back to selected after we"re done with that check.
        node.selected = false;
        safeToDeselect = nodeAvailableForSelection(child) && safeToDeselect;
        node.selected = true;
      }
    }
    return safeToDeselect;
  }

  function setNodeColorBasedOnSelectionStatus(node, selectionStatus) {
    let nodeElement = document.getElementById(node.id);
    removeNodeClasses(nodeElement);
    nodeElement.classList.add(selectionStatus);
  }

  function updateNodeCounters() {
    document.getElementById('node-selection-counter').textContent = SkillTree.getSelectedNodes().length;
    let activeTree = SkillTree.getActiveTreeName();
    let activeTabCounter = document.getElementById(activeTree.toLowerCase() + '-tab-counter');
    let nodesSelected = SkillTree.getSelectedNodes(activeTree).length;
    let nodesTotal = SkillTree.getNodeCount(activeTree);
    activeTabCounter.textContent = nodesSelected + ' / ' + nodesTotal;
  }

  function updateBonuses() {
    let bonuses = [];
    let nodes = SkillTree.getSelectedNodes();
    for (let node of nodes) {
      let bonusForAttribute = getBonusForAttribute(bonuses, node.attribute);
      if (bonusForAttribute != undefined) {
        bonusForAttribute.value = bonusForAttribute.value + node.value;
      } else {
        bonuses.push({attribute: node.attribute, value: node.value, valueTemplate: node.valueTemplate});
      }
    }
    document.getElementById("bonuses-display").innerHTML = null;

    bonuses.forEach(function(bonus, index) {

      let bonusDisplayElement = document.createElement("div");
      bonusDisplayElement.id = bonusAttributeToId(bonus.attribute);
      bonusDisplayElement.classList.add("bonus-display");
      bonusDisplayElement.textContent = bonus.attribute + ' ' + getValueTemplate(bonus.attribute)[0] + bonus.value + getValueTemplate(bonus.attribute)[1];

      document.getElementById("bonuses-display").append(bonusDisplayElement);

    });
  }

  function getBonusForAttribute(bonuses, attribute) {
    for (let bonus of bonuses) {
      if (bonus.attribute == attribute) {
        return bonus;
      }
    }
  }

  function getValueTemplate(attribute) {
    for (let mapping of attributeTemplateMap) {
      if (mapping.attribute == attribute) {
        return [ mapping.template.split('{}')[0], mapping.template.split('{}')[1] ];
      }
    }
  }

  function changeSkillTree(treeName) {
    SkillTree.setActiveTreeName(treeName);
    document.querySelectorAll('.skill-tree').forEach(function (el) {
      el.classList.add('hide');
    });
    document.getElementById(treeNameToId(treeName)).classList.remove('hide');
  }

  function treeNameToId(treeName) {
    return treeName.replace(/ /g, "-").toLowerCase() + '-skill-tree';
  }

  function bonusAttributeToId(attribute) {
    return attribute.replace(/ /g, "-").toLowerCase() + '-bonus-display';
  }

  // strips the 'px' off the end of a CSS dimension, returns the number value
  function dimensionAsNumber(dimension) {
    return parseInt(dimension.slice(0, -2));
  }

  function removeNodeClasses(nodeElement) {
    nodeElement.classList.remove("selected");
    nodeElement.classList.remove("available");
    nodeElement.classList.remove("unavailable");
  }

  function nodeNameToId(nodeName) {
    return nodeName.replace(/ /g, "-").toLowerCase();
  }

});
<commit_msg>Fixed a parseInt to parseFloat to support fractional CSS values.  Reordered some methods because clarity.<commit_after>"use strict";

document.addEventListener("DOMContentLoaded", function() {

  let maxSkillNodes = 91;

  let SkillTree = (function() {

    var activeTreeName = treeSource[0].name;
    let skillTrees = buildSkillTrees(treeSource);

    function buildSkillTrees(treeSource) {
      let skillTrees = [];
      for (let treeDef of treeSource) {
        let skillTree = {}
        skillTree.name = treeDef.name;
        skillTree.nodes = [];
        for (let nodeDef of treeDef.nodes) {
          skillTree.nodes.push(new Node(nodeDef.name,
                                        nodeDef.attribute,
                                        parseFloat(nodeDef.value),
                                        nodeDef.valueTemplate,
                                        nodeNameToId(nodeDef.name),
                                        nodeDef.leftChildId,
                                        nodeDef.centerChildId,
                                        nodeDef.rightChildId
                              ));
        }
        skillTrees.push(skillTree);
      }
      return skillTrees;
    }

    function setActiveTreeName(newName) {
      activeTreeName = newName;
    }

    function getActiveTreeName() {
      return activeTreeName;
    }

    function getTrees() {
      return skillTrees;
    }

    function getTree(treeName) {
      if (treeName == undefined) {
        return getTree(activeTreeName);
      } else {
        for (let tree of skillTrees) {
          if (tree.name == treeName) {
            return tree;
          }
        }
      }
    }

    function getNode(nodeId) {
      for (let tree of skillTrees) {
        for (let node of tree.nodes) {
          if (node.id == nodeId) {
            return node;
          }
        }
      }
    }

    function toggleNodeSelection(node) {
      node.toggleSelection();
    }

    function getNodeCount(treeName) {
      var nodeCount = 0;
      if (treeName != undefined) {
        return getTree(treeName).nodes.length;
      } else {
        for (let tree of skillTrees) {
          nodeCount = getNodeCount(tree.name);
        }
      }
      return nodeCount;
    }

    function getSelectedNodes(treeName) {
      let selectedNodes = [];

      if (treeName != undefined) {
        for (let node of getTree(treeName).nodes) {
          if (node.selected) {
            selectedNodes.push(node);
          }
        }
      } else {
        for (let skillTree of skillTrees) {
          selectedNodes = selectedNodes.concat(getSelectedNodes(skillTree.name));
        }
      }
      return selectedNodes;
    }

    function childrenOf(node) {
      let childNodes = [];
      pushIfDefined(childNodes, getNode(node.leftChildId));
      pushIfDefined(childNodes, getNode(node.centerChildId));
      pushIfDefined(childNodes, getNode(node.rightChildId));
      return childNodes;
    }

    function parentsOf(node) {
      let parentNodes = [];
      for (let tree of skillTrees) {
        for (let potentialParent of tree.nodes) {
          if (potentialParent.leftChildId == node.id || potentialParent.centerChildId == node.id || potentialParent.rightChildId == node.id) {
            parentNodes.push(potentialParent);
          }
        }
      }
      return parentNodes;
    }

    function pushIfDefined(collection, node) {
      if (node !== undefined) {
        collection.push(node);
      }
    }

    // public interface
    return {
      getActiveTreeName: getActiveTreeName,
      setActiveTreeName: setActiveTreeName,
      getTrees: getTrees,
      getNode: getNode,
      getNodeCount: getNodeCount,
      getSelectedNodes: getSelectedNodes,
      parentsOf: parentsOf,
      childrenOf: childrenOf
    }

  })();

  function Node(newName, newAttribute, newValue, newValueTemplate, newId, newLeftChildId, newCenterChildId, newRightCenterChildId) {
    var selected = false;

    this.name = newName;
    this.attribute = newAttribute;
    this.value = newValue;
    this.valueTemplate = newValueTemplate
    this.id = newId;

    this.leftChildId = newLeftChildId,
    this.centerChildId = newCenterChildId,
    this.rightChildId = newRightCenterChildId,
    this.parents = function() {
      return SkillTree.parentsOf(this);
    };
    this.children = function() {
      return SkillTree.childrenOf(this);
    };

    this.selected = selected;
    this.toggleSelection = function() {
      nodeSelectionChanged(this);
    };

  }

  function buildUI(trees) {
    trees.forEach(function(tree, index) {
      buildTab(tree, index);
      buildTreeDisplay(tree);
    });
    updateNodeCounters();
    document.getElementById('node-total').textContent = maxSkillNodes;
    document.getElementById(trees[0].name.toLowerCase() + '-tab').click();
  }

  buildUI(SkillTree.getTrees());

  function buildTab(tree, index) {
    let topOffset = 50;
    let tabElement = document.createElement("div");
    tabElement.id = tree.name.toLowerCase() + '-tab';
    tabElement.classList.add("tab");
    tabElement.style.top = (40 * index) + 50 + "px";
    tabElement.style.left = 0;
    tabElement.textContent = tree.name;

    let counterElement = document.createElement("div");
    counterElement.id = tree.name.toLowerCase() + '-tab-counter';
    counterElement.classList.add("tab-counter");
    counterElement.textContent = '0 / ' + SkillTree.getNodeCount(tree.name);
    tabElement.appendChild(counterElement);

    tabElement.addEventListener("click", function() {
      document.querySelectorAll('.tab').forEach(function (el) {
        el.classList.remove('selected');
      });
      tabElement.classList.add('selected');
      changeSkillTree(tree.name);
    });
    document.getElementById("left-sidebar").appendChild(tabElement);
  }

  function buildTreeDisplay(tree) {
    let treeElement = document.createElement("div");
    treeElement.id = treeNameToId(tree.name);
    treeElement.classList.add('skill-tree');
    treeElement.classList.add('hide');
    document.getElementById("graph-view").appendChild(treeElement);

    // need to do something here to sort the node array.  Probably search it each time you add a node
    // and add the children of that node to a queue to be the next loaded

    let xOffset = 55;
    let yOffset = 30;
    var leftmostNodeElement = 0;
    var rightmostNodeElement = 0;

    for (let node of tree.nodes) {
      let nodeElement = document.createElement("div");
      nodeElement.classList.add("graph-node");
      nodeElement.id = node.id;
      nodeElement.textContent = node.name;

      // the first element in nodes is the root node, so it starts available
      if (node == tree.nodes[0])
        nodeElement.classList.add("available");
      else {
        nodeElement.classList.add("unavailable");
      }

      let parent = node.parents()[0];
      if (parent != undefined) {
        let relativeChildPostiion = getRelativeChildPosition(parent, node.id);
        let parentElement = document.getElementById(parent.id);
        let parentTop = dimensionAsNumber(parentElement.style.top);
        let parentLeft = dimensionAsNumber(parentElement.style.left);
        if (relativeChildPostiion == 'left') {
          nodeElement.style.top = parentTop + yOffset + 'px';
          nodeElement.style.left = parentLeft - xOffset + 'px';
        } else if (relativeChildPostiion == 'right') {
          nodeElement.style.top = parentTop + yOffset + 'px';
          nodeElement.style.left = parentLeft + xOffset + 'px';
        } else {
          nodeElement.style.top = parentTop + (yOffset * 2) + 'px';
          nodeElement.style.left = parentLeft + 'px';
        }
      } else {
        nodeElement.style.top = '25px';
        nodeElement.style.left = '26px';
      }

      let leftPosition = dimensionAsNumber(nodeElement.style.left);
      if (leftPosition < leftmostNodeElement) {
        leftmostNodeElement = leftPosition;
      }
      if (leftPosition > rightmostNodeElement) {
        rightmostNodeElement = leftPosition;
      }

      treeElement.appendChild(nodeElement);
      nodeElement.addEventListener("click", function() {
        nodeSelectionChanged(node);
      });
    }

    let nodeWidth = 52; // width of a graph node, per planner.css
    let padding = 25; // 'padding' here rather than in css because 'absolute' positioning of the
                      // node elements throws off alignment of css padding
    treeElement.style.width = rightmostNodeElement - leftmostNodeElement + nodeWidth + (padding * 2) + 'px';
    document.getElementById(treeNameToId(tree.name)).querySelectorAll('.graph-node').forEach(function (el) {
      let newLeft = dimensionAsNumber(el.style.left) + (-leftmostNodeElement) + padding + 'px';
      el.style.left = newLeft;
    });

  }

  function getRelativeChildPosition(parent, childId) {
    if (parent.leftChildId == childId) {
      return 'left';
    } else if (parent.centerChildId == childId) {
      return 'center';
    } else if (parent.rightChildId == childId) {
      return 'right';
    }
    return 'child not found'
  }

  function nodeSelectionChanged(node) {
    if (node.selected) {
      if (safeToDeselect(node)) {
        node.selected = false;
        if (nodeAvailableForSelection(node)) {
          setNodeColorBasedOnSelectionStatus(node, "available");
        } else {
          setNodeColorBasedOnSelectionStatus(node, "unavailable");
        }
      }
    } else {
      if (nodeAvailableForSelection(node) && (SkillTree.getSelectedNodes().length < maxSkillNodes)) {
        node.selected = true;
        setNodeColorBasedOnSelectionStatus(node, "selected");
      }
    }
    // update children
    for (let child of node.children()) {
      if (child.selected) {
        setNodeColorBasedOnSelectionStatus(child, "selected");
      } else if (nodeAvailableForSelection(child)) {
        setNodeColorBasedOnSelectionStatus(child, "available");
      } else {
        setNodeColorBasedOnSelectionStatus(child, "unavailable");
      }
    }
    updateNodeCounters();
    updateBonuses();
  }

  function nodeAvailableForSelection(node) {
    var parentIsSelected = false;
    for (let parent of node.parents()) {
      parentIsSelected = parent.selected || parentIsSelected;
    }
    parentIsSelected = parentIsSelected || (node.parents().length === 0);
    return parentIsSelected;
  }

  function safeToDeselect(node) {
    var safeToDeselect = true;
    for (let child of node.children()) {
      if (child.selected) {
        // Set node in question to deselected to see if the chlid is still elegible for selection
        // based on other parents.  We"ll set it back to selected after we"re done with that check.
        node.selected = false;
        safeToDeselect = nodeAvailableForSelection(child) && safeToDeselect;
        node.selected = true;
      }
    }
    return safeToDeselect;
  }

  function setNodeColorBasedOnSelectionStatus(node, selectionStatus) {
    let nodeElement = document.getElementById(node.id);
    removeNodeClasses(nodeElement);
    nodeElement.classList.add(selectionStatus);
  }

  function updateNodeCounters() {
    document.getElementById('node-selection-counter').textContent = SkillTree.getSelectedNodes().length;
    let activeTree = SkillTree.getActiveTreeName();
    let activeTabCounter = document.getElementById(activeTree.toLowerCase() + '-tab-counter');
    let nodesSelected = SkillTree.getSelectedNodes(activeTree).length;
    let nodesTotal = SkillTree.getNodeCount(activeTree);
    activeTabCounter.textContent = nodesSelected + ' / ' + nodesTotal;
  }

  function updateBonuses() {
    let bonuses = [];
    let nodes = SkillTree.getSelectedNodes();
    for (let node of nodes) {
      let bonusForAttribute = getBonusForAttribute(bonuses, node.attribute);
      if (bonusForAttribute != undefined) {
        bonusForAttribute.value = bonusForAttribute.value + node.value;
      } else {
        bonuses.push({attribute: node.attribute, value: node.value, valueTemplate: node.valueTemplate});
      }
    }
    document.getElementById("bonuses-display").innerHTML = null;

    bonuses.forEach(function(bonus, index) {

      let bonusDisplayElement = document.createElement("div");
      bonusDisplayElement.id = bonusAttributeToId(bonus.attribute);
      bonusDisplayElement.classList.add("bonus-display");
      bonusDisplayElement.textContent = bonus.attribute + ' ' + getValueTemplate(bonus.attribute)[0] + bonus.value + getValueTemplate(bonus.attribute)[1];

      document.getElementById("bonuses-display").append(bonusDisplayElement);

    });
  }

  function getBonusForAttribute(bonuses, attribute) {
    for (let bonus of bonuses) {
      if (bonus.attribute == attribute) {
        return bonus;
      }
    }
  }

  function getValueTemplate(attribute) {
    for (let mapping of attributeTemplateMap) {
      if (mapping.attribute == attribute) {
        return [ mapping.template.split('{}')[0], mapping.template.split('{}')[1] ];
      }
    }
  }

  function changeSkillTree(treeName) {
    SkillTree.setActiveTreeName(treeName);
    document.querySelectorAll('.skill-tree').forEach(function (el) {
      el.classList.add('hide');
    });
    document.getElementById(treeNameToId(treeName)).classList.remove('hide');
  }

  function treeNameToId(treeName) {
    return treeName.replace(/ /g, "-").toLowerCase() + '-skill-tree';
  }

  function bonusAttributeToId(attribute) {
    return attribute.replace(/ /g, "-").toLowerCase() + '-bonus-display';
  }

  function nodeNameToId(nodeName) {
    return nodeName.replace(/ /g, "-").toLowerCase();
  }

  // strips the 'px' off the end of a CSS dimension, returns the number value
  function dimensionAsNumber(dimension) {
    return parseFloat(dimension.slice(0, -2));
  }

  function removeNodeClasses(nodeElement) {
    nodeElement.classList.remove("selected");
    nodeElement.classList.remove("available");
    nodeElement.classList.remove("unavailable");
  }

});
<|endoftext|>


<commit_before>/* Step interval for changes */
var stepInterval = 750;

var map;
var layers = [];
var responses = [];
var timeouts = [];
var propertyNames = [];

var playState = "Pause";

/* Years for which we want to plot data */
var years = [];
/* Simple array of deaths per year */
var killed_values_all = [];
/* Values, rearranged for stacked bar chart */
var killed_values;

/* DB connection - not geo */
var sql_simple = new cartodb.SQL({user: 'crowdsalad', format: 'json'});

/* DB connection - geo */
var sql = new cartodb.SQL({user: 'crowdsalad', format: 'geojson'});

/* Grab the initial data from cartodb */
//TODO: Get the data from datahub.io instead?
var query =
        "select EXTRACT(YEAR FROM date) as year, sum(no_killed_numeric) as deaths from bangladesh_clothing_diasters_for_cartadb GROUP BY year ORDER BY year";

sql_simple.execute(query).done(function(response) {
  for (var i = response.rows[0].year; i < (response.rows[(response.rows.length - 1)].year) + 1; i++) {
    years[i - response.rows[0].year] = i; //Initialize all years, even if we don't have any data for them
    killed_values_all[i - response.rows[0].year] = 0; //Start with zeroes
  }

  for (var i = 0; i < response.rows.length; i++) {
    killed_values_all[response.rows[i].year - response.rows[0].year] = response.rows[i].deaths;
  }

  /* Rearrange the data for bar stacking */
  killed_values = create2dArray(killed_values_all);

  $(document).ready(init);
});

function pause() {
  for (var i = 0; i < timeouts.length; i++) {
    window.clearTimeout(timeouts[i]);
  }
  timeouts = [];
}

function play() {
  var currentValue = $("div#slider").slider("value");

  if (currentValue > (years.length - 2))
    $("div#slider").slider("value", 0);
  currentValue = 0;

  for (var i = 0; i < (years.length - 1) - currentValue; i++) {

    var theTimeout = window.setTimeout(setMe, 10 + (stepInterval * i));
    timeouts.push(theTimeout);
  }

  /* Just after last step, "stop" the playback */
  var myTimeout = window.setTimeout(stop, 10 + (stepInterval * ((years.length - 1) -
          currentValue)) + 10);
  timeouts.push(myTimeout);

}

function setMe() {
  if ($("div#slider").slider("value") < (years.length - 1)) {
    $("div#slider").slider("value", $("div#slider").slider("value") + 1);
    updateLayers(0);
  }

}

function updateLayers(event) {
  var layerno = $("div#slider").slider("value");
  updateNumbers(layerno);

  for (var i = 0; i <= layerno; i++) {
    $.each(responses[i].features, function(index, obj) {
      map.addLayer(obj.properties.theLayer);
    });

  }

  if (layerno < (years.length - 1))
    for (var i = layerno + 1; i < years.length; i++) {
      $.each(responses[i].features, function(index, obj) {
        map.removeLayer(obj.properties.theLayer);
      });
    }
}

function create2dArray(myArray) {
  var final_values = [];
  for (var i = 0; i < (myArray.length); i++) {
    final_values[i] = [];
    var j = 0;
    while (j <= i) {
      final_values[i][j] = myArray[j]
      j++;
    }
    while ((j > i) && (j <= myArray.length - 1)) {
      final_values[i][j] = 0;
      j++;
    }
  }
  return final_values;
}

function updateNumbers(layerno) {
  var data = {};
  for (var i = 0; i < years.length; i++) {
    data["y" + years[i]] = killed_values[layerno][i];
  }
  data.id = "v1";
  addData("graph_killed", data, "red");
}

function init() {
  /* calculate ceiling value and generate property names needed for the stacked bar chart */
  ceiling = 0;
  for (var i = 0; i < years.length; i++) {
    ceiling += killed_values_all[i];
    propertyNames[i] = "y" + years[i].toString();
  }
  ceiling += 250;

  /* D3/SVG Stuff */
  /* Create the scale for the slider */
  var svg =
          d3.select("#years")
          .append("svg")
          .attr("width", 760)
          .attr("height", 25);

  var scale =
          d3.scale
          .linear()
          .domain([years[0], years[years.length - 1]])
          .range([0, 608]);

  var xAxis =
          d3.svg.axis()
          .scale(scale)
          .orient("bottom")
          .ticks(24)
          .tickFormat(d3.format("4.0d"));

  svg.append("g")
          .attr("class", "axis")
          .attr("transform", "translate(76,10)")
          .call(xAxis);

  /* Set year labels */
  $("#startYearLabel").html(years[0]);
  $("#endYearLabel").html(years[years.length - 1]);

  /* Initiate leaflet map */
  map = new L.Map('cartodb-map', {
    center: [23.8, 90.5],
    zoom: 11
  });

  L.tileLayer(
          'https://dnv9my2eseobd.cloudfront.net/v3/cartodb.map-4xtxp73f/{z}/{x}/{y}.png', {
    attribution: 'Mapbox (Terms & Feedback), CartoDB & d3.js'
  }).addTo(map);

  var layerUrl =
          'http://crowdsalad.cartodb.com/api/v1/viz/bangladesh_clothing_diasters_for_cartadb/viz.json';

  for (var i = years[0]; i <= years[years.length - 1]; i++) {
    var query =
            "select the_geom, no_killed_numeric, location, date, date_only_year, factory_incident_company_name, source_not_exhaustive_need_to_list_all_pdfs, supplying, text_description, type_of_incident from bangladesh_clothing_diasters_for_cartadb WHERE date > '" +
            (i - 1) + "-12-31T23:59:59+00:00' AND date <='" + i +
            "-12-31T23:59:59+00:00' ORDER BY no_killed_numeric DESC";

    sql.execute(query).done(function(response) {
      responses.push(response);

      $.each(response.features, function(index, obj) {

        var myLayer = L.circle([obj.geometry.coordinates[1],
          obj.geometry.coordinates[0]
        ], 200 * Math.round(Math.sqrt(((+obj.properties.no_killed_numeric)) /
                3.14)), {
          fill: true,
          color: 'red',
          weight: 2,
          fillOpacity: 0.5
        });

        //TODO: Generate HTML for the popups
        var printdate = "";
        if (obj.properties.date_only_year)
          printdate = obj.properties
                  .date.substr(0, 4);
        else
          printdate = obj.properties.date.substr(0, 10);

        var suppl = obj.properties.supplying;
        var txt = obj.properties.text_description;
        if ((suppl === "") || (suppl === null)) {
          var suppl = "Unknown";
        }
        if ((txt === "") || (txt === null)) {
          var txt = "No details available";
        }

        var theHTML = "" + obj.properties
                .factory_incident_company_name + "
" +
                printdate + "

 Number killed: " +
                obj.properties.no_killed_numeric +
                "
Supplying: " + suppl +
                "
Description: " + txt +
                "

Source(s): " +
                obj.properties.source_not_exhaustive_need_to_list_all_pdfs +
                "";
        ;

        myLayer.bindPopup(theHTML, {
          maxWidth: 500
        });
        obj.properties.theLayer = myLayer;

      });

    });

  }

  $("div#slider").slider({
    animate: false,
    step: 1,
    min: 0,
    max: years.length - 1,
    slide: stop,
    stop: stop
  });

  // capture the height/width defined in the div so we only have it defined in one place
  chartHeight = parseInt(document.getElementById('graph_killed').clientHeight);
  chartWidth = parseInt(document.getElementById('graph_killed').clientWidth);

  // Y scale will fit values from 0-10 within pixels 0 - height
  y = d3.scale.linear().domain([0, ceiling]).range([0, chartHeight]);
  yneg = d3.scale.linear().domain([ceiling, 0]).range([0, chartHeight]);

  /* initialize the chart without any data */
  displayStackedChart("graph_killed");
  //displayStackedChart("graph_injured");

  $("div#slider").slider("value", 0);
  updateNumbers(0);

  //Kick off playback from 0, delay of 5s to ensure tiles are loaded (not good)
  var myTimeout = window.setTimeout(play, 1000);
  timeouts.push(myTimeout);
}

function togglePlayPause() {

  if (playState == "Pause") {
    pause();
    $("#playpause").html("Play");
    playState = "Play";
  } else if (playState == "Play") {
    play();
    $("#playpause").html("Pause");
    playState = "Pause";
  }
}

function stop() {
  if (responses.length > 0) {
    //if (playState == "Pause") {
    pause();
    $("#playpause").html("Play");
    playState = "Play";
    //}
    updateLayers(0);
  }
}<commit_msg>Fixes #1, code clean up<commit_after>/* Step interval for changes */
var stepInterval = 750;
var map;
var layers = [];
var responses = [];
var timeouts = [];
var propertyNames = [];
var playState = "Pause";
/* Years for which we want to plot data */
var years = [];
/* Simple array of deaths per year */
var killed_values_all = [];
/* Values, rearranged for stacked bar chart */
var killed_values;
/* DB connection - not geo */
var sql_simple = new cartodb.SQL({
  user: 'crowdsalad',
  format: 'json'
});
/* DB connection - geo */
var sql = new cartodb.SQL({
  user: 'crowdsalad',
  format: 'geojson'
});
/* Grab the initial data from cartodb */
var query =
  "select EXTRACT(YEAR FROM date) as year, sum(no_killed_numeric) as deaths from bangladesh_clothing_diasters_for_cartadb GROUP BY year ORDER BY year";
/* Use the minimum and maximum year to define the bounds of the data */
sql_simple.execute(query).done(function (response) {
  for (var i = response.rows[0].year; i < (response.rows[(response.rows.length - 1)].year) + 1; i++) {
    years[i - response.rows[0].year] = i; //Initialize all years, even if we don't have any data for them
    killed_values_all[i - response.rows[0].year] = 0; //Start with zeroes
  }
  /* Store lowest year */
  firstYear = response.rows[0].year;
  /* Fill the correct elements with the correct data */
  for (var i = 0; i < response.rows.length; i++) {
    killed_values_all[response.rows[i].year - response.rows[0].year] = response.rows[i].deaths;
  }
  /* Rearrange the data for bar stacking */
  killed_values = create2dArray(killed_values_all);
  $(document).ready(init);
});

function pause() {
  for (var i = 0; i < timeouts.length; i++) {
    window.clearTimeout(timeouts[i]);
  }
  timeouts = [];
}

function play() {
  var currentValue = $("div#slider").slider("value");
  if (currentValue > (years.length - 2))
    $("div#slider").slider("value", 0);
  currentValue = 0;
  for (var i = 0; i < (years.length - 1) - currentValue; i++) {
    var theTimeout = window.setTimeout(setMe, 10 + (stepInterval * i));
    timeouts.push(theTimeout);
  }
  /* Just after last step, "stop" the playback */
  var myTimeout = window.setTimeout(stop, 10 + (stepInterval * ((years.length - 1) -
    currentValue)) + 10);
  timeouts.push(myTimeout);
}

function setMe() {
  if ($("div#slider").slider("value") < (years.length - 1)) {
    $("div#slider").slider("value", $("div#slider").slider("value") + 1);
    updateLayers(0);
  }
}

function updateLayers(event) {
  var year = years[0] + $("div#slider").slider("value");
  updateNumbers(year);
  $.each(responses, function (index, obj) {
    if ((obj.features.length > 0) && (obj.features[0].properties.year <= year)) {
      $.each(obj.features, function (index, feature) {
        map.addLayer(feature.properties.theLayer);
      });
    }
  });
  if (year < (years[years.length - 1]))
  //for (var i = layerno + 1; i < years.length; i++) {
    $.each(responses, function (index, obj) {
      if ((obj.features.length > 0) && (obj.features[0].properties.year > year)) {
        $.each(obj.features, function (index, feature) {
          map.removeLayer(feature.properties.theLayer);
        });
      }
    });
  //}
}

function create2dArray(myArray) {
  var final_values = [];
  for (var i = 0; i < (myArray.length); i++) {
    final_values[i] = [];
    var j = 0;
    while (j <= i) {
      final_values[i][j] = myArray[j]
      j++;
    }
    while ((j > i) && (j <= myArray.length - 1)) {
      final_values[i][j] = 0;
      j++;
    }
  }
  return final_values;
}

function updateNumbers(year) {
  var data = {};
  if (years.length > 0) {
    for (var i = 0; i < years.length; i++) {
      data["y" + years[i]] = killed_values[year - years[0]][i];
    }
  }
  data.id = "v1";
  addData("graph_killed", data, "red");
}

function init() {
  /* calculate ceiling value and generate property names needed for the stacked bar chart */
  ceiling = 0;
  for (var i = 0; i < years.length; i++) {
    ceiling += killed_values_all[i];
    propertyNames[i] = "y" + years[i].toString();
  }
  ceiling += 250;
  /* D3/SVG Stuff */
  /* Create the scale for the slider */
  var svg =
    d3.select("#years")
    .append("svg")
    .attr("width", 760)
    .attr("height", 25);
  var scale =
    d3.scale
    .linear()
    .domain([years[0], years[years.length - 1]])
    .range([0, 608]);
  var xAxis =
    d3.svg.axis()
    .scale(scale)
    .orient("bottom")
    .ticks(24)
    .tickFormat(d3.format("4.0d"));
  svg.append("g")
    .attr("class", "axis")
    .attr("transform", "translate(76,10)")
    .call(xAxis);
  /* Set year labels */
  $("#startYearLabel").html(years[0]);
  $("#endYearLabel").html(years[years.length - 1]);
  /* Initiate leaflet map */
  map = new L.Map('cartodb-map', {
    center: [23.8, 90.5],
    zoom: 11
  });
  L.tileLayer(
    'https://dnv9my2eseobd.cloudfront.net/v3/cartodb.map-4xtxp73f/{z}/{x}/{y}.png', {
      attribution: 'Mapbox (Terms & Feedback), CartoDB & d3.js'
    }).addTo(map);
  var layerUrl =
    'http://crowdsalad.cartodb.com/api/v1/viz/bangladesh_clothing_diasters_for_cartadb/viz.json';
  for (var i = years[0]; i <= years[years.length - 1]; i++) {
    var query =
      "select the_geom, no_killed_numeric, location, EXTRACT(YEAR FROM date) as year, date, date_only_year, factory_incident_company_name, source_not_exhaustive_need_to_list_all_pdfs, supplying, text_description, type_of_incident from bangladesh_clothing_diasters_for_cartadb WHERE date > '" +
      (i - 1) + "-12-31T23:59:59+00:00' AND date <='" + i +
      "-12-31T23:59:59+00:00' ORDER BY no_killed_numeric DESC";
    sql.execute(query).done(function (response) {
      responses.push(response);
      $.each(response.features, function (index, obj) {
        var myLayer = L.circle([obj.geometry.coordinates[1],
          obj.geometry.coordinates[0]
        ], 200 * Math.round(Math.sqrt(((+obj.properties.no_killed_numeric)) /
          3.14)), {
          fill: true,
          color: 'red',
          weight: 2,
          fillOpacity: 0.5
        });

        var printdate = "";
        if (obj.properties.date_only_year)
          printdate = obj.properties
            .date.substr(0, 4);
        else
          printdate = obj.properties.date.substr(0, 10);
        var suppl = obj.properties.supplying;
        var txt = obj.properties.text_description;
        if ((suppl === "") || (suppl === null)) {
          var suppl = "Unknown";
        }
        if ((txt === "") || (txt === null)) {
          var txt = "No details available";
        }
        var theHTML = "" + obj.properties
          .factory_incident_company_name + "
" +
          printdate + "

 Number killed: " +
          obj.properties.no_killed_numeric +
          "
Supplying: " + suppl +
          "
Description: " + txt +
          "

Source(s): " +
          obj.properties.source_not_exhaustive_need_to_list_all_pdfs +
          "";;
        myLayer.bindPopup(theHTML, {
          maxWidth: 500
        });
        obj.properties.theLayer = myLayer;
      });
    });
  }
  $("div#slider").slider({
    animate: false,
    step: 1,
    min: 0,
    max: years.length - 1,
    slide: stop,
    stop: stop
  });
  // capture the height/width defined in the div so we only have it defined in one place
  chartHeight = parseInt(document.getElementById('graph_killed').clientHeight);
  chartWidth = parseInt(document.getElementById('graph_killed').clientWidth);
  // Y scale will fit values from 0-10 within pixels 0 - height
  y = d3.scale.linear().domain([0, ceiling]).range([0, chartHeight]);
  yneg = d3.scale.linear().domain([ceiling, 0]).range([0, chartHeight]);
  /* initialize the chart without any data */
  displayStackedChart("graph_killed");
  $("div#slider").slider("value", 0);
  updateNumbers(years[0]);
  //Kick off playback from 0, delay of 5s to ensure tiles are loaded (not good)
  var myTimeout = window.setTimeout(play, 1000);
  timeouts.push(myTimeout);
}

function togglePlayPause() {
  if (playState == "Pause") {
    pause();
    $("#playpause").html("Play");
    playState = "Play";
  } else if (playState == "Play") {
    play();
    $("#playpause").html("Pause");
    playState = "Pause";
  }
}

function stop() {
  if (responses.length > 0) {
    pause();
    $("#playpause").html("Play");
    playState = "Play";
    updateLayers(0);
  }
}<|endoftext|>