<commit_before>module.exports = function(grunt) {

	// Configuración del proyecto.
	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json'),

		sass: {
			dist: {
				options: {
					style: 'compressed'
				},
				files: {
					'app/css/style.min.css': 'app/scss/main.scss' // Destino: Origen
				}
			}
		},

		handlebars: {
			compile: {
				options: {
					namespace: 'JST'
				},
				files: {
					'app/js/compiled-templates.js': 'app/templates/**/*.hbs' // Destino: Origen
				}
			}
		},

		watch: {
			livereload: {
				files: ['app/**/*'], // Ficheros que se van a vigilar.
				options: { livereload: true } // Habilitamos livereload en el puerto por defecto (35729) para cuando cambien los ficheros bajo vigilancia.
			},
			css: {
				files: ['**/*.scss'], // Ficheros que se van a vigilar.
				tasks: ['sass'] // Tarea(s) a ejecutar cuando cambien los ficheros bajo vigilancia.
			},
			templates: {
				files: ['**/*.hbs'], // Ficheros que se van a vigilar.
				tasks: ['handlebars'] // Tarea(s) a ejecutar cuando cambien los ficheros bajo vigilancia.
			}
		}
	});

	// Carga de plugins que proveen de las tareas.
	grunt.loadNpmTasks('grunt-contrib-sass');
	grunt.loadNpmTasks('grunt-contrib-handlebars');
	grunt.loadNpmTasks('grunt-contrib-watch');

	// Tarea(s) por defecto.
	grunt.registerTask('default', ['watch']);
};
<commit_msg>Improvements on grunt workflow<commit_after>module.exports = function(grunt) {

	// Configuración del proyecto.
	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json'),

		sass: {
			dist: {
				options: {
					style: 'compressed'
				},
				files: {
					'app/css/style.min.css': 'app/scss/main.scss' // Destino: Origen
				}
			}
		},

		handlebars: {
			compile: {
				options: {
					namespace: 'JST'
				},
				files: {
					'app/js/compiled-templates.js': 'app/templates/**/*.hbs' // Destino: Origen
				}
			}
		},

		watch: {
			livereload: {
				files: ['app/**/*'], // Ficheros que se van a vigilar.
				options: { livereload: true } // Habilitamos livereload en el puerto por defecto (35729) para cuando cambien los ficheros bajo vigilancia.
			},
			css: {
				files: ['**/*.scss'], // Ficheros que se van a vigilar.
				tasks: ['sass'] // Tarea(s) a ejecutar cuando cambien los ficheros bajo vigilancia.
			},
			templates: {
				files: ['**/*.hbs'], // Ficheros que se van a vigilar.
				tasks: ['handlebars'] // Tarea(s) a ejecutar cuando cambien los ficheros bajo vigilancia.
			}
		}
	});

	// Carga de plugins que proveen de las tareas.
	grunt.loadNpmTasks('grunt-contrib-sass');
	grunt.loadNpmTasks('grunt-contrib-handlebars');
	grunt.loadNpmTasks('grunt-contrib-watch');

	// Tarea(s) por defecto.
	grunt.registerTask('default', ['sass', 'handlebars', 'watch']);
};
<|endoftext|>


<commit_before>/* A logical combo of Techniques and the intersection of their outcomes. */
quail.lib.SuccessCriteria = (function () {

  /**
   * A Collection of Tests.
   */
  function SuccessCriteria (tests) {
    return new SuccessCriteria.fn.init(tests);
  }

  // Prototype object of the SuccessCriteria.
  SuccessCriteria.fn = SuccessCriteria.prototype = {
    constructor: SuccessCriteria,
    init: function (options) {
      // Event listeners.
      this.listeners = {};

      // By default a Success Criteria is untested.
      this.attributes = this.attributes || {};
      this.attributes.status = 'untested';
      this.attributes.results = {};
      this.attributes.totals = {};

      // The evaluator is a callback that will be invoked when tests have
      // finished running.
      this.set(options || {});

      return this;
    },
    // Setting a length property makes it behave like an array.
    length: 0,
    // Details of the test.
    attributes: null,
    get: function (attr) {
      // Return the document wrapped in jQuery if scope is not defined.
      if (attr === '$scope') {
        var scope = this.attributes.scope;
        var $scope = $(this.attributes.scope);
        // @todo, pass in a ref to jQuery to this module.
        return (this.attributes[attr]) ? this.attributes[attr] : ((scope) ? $scope : $(document));
      }
      return this.attributes[attr];
    },
    set: function (attr, value) {
      var isStatusChanged = false;
      // Allow an object of attributes to be passed in.
      if (typeof attr === 'object') {
        for (var prop in attr) {
          if (attr.hasOwnProperty(prop)) {
            if (prop === 'status') {
              isStatusChanged = true;
            }
            this.attributes[prop] = attr[prop];
          }
        }
      }
      // Assign a single attribute value.
      else {
        this.attributes[attr] = value;
      }
      return this;
    },
    /**
     * Execute a callback for every element in the matched set.
     */
    each: function (iterator) {
      var args = [].slice.call(arguments, 1);
      for (var i = 0, len = this.length; i < len; ++i) {
        args.unshift(this[i]);
        args.unshift(i);
        var cont = iterator.apply(this[i], args);
        // Allow an iterator to break from the loop.
        if (cont === false) {
          break;
        }
      }
      return this;
    },
    /**
     * Add a Case to the Success Criteria instance, keyed by selector.
     */
    add: function (_case) {
      if (!this.find(_case.get('selector'))) {
        this.push(_case);
      }
    },
    /**
     * Finds a case by its selector.
     */
    find: function (selector) {
      for (var i = 0, il = this.length; i < il; ++i) {
        if (this[i].get('selector') === selector) {
          return this[i];
        }
      }
      return null;
    },
    /**
     * Adds a TestCollection to be listened to.
     *
     * There is a preEvaluator function run before tests are added to make sure
     * that the test is actually needed.
     */
    registerTests: function (testCollection) {
      var preEvaluator = this.get('preEvaluator');
      var hasPreEvaluator = typeof preEvaluator !== 'undefined';
      // true means we'll run all the tests as usual, false, we skip the whole thing.
      var passedPreEvaluation = true;
      if (hasPreEvaluator) {
        passedPreEvaluation = preEvaluator.call(this, testCollection);
      }
      if (!passedPreEvaluation) {
        this.set('status', 'inapplicable');
      }
      this.set('tests', testCollection);
      this.listenTo(testCollection, 'complete', this.evaluate);
    },
    /**
     * Returns a collection of tests for this success criteria.
     */
    filterTests: function (tests) {
      var criteriaTests = new quail.lib.TestCollection();
      var name = this.get('name');
      if (!name) {
        throw new Error('Success Criteria instances require a name in order to have tests filtered.');
      }
      var identifier = name.split(':')[1];
      tests.each(function (index, test) {
        var guidelineCoverage = test.getGuidelineCoverage('wcag');
        // Get tests for this success criteria.
        for (var criteriaID in guidelineCoverage) {
          if (guidelineCoverage.hasOwnProperty(criteriaID)) {
            if (criteriaID === identifier) {
              criteriaTests.add(test);
            }
          }
        }
      });
      return criteriaTests;
    },
    /**
     * Adds a Case conclusion to the Success Criteria.
     *
     * @param string conclusion
     * @param quail.lib.Case _case
     */
    addConclusion: function (conclusion, _case) {
      if (!this.get('results')[conclusion]) {
        this.get('results')[conclusion] = quail.lib.Test();
      }
      this.get('results')[conclusion].push(_case);
      // Incremement totals for this conclusion type.
      if (!this.get('totals')[conclusion]) {
        this.get('totals')[conclusion] = 0;
      }
      ++(this.get('totals')[conclusion]);
      // Incremement totals for the number of cases found.
      if (!this.get('totals').cases) {
        this.get('totals').cases = 0;
      }
      ++(this.get('totals').cases);
    },
    /**
     * Runs the evaluator callbacks against the completed TestCollection.
     */
    evaluate: function (eventName, testCollection) {
      if (this.get('status') !== 'inapplicable') {
        var sc = this;
        var associatedTests = this.filterTests(testCollection);

        // If there are no associated tests, then this Success
        // Criteria has no coverage.
        if (associatedTests.length === 0) {
          this.set('status', 'noTestCoverage');
        }
        else {
          associatedTests.each(function (index, test) {
            test.each(function (index, _case) {
              sc.addConclusion(_case.get('status'), _case);
            });
          });
          if (size(this.get('results')) === 0) {
            this.set('status', 'noResults');
          }
          else {
            this.set('status', 'tested');
          }
        }
      }
      this.report();
    },
    /**
     * Dispatches the complete event.
     */
    report: function () {
      var args = Array.prototype.slice.call(arguments);
      args = [].concat([ 'successCriteriaEvaluated', this, this.get('tests') ], args);
      this.dispatch.apply(this, args);
    },
    // @todo, make this a set of methods that all classes extend.
    listenTo: function (dispatcher, eventName, handler) {
      // @todo polyfill Function.prototype.bind.
      handler = handler.bind(this);
      dispatcher.registerListener.call(dispatcher, eventName, handler);
    },
    registerListener: function (eventName, handler) {
      // nb: 'this' is the dispatcher object, not the one that invoked listenTo.
      if (!this.listeners[eventName]) {
        this.listeners[eventName] = [];
      }

      this.listeners[eventName].push(handler);
    },
    dispatch: function (eventName) {
      if (this.listeners[eventName] && this.listeners[eventName].length) {
        var eventArgs = [].slice.call(arguments);
        this.listeners[eventName].forEach(function (handler) {
          // Pass any additional arguments from the event dispatcher to the
          // handler function.
          handler.apply(null, eventArgs);
        });
      }
    },
    push: [].push,
    sort: [].sort,
    splice: [].splice
  };

  /**
   * Determines the length of an object.
   *
   * @param object obj
   *   The object whose size will be determined.
   *
   * @return number
   *   The size of the object determined by the number of keys.
   */
  function size (obj) {
    return Object.keys(obj).length;
  }

  // Give the init function the SuccessCriteria prototype.
  SuccessCriteria.fn.init.prototype = SuccessCriteria.fn;

  return SuccessCriteria;
}());
<commit_msg>Ran jscs on SuccessCriteria<commit_after>/* A logical combo of Techniques and the intersection of their outcomes. */
quail.lib.SuccessCriteria = (function () {

  /**
   * A Collection of Tests.
   */
  function SuccessCriteria (tests) {
    return new SuccessCriteria.fn.init(tests);
  }

  // Prototype object of the SuccessCriteria.
  SuccessCriteria.fn = SuccessCriteria.prototype = {
    constructor: SuccessCriteria,
    init: function (options) {
      // Event listeners.
      this.listeners = {};

      // By default a Success Criteria is untested.
      this.attributes = this.attributes || {};
      this.attributes.status = 'untested';
      this.attributes.results = {};
      this.attributes.totals = {};

      // The evaluator is a callback that will be invoked when tests have
      // finished running.
      this.set(options || {});

      return this;
    },
    // Setting a length property makes it behave like an array.
    length: 0,
    // Details of the test.
    attributes: null,
    get: function (attr) {
      // Return the document wrapped in jQuery if scope is not defined.
      if (attr === '$scope') {
        var scope = this.attributes.scope;
        var $scope = $(this.attributes.scope);
        // @todo, pass in a ref to jQuery to this module.
        return (this.attributes[attr]) ? this.attributes[attr] : ((scope) ? $scope : $(document));
      }
      return this.attributes[attr];
    },
    set: function (attr, value) {
      var isStatusChanged = false;
      // Allow an object of attributes to be passed in.
      if (typeof attr === 'object') {
        for (var prop in attr) {
          if (attr.hasOwnProperty(prop)) {
            if (prop === 'status') {
              isStatusChanged = true;
            }
            this.attributes[prop] = attr[prop];
          }
        }
      }
      // Assign a single attribute value.
      else {
        this.attributes[attr] = value;
      }
      return this;
    },
    /**
     * Execute a callback for every element in the matched set.
     */
    each: function (iterator) {
      var args = [].slice.call(arguments, 1);
      for (var i = 0, len = this.length; i < len; ++i) {
        args.unshift(this[i]);
        args.unshift(i);
        var cont = iterator.apply(this[i], args);
        // Allow an iterator to break from the loop.
        if (cont === false) {
          break;
        }
      }
      return this;
    },
    /**
     * Add a Case to the Success Criteria instance, keyed by selector.
     */
    add: function (_case) {
      if (!this.find(_case.get('selector'))) {
        this.push(_case);
      }
    },
    /**
     * Finds a case by its selector.
     */
    find: function (selector) {
      for (var i = 0, il = this.length; i < il; ++i) {
        if (this[i].get('selector') === selector) {
          return this[i];
        }
      }
      return null;
    },
    /**
     * Adds a TestCollection to be listened to.
     *
     * There is a preEvaluator function run before tests are added to make sure
     * that the test is actually needed.
     */
    registerTests: function (testCollection) {
      var preEvaluator = this.get('preEvaluator');
      var hasPreEvaluator = typeof preEvaluator !== 'undefined';
      // true means we'll run all the tests as usual, false, we skip the whole thing.
      var passedPreEvaluation = true;
      if (hasPreEvaluator) {
        passedPreEvaluation = preEvaluator.call(this, testCollection);
      }
      if (!passedPreEvaluation) {
        this.set('status', 'inapplicable');
      }
      this.set('tests', testCollection);
      this.listenTo(testCollection, 'complete', this.evaluate);
    },
    /**
     * Returns a collection of tests for this success criteria.
     */
    filterTests: function (tests) {
      var criteriaTests = new quail.lib.TestCollection();
      var name = this.get('name');
      if (!name) {
        throw new Error('Success Criteria instances require a name in order to have tests filtered.');
      }
      var identifier = name.split(':')[1];
      tests.each(function (index, test) {
        var guidelineCoverage = test.getGuidelineCoverage('wcag');
        // Get tests for this success criteria.
        for (var criteriaID in guidelineCoverage) {
          if (guidelineCoverage.hasOwnProperty(criteriaID)) {
            if (criteriaID === identifier) {
              criteriaTests.add(test);
            }
          }
        }
      });
      return criteriaTests;
    },
    /**
     * Adds a Case conclusion to the Success Criteria.
     *
     * @param string conclusion
     * @param quail.lib.Case _case
     */
    addConclusion: function (conclusion, _case) {
      if (!this.get('results')[conclusion]) {
        this.get('results')[conclusion] = quail.lib.Test();
      }
      this.get('results')[conclusion].push(_case);
      // Incremement totals for this conclusion type.
      if (!this.get('totals')[conclusion]) {
        this.get('totals')[conclusion] = 0;
      }
      ++(this.get('totals')[conclusion]);
      // Incremement totals for the number of cases found.
      if (!this.get('totals').cases) {
        this.get('totals').cases = 0;
      }
      ++(this.get('totals').cases);
    },
    /**
     * Runs the evaluator callbacks against the completed TestCollection.
     */
    evaluate: function (eventName, testCollection) {
      if (this.get('status') !== 'inapplicable') {
        var self = this;
        var associatedTests = this.filterTests(testCollection);

        // If there are no associated tests, then this Success
        // Criteria has no coverage.
        if (associatedTests.length === 0) {
          this.set('status', 'noTestCoverage');
        }
        else {
          associatedTests.each(function (index, test) {
            test.each(function (index, _case) {
              self.addConclusion(_case.get('status'), _case);
            });
          });
          if (size(this.get('results')) === 0) {
            this.set('status', 'noResults');
          }
          else {
            this.set('status', 'tested');
          }
        }
      }
      this.report();
    },
    /**
     * Dispatches the complete event.
     */
    report: function () {
      var args = Array.prototype.slice.call(arguments);
      args = [].concat(['successCriteriaEvaluated', this, this.get('tests')], args);
      this.dispatch.apply(this, args);
    },
    // @todo, make this a set of methods that all classes extend.
    listenTo: function (dispatcher, eventName, handler) {
      // @todo polyfill Function.prototype.bind.
      handler = handler.bind(this);
      dispatcher.registerListener.call(dispatcher, eventName, handler);
    },
    registerListener: function (eventName, handler) {
      // nb: 'this' is the dispatcher object, not the one that invoked listenTo.
      if (!this.listeners[eventName]) {
        this.listeners[eventName] = [];
      }

      this.listeners[eventName].push(handler);
    },
    dispatch: function (eventName) {
      if (this.listeners[eventName] && this.listeners[eventName].length) {
        var eventArgs = [].slice.call(arguments);
        this.listeners[eventName].forEach(function (handler) {
          // Pass any additional arguments from the event dispatcher to the
          // handler function.
          handler.apply(null, eventArgs);
        });
      }
    },
    push: [].push,
    sort: [].sort,
    splice: [].splice
  };

  /**
   * Determines the length of an object.
   *
   * @param object obj
   *   The object whose size will be determined.
   *
   * @return number
   *   The size of the object determined by the number of keys.
   */
  function size (obj) {
    return Object.keys(obj).length;
  }

  // Give the init function the SuccessCriteria prototype.
  SuccessCriteria.fn.init.prototype = SuccessCriteria.fn;

  return SuccessCriteria;
}());
<|endoftext|>


<commit_before>/*
Copyright (C) 2016 andrea rota
Copyright (C) 2015 Forensic Architecture

This file is part of Pattrn - http://pattrn.co/.

It includes code originally developed as part of version 1.0 of Pattrn and
distributed under the PATTRN-V1-LICENSE, with changes (licensed under AGPL-3.0)
adding new features and allowing integration of the legacy code with the
AGPL-3.0 Pattrn 2.0 distribution.

Pattrn is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Pattrn 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with Pattrn.  If not, see <http://www.gnu.org/licenses/>.
*/

var $ = require('jquery');

import {
  is_defined
} from '../utils/is_defined.js';

/**
 * Handle data and setup of events for each data point
 *
 * @x-technical-debt: this function was taken out of the legacy code's single
 * scope while refactoring, but as we need several (7+, depending on how we
 * count) variables from the parent scope, we should really pass these in
 * as an object.
 */
export function point_data(
    config,
    instance_settings,
    layer_group,
    layer_data,
    _map,
    data_source_type,
    variable_list,
    pattrn_data_sets,
    non_empty_variables,
    markerChart,
    item,
    index) {
  // If data on source data set is available, set colour of markers accordingly, otherwise use defaults
  var marker_color = is_defined(item.pattrn_data_set) && is_defined(pattrn_data_sets[item.pattrn_data_set]) ?
    pattrn_data_sets[item.pattrn_data_set] :
    instance_settings.map.markers.color;

  item.i = index;

  var dayMonthFormat = d3.time.format("%d/%m/%y");
  var fullDateFormat = d3.time.format("%A, %d %B %Y");

  // Marker settings
  item.marker = new L.circleMarker(new L.LatLng(item.latitude, item.longitude), {
    title: "",
    radius: 7,
    color: marker_color,
    opacity: 0.9,
    fillOpacity: 0.8,
    clickable: true,
  });

  item.marker.data = item;

  // Tooltip content
  var content = {};

  content.event_details = "
";
  if (is_defined(item.event_ID)) content.event_details += "
EVENT ID:

" + item.event_ID + "
";
  if (is_defined(item.dd)) content.event_details += "
DATE:

 " + fullDateFormat(item.dd) + "
";
  if (is_defined(item.location_name)) content.event_details += "
LOCATION:

 " + item.location_name + "

";
  content.event_details += "
";

  // Summary content
  content.event_summary = "
";
  if (is_defined(item.event_summary)) {
    content.event_summary += "
" + item.event_summary + "
";
  }
  if (is_defined(item.source_name)) {
    content.event_summary += "
SOURCE:

" + item.source_name + "

";
  }
  content.event_summary += (
    "

" +
    "
"
  );

  // set empty popup to leverage leaflet functions
  var hoverContent = "";
  var popup = item.marker.popup = new L.popup()
    .setLatLng(item.marker.getLatLng())
    .setContent(hoverContent);

  var elements = {
    details: document.getElementById('dateTime'),
    summary: document.getElementById('summary'),
    media: document.getElementById('media')
  };

  item.marker.on("click", point_data_click.bind(undefined, config, instance_settings, layer_group, layer_data, _map, data_source_type, variable_list, non_empty_variables, pattrn_data_sets, elements, content, markerChart, item, index));
}

function point_data_click(
      config,
      instance_settings,
      layer_group,
      layer_data,
      _map,
      data_source_type,
      variable_list,
      non_empty_variables,
      pattrn_data_sets,
      elements,
      content,
      markerChart,
      item,
      index,
      e) {
  var image_html = document.getElementById("image_gallery").innerHTML = '';
  var video_html = document.getElementById("video_gallery").innerHTML = '';
  var summary_table = document.getElementById("summaryTable").innerHTML = '';
  var urls = document.getElementById("urls").innerHTML = '';

  var highlightColour = instance_settings.colour;

  $('.edit_dropdown').remove();

  if (is_defined(markerChart) && markerChart.filter() == e.target.data.i) {
    markerChart.filter(true);
  } else {

    $('#edit_dropdown').append(
      "
Edit this event
"
    );

    if (is_defined(e.target.data.photos)) {
      // Photos
      d3.json(e.target.data.photos, function(D) {

        var json_photos = $.parseJSON('[' + item.photos + ']');

        for (j = 0; j < json_photos.length; j++) {
          $('#image_gallery').append(
            '
' +
            '' +
            '
' +
            json_photos[j].caption +
            '
Source: ' + json_photos[j].source + '
' +
            '
'
          );
        }

        $(document).ready(function() {
          $("#image_gallery").lightGallery();
        });

      });
    }

    if (is_defined(e.target.data.videos)) {
      // Videos
      d3.json(e.target.data.videos, function(D) {

        var json_videos = $.parseJSON('[' + item.videos + ']');

        for (j = 0; j < json_videos.length; j++) {
          $('#video_gallery').append(
            '
' +
            '
Video: ' + json_videos[j].caption + '
' +
            '
Source: ' + json_videos[j].source + '
' +
            '
'
          );
        }

        $(document).ready(function() {
          $("#video_gallery").lightGallery();
        });

      });
    }

    if (is_defined(e.target.data.links)) {
      // Urls
      d3.json(e.target.data.links, function(D) {

        var array_urls = $.parseJSON('[' + item.links + ']');

        for (j = 0; j < array_urls.length; j++) {
          $('#urls').append(
            '
 Title: ' + array_urls[j].title + '

Subtitle: ' + array_urls[j].subtitle + '
'
          );
        }

      });
    }

    // Open popup
    // @x-technical-debt: ok to check if var is defined, but it should be
    // actually *always* defined here - if it is undefined, something has
    // failed upstream
    if (is_defined(markerChart)) {
      e.target.popup.openOn(markerChart.getMap());
    }

    // Change style of popup
    $('.leaflet-popup-content-wrapper').addClass('transparent');
    $('.leaflet-popup-tip').addClass('transparent');
    $('.leaflet-popup-close-button').addClass('transparent');

    // Style marker on click
    item.marker.setRadius(10);
    item.marker.setStyle({
      color: highlightColour,
      fillColor: highlightColour,
      fillOpacity: 0.8,
    });

    // Add infowindow content
    elements.details.innerHTML = content.event_details;
    elements.summary.innerHTML = content.event_summary;

    if ('geojson_file' === data_source_type) {
      Object.keys(item.source_variables)
        .filter(function(value) {
          return !value.match(/^pattrn_[^_]{2,}/);
        })
        .forEach(function(value, index, array) {
          if (is_defined(item.source_variables[value])) appendGeoJSONPropertyToTable(value, item.source_variables[value], variable_list);
        });
    }

    if(layer_data.non_empty_variables.find(vt => vt.type === 'integer')) {
      layer_data.non_empty_variables.find(vt => vt.type === 'integer').names.forEach(function(item, index) {
        appendIntegerValueToTable(e, item, variable_list);
      });
    }

    if(layer_data.non_empty_variables.find(vt => vt.type === 'tag')) {
      layer_data.non_empty_variables.find(vt => vt.type === 'tag').names.forEach(function(item, index) {
        appendTagValueToTable(e, item, variable_list);
      });
    }

    if(layer_data.non_empty_variables.find(vt => vt.type === 'boolean')) {
      layer_data.non_empty_variables.find(vt => vt.type === 'boolean').names.forEach(function(item, index) {
        appendTagValueToTable(e, item, variable_list);
      });
    }

    /**
     * diagnostic output in development mode
     */
    if(is_defined(instance_settings.environment) && instance_settings.environment === 'development') {
      appendPropertyToTable('layer_group', layer_group.id);
      appendPropertyToTable('layer_group', layer_data.id);
    }
  }

  _map.on("popupclose", function(item) {
    // If data on source data set is available, set colour of markers accordingly, otherwise use defaults
    var marker_color = is_defined(item.pattrn_data_set) && is_defined(pattrn_data_sets[item.pattrn_data_set]) ?
      pattrn_data_sets[item.pattrn_data_set] :
      instance_settings.map.markers.color;

    item.marker.setRadius(7);
    item.marker.setStyle({
      fillColor: marker_color,
      color: marker_color,
      fillOpacity: instance_settings.map.markers.opacity
    });
    content.innerHTML = "
Please click a marker

";
    summary.innerHTML = "
This panel will update when a marker is clicked
";
    var summary_table = document.getElementById("summaryTable").innerHTML = '';
    var image_html = document.getElementById("image_gallery").innerHTML = '';
    var video_html = document.getElementById("video_gallery").innerHTML = '';
    var urls = document.getElementById("urls").innerHTML = '';
    $('.edit_dropdown').remove();
  });
}

// Table content - generic functions
function appendIntegerValueToTable(d, field_name, variable_list) {
  if (!is_defined(d[field_name])) return;

  let variable = variable_list.find(variable => variable.id === field_name);
  let full_field_name = is_defined(variable) && is_defined(variable.name) ? variable.name : field_name;

  appendPropertyToTable(full_field_name, d[field_name]);
}

function appendTagValueToTable(d, field_name, variable_list) {
  if (!is_defined(d[field_name])) return;

  let variable = variable_list.find(variable => variable.id === field_name);
  let full_field_name = is_defined(variable) && is_defined(variable.name) ? variable.name : field_name;

  appendPropertyToTable(full_field_name, d[field_name].split(',').join(', '));
}

function appendGeoJSONPropertyToTable(key, value, variable_list) {
  let variable = variable_list.find(variable => variable.id === key);
  let full_field_name = is_defined(variable) && is_defined(variable.name) ? variable.name : key;

  appendPropertyToTable(full_field_name, value);
}

/**
 * Add property to the details table.
 * Used by other (more specific) append[Something]ToTable() functions, or
 * manually for generated properties.
 * For example, while in development mode, this may be used to output the
 * row's parent layer and layer group ids for diagnostic purposes.
 */
function appendPropertyToTable(key, value) {
  $('#summaryTable').append(
    "

" + key +
    "

 " + value +
    "

"
  );
}
<commit_msg>add pattrn_data_set to variables treated as geojson properties<commit_after>/*
Copyright (C) 2016 andrea rota
Copyright (C) 2015 Forensic Architecture

This file is part of Pattrn - http://pattrn.co/.

It includes code originally developed as part of version 1.0 of Pattrn and
distributed under the PATTRN-V1-LICENSE, with changes (licensed under AGPL-3.0)
adding new features and allowing integration of the legacy code with the
AGPL-3.0 Pattrn 2.0 distribution.

Pattrn is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Pattrn 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with Pattrn.  If not, see <http://www.gnu.org/licenses/>.
*/

var $ = require('jquery');

import {
  is_defined
} from '../utils/is_defined.js';

/**
 * Handle data and setup of events for each data point
 *
 * @x-technical-debt: this function was taken out of the legacy code's single
 * scope while refactoring, but as we need several (7+, depending on how we
 * count) variables from the parent scope, we should really pass these in
 * as an object.
 */
export function point_data(
    config,
    instance_settings,
    layer_group,
    layer_data,
    _map,
    data_source_type,
    variable_list,
    pattrn_data_sets,
    non_empty_variables,
    markerChart,
    item,
    index) {
  // If data on source data set is available, set colour of markers accordingly, otherwise use defaults
  var marker_color = is_defined(item.pattrn_data_set) && is_defined(pattrn_data_sets[item.pattrn_data_set]) ?
    pattrn_data_sets[item.pattrn_data_set] :
    instance_settings.map.markers.color;

  item.i = index;

  var dayMonthFormat = d3.time.format("%d/%m/%y");
  var fullDateFormat = d3.time.format("%A, %d %B %Y");

  // Marker settings
  item.marker = new L.circleMarker(new L.LatLng(item.latitude, item.longitude), {
    title: "",
    radius: 7,
    color: marker_color,
    opacity: 0.9,
    fillOpacity: 0.8,
    clickable: true,
  });

  item.marker.data = item;

  // Tooltip content
  var content = {};

  content.event_details = "
";
  if (is_defined(item.event_ID)) content.event_details += "
EVENT ID:

" + item.event_ID + "
";
  if (is_defined(item.dd)) content.event_details += "
DATE:

 " + fullDateFormat(item.dd) + "
";
  if (is_defined(item.location_name)) content.event_details += "
LOCATION:

 " + item.location_name + "

";
  content.event_details += "
";

  // Summary content
  content.event_summary = "
";
  if (is_defined(item.event_summary)) {
    content.event_summary += "
" + item.event_summary + "
";
  }
  if (is_defined(item.source_name)) {
    content.event_summary += "
SOURCE:

" + item.source_name + "

";
  }
  content.event_summary += (
    "

" +
    "
"
  );

  // set empty popup to leverage leaflet functions
  var hoverContent = "";
  var popup = item.marker.popup = new L.popup()
    .setLatLng(item.marker.getLatLng())
    .setContent(hoverContent);

  var elements = {
    details: document.getElementById('dateTime'),
    summary: document.getElementById('summary'),
    media: document.getElementById('media')
  };

  item.marker.on("click", point_data_click.bind(undefined, config, instance_settings, layer_group, layer_data, _map, data_source_type, variable_list, non_empty_variables, pattrn_data_sets, elements, content, markerChart, item, index));
}

function point_data_click(
      config,
      instance_settings,
      layer_group,
      layer_data,
      _map,
      data_source_type,
      variable_list,
      non_empty_variables,
      pattrn_data_sets,
      elements,
      content,
      markerChart,
      item,
      index,
      e) {
  var image_html = document.getElementById("image_gallery").innerHTML = '';
  var video_html = document.getElementById("video_gallery").innerHTML = '';
  var summary_table = document.getElementById("summaryTable").innerHTML = '';
  var urls = document.getElementById("urls").innerHTML = '';

  var highlightColour = instance_settings.colour;

  $('.edit_dropdown').remove();

  if (is_defined(markerChart) && markerChart.filter() == e.target.data.i) {
    markerChart.filter(true);
  } else {

    $('#edit_dropdown').append(
      "
Edit this event
"
    );

    if (is_defined(e.target.data.photos)) {
      // Photos
      d3.json(e.target.data.photos, function(D) {

        var json_photos = $.parseJSON('[' + item.photos + ']');

        for (j = 0; j < json_photos.length; j++) {
          $('#image_gallery').append(
            '
' +
            '' +
            '
' +
            json_photos[j].caption +
            '
Source: ' + json_photos[j].source + '
' +
            '
'
          );
        }

        $(document).ready(function() {
          $("#image_gallery").lightGallery();
        });

      });
    }

    if (is_defined(e.target.data.videos)) {
      // Videos
      d3.json(e.target.data.videos, function(D) {

        var json_videos = $.parseJSON('[' + item.videos + ']');

        for (j = 0; j < json_videos.length; j++) {
          $('#video_gallery').append(
            '
' +
            '
Video: ' + json_videos[j].caption + '
' +
            '
Source: ' + json_videos[j].source + '
' +
            '
'
          );
        }

        $(document).ready(function() {
          $("#video_gallery").lightGallery();
        });

      });
    }

    if (is_defined(e.target.data.links)) {
      // Urls
      d3.json(e.target.data.links, function(D) {

        var array_urls = $.parseJSON('[' + item.links + ']');

        for (j = 0; j < array_urls.length; j++) {
          $('#urls').append(
            '
 Title: ' + array_urls[j].title + '

Subtitle: ' + array_urls[j].subtitle + '
'
          );
        }

      });
    }

    // Open popup
    // @x-technical-debt: ok to check if var is defined, but it should be
    // actually *always* defined here - if it is undefined, something has
    // failed upstream
    if (is_defined(markerChart)) {
      e.target.popup.openOn(markerChart.getMap());
    }

    // Change style of popup
    $('.leaflet-popup-content-wrapper').addClass('transparent');
    $('.leaflet-popup-tip').addClass('transparent');
    $('.leaflet-popup-close-button').addClass('transparent');

    // Style marker on click
    item.marker.setRadius(10);
    item.marker.setStyle({
      color: highlightColour,
      fillColor: highlightColour,
      fillOpacity: 0.8,
    });

    // Add infowindow content
    elements.details.innerHTML = content.event_details;
    elements.summary.innerHTML = content.event_summary;

    if ('geojson_file' === data_source_type) {
      Object.keys(item.source_variables)
        .filter(function(value) {
          return value === 'pattrn_data_set' || !value.match(/^pattrn_[^_]{2,}/);
        })
        .forEach(function(value, index, array) {
          if (is_defined(item.source_variables[value])) appendGeoJSONPropertyToTable(value, item.source_variables[value], variable_list);
        });
    }

    if(layer_data.non_empty_variables.find(vt => vt.type === 'integer')) {
      layer_data.non_empty_variables.find(vt => vt.type === 'integer').names.forEach(function(item, index) {
        appendIntegerValueToTable(e, item, variable_list);
      });
    }

    if(layer_data.non_empty_variables.find(vt => vt.type === 'tag')) {
      layer_data.non_empty_variables.find(vt => vt.type === 'tag').names.forEach(function(item, index) {
        appendTagValueToTable(e, item, variable_list);
      });
    }

    if(layer_data.non_empty_variables.find(vt => vt.type === 'boolean')) {
      layer_data.non_empty_variables.find(vt => vt.type === 'boolean').names.forEach(function(item, index) {
        appendTagValueToTable(e, item, variable_list);
      });
    }

    /**
     * diagnostic output in development mode
     */
    if(is_defined(instance_settings.environment) && instance_settings.environment === 'development') {
      appendPropertyToTable('layer_group', layer_group.id);
      appendPropertyToTable('layer_group', layer_data.id);
    }
  }

  _map.on("popupclose", function(item) {
    // If data on source data set is available, set colour of markers accordingly, otherwise use defaults
    var marker_color = is_defined(item.pattrn_data_set) && is_defined(pattrn_data_sets[item.pattrn_data_set]) ?
      pattrn_data_sets[item.pattrn_data_set] :
      instance_settings.map.markers.color;

    item.marker.setRadius(7);
    item.marker.setStyle({
      fillColor: marker_color,
      color: marker_color,
      fillOpacity: instance_settings.map.markers.opacity
    });
    content.innerHTML = "
Please click a marker

";
    summary.innerHTML = "
This panel will update when a marker is clicked
";
    var summary_table = document.getElementById("summaryTable").innerHTML = '';
    var image_html = document.getElementById("image_gallery").innerHTML = '';
    var video_html = document.getElementById("video_gallery").innerHTML = '';
    var urls = document.getElementById("urls").innerHTML = '';
    $('.edit_dropdown').remove();
  });
}

// Table content - generic functions
function appendIntegerValueToTable(d, field_name, variable_list) {
  if (!is_defined(d[field_name])) return;

  let variable = variable_list.find(variable => variable.id === field_name);
  let full_field_name = is_defined(variable) && is_defined(variable.name) ? variable.name : field_name;

  appendPropertyToTable(full_field_name, d[field_name]);
}

function appendTagValueToTable(d, field_name, variable_list) {
  if (!is_defined(d[field_name])) return;

  let variable = variable_list.find(variable => variable.id === field_name);
  let full_field_name = is_defined(variable) && is_defined(variable.name) ? variable.name : field_name;

  appendPropertyToTable(full_field_name, d[field_name].split(',').join(', '));
}

function appendGeoJSONPropertyToTable(key, value, variable_list) {
  let variable = variable_list.find(variable => variable.id === key);
  let full_field_name = is_defined(variable) && is_defined(variable.name) ? variable.name : key;

  appendPropertyToTable(full_field_name, value);
}

/**
 * Add property to the details table.
 * Used by other (more specific) append[Something]ToTable() functions, or
 * manually for generated properties.
 * For example, while in development mode, this may be used to output the
 * row's parent layer and layer group ids for diagnostic purposes.
 */
function appendPropertyToTable(key, value) {
  $('#summaryTable').append(
    "

" + key +
    "

 " + value +
    "

"
  );
}
<|endoftext|>


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

    // Plugin constructor and defaults
    // -------------------------------

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

        // Used during card generation
        this.currentCard = null;
        this.currentElement = null;
        this.listReduced = [];
        this.initialIndex = null;
        this.initialY = null;
    };

    Crud.DEFAULTS = {
        dragComponent: '---'
    };

    // Plugin attach events
    // --------------------

    // Publish click event on Card DOM element
    Crud.prototype.attachClick = function() {
        Planner.Events.subscribe('cardDomDrawn', function(card, $element) {
            $element.on({
                mouseup: function(event) {
                    // Avoid this action on event propagation from children
                    if (event.currentTarget === event.target) {
                        Planner.Events.publish('cardClicked', [card, $element]);
                    }
                }
            });
        });
    };

    Crud.prototype.attachDragCreation = function() {
        var self = this;

        self.$element.find('.planner-column > div').on({
            mousedown: function(event) {
                // Avoid this action on event propagation from children
                if (event.currentTarget === event.target) {
                    var $this = $(this);

                    // Start interaction with created objects
                    var card = self._createCard($this);

                    // TODO: $this.index() doesn't match domId. After full migration to data-attribute, we can use this value to find the correct domId
                    var domId = card.columns[0];
                    self._startInteraction(card, Planner.mapCard.get(card)[domId], $this.index(), event.clientY);
                    event.preventDefault();
                }
            },
            mousemove: function(event) {
                if (self.currentCard !== null) {
                    self._resize(event.clientY);

                    event.preventDefault();
                }
            },
            mouseup: function(event) {
                if (self.currentCard !== null) {
                    Planner.Events.publish('cardCreated', [self.currentCard, self.currentElement]);

                    self._stopInteraction();
                    event.preventDefault();
                }
            }
        });
    };

    Crud.prototype.attachDragAndDrop = function() {
        var self = this;

        // Happend drag events to timeslots
        self.$element.find('.planner-column > div').on({
            dragover:  function(event) {
                event.preventDefault();
            },
            drop: function(event) {
                self._drag(this);
                self._resetDrag();

                // Note: remove all temporary clones because of webkit bug/feature
                if (!!window.webkitURL) {
                    $('[data-planner=container] > .planner-card').remove();
                }

                Planner.Events.publish('cardUpdated', [self.currentCard, self.currentElement]);

                self._stopInteraction();
                event.preventDefault();
            }
        });

        Planner.Events.subscribe('cardDomDrawn', function(card, $element) {
            $element.attr('draggable', true);
            $element.on({
                dragstart: function(event) {
                    // Start interaction with created objects
                    self._startInteraction(card, $element);

                    // Required for Firefox
                    event.originalEvent.dataTransfer.effectAllowed = 'move';
                    event.originalEvent.dataTransfer.setData('text/html', '[Object] Card');

                    // Note: in webkit browsers, drag-n-drop doesn't create a ghost image if the original
                    // object is placed inside a container with -webkit-transform attribute. The only available
                    // solution (at the moment) is to clone the object and put it inside an upper container.
                    var draggedNode = this;
                    if (!!window.webkitURL) {
                        draggedNode = this.cloneNode(true);
                        $(draggedNode).width($(this).width());
                        $(draggedNode).addClass('dragging');
                        $('[data-planner=container]').append(draggedNode);
                    }

                    event.originalEvent.dataTransfer.setDragImage(draggedNode, 20, 20);

                    // Add a ghost effect
                    $element.addClass('dragging');
                },
                dragenter: function() {
                    // Reduce card size if draggedElement goes upfront another card
                    // and store the node to remove this effect later
                    if (!$element.hasClass('card-small')) {
                        $element.addClass('card-small');
                        self.listReduced.push($element);
                    }
                },
                drop: function(event) {
                    // Avoid to drop a card over another card
                    event.preventDefault();
                    event.stopPropagation();
                    self._resetDrag();
                },
                dragend: function() {
                    // Remove ghost effect
                    $element.removeClass('dragging');
                }
            });
        });
    };

    Crud.prototype.attachResize = function() {
        var self = this;

        Planner.Events.subscribe('cardDomDrawn', function(card, $element) {
            var draggableDom = $(Planner.Templates.drag({dragComponent: self.options.dragComponent})).on({
                mousedown: function(event) {
                    self._startInteraction(card, $element, Planner.Helpers.attributeToIndex(card.start), $element.offset().top - $(window).scrollTop());
                    self.currentElement.addClass('resizable');

                    event.preventDefault();
                }
            });

            // Append element after latest DOM object of a Card
            $element.find('.planner-card-title').after(draggableDom);

            // Attach resize listeners
            $element.on({
                mousemove: function(event) {
                    if ($element.hasClass('resizable')) {
                        self._resize(event.clientY);

                        // Avoid other actions
                        event.preventDefault();
                    }
                },
                mouseup: function(event) {
                    if ($element.hasClass('resizable')) {
                        Planner.Events.publish('cardUpdated', [card, $element]);
                        self.currentElement.removeClass('resizable');

                        self._stopInteraction();
                        event.preventDefault();
                    }
                }
            });
        });
    };

    Crud.prototype.attachMobileListeners = function() {
        var self = this;

        self.$element.find('.planner-column > div').on({
            touchmove: function(event) {
                if (self.currentCard !== null) {
                    self._resize(event.originalEvent.touches[0].clientY);
                    event.preventDefault();
                }
            },
            touchend: function(event) {
                Planner.Events.publish('cardCreated', [self.currentCard, self.currentElement]);

                self._stopInteraction();
                event.preventDefault();
            }
        });

        Planner.Events.subscribe('cardDomDrawn', function(card, $element) {
            $element.on({
                touchend: function() {
                    Planner.Events.publish('cardClicked', [card, $element]);
                    // TODO: check if this is required to "Avoid propagation of element on child/parent elements"
                    // event.stopPropagation();
                }
            });
        });
    };

    // Private plugin helpers
    // ----------------------

    Crud.prototype._createCard = function($element) {
        // Create a new card and draw DOM element
        var startAttribute = Planner.Helpers.indexToAttribute($element.index());
        var endAttribute = Planner.Helpers.indexToAttribute($element.index() + 1);
        var column = [$element.parent().index() + 1];
        var card = this.options.model({start: startAttribute, end: endAttribute, columns: column});
        card.draw();

        return card;
    };

    Crud.prototype._startInteraction = function(card, $element, initialIndex, initialY) {
        // Store variables for further interactions
        this.currentCard = card;
        this.currentElement = $element;
        this.initialIndex = initialIndex;
        this.initialY = initialY;
    };

    Crud.prototype._stopInteraction = function() {
        this.currentCard = null;
        this.currentElement = null;
        this.initialIndex = null;
        this.initialY = null;
    };

    Crud.prototype._drag = function(destination) {
        var length = this.currentElement.data('end') - this.currentElement.data('start');

        this.currentElement.appendTo(destination);
        var column = this.currentElement.parent().parent().index() + 1;
        var startPosition = this.currentElement.parent().index();

        // Update Card  and DOM object
        this.currentCard.refresh({
            columns: [column],
            start: Planner.Helpers.indexToAttribute(startPosition),
            end: Planner.Helpers.indexToAttribute(startPosition + length + 1)
        });

        this.currentCard.refreshDom(this.currentElement, column);
    };

    Crud.prototype._resize = function(pointerY) {
        // Calculate new length
        var currentCardPosition = Math.floor((pointerY - this.initialY) / this.options.timeslotHeight);
        var endIndex = this.initialIndex + currentCardPosition;

        // Update Card  and DOM object
        this.currentCard.refresh({end: Planner.Helpers.indexToAttribute(endIndex + 1)});
        this.currentCard.refreshDom(this.currentElement);
    };

    Crud.prototype._resetDrag = function() {
        while (this.listReduced.length > 0) {
            this.listReduced.pop().removeClass('card-small');
        }
    };

    // Crud plugin definition
    // ----------------------

    var old = $.fn.crud;

    $.fn.crud = function(option) {
        var planner = this;

        return this.$element.each(function() {
            var $this = $(this);
            var data = $this.data('pl.plugins.crud');
            var options = $.extend({}, Crud.DEFAULTS, $this.data('pl.planner').options, typeof option === 'object' && option);

            // If this node isn't initialized, call the constructor
            if (!data) {
                $this.data('pl.plugins.crud', (data = new Crud(planner, options)));
            }

            data.attachClick();
            data.attachDragCreation();
            data.attachDragAndDrop();
            data.attachResize();

            if (Planner.Plugins.isRegistered('mobile')) {
                data.attachMobileListeners();
            }

            // Add interaction styles
            $('head').append(Planner.Templates.interaction());
        });
    };

    $.fn.crud.constructor = Crud;

    // Crud no conflict
    // ----------------

    $.fn.crud.noConflict = function() {
        $.fn.crud = old;
        return this;
    };

    // Register this plugin to plugins list
    // ------------------------------------

    Planner.Plugins.register('interaction', $.fn.crud);

})(jQuery, Planner);
<commit_msg>Avoid more than one event propagation when interaction is active. Closes #15<commit_after>;(function($, Planner) { 'use strict';

    // Plugin constructor and defaults
    // -------------------------------

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

        // Used during card generation
        this.currentCard = null;
        this.currentElement = null;
        this.listReduced = [];
        this.initialIndex = null;
        this.initialY = null;
    };

    Crud.DEFAULTS = {
        dragComponent: '---'
    };

    // Plugin attach events
    // --------------------

    // Publish click event on Card DOM element
    Crud.prototype.attachClick = function() {
        Planner.Events.subscribe('cardDomDrawn', function(card, $element) {
            $element.on({
                mouseup: function(event) {
                    // Avoid this action on event propagation from children or if
                    // another interaction is active
                    if (event.currentTarget === event.target && this.currentCard === null) {
                        Planner.Events.publish('cardClicked', [card, $element]);
                    }
                }
            });
        });
    };

    Crud.prototype.attachDragCreation = function() {
        var self = this;

        self.$element.find('.planner-column > div').on({
            mousedown: function(event) {
                // Avoid this action on event propagation from children
                if (event.currentTarget === event.target) {
                    var $this = $(this);

                    // Start interaction with created objects
                    var card = self._createCard($this);

                    // TODO: $this.index() doesn't match domId. After full migration to data-attribute, we can use this value to find the correct domId
                    var domId = card.columns[0];
                    self._startInteraction(card, Planner.mapCard.get(card)[domId], $this.index(), event.clientY);
                    event.preventDefault();
                }
            },
            mousemove: function(event) {
                if (self.currentCard !== null) {
                    self._resize(event.clientY);

                    event.preventDefault();
                }
            },
            mouseup: function(event) {
                if (self.currentCard !== null) {
                    Planner.Events.publish('cardCreated', [self.currentCard, self.currentElement]);

                    self._stopInteraction();
                    event.preventDefault();
                }
            }
        });
    };

    Crud.prototype.attachDragAndDrop = function() {
        var self = this;

        // Happend drag events to timeslots
        self.$element.find('.planner-column > div').on({
            dragover:  function(event) {
                event.preventDefault();
            },
            drop: function(event) {
                self._drag(this);
                self._resetDrag();

                // Note: remove all temporary clones because of webkit bug/feature
                if (!!window.webkitURL) {
                    $('[data-planner=container] > .planner-card').remove();
                }

                Planner.Events.publish('cardUpdated', [self.currentCard, self.currentElement]);

                self._stopInteraction();
                event.preventDefault();
            }
        });

        Planner.Events.subscribe('cardDomDrawn', function(card, $element) {
            $element.attr('draggable', true);
            $element.on({
                dragstart: function(event) {
                    // Start interaction with created objects
                    self._startInteraction(card, $element);

                    // Required for Firefox
                    event.originalEvent.dataTransfer.effectAllowed = 'move';
                    event.originalEvent.dataTransfer.setData('text/html', '[Object] Card');

                    // Note: in webkit browsers, drag-n-drop doesn't create a ghost image if the original
                    // object is placed inside a container with -webkit-transform attribute. The only available
                    // solution (at the moment) is to clone the object and put it inside an upper container.
                    var draggedNode = this;
                    if (!!window.webkitURL) {
                        draggedNode = this.cloneNode(true);
                        $(draggedNode).width($(this).width());
                        $(draggedNode).addClass('dragging');
                        $('[data-planner=container]').append(draggedNode);
                    }

                    event.originalEvent.dataTransfer.setDragImage(draggedNode, 20, 20);

                    // Add a ghost effect
                    $element.addClass('dragging');
                },
                dragenter: function() {
                    // Reduce card size if draggedElement goes upfront another card
                    // and store the node to remove this effect later
                    if (!$element.hasClass('card-small')) {
                        $element.addClass('card-small');
                        self.listReduced.push($element);
                    }
                },
                drop: function(event) {
                    // Avoid to drop a card over another card
                    event.preventDefault();
                    event.stopPropagation();
                    self._resetDrag();
                },
                dragend: function() {
                    // Remove ghost effect
                    $element.removeClass('dragging');
                }
            });
        });
    };

    Crud.prototype.attachResize = function() {
        var self = this;

        Planner.Events.subscribe('cardDomDrawn', function(card, $element) {
            var draggableDom = $(Planner.Templates.drag({dragComponent: self.options.dragComponent})).on({
                mousedown: function(event) {
                    self._startInteraction(card, $element, Planner.Helpers.attributeToIndex(card.start), $element.offset().top - $(window).scrollTop());
                    self.currentElement.addClass('resizable');

                    event.preventDefault();
                }
            });

            // Append element after latest DOM object of a Card
            $element.find('.planner-card-title').after(draggableDom);

            // Attach resize listeners
            $element.on({
                mousemove: function(event) {
                    if ($element.hasClass('resizable')) {
                        self._resize(event.clientY);

                        // Avoid other actions
                        event.preventDefault();
                    }
                },
                mouseup: function(event) {
                    if ($element.hasClass('resizable')) {
                        Planner.Events.publish('cardUpdated', [card, $element]);
                        self.currentElement.removeClass('resizable');

                        self._stopInteraction();
                        event.preventDefault();
                    }
                }
            });
        });
    };

    Crud.prototype.attachMobileListeners = function() {
        var self = this;

        self.$element.find('.planner-column > div').on({
            touchmove: function(event) {
                if (self.currentCard !== null) {
                    self._resize(event.originalEvent.touches[0].clientY);
                    event.preventDefault();
                }
            },
            touchend: function(event) {
                Planner.Events.publish('cardCreated', [self.currentCard, self.currentElement]);

                self._stopInteraction();
                event.preventDefault();
            }
        });

        Planner.Events.subscribe('cardDomDrawn', function(card, $element) {
            $element.on({
                touchend: function() {
                    Planner.Events.publish('cardClicked', [card, $element]);
                    // TODO: check if this is required to "Avoid propagation of element on child/parent elements"
                    // event.stopPropagation();
                }
            });
        });
    };

    // Private plugin helpers
    // ----------------------

    Crud.prototype._createCard = function($element) {
        // Create a new card and draw DOM element
        var startAttribute = Planner.Helpers.indexToAttribute($element.index());
        var endAttribute = Planner.Helpers.indexToAttribute($element.index() + 1);
        var column = [$element.parent().index() + 1];
        var card = this.options.model({start: startAttribute, end: endAttribute, columns: column});
        card.draw();

        return card;
    };

    Crud.prototype._startInteraction = function(card, $element, initialIndex, initialY) {
        // Store variables for further interactions
        this.currentCard = card;
        this.currentElement = $element;
        this.initialIndex = initialIndex;
        this.initialY = initialY;
    };

    Crud.prototype._stopInteraction = function() {
        this.currentCard = null;
        this.currentElement = null;
        this.initialIndex = null;
        this.initialY = null;
    };

    Crud.prototype._drag = function(destination) {
        var length = this.currentElement.data('end') - this.currentElement.data('start');

        this.currentElement.appendTo(destination);
        var column = this.currentElement.parent().parent().index() + 1;
        var startPosition = this.currentElement.parent().index();

        // Update Card  and DOM object
        this.currentCard.refresh({
            columns: [column],
            start: Planner.Helpers.indexToAttribute(startPosition),
            end: Planner.Helpers.indexToAttribute(startPosition + length + 1)
        });

        this.currentCard.refreshDom(this.currentElement, column);
    };

    Crud.prototype._resize = function(pointerY) {
        // Calculate new length
        var currentCardPosition = Math.floor((pointerY - this.initialY) / this.options.timeslotHeight);
        var endIndex = this.initialIndex + currentCardPosition;

        // Update Card  and DOM object
        this.currentCard.refresh({end: Planner.Helpers.indexToAttribute(endIndex + 1)});
        this.currentCard.refreshDom(this.currentElement);
    };

    Crud.prototype._resetDrag = function() {
        while (this.listReduced.length > 0) {
            this.listReduced.pop().removeClass('card-small');
        }
    };

    // Crud plugin definition
    // ----------------------

    var old = $.fn.crud;

    $.fn.crud = function(option) {
        var planner = this;

        return this.$element.each(function() {
            var $this = $(this);
            var data = $this.data('pl.plugins.crud');
            var options = $.extend({}, Crud.DEFAULTS, $this.data('pl.planner').options, typeof option === 'object' && option);

            // If this node isn't initialized, call the constructor
            if (!data) {
                $this.data('pl.plugins.crud', (data = new Crud(planner, options)));
            }

            data.attachClick();
            data.attachDragCreation();
            data.attachDragAndDrop();
            data.attachResize();

            if (Planner.Plugins.isRegistered('mobile')) {
                data.attachMobileListeners();
            }

            // Add interaction styles
            $('head').append(Planner.Templates.interaction());
        });
    };

    $.fn.crud.constructor = Crud;

    // Crud no conflict
    // ----------------

    $.fn.crud.noConflict = function() {
        $.fn.crud = old;
        return this;
    };

    // Register this plugin to plugins list
    // ------------------------------------

    Planner.Plugins.register('interaction', $.fn.crud);

})(jQuery, Planner);
<|endoftext|>


<commit_before>module.exports = function(grunt) {
	"use strict";

	var merge = require('merge'),
		config = {},
		pkg = grunt.file.readJSON('package.json');

	// Load the config file if one exists.
	try {
		config = grunt.file.readJSON('config.json');
	}
	catch(e) {

	}

	// Merge config defaults.
	merge({
		version: '0.0.0',
		copyright: 'Copyright 2009 - ' + (new Date()).getFullYear() + ' Ian Renyard',
		gaid: 'UA-XXXXXX-X'
	}, config);

	grunt.initConfig({
		pkg: pkg,
		clean: {
			dist: {
				files: {
					src: ['dist/']
				}
			}
		},
		copy: {
			src: {
				files: [
					{
						expand: true,
						cwd: 'src/',
						src: ['**'],
						dest: 'dist/'
					}
				]
			}
		},
		replace: {
			dist: {
				options: {
					patterns: [
						{
							match: '@version@',
							replacement: pkg.version
						},
						{
							match: '@copyright@',
							replacement: config.copyright
						},
						{
							match: '@gaid@',
							replacement: config.gaid
						},
						{
							match: /\/\*!debug\*\/[\s\S]*?\/\*gubed!\*\/[\s\n]*/g,
							replacement: ''
						}
					],
					prefix: ''
				},
				files: [
					{
						expand: true,
						cwd: 'dist/',
						src: ['**/*'],
						dest: 'dist/'
					}
				]
			},
		},
		jshint: {
			all: ['dist/**/*.js'],
			options: {
				globals: {
					document: true,
					window: true,
					XMLHttpRequest: true,
					console: true,
					localStorage: true,
					chrome: true
				},
				evil: true
			}
		},
		qunit: {
			all: ['test/*.html']
		},
		compress: {
			dist: {
				options: {
					mode: 'zip',
					archive: 'validity-<%= pkg.version %>.zip'
				},
				files: [
					{
						expand: true,
						src: ['**/*'],
						cwd: 'dist/',
						dest: '/'
					}
				]
			}
		}
	});

	grunt.loadNpmTasks('grunt-contrib-clean');
	grunt.loadNpmTasks('grunt-contrib-copy');
	grunt.loadNpmTasks('grunt-replace');
	grunt.loadNpmTasks('grunt-contrib-jshint');
	grunt.loadNpmTasks('grunt-contrib-qunit');
	grunt.loadNpmTasks('grunt-contrib-compress');

	grunt.registerTask('build', ['clean', 'copy', 'replace']);
	grunt.registerTask('test', ['clean', 'copy', 'replace', 'jshint', 'qunit']);
	grunt.registerTask('default', ['clean', 'copy', 'replace', 'jshint', 'qunit', 'compress']);
}
<commit_msg>Fixed merging of config with defaults in Gruntfile.js.<commit_after>module.exports = function(grunt) {
	"use strict";

	var merge = require('merge'),
		config = {},
		pkg = grunt.file.readJSON('package.json');

	// Load the config file if one exists.
	try {
		config = grunt.file.readJSON('config.json');
	}
	catch(e) {

	}

	// Merge config defaults.
	config = merge({
		copyright: 'Copyright 2009 - ' + (new Date()).getFullYear() + ' Ian Renyard',
		gaid: 'UA-XXXXXX-X'
	}, config);

	grunt.initConfig({
		pkg: pkg,
		clean: {
			dist: {
				files: {
					src: ['dist/']
				}
			}
		},
		copy: {
			src: {
				files: [
					{
						expand: true,
						cwd: 'src/',
						src: ['**'],
						dest: 'dist/'
					}
				]
			}
		},
		replace: {
			dist: {
				options: {
					patterns: [
						{
							match: '@version@',
							replacement: pkg.version
						},
						{
							match: '@copyright@',
							replacement: config.copyright
						},
						{
							match: '@gaid@',
							replacement: config.gaid
						},
						{
							match: /\/\*!debug\*\/[\s\S]*?\/\*gubed!\*\/[\s\n]*/g,
							replacement: ''
						}
					],
					prefix: ''
				},
				files: [
					{
						expand: true,
						cwd: 'dist/',
						src: ['**/*'],
						dest: 'dist/'
					}
				]
			},
		},
		jshint: {
			all: ['dist/**/*.js'],
			options: {
				globals: {
					document: true,
					window: true,
					XMLHttpRequest: true,
					console: true,
					localStorage: true,
					chrome: true
				},
				evil: true
			}
		},
		qunit: {
			all: ['test/*.html']
		},
		compress: {
			dist: {
				options: {
					mode: 'zip',
					archive: 'validity-<%= pkg.version %>.zip'
				},
				files: [
					{
						expand: true,
						src: ['**/*'],
						cwd: 'dist/',
						dest: '/'
					}
				]
			}
		}
	});

	grunt.loadNpmTasks('grunt-contrib-clean');
	grunt.loadNpmTasks('grunt-contrib-copy');
	grunt.loadNpmTasks('grunt-replace');
	grunt.loadNpmTasks('grunt-contrib-jshint');
	grunt.loadNpmTasks('grunt-contrib-qunit');
	grunt.loadNpmTasks('grunt-contrib-compress');

	grunt.registerTask('build', ['clean', 'copy', 'replace']);
	grunt.registerTask('test', ['clean', 'copy', 'replace', 'jshint', 'qunit']);
	grunt.registerTask('default', ['clean', 'copy', 'replace', 'jshint', 'qunit', 'compress']);
}
<|endoftext|>


<commit_before>/*
 * Responsive Dropdown
 */
/*jshint expr:true*/
/*global jQuery*/
(function ($, w, ns) {

    "use strict";

    if (w.RESPONSIVE_DROPDOWN) {
        return;
    }

    // General variables.
    var supportTransition = w.getComputedStyle && $.support.transition,
        rtl = $.support.rtl,
        eready = "ready" + ns,
        eclick = "click",
        ekeydown = "keydown",
        eshow = "show" + ns,
        eshown = "shown" + ns,
        ehide = "hide" + ns,
        ehidden = "hidden" + ns;

    var keys = {
        SPACE: 32,
        LEFT: 37,
        RIGHT: 39
    };

    // The Dropdown class definition
    var Dropdown = function (element, options) {

        this.$element = $(element);
        this.$target = $(options.target);
        this.defaults = {
            dimension: "height"
        };
        this.options = $.extend({}, this.defaults, options);
        this.$parent = null;
        this.transitioning = null;
        this.endSize = null;

        if (this.options.parent) {
            this.$parent = this.$target.closest(this.options.parent);
        }

        // Add accessibility features.
        if (this.$parent) {
            this.$parent.attr({ "role": "tablist", "aria-multiselectable": "true" })
                .find("div:not(.collapse,.accordion-body)").attr("role", "presentation");
        } else {
            $(".accordion").find("div:not(.collapse,.accordion-body)").addBack().attr("role", "presentation");
        }

        var $tab = $("[href=" + this.options.target + "], [data-dropdown-target=" + this.options.target + "]"),
            tabId = $tab.attr("id") || "dropdown-" + $.pseudoUnique(),
            paneId = this.$target.attr("id") || "dropdown-" + $.pseudoUnique(),
            active = !this.$target.hasClass("collapse");

        $tab.attr({
            "id": tabId,
            "role": "tab",
            "aria-controls": paneId,
            "aria-selected": active,
            "aria-expanded": active,
            "tabindex": 0
        });

        this.$target.attr({
            "id": paneId,
            "role": "tabpanel",
            "aria-labelledby": tabId,
            "aria-hidden": !active,
            "tabindex": active ? 0 : -1
        });

        // Bind events.
        this.$element.on(eclick, $.proxy(this.click, this));
        this.$element.on(ekeydown, $.proxy(this.keydown, this));
    };

    Dropdown.prototype.show = function () {

        if (this.transitioning || this.$target.hasClass("expand")) {
            return;
        }

        var self = this,
            dimension = this.options.dimension,
            $actives = [];

        if (this.$parent) {
            // Get all the related open panes.
            $actives = this.$parent.find(" > [role=presentation] > [role=presentation]").children("[role=tab]");

            $actives = $.grep($actives, function (a) {
                var data = $(a).data("r.dropdown"),
                    $target = data && data.$target;

                return $target && $target.hasClass("dropdown-group") && !$target.hasClass("collapse") && data.$parent && data.$parent[0] === self.$parent[0];
            });
        }

        // Set the height/width to zero then to the height/width
        // so animation can take place.
        this.$target[dimension](0);

        if (supportTransition) {

            // Calculate the height/width.
            this.$target[dimension]("auto");
            this.endSize = w.getComputedStyle(this.$target[0])[dimension];

            // Reset to zero and force repaint.
            this.$target[dimension](0).redraw();
        }

        this.$target[dimension](this.endSize || "");

        this.transition("removeClass", $.Event(eshow), eshown);

        if ($actives && $actives.length) {
            $.each($actives, function () {
                $(this).dropdown("hide");
            });
        }
    };

    Dropdown.prototype.hide = function () {

        if (this.transitioning || this.$target.hasClass("collapse")) {
            return;
        }

        // Reset the height/width and then reduce to zero.
        var dimension = this.options.dimension,
            size;

        if (supportTransition) {

            // Set the height to auto, calculate the height/width and reset.
            size = w.getComputedStyle(this.$target[0])[dimension];

            // Reset the size and force repaint.
            this.$target[dimension](size).redraw(); // Force reflow ;
        }

        this.$target.removeClass("expand");
        this.$target[dimension](0);
        this.transition("addClass", $.Event(ehide), ehidden);
    };

    Dropdown.prototype.toggle = function () {
        // Run the correct command based on the presence of the class "collapse".
        this[this.$target.hasClass("collapse") ? "show" : "hide"]();
    };

    Dropdown.prototype.transition = function (method, startEvent, completeEvent) {

        var self = this,
            doShow = method === "removeClass",
            complete = function () {

                // The event to expose.
                var eventToTrigger = $.Event(completeEvent);

                // Ensure the height/width is set to auto.
                self.$target.removeClass("trans")[self.options.dimension]("");

                self.transitioning = false;

                // Set the correct aria attributes.
                self.$target.attr({
                    "aria-hidden": !doShow,
                    "tabindex": doShow ? 0 : -1,
                });

                var $tab = $("#" + self.$target.attr("aria-labelledby")).attr({
                    "aria-selected": doShow,
                    "aria-expanded": doShow
                });

                if (doShow) {
                    $tab.focus();
                }

                // Toggle any children.
                self.$target.find("[tabindex]:not(.collapse)").attr({
                    "aria-hidden": !doShow,
                    "tabindex": doShow ? 0 : -1,
                });

                self.$target.trigger(eventToTrigger);
            };

        if (this.transitioning || startEvent.isDefaultPrevented()) {
            return;
        }

        this.transitioning = true;

        // Remove or add the expand classes.
        this.$target.trigger(startEvent)[method]("collapse");
        this.$target[startEvent.type === "show" ? "addClass" : "removeClass"]("expand trans");

        this.$target.onTransitionEnd(complete);
    };

    Dropdown.prototype.click = function (event) {
        event.preventDefault();
        event.stopPropagation();
        this.toggle();
    };

    Dropdown.prototype.keydown = function (event) {

        var which = event.which;

        if (which === keys.SPACE || which === keys.LEFT || which === keys.RIGHT) {

            event.preventDefault();
            event.stopPropagation();

            var $this = $(event.target),
                $parent = this.options.parent ? $this.closest("[role=tablist]") : $this.closest(".accordion"),
                $items = $parent.find(" > [role=presentation] > [role=presentation]").children("[role=tab]"),
                index = $items.index($items.filter(":focus")),
                length = $items.length;

            if (which === keys.SPACE) {
                $($items.eq(index)).data("r.dropdown").toggle();
                return;
            }

            if (which === keys.LEFT) {
                rtl ? index += 1 : index -= 1;
            } else if (which === keys.RIGHT) {
                rtl ? index -= 1 : index += 1;
            }

            // Ensure that the index stays within bounds.
            if (index === length) {
                index = 0;
            }

            if (index < 0) {
                index = length - 1;
            }

            $($items.eq(index)).data("r.dropdown").show();
        }
    };

    // Plug-in definition
    $.fn.dropdown = function (options) {
        return this.each(function () {
            var $this = $(this),
                data = $this.data("r.dropdown"),
                opts = typeof options === "object" ? options : null;

            if (!data) {
                // Check the data and reassign if not present.
                $this.data("r.dropdown", (data = new Dropdown(this, opts)));
            }

            // Run the appropriate function if a string is passed.
            if (typeof options === "string" && /(show|hide|toggle)/.test(options)) {
                data[options]();
            }
        });
    };

    // Set the public constructor.
    $.fn.dropdown.Constructor = Dropdown;

    // No conflict.
    var old = $.fn.dropdown;
    $.fn.dropdown.noConflict = function () {
        $.fn.dropdown = old;
        return this;
    };

    // Dropdown data api initialization.
    $(document).on(eready, function () {
        $(":attrStart(data-dropdown)").each(function () {
            var $this = $(this),
                data = $this.data("r.dropdownOptions"),
                options = data || $.buildDataOptions($this, {}, "dropdown", "r");

            options.target || (options.target = $this.attr("href"));
            // Run the dropdown method.
            $this.dropdown(options);
        });
    });

    w.RESPONSIVE_DROPDOWN = true;

}(jQuery, window, ".r.dropdown"));<commit_msg>Fixing event trigger<commit_after>/*
 * Responsive Dropdown
 */
/*jshint expr:true*/
/*global jQuery*/
(function ($, w, ns) {

    "use strict";

    if (w.RESPONSIVE_DROPDOWN) {
        return;
    }

    // General variables.
    var supportTransition = w.getComputedStyle && $.support.transition,
        rtl = $.support.rtl,
        eready = "ready" + ns,
        eclick = "click",
        ekeydown = "keydown",
        eshow = "show" + ns,
        eshown = "shown" + ns,
        ehide = "hide" + ns,
        ehidden = "hidden" + ns;

    var keys = {
        SPACE: 32,
        LEFT: 37,
        RIGHT: 39
    };

    // The Dropdown class definition
    var Dropdown = function (element, options) {

        this.$element = $(element);
        this.$target = $(options.target);
        this.defaults = {
            dimension: "height"
        };
        this.options = $.extend({}, this.defaults, options);
        this.$parent = null;
        this.transitioning = null;
        this.endSize = null;

        if (this.options.parent) {
            this.$parent = this.$target.closest(this.options.parent);
        }

        // Add accessibility features.
        if (this.$parent) {
            this.$parent.attr({ "role": "tablist", "aria-multiselectable": "true" })
                .find("div:not(.collapse,.accordion-body)").attr("role", "presentation");
        } else {
            $(".accordion").find("div:not(.collapse,.accordion-body)").addBack().attr("role", "presentation");
        }

        var $tab = $("[href=" + this.options.target + "], [data-dropdown-target=" + this.options.target + "]"),
            tabId = $tab.attr("id") || "dropdown-" + $.pseudoUnique(),
            paneId = this.$target.attr("id") || "dropdown-" + $.pseudoUnique(),
            active = !this.$target.hasClass("collapse");

        $tab.attr({
            "id": tabId,
            "role": "tab",
            "aria-controls": paneId,
            "aria-selected": active,
            "aria-expanded": active,
            "tabindex": 0
        });

        this.$target.attr({
            "id": paneId,
            "role": "tabpanel",
            "aria-labelledby": tabId,
            "aria-hidden": !active,
            "tabindex": active ? 0 : -1
        });

        // Bind events.
        this.$element.on(eclick, $.proxy(this.click, this));
        this.$element.on(ekeydown, $.proxy(this.keydown, this));
    };

    Dropdown.prototype.show = function () {

        if (this.transitioning || this.$target.hasClass("expand")) {
            return;
        }

        var self = this,
            dimension = this.options.dimension,
            $actives = [];

        if (this.$parent) {
            // Get all the related open panes.
            $actives = this.$parent.find(" > [role=presentation] > [role=presentation]").children("[role=tab]");

            $actives = $.grep($actives, function (a) {
                var data = $(a).data("r.dropdown"),
                    $target = data && data.$target;

                return $target && $target.hasClass("dropdown-group") && !$target.hasClass("collapse") && data.$parent && data.$parent[0] === self.$parent[0];
            });
        }

        // Set the height/width to zero then to the height/width
        // so animation can take place.
        this.$target[dimension](0);

        if (supportTransition) {

            // Calculate the height/width.
            this.$target[dimension]("auto");
            this.endSize = w.getComputedStyle(this.$target[0])[dimension];

            // Reset to zero and force repaint.
            this.$target[dimension](0).redraw();
        }

        this.$target[dimension](this.endSize || "");

        this.transition("removeClass", $.Event(eshow), eshown);

        if ($actives && $actives.length) {
            $.each($actives, function () {
                $(this).dropdown("hide");
            });
        }
    };

    Dropdown.prototype.hide = function () {

        if (this.transitioning || this.$target.hasClass("collapse")) {
            return;
        }

        // Reset the height/width and then reduce to zero.
        var dimension = this.options.dimension,
            size;

        if (supportTransition) {

            // Set the height to auto, calculate the height/width and reset.
            size = w.getComputedStyle(this.$target[0])[dimension];

            // Reset the size and force repaint.
            this.$target[dimension](size).redraw(); // Force reflow ;
        }

        this.$target.removeClass("expand");
        this.$target[dimension](0);
        this.transition("addClass", $.Event(ehide), ehidden);
    };

    Dropdown.prototype.toggle = function () {
        // Run the correct command based on the presence of the class "collapse".
        this[this.$target.hasClass("collapse") ? "show" : "hide"]();
    };

    Dropdown.prototype.transition = function (method, startEvent, completeEvent) {

        var self = this,
            doShow = method === "removeClass",
            complete = function () {

                // The event to expose.
                var eventToTrigger = $.Event(completeEvent);

                // Ensure the height/width is set to auto.
                self.$target.removeClass("trans")[self.options.dimension]("");

                self.transitioning = false;

                // Set the correct aria attributes.
                self.$target.attr({
                    "aria-hidden": !doShow,
                    "tabindex": doShow ? 0 : -1,
                });

                var $tab = $("#" + self.$target.attr("aria-labelledby")).attr({
                    "aria-selected": doShow,
                    "aria-expanded": doShow
                });

                if (doShow) {
                    $tab.focus();
                }

                // Toggle any children.
                self.$target.find("[tabindex]:not(.collapse)").attr({
                    "aria-hidden": !doShow,
                    "tabindex": doShow ? 0 : -1,
                });

                self.$element.trigger(eventToTrigger);
            };

        if (this.transitioning || startEvent.isDefaultPrevented()) {
            return;
        }

        this.transitioning = true;

        // Remove or add the expand classes.
        this.$element.trigger(startEvent);
        this.$target[method]("collapse");
        this.$target[startEvent.type === "show" ? "addClass" : "removeClass"]("expand trans");

        this.$target.onTransitionEnd(complete);
    };

    Dropdown.prototype.click = function (event) {
        event.preventDefault();
        event.stopPropagation();
        this.toggle();
    };

    Dropdown.prototype.keydown = function (event) {

        var which = event.which;

        if (which === keys.SPACE || which === keys.LEFT || which === keys.RIGHT) {

            event.preventDefault();
            event.stopPropagation();

            var $this = $(event.target),
                $parent = this.options.parent ? $this.closest("[role=tablist]") : $this.closest(".accordion"),
                $items = $parent.find(" > [role=presentation] > [role=presentation]").children("[role=tab]"),
                index = $items.index($items.filter(":focus")),
                length = $items.length;

            if (which === keys.SPACE) {
                $($items.eq(index)).data("r.dropdown").toggle();
                return;
            }

            if (which === keys.LEFT) {
                rtl ? index += 1 : index -= 1;
            } else if (which === keys.RIGHT) {
                rtl ? index -= 1 : index += 1;
            }

            // Ensure that the index stays within bounds.
            if (index === length) {
                index = 0;
            }

            if (index < 0) {
                index = length - 1;
            }

            $($items.eq(index)).data("r.dropdown").show();
        }
    };

    // Plug-in definition
    $.fn.dropdown = function (options) {
        return this.each(function () {
            var $this = $(this),
                data = $this.data("r.dropdown"),
                opts = typeof options === "object" ? options : null;

            if (!data) {
                // Check the data and reassign if not present.
                $this.data("r.dropdown", (data = new Dropdown(this, opts)));
            }

            // Run the appropriate function if a string is passed.
            if (typeof options === "string" && /(show|hide|toggle)/.test(options)) {
                data[options]();
            }
        });
    };

    // Set the public constructor.
    $.fn.dropdown.Constructor = Dropdown;

    // No conflict.
    var old = $.fn.dropdown;
    $.fn.dropdown.noConflict = function () {
        $.fn.dropdown = old;
        return this;
    };

    // Dropdown data api initialization.
    $(document).on(eready, function () {
        $(":attrStart(data-dropdown)").each(function () {
            var $this = $(this),
                data = $this.data("r.dropdownOptions"),
                options = data || $.buildDataOptions($this, {}, "dropdown", "r");

            options.target || (options.target = $this.attr("href"));
            // Run the dropdown method.
            $this.dropdown(options);
        });
    });

    w.RESPONSIVE_DROPDOWN = true;

}(jQuery, window, ".r.dropdown"));
<|endoftext|>


<commit_before>#!/usr/bin/node

/**
 * Gruntfile.js
 * Compress and uploads the stylesheets, scripts and images to Amazon S3.
 *
 * @author  Jason Valdron <jason@valdron.ca>
 * @package portfolio
 */

(function(){
  'use strict';

  module.exports = function(grunt) {

    // Let's setup our environment.
    var target = grunt.option('target') || 'development';

    process.env.NODE_ENV = target;

    var config = require(process.cwd() + '/config/' + target)();

    // Project configuration.
    grunt.initConfig({

      pkg: grunt.file.readJSON('package.json'),

      clean: {
        dist: 'dist/',
        tmp: [
          'tmp/concatenated/',
          'tmp/compiled/',
          'tmp/pre-gzip/'
        ]
      },

      jsonmin: {
        dist: {
          files: [{
            expand: true,
            cwd: 'assets/locales/',
            src: [ '**/*.json' ],
            dest: 'tmp/pre-gzip/locales/'
          }]
        }
      },

      jade: {
        options: {
          data: {
            environment: target,
            config: config
          }
        },
        dist: {
          expand: true,
          cwd: 'views/',
          src: [ '**/*.jade' ],
          dest: 'tmp/pre-gzip/views/',
          ext: '.html'
        }
      },

      import: {
        dist: {
          expand: true,
          cwd: 'assets/js/',
          src: [ '**/*.js' ],
          dest: 'tmp/concatenated/js/'
        },
        tasks: [ 'uglify:dist' ]
      },

      uglify: {
        options: {
          banner: '/*! Copyright © <%= grunt.template.today("yyyy") %> Dealer Instincts - <%= pkg.name %> v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> */\n',
          sourceMap: true
        },
        dist: {
          expand: true,
          cwd: 'tmp/concatenated/js/',
          src: [ '**/*.js' ],
          dest: 'tmp/pre-gzip/js/'
        }
      },

      less: {
        options: {
          compress: false
        },
        dist: {
          expand: true,
          cwd: 'assets/css/',
          src: [
            '**/*.less',
            '!vendors/**'
          ],
          dest: 'tmp/compiled/css/',
          ext: '.css'
        }
      },

      cssmin: {
        options: {
          banner: '/*! Copyright © <%= grunt.template.today("yyyy") %> Dealer Instincts - <%= pkg.name %> v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> */\n',
          keepSpecialComments: 0
        },
        dist: {
          expand: true,
          cwd: 'tmp/compiled/css/',
          src: [ '**/*.css' ],
          dest: 'tmp/pre-gzip/css/'
        }
      },

      imagemin: {
        dist: {
          expand: true,
          cwd: 'public/img/',
          src: [ '**/*.{png,jpg,gif,svg}' ],
          dest: 'tmp/pre-gzip/img/'
        }
      },

      copy: {

        pdf: {
          expand: true,
          cwd: 'public/',
          src: [ '**/*.pdf' ],
          dest: 'tmp/pre-gzip/'
        },

        img: {
          expand: true,
          cwd: 'public/img/',
          src: [ '**/*.ico' ],
          dest: 'tmp/pre-gzip/img/'
        },

        fonts: {
          expand: true,
          cwd: 'public/fonts/',
          src: [ '**/*' ],
          dest: 'tmp/pre-gzip/fonts/'
        }

      },

      compress: {
        dist: {
          options: {
            mode: 'gzip'
          },
          files: [{
            expand: true,
            cwd: 'tmp/pre-gzip/',
            src: [ '**/*.*' ],
            dest: 'dist/',
            ext: function(ext){
              return ext;
            }
          }]
        },
        tasks: [ 'clean:tmp' ]
      },

      aws_s3: {
        options: {
          accessKeyId: config.aws.key,
          secretAccessKey: config.aws.secret,
          region: 'us-east-1'
        },
        deploy: {
          options: {
            bucket: config.aws.s3.bucket,
            params: {
              ContentEncoding: 'gzip'
            },
            differential: true
          },
          files: [{
            cwd: 'dist/',
            dest: '/',
            action: 'delete'
          }, {
            expand: true,
            cwd: 'dist/',
            src: [ '**/*' ],
            dest: '/',
            action: 'upload'
          }]
        }
      }

    });

    // Load our tasks.
    grunt.loadNpmTasks('grunt-aws-s3');
    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-compress');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.loadNpmTasks('grunt-contrib-imagemin');
    grunt.loadNpmTasks('grunt-contrib-jade');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-import');
    grunt.loadNpmTasks('grunt-jsonmin');

    var build = [
      'clean:dist',
      'jade',
      'jsonmin',
      'import',
      'uglify',
      'less',
      'cssmin',
      'imagemin',
      'copy:pdf',
      'copy:img',
      'copy:fonts',
      'compress',
      'clean:tmp'
    ];

    // Default tasks.
    grunt.registerTask('default', build);
    grunt.registerTask('build', build);

    grunt.registerTask('deploy', [
      'aws_s3'
    ]);

  };

})();<commit_msg>still had the wrong folder<commit_after>#!/usr/bin/node

/**
 * Gruntfile.js
 * Compress and uploads the stylesheets, scripts and images to Amazon S3.
 *
 * @author  Jason Valdron <jason@valdron.ca>
 * @package portfolio
 */

(function(){
  'use strict';

  module.exports = function(grunt) {

    // Let's setup our environment.
    var target = grunt.option('target') || 'development';

    process.env.NODE_ENV = target;

    var config = require(process.cwd() + '/config/' + target)();

    // Project configuration.
    grunt.initConfig({

      pkg: grunt.file.readJSON('package.json'),

      clean: {
        dist: 'dist/',
        tmp: [
          'tmp/concatenated/',
          'tmp/compiled/',
          'tmp/pre-gzip/'
        ]
      },

      jsonmin: {
        dist: {
          files: [{
            expand: true,
            cwd: 'assets/locales/',
            src: [ '**/*.json' ],
            dest: 'tmp/pre-gzip/locales/'
          }]
        }
      },

      jade: {
        options: {
          data: {
            environment: target,
            config: config
          }
        },
        dist: {
          expand: true,
          cwd: 'views/',
          src: [ '**/*.jade' ],
          dest: 'tmp/pre-gzip/views/',
          ext: '.html'
        }
      },

      import: {
        dist: {
          expand: true,
          cwd: 'assets/js/',
          src: [ '**/*.js' ],
          dest: 'tmp/concatenated/js/'
        },
        tasks: [ 'uglify:dist' ]
      },

      uglify: {
        options: {
          banner: '/*! Copyright © <%= grunt.template.today("yyyy") %> Dealer Instincts - <%= pkg.name %> v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> */\n',
          sourceMap: true
        },
        dist: {
          expand: true,
          cwd: 'tmp/concatenated/js/',
          src: [ '**/*.js' ],
          dest: 'tmp/pre-gzip/js/'
        }
      },

      less: {
        options: {
          compress: false
        },
        dist: {
          expand: true,
          cwd: 'assets/css/',
          src: [
            '**/*.less',
            '!vendors/**'
          ],
          dest: 'tmp/compiled/css/',
          ext: '.css'
        }
      },

      cssmin: {
        options: {
          banner: '/*! Copyright © <%= grunt.template.today("yyyy") %> Dealer Instincts - <%= pkg.name %> v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> */\n',
          keepSpecialComments: 0
        },
        dist: {
          expand: true,
          cwd: 'tmp/compiled/css/',
          src: [ '**/*.css' ],
          dest: 'tmp/pre-gzip/css/'
        }
      },

      imagemin: {
        dist: {
          expand: true,
          cwd: 'public/img/',
          src: [ '**/*.{png,jpg,gif,svg}' ],
          dest: 'tmp/pre-gzip/img/'
        }
      },

      copy: {

        pdf: {
          expand: true,
          cwd: 'public/',
          src: [ '**/*.pdf' ],
          dest: 'tmp/pre-gzip/'
        },

        img: {
          expand: true,
          cwd: 'public/img/',
          src: [ '**/*.ico' ],
          dest: 'tmp/pre-gzip/img/'
        },

        fonts: {
          expand: true,
          cwd: 'public/fonts/',
          src: [ '**/*' ],
          dest: 'tmp/pre-gzip/fonts/'
        }

      },

      compress: {
        dist: {
          options: {
            mode: 'gzip'
          },
          files: [{
            expand: true,
            cwd: 'tmp/pre-gzip/',
            src: [ '**/*.*' ],
            dest: 'dist/',
            ext: function(ext){
              return ext;
            }
          }]
        },
        tasks: [ 'clean:tmp' ]
      },

      aws_s3: {
        options: {
          accessKeyId: config.aws.key,
          secretAccessKey: config.aws.secret,
          region: 'us-east-1'
        },
        deploy: {
          options: {
            bucket: config.aws.s3.bucket,
            params: {
              ContentEncoding: 'gzip'
            },
            differential: true
          },
          files: [{
            cwd: 'dist/',
            dest: '',
            action: 'delete'
          }, {
            expand: true,
            cwd: 'dist/',
            src: [ '**/*' ],
            dest: '',
            action: 'upload'
          }]
        }
      }

    });

    // Load our tasks.
    grunt.loadNpmTasks('grunt-aws-s3');
    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-compress');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.loadNpmTasks('grunt-contrib-imagemin');
    grunt.loadNpmTasks('grunt-contrib-jade');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-import');
    grunt.loadNpmTasks('grunt-jsonmin');

    var build = [
      'clean:dist',
      'jade',
      'jsonmin',
      'import',
      'uglify',
      'less',
      'cssmin',
      'imagemin',
      'copy:pdf',
      'copy:img',
      'copy:fonts',
      'compress',
      'clean:tmp'
    ];

    // Default tasks.
    grunt.registerTask('default', build);
    grunt.registerTask('build', build);

    grunt.registerTask('deploy', [
      'aws_s3'
    ]);

  };

})();<|endoftext|>