<commit_before>import Backbone from 'backbone';
import Marionette from 'backbone.marionette';
import SearchResultListView from './SearchResultListView';

import './SearchResultView.css';
import template from './SearchResultView.hbs';

import noLayerSelectedTemplate from './NoLayerSelected.hbs';
import noLayersAvailableTemplate from './NoLayersAvailable.hbs';
import nLayersSelectedTemplate from './NLayersSelected.hbs';

// eslint-disable-next-line max-len
const SearchResultView = Marionette.LayoutView.extend(/** @lends search/views/layers.SearchResultView# */{
  template,
  templateHelpers() {
    return {
      showDownloadOptions: this.collection.any(searchModel => (
        searchModel.get('layerModel').get('download.protocol') === 'EO-WCS'
      )),
      layers: this.collection.map(model => model.get('layerModel').toJSON()),
      selectedLayer: this.selectedLayer ? this.selectedLayer.toJSON() : null,
    };
  },
  className: 'search-result-view',

  childView: SearchResultListView,
  childViewContainer: '.result-contents',

  events: {
    'change input[data-layer]': 'onLayerSelectionChange',
  },
  regions: {
    'search-results': '.result-contents',
  },

  initialize(options) {
    this.filtersModel = options.filtersModel;
    this.highlightModel = options.highlightModel;
    this.onResultItemClicked = options.onResultItemClicked;
    this.onResultItemInfo = options.onResultItemInfo;

    this.selectedSearchModels = [];

    this.listenTo(this.collection, 'change', this.onSearchModelsChange);

    this.collection.each((searchModel) => {
      this.listenTo(searchModel.get('layerModel'), 'change:display.visible', (layerModel) => {
        let newSelectedSearchModels;
        const $checkbox = this.$(`[data-layer="${layerModel.get('id')}"]`);
        if (layerModel.get('display.visible')) {
          if (!this.selectedSearchModels.find(m => m === searchModel)) {
            newSelectedSearchModels = this.selectedSearchModels.concat([searchModel]);
          }
          $checkbox.parent().show();
          $checkbox.prop('checked', true);
        } else {
          newSelectedSearchModels = this.selectedSearchModels.filter(
            model => model !== searchModel
          );
          $checkbox.parent().hide();
        }
        this.setSelectedSearchModels(newSelectedSearchModels);
        this.onSearchModelsChange();
      });
    });
  },

  onAttach() {
    this.onLayerSelectionChange();
  },

  setSelectedSearchModels(searchModels) {
    const names = searchModels.map(
      searchModel => searchModel.get('layerModel').get('displayName')
    );
    const visibleLayers = this.collection.filter(model => model.get('layerModel').get('display.visible'));
    if (visibleLayers.length) {
      if (names.length) {
        this.$('.selected-layer-names').html(nLayersSelectedTemplate({ count: names.length }));
      } else {
        this.$('.selected-layer-names').html(noLayerSelectedTemplate({}));
      }
      this.$('.dropdown button').prop('disabled', false);
    } else {
      this.$('.selected-layer-names').html(noLayersAvailableTemplate({}));
      this.$('.dropdown button').prop('disabled', true);
    }

    // adjust events
    const previousSearchModels = this.selectedSearchModels;
    previousSearchModels.forEach((previousSearchModel) => {
      if (!searchModels.indexOf(previousSearchModel) !== -1) {
        this.stopListening(previousSearchModel.get('results'));
      }
    });
    searchModels.forEach((searchModel) => {
      if (!previousSearchModels.indexOf(searchModel) !== -1) {
        this.listenTo(searchModel.get('results'), 'reset add', this.onResultsChange);
      }
    });

    this.selectedSearchModels = searchModels;
    this.onResultsChange();
  },

  onResultsChange() {
    const collection = new Backbone.Collection(
      this.selectedSearchModels.reduce((sum, searchModel) => (
        sum.concat(searchModel.get('results').models)
      ), [])
    );
    this.showChildView('search-results', new SearchResultListView({
      searchModel: this.selectedSearchModel,
      referenceCollection: collection,
      highlightModel: this.highlightModel,
      availableSpace: this.$el.outerHeight(),
    }));
  },

  onLayerSelectionChange() {
    // get a list of all selected layers
    const ids = this.$('[data-layer]:checked')
      .map((i, elem) => elem.dataset.layer)
      .toArray();

    // filter selected
    const selectedSearchModels = this.collection.filter(searchModel => (
      ids.indexOf(searchModel.get('layerModel').get('id')) > -1
    ));
    this.setSelectedSearchModels(selectedSearchModels);
    this.onSearchModelsChange();
  },

  onSearchModelsChange(searchModel) {
    if (searchModel) {
      // update the layers status
      const layerModel = searchModel.get('layerModel');
      const $status = this.$(`[data-layer="${layerModel.get('id')}"]`)
        .parent()
        .find('.search-status');

      if (searchModel.get('isSearching')) {
        $status.html('');
      } else if (searchModel.get('hasError')) {
        $status.html('');
      } else {
        $status.html(`${searchModel.get('hasLoaded')}/${searchModel.get('totalResults')}`);
      }
    }

    // update the global status
    const $globalStatus = this.$('.global-search-status');
    const isSearching = this.collection.any(model => model.get('isSearching'));
    const hasError = this.collection.any(model => model.get('hasError'));

    if (hasError) {
      $globalStatus.html('');
    } else if (isSearching) {
      $globalStatus.html('');
    } else if (this.selectedSearchModels.length) {
      const sumTotalResults = this.selectedSearchModels.reduce(
        (current, model) => (current + model.get('totalResults')), 0
      );
      const sumHasLoaded = this.selectedSearchModels.reduce(
        (current, model) => (current + model.get('hasLoaded')), 0
      );

      if (!isNaN(sumTotalResults)) {
        $globalStatus.html(`${sumHasLoaded}/${sumTotalResults}`);
      } else {
        $globalStatus.html('');
      }
    } else {
      $globalStatus.html('');
    }

    // update the tab header
    if (hasError) {
      this.triggerMethod('update:status', '');
    } else if (isSearching) {
      this.triggerMethod('update:status', '');
    } else {
      this.triggerMethod('update:status', '');
    }
  },
});

export default SearchResultView;
<commit_msg>Using fixed width icon, to try to mitigate 'jitter' effect<commit_after>import Backbone from 'backbone';
import Marionette from 'backbone.marionette';
import SearchResultListView from './SearchResultListView';

import './SearchResultView.css';
import template from './SearchResultView.hbs';

import noLayerSelectedTemplate from './NoLayerSelected.hbs';
import noLayersAvailableTemplate from './NoLayersAvailable.hbs';
import nLayersSelectedTemplate from './NLayersSelected.hbs';

// eslint-disable-next-line max-len
const SearchResultView = Marionette.LayoutView.extend(/** @lends search/views/layers.SearchResultView# */{
  template,
  templateHelpers() {
    return {
      showDownloadOptions: this.collection.any(searchModel => (
        searchModel.get('layerModel').get('download.protocol') === 'EO-WCS'
      )),
      layers: this.collection.map(model => model.get('layerModel').toJSON()),
      selectedLayer: this.selectedLayer ? this.selectedLayer.toJSON() : null,
    };
  },
  className: 'search-result-view',

  childView: SearchResultListView,
  childViewContainer: '.result-contents',

  events: {
    'change input[data-layer]': 'onLayerSelectionChange',
  },
  regions: {
    'search-results': '.result-contents',
  },

  initialize(options) {
    this.filtersModel = options.filtersModel;
    this.highlightModel = options.highlightModel;
    this.onResultItemClicked = options.onResultItemClicked;
    this.onResultItemInfo = options.onResultItemInfo;

    this.selectedSearchModels = [];

    this.listenTo(this.collection, 'change', this.onSearchModelsChange);

    this.collection.each((searchModel) => {
      this.listenTo(searchModel.get('layerModel'), 'change:display.visible', (layerModel) => {
        let newSelectedSearchModels;
        const $checkbox = this.$(`[data-layer="${layerModel.get('id')}"]`);
        if (layerModel.get('display.visible')) {
          if (!this.selectedSearchModels.find(m => m === searchModel)) {
            newSelectedSearchModels = this.selectedSearchModels.concat([searchModel]);
          }
          $checkbox.parent().show();
          $checkbox.prop('checked', true);
        } else {
          newSelectedSearchModels = this.selectedSearchModels.filter(
            model => model !== searchModel
          );
          $checkbox.parent().hide();
        }
        this.setSelectedSearchModels(newSelectedSearchModels);
        this.onSearchModelsChange();
      });
    });
  },

  onAttach() {
    this.onLayerSelectionChange();
  },

  setSelectedSearchModels(searchModels) {
    const names = searchModels.map(
      searchModel => searchModel.get('layerModel').get('displayName')
    );
    const visibleLayers = this.collection.filter(model => model.get('layerModel').get('display.visible'));
    if (visibleLayers.length) {
      if (names.length) {
        this.$('.selected-layer-names').html(nLayersSelectedTemplate({ count: names.length }));
      } else {
        this.$('.selected-layer-names').html(noLayerSelectedTemplate({}));
      }
      this.$('.dropdown button').prop('disabled', false);
    } else {
      this.$('.selected-layer-names').html(noLayersAvailableTemplate({}));
      this.$('.dropdown button').prop('disabled', true);
    }

    // adjust events
    const previousSearchModels = this.selectedSearchModels;
    previousSearchModels.forEach((previousSearchModel) => {
      if (!searchModels.indexOf(previousSearchModel) !== -1) {
        this.stopListening(previousSearchModel.get('results'));
      }
    });
    searchModels.forEach((searchModel) => {
      if (!previousSearchModels.indexOf(searchModel) !== -1) {
        this.listenTo(searchModel.get('results'), 'reset add', this.onResultsChange);
      }
    });

    this.selectedSearchModels = searchModels;
    this.onResultsChange();
  },

  onResultsChange() {
    const collection = new Backbone.Collection(
      this.selectedSearchModels.reduce((sum, searchModel) => (
        sum.concat(searchModel.get('results').models)
      ), [])
    );
    this.showChildView('search-results', new SearchResultListView({
      searchModel: this.selectedSearchModel,
      referenceCollection: collection,
      highlightModel: this.highlightModel,
      availableSpace: this.$el.outerHeight(),
    }));
  },

  onLayerSelectionChange() {
    // get a list of all selected layers
    const ids = this.$('[data-layer]:checked')
      .map((i, elem) => elem.dataset.layer)
      .toArray();

    // filter selected
    const selectedSearchModels = this.collection.filter(searchModel => (
      ids.indexOf(searchModel.get('layerModel').get('id')) > -1
    ));
    this.setSelectedSearchModels(selectedSearchModels);
    this.onSearchModelsChange();
  },

  onSearchModelsChange(searchModel) {
    if (searchModel) {
      // update the layers status
      const layerModel = searchModel.get('layerModel');
      const $status = this.$(`[data-layer="${layerModel.get('id')}"]`)
        .parent()
        .find('.search-status');

      if (searchModel.get('isSearching')) {
        $status.html('');
      } else if (searchModel.get('hasError')) {
        $status.html('');
      } else {
        $status.html(`${searchModel.get('hasLoaded')}/${searchModel.get('totalResults')}`);
      }
    }

    // update the global status
    const $globalStatus = this.$('.global-search-status');
    const isSearching = this.collection.any(model => model.get('isSearching'));
    const hasError = this.collection.any(model => model.get('hasError'));

    if (hasError) {
      $globalStatus.html('');
    } else if (isSearching) {
      $globalStatus.html('');
    } else if (this.selectedSearchModels.length) {
      const sumTotalResults = this.selectedSearchModels.reduce(
        (current, model) => (current + model.get('totalResults')), 0
      );
      const sumHasLoaded = this.selectedSearchModels.reduce(
        (current, model) => (current + model.get('hasLoaded')), 0
      );

      if (!isNaN(sumTotalResults)) {
        $globalStatus.html(`${sumHasLoaded}/${sumTotalResults}`);
      } else {
        $globalStatus.html('');
      }
    } else {
      $globalStatus.html('');
    }

    // update the tab header
    if (hasError) {
      this.triggerMethod('update:status', '');
    } else if (isSearching) {
      this.triggerMethod('update:status', '');
    } else {
      this.triggerMethod('update:status', '');
    }
  },
});

export default SearchResultView;
<|endoftext|>


<commit_before>const { authenticate } = require('@feathersjs/authentication');
const globalHooks = require('../../../hooks');

const restrictToCurrentSchool = globalHooks.ifNotLocal(globalHooks.restrictToCurrentSchool);

function createInfoText(user, data) {
	return `
Ein neues Problem wurde gemeldet.
User: ${user}
Betreff: ${data.subject}
Schaue für weitere Details und zur Bearbeitung bitte in den Helpdesk-Bereich der ${data.cloud}.\n
Mit freundlichen Grüßen
Deine ${data.cloud}
	`;
}

function createFeedbackText(user, data) {
	const device = data.deviceUserAgent ? `${data.device} [auto-detection: ${data.deviceUserAgent}]` : data.device;
	let text = `
ReplyEmail: ${data.replyEmail}
User: ${user}
User-ID: ${data.userId}
Schule: ${data.schoolName}
Schule-ID: ${data.schoolId}
Instanz: ${data.cloud}
Browser: ${data.browserName}
Browser Version: ${data.browserVersion}
Betriebssystem: ${data.os}
Gerät: ${device}
    `;

	if (data.desire && data.desire !== '') {
		text = `
${text}
User schrieb folgendes:
Als ${data.role}
möchte ich ${data.desire},
um ${data.benefit}.
Akzeptanzkriterien: ${data.acceptanceCriteria}
        `;
	} else {
		text = `
${text}
User meldet folgendes:
Problem Kurzbeschreibung: ${data.subject}
Problembeschreibung: ${data.problemDescription}
        `;
		if (data.notes) {
			text = `
${text}
Anmerkungen: ${data.notes}
            `;
		}
	}
	return text;
}

const denyDbWriteOnType = (hook) => {
	if (hook.data.type === 'contactHPI') {
		hook.result = {}; // interrupts db interaction
	}
	return hook;
};

const feedback = () => (hook) => {
	const data = hook.data || {};
	if (data.type === 'contactAdmin') {
		globalHooks.sendEmail(hook, {
			subject: 'Ein Problem wurde gemeldet.',
			roles: ['helpdesk', 'administrator'],
			content: {
				text: createInfoText(
					(hook.params.account || {}).username || 'nouser', data,
				),
			},
			attachments: data.files,
		});
		// TODO: NOTIFICATION SERVICE
	} else {
		globalHooks.sendEmail(hook, {
			subject: data.title || data.subject || 'nosubject',
			emails: ['ticketsystem@schul-cloud.org'],
			replyEmail: data.replyEmail,
			content: {
				text: createFeedbackText(
					(hook.params.account || {}).username || 'nouser',
					data,
				),
			},
			attachments: data.files,
		});
	}
	return Promise.resolve(hook);
};

exports.before = {
	all: [authenticate('jwt')],
	find: [globalHooks.hasPermission('HELPDESK_VIEW')],
	get: [globalHooks.hasPermission('HELPDESK_VIEW')],
	create: [globalHooks.hasPermission('HELPDESK_CREATE'), restrictToCurrentSchool, denyDbWriteOnType],
	update: [globalHooks.hasPermission('HELPDESK_EDIT'), restrictToCurrentSchool],
	patch: [globalHooks.hasPermission('HELPDESK_EDIT'), globalHooks.permitGroupOperation, restrictToCurrentSchool],
	remove: [
		globalHooks.hasPermission('HELPDESK_CREATE'),
		globalHooks.permitGroupOperation,
		globalHooks.ifNotLocal(globalHooks.checkSchoolOwnership),
	],
};

exports.after = {
	all: [],
	find: [],
	get: [],
	create: [feedback()],
	update: [],
	patch: [],
	remove: [],
};
<commit_msg>add user login, user email and user role<commit_after>const { authenticate } = require('@feathersjs/authentication');
const globalHooks = require('../../../hooks');
const userModel = require('../../user/model');

const restrictToCurrentSchool = globalHooks.ifNotLocal(globalHooks.restrictToCurrentSchool);

function createInfoText(user, data) {
	return `
Ein neues Problem wurde gemeldet.
User: ${user}
Betreff: ${data.subject}
Schaue für weitere Details und zur Bearbeitung bitte in den Helpdesk-Bereich der ${data.cloud}.\n
Mit freundlichen Grüßen
Deine ${data.cloud}
	`;
}

async function generateSystemInformation(account) {
	const { userId, username } = account

	const userInformation = await userModel.userModel.findOne(userId)
		.select({ 'roles': '1', 'email': '1' })
		.populate('roles', 'name')
		.lean()

	const roles = userInformation.roles.length ? userInformation.roles.map(info => info.name) : 'NO ROLES'
	const email = userInformation.email || 'NO EMAIL'
	const systemInformation =
		` System Information:
	User login: ${username}
	User role(s): ${roles}
	User registrated email: ${email}\n`
	return systemInformation
}

function createFeedbackText(user, data) {
	const device = data.deviceUserAgent ? `${data.device} [auto-detection: ${data.deviceUserAgent}]` : data.device;
	let text = `
SystemInformation: ${data.systemInformation}
ReplyEmail: ${data.replyEmail}
User: ${user}
User-ID: ${data.userId}
Schule: ${data.schoolName}
Schule-ID: ${data.schoolId}
Instanz: ${data.cloud}
Browser: ${data.browserName}
Browser Version: ${data.browserVersion}
Betriebssystem: ${data.os}
Gerät: ${device}
`;

	if (data.desire && data.desire !== '') {
		text = `
${text}
User schrieb folgendes:
Als ${data.role}
möchte ich ${data.desire},
um ${data.benefit}.
Akzeptanzkriterien: ${data.acceptanceCriteria}
`;
	} else {
		text = `
${text}
User meldet folgendes:
Problem Kurzbeschreibung: ${data.subject}
Problembeschreibung: ${data.problemDescription}
`;
		if (data.notes) {
			text = `
${text}
Anmerkungen: ${data.notes}
`;
		}
	}
	return text;
}

const denyDbWriteOnType = (hook) => {
	if (hook.data.type === 'contactHPI') {
		hook.result = {}; // interrupts db interaction
	}
	return hook;
};

const feedback = () => async (hook) => {
	const data = hook.data || {};
	if (data.type === 'contactAdmin') {
		globalHooks.sendEmail(hook, {
			subject: 'Ein Problem wurde gemeldet.',
			roles: ['helpdesk', 'administrator'],
			content: {
				text: createInfoText(
					(hook.params.account || {}).username || 'nouser', data,
				),
			},
			attachments: data.files,
		});
		// TODO: NOTIFICATION SERVICE
	} else {
		data.systemInformation = await generateSystemInformation(hook.params.account)
		globalHooks.sendEmail(hook, {
			subject: data.title || data.subject || 'nosubject',
			emails: ['ticketsystem@schul-cloud.org'],
			replyEmail: data.replyEmail,
			content: {
				text: createFeedbackText(
					(hook.params.account || {}).username || 'nouser',
					data,
				),
			},
			attachments: data.files,
		});
	}
	return Promise.resolve(hook);
};

exports.before = {
	all: [authenticate('jwt')],
	find: [globalHooks.hasPermission('HELPDESK_VIEW')],
	get: [globalHooks.hasPermission('HELPDESK_VIEW')],
	create: [globalHooks.hasPermission('HELPDESK_CREATE'), restrictToCurrentSchool, denyDbWriteOnType],
	update: [globalHooks.hasPermission('HELPDESK_EDIT'), restrictToCurrentSchool],
	patch: [globalHooks.hasPermission('HELPDESK_EDIT'), globalHooks.permitGroupOperation, restrictToCurrentSchool],
	remove: [
		globalHooks.hasPermission('HELPDESK_CREATE'),
		globalHooks.permitGroupOperation,
		globalHooks.ifNotLocal(globalHooks.checkSchoolOwnership),
	],
};

exports.after = {
	all: [],
	find: [],
	get: [],
	create: [feedback()],
	update: [],
	patch: [],
	remove: [],
};
<|endoftext|>


<commit_before>var DocumentBrowser = Backbone.View.extend({
  events: {
    'click a.add-criterion': 'addCriterion',
    'click a.remove-criterion': 'removeCriterion'
  },

  addCriterion: function(e) {
    var property = $(e.currentTarget).attr('property'),
        operator = $(e.currentTarget).attr('operator'),
        value = $(e.currentTarget).attr('value');

    this.applyCommand({command: 'add_criterion', options: {
      property: property,
      operator: operator,
      value: value
    }});
    this.render();
    return false;
  },

  removeCriterion: function(e) {
    var property = $(e.currentTarget).attr('property'),
        operator = $(e.currentTarget).attr('operator'),
        value = $(e.currentTarget).attr('value');

    this.applyCommand({command: 'remove_criterion', options: {
      property: property,
      operator: operator,
      value: value
    }});
    this.render();
    return false;
  },

  initialize: function(options) {
    var that = this;

    that.commands = [];
    this.load();
  },

  load: function() {
    var that = this;
    graph.fetch(this.options.query, {expand: false}, function(err, g) {
      if (err) alert('An error occured during fetching the documents');

      // Initialize Facets View
      that.facets = new Facets({el: '#facets', browser: that});
      that.render();
    });
  },

  render: function() {
    // TODO: use this.options.query here
    var documents = graph.find({'type': '/type/document'}).toArray();

    _.each(documents, function(doc) {
      doc.username = doc.value.data.creator.split('/')[2]
      doc.last_modified = _.prettyDate(new Date(doc.value.get('updated_at')).toJSON())
      doc.status = doc.value.get('published_on') ? 'published' : 'draft';
    });

    var DESC_BY_UPDATED_AT = function(item1, item2) {
      var v1 = item1.value.get('updated_at'),
          v2 = item2.value.get('updated_at');
      return v1 === v2 ? 0 : (v1 > v2 ? -1 : 1);
    };

    documents = documents.sort(DESC_BY_UPDATED_AT);

    $(this.el).html(_.renderTemplate('document_browser', {
      num_documents: documents.length,
      documents: documents
    }));

    this.facets.render();
  },

  // Takes a command spec and applies the command
  applyCommand: function(spec) {
    var cmd;

    if (spec.command === 'add_criterion') {
      cmd = new AddCriterion(this, spec.options);
    } else if (spec.command === 'remove_criterion') {
      cmd = new RemoveCriterion(this, spec.options);
    }

    // remove follow-up commands (redo-able commands)
    if (this.currentCommand < this.commands.length-1) {
      this.commands.splice(this.currentCommand+1);
    }

    // insertion position
    var pos = undefined;
    $.each(this.commands, function(index, c) {
      if (c.matchesInverse(cmd)) {
        pos = index;
      }
    });

    if (pos >= 0) {
      // restore state
      this.commands[pos].unexecute();
      // remove matched inverse command
      this.commands.splice(pos, 1);
      // execute all follow-up commands [pos..this.commands.length-1]
      for (var i=pos; i < this.commands.length; i++) {
        this.commands[i].execute();
      }
    } else {
      this.commands.push(cmd);
      cmd.execute();
    }

    this.currentCommand = this.commands.length-1;
    return cmd;
  },

  undo: function() {
    if (this.currentCommand >= 0) {
      this.commands[this.currentCommand].unexecute();
      this.currentCommand -= 1;
      this.render();
    }
  },

  redo: function() {
    if (this.currentCommand < this.commands.length-1) {
      this.currentCommand += 1;
      this.commands[this.currentCommand].execute();
      this.render();
    }
  }
});
<commit_msg>Due to the latest feature of Data.js (Prototypal Inheritance) the query for browsing documents needed to be updated.<commit_after>var DocumentBrowser = Backbone.View.extend({
  events: {
    'click a.add-criterion': 'addCriterion',
    'click a.remove-criterion': 'removeCriterion'
  },

  addCriterion: function(e) {
    var property = $(e.currentTarget).attr('property'),
        operator = $(e.currentTarget).attr('operator'),
        value = $(e.currentTarget).attr('value');

    this.applyCommand({command: 'add_criterion', options: {
      property: property,
      operator: operator,
      value: value
    }});
    this.render();
    return false;
  },

  removeCriterion: function(e) {
    var property = $(e.currentTarget).attr('property'),
        operator = $(e.currentTarget).attr('operator'),
        value = $(e.currentTarget).attr('value');

    this.applyCommand({command: 'remove_criterion', options: {
      property: property,
      operator: operator,
      value: value
    }});
    this.render();
    return false;
  },

  initialize: function(options) {
    var that = this;

    that.commands = [];
    this.load();
  },

  load: function() {
    var that = this;
    graph.fetch({"type|=": ["/type/document"]}, {expand: false}, function(err, g) {
      if (err) alert('An error occured during fetching the documents');

      // Initialize Facets View
      that.facets = new Facets({el: '#facets', browser: that});
      that.render();
    });
  },

  render: function() {
    // TODO: use this.options.query here
    var documents = graph.find({'type|=': '/type/document'}).toArray();

    _.each(documents, function(doc) {
      doc.username = doc.value.data.creator.split('/')[2]
      doc.last_modified = _.prettyDate(new Date(doc.value.get('updated_at')).toJSON())
      doc.status = doc.value.get('published_on') ? 'published' : 'draft';
      doc.document_type = doc.value.type.key.split('/')[2];
    });

    var DESC_BY_UPDATED_AT = function(item1, item2) {
      var v1 = item1.value.get('updated_at'),
          v2 = item2.value.get('updated_at');
      return v1 === v2 ? 0 : (v1 > v2 ? -1 : 1);
    };

    documents = documents.sort(DESC_BY_UPDATED_AT);

    $(this.el).html(_.renderTemplate('document_browser', {
      num_documents: documents.length,
      documents: documents
    }));

    this.facets.render();
  },

  // Takes a command spec and applies the command
  applyCommand: function(spec) {
    var cmd;

    if (spec.command === 'add_criterion') {
      cmd = new AddCriterion(this, spec.options);
    } else if (spec.command === 'remove_criterion') {
      cmd = new RemoveCriterion(this, spec.options);
    }

    // remove follow-up commands (redo-able commands)
    if (this.currentCommand < this.commands.length-1) {
      this.commands.splice(this.currentCommand+1);
    }

    // insertion position
    var pos = undefined;
    $.each(this.commands, function(index, c) {
      if (c.matchesInverse(cmd)) {
        pos = index;
      }
    });

    if (pos >= 0) {
      // restore state
      this.commands[pos].unexecute();
      // remove matched inverse command
      this.commands.splice(pos, 1);
      // execute all follow-up commands [pos..this.commands.length-1]
      for (var i=pos; i < this.commands.length; i++) {
        this.commands[i].execute();
      }
    } else {
      this.commands.push(cmd);
      cmd.execute();
    }

    this.currentCommand = this.commands.length-1;
    return cmd;
  },

  undo: function() {
    if (this.currentCommand >= 0) {
      this.commands[this.currentCommand].unexecute();
      this.currentCommand -= 1;
      this.render();
    }
  },

  redo: function() {
    if (this.currentCommand < this.commands.length-1) {
      this.currentCommand += 1;
      this.commands[this.currentCommand].execute();
      this.render();
    }
  }
});
<|endoftext|>


<commit_before>/**
 * FoodCoopShop - The open source software for your foodcoop
 *
 * Licensed under the GNU Affero General Public License version 3
 * For full copyright and license information, please see LICENSE
 * Redistributions of files must retain the above copyright notice.
 *
 * @since         FoodCoopShop 3.1.0
 * @license       https://opensource.org/licenses/AGPL-3.0
 * @author        Mario Rothauer <office@foodcoopshop.com>
 * @copyright     Copyright (c) Mario Rothauer, https://www.rothauer-it.com
 * @link          https://www.foodcoopshop.com
 */
foodcoopshop.ModalProductStatusEditBulk = {

    init : function() {

        var modalSelector = '#modal-product-status-edit-bulk';

        var button = $('#editStatusForSelectedProducts');
        foodcoopshop.Helper.disableButton(button);

        $('table.list').find('input.row-marker[type="checkbox"]').on('click', function () {
            foodcoopshop.Admin.updateObjectSelectionActionButton(button);
        });

        button.on('click', function () {

            var productIds = foodcoopshop.Admin.getSelectedProductIds();

            var html = '';
            var infoText = '';

            if (productIds.length == 1) {
                infoText = foodcoopshop.LocalizedJs.admin.YouSelectedOneProduct;
            } else {
                infoText = foodcoopshop.LocalizedJs.admin.YouSelected0Products.replace(/\{0\}/, '' + productIds.length + '');
            }
            infoText += '
';

            var products = [];
            for (var i in productIds) {
                products.push($('tr#product-' + productIds[i] + ' span.product-name').html());
            }
            html += '

' + products.join('

') + '

';

            var buttons = [
                foodcoopshop.Modal.createButton(['btn-success'], foodcoopshop.LocalizedJs.admin.Activate, 'fa-fw fas fa-check'),
                foodcoopshop.Modal.createButton(['btn-danger'], foodcoopshop.LocalizedJs.admin.Deactivate, 'fa-fw fas fa-minus-circle'),
                foodcoopshop.Modal.createButton(['btn-outline-light'], foodcoopshop.LocalizedJs.helper.cancel, null, true)
            ];

            foodcoopshop.Modal.appendModalToDom(
                modalSelector,
                foodcoopshop.LocalizedJs.admin.ChangeStatus,
                html,
                buttons,
            );

            foodcoopshop.Modal.bindSuccessButton(modalSelector, function() {
                foodcoopshop.ModalProductStatusEditBulk.getSuccessHandler(modalSelector, 1);
            });

            $(modalSelector + ' .modal-footer .btn-danger').on('click', function() {
                foodcoopshop.Helper.addSpinnerToButton($(this), 'fa-minus-circle');
                foodcoopshop.Helper.disableButton($(this));
                foodcoopshop.ModalProductStatusEditBulk.getSuccessHandler(modalSelector, 0);
            });

            $(modalSelector).on('hidden.bs.modal', function (e) {
                foodcoopshop.ModalProductStatusEditBulk.getCloseHandler(modalSelector);
            });
            foodcoopshop.ModalProductStatusEditBulk.getOpenHandler(modalSelector);

        });

    },

    getCloseHandler : function(modalSelector) {
        $(modalSelector).remove();
    },

    getSuccessHandler : function(modalSelector, status) {

        var productIds = foodcoopshop.Admin.getSelectedProductIds();

        foodcoopshop.Helper.ajaxCall(
            '/admin/products/changeStatusBulk/',
            {
                productIds: productIds,
                status: status,
            },
            {
                onOk: function (data) {
                    document.location.reload();
                },
                onError: function (data) {
                    var message = '
';
                    message += foodcoopshop.LocalizedJs.admin.ErrorsOccurredWhileProductStatusWasChanged;
                    message += ':
';
                    message = message + data.msg;
                    foodcoopshop.Modal.appendFlashMessage(modalSelector, message);
                    foodcoopshop.Modal.resetButtons(modalSelector);
                }
            }
        );
    },

    getOpenHandler : function(modalSelector) {
        new bootstrap.Modal(document.getElementById(modalSelector.replace(/#/, ''))).show();
    }

};<commit_msg>fix error<commit_after>/**
 * FoodCoopShop - The open source software for your foodcoop
 *
 * Licensed under the GNU Affero General Public License version 3
 * For full copyright and license information, please see LICENSE
 * Redistributions of files must retain the above copyright notice.
 *
 * @since         FoodCoopShop 3.1.0
 * @license       https://opensource.org/licenses/AGPL-3.0
 * @author        Mario Rothauer <office@foodcoopshop.com>
 * @copyright     Copyright (c) Mario Rothauer, https://www.rothauer-it.com
 * @link          https://www.foodcoopshop.com
 */
foodcoopshop.ModalProductStatusEditBulk = {

    init : function() {

        var modalSelector = '#modal-product-status-edit-bulk';

        var button = $('#editStatusForSelectedProducts');
        foodcoopshop.Helper.disableButton(button);

        $('table.list').find('input.row-marker[type="checkbox"]').on('click', function () {
            foodcoopshop.Admin.updateObjectSelectionActionButton(button);
        });

        button.on('click', function () {

            var productIds = foodcoopshop.Admin.getSelectedProductIds();

            var html = '';
            var infoText = '';

            if (productIds.length == 1) {
                infoText = foodcoopshop.LocalizedJs.admin.YouSelectedOneProduct;
            } else {
                infoText = foodcoopshop.LocalizedJs.admin.YouSelected0Products.replace(/\{0\}/, '' + productIds.length + '');
            }
            infoText += '
';

            var products = [];
            for (var i in productIds) {
                products.push($('tr#product-' + productIds[i] + ' span.product-name').html());
            }
            html += '

' + products.join('

') + '

';

            var buttons = [
                foodcoopshop.Modal.createButton(['btn-success'], foodcoopshop.LocalizedJs.admin.Activate, 'fa-fw fas fa-check'),
                foodcoopshop.Modal.createButton(['btn-danger'], foodcoopshop.LocalizedJs.admin.Deactivate, 'fa-fw fas fa-minus-circle'),
                foodcoopshop.Modal.createButton(['btn-outline-light'], foodcoopshop.LocalizedJs.helper.cancel, null, true)
            ];

            foodcoopshop.Modal.appendModalToDom(
                modalSelector,
                foodcoopshop.LocalizedJs.admin.ChangeStatus,
                html,
                buttons
            );

            foodcoopshop.Modal.bindSuccessButton(modalSelector, function() {
                foodcoopshop.ModalProductStatusEditBulk.getSuccessHandler(modalSelector, 1);
            });

            $(modalSelector + ' .modal-footer .btn-danger').on('click', function() {
                foodcoopshop.Helper.addSpinnerToButton($(this), 'fa-minus-circle');
                foodcoopshop.Helper.disableButton($(this));
                foodcoopshop.ModalProductStatusEditBulk.getSuccessHandler(modalSelector, 0);
            });

            $(modalSelector).on('hidden.bs.modal', function (e) {
                foodcoopshop.ModalProductStatusEditBulk.getCloseHandler(modalSelector);
            });
            foodcoopshop.ModalProductStatusEditBulk.getOpenHandler(modalSelector);

        });

    },

    getCloseHandler : function(modalSelector) {
        $(modalSelector).remove();
    },

    getSuccessHandler : function(modalSelector, status) {

        var productIds = foodcoopshop.Admin.getSelectedProductIds();

        foodcoopshop.Helper.ajaxCall(
            '/admin/products/changeStatusBulk/',
            {
                productIds: productIds,
                status: status,
            },
            {
                onOk: function (data) {
                    document.location.reload();
                },
                onError: function (data) {
                    var message = '
';
                    message += foodcoopshop.LocalizedJs.admin.ErrorsOccurredWhileProductStatusWasChanged;
                    message += ':
';
                    message = message + data.msg;
                    foodcoopshop.Modal.appendFlashMessage(modalSelector, message);
                    foodcoopshop.Modal.resetButtons(modalSelector);
                }
            }
        );
    },

    getOpenHandler : function(modalSelector) {
        new bootstrap.Modal(document.getElementById(modalSelector.replace(/#/, ''))).show();
    }

};<|endoftext|>


<commit_before>angular.module('profilePage').component('profilePage', {

  templateUrl: 'profile-page/profile-page.template.html',
  controller: function profileController($http) {
    var vm = this;
    vm.loadingInfo = true;
    vm.loadingReviews = true;

    $http.get('user-info').then(function(response) {
      vm.userInfo = response.data;
      vm.loadingInfo = false;
    });

    $http.get('user-review').then(function(response) {
      vm.userReviews = response.data;
      vm.loadingReviews = false;
    })

    vm.isLoggedIn = function() {
      if (!(vm.userInfo === 'Logged Out')) {
        return true;
      } else {
        return false;
      }
    };
  },
  controllerAs: 'profileCtrl'
});
<commit_msg>debug getting book titles<commit_after>angular.module('profilePage').component('profilePage', {

  templateUrl: 'profile-page/profile-page.template.html',
  controller: function profileController($http) {
    var vm = this;
    vm.loadingInfo = true;
    vm.loadingReviews = true;

    $http.get('user-info').then(function(response) {
      vm.userInfo = response.data;
      vm.loadingInfo = false;
    });

    $http.get('user-review').then(function(response) {
      vm.userReviews = response.data;
      vm.loadingReviews = false;

    });

    function getBooks() {
        correspondingBooks = [];

        vm.userReviews.forEach(review =>
            $http.get('book', {params: {'isbn': review.isbn}})
            .then(function(response) {
                console.log(response.data);
                correspondingBooks.push(response.data);
            })
        )
        return correspondingBooks;
    };

    vm.correspondingBooks = getBooks();

    vm.isLoggedIn = function() {
      if (!(vm.userInfo === 'Logged Out')) {
        return true;
      } else {
        return false;
      }
    };
  },
  controllerAs: 'profileCtrl'
});
<|endoftext|>


<commit_before>
var eventSignal = new Signal();
/**
 * 精灵事件接口，用来绑定回调事件
 * @param {[type]} displayObject [description]
 */
function Events(displayObject){
    this.obj = displayObject;

    //pointer
    this.onPointerDown = function(cbk){
        eventSignal.on('pointerdown',cbk,this.obj);
    }
    this.onPointerTap = function(cbk){
        eventSignal.on('pointertap',cbk,this.obj);
    }
    this.onPointerDblTap = function(cbk){
        eventSignal.on('pointerdbltap',cbk,this.obj);
    }
    this.onPointerUp = function(cbk){
        eventSignal.on('pointerup',cbk,this.obj);
    }
    this.onPointerMove = function(cbk){
        eventSignal.on('pointermove',cbk,this.obj);
    }
    this.onPointerOver = function(cbk){
        eventSignal.on('pointerover',cbk,this.obj);
    }
    this.onPointerOut = function(cbk){
        eventSignal.on('pointerout',cbk,this.obj);
    }
    this.onPointerCancel = function(cbk){
        eventSignal.on('pointercancel',cbk,this.obj);
    }

    //keyboard
    this.onKeyDown = function(cbk){
        eventSignal.on('keydown',cbk,this.obj);
    }
    this.onKeyPress = function(cbk){
        eventSignal.on('keypress',cbk,this.obj);
    }
    this.onKeyUp = function(cbk){
        eventSignal.on('keyup',cbk,this.obj);
    }

    //device
    this.onDeviceHov = function(cbk){
        eventSignal.on('hov',cbk,this.obj);
    }
    this.onDeviceTilt = function(cbk){
        eventSignal.on('tilt',cbk,this.obj);
    }
    this.onDeviceMotion = function(cbk){
        eventSignal.on('motion',cbk,this.obj);
    }

    //physics
    this.onCollisionStart = function(cbk){
        eventSignal.on('collisionstart',cbk,this.obj);
    }
    this.onCollisionEnd = function(cbk){
        eventSignal.on('collisionend',cbk,this.obj);
    }
}<commit_msg>v 2.1<commit_after>
var eventSignal = new Signal();
/**
 * 精灵事件接口，用来绑定回调事件
 * @param {[type]} displayObject [description]
 */
function Events(displayObject){
    this.obj = displayObject;

    //pointer
    this.onPointerDown = function(cbk){
        eventSignal.on('pointerdown',cbk,this.obj);
    }
    this.onPointerTap = function(cbk){
        eventSignal.on('pointertap',cbk,this.obj);
    }
    this.onPointerDblTap = function(cbk){
        eventSignal.on('pointerdbltap',cbk,this.obj);
    }
    this.onPointerUp = function(cbk){
        eventSignal.on('pointerup',cbk,this.obj);
    }
    this.onPointerMove = function(cbk){
        eventSignal.on('pointermove',cbk,this.obj);
    }
    this.onPointerOver = function(cbk){
        eventSignal.on('pointerover',cbk,this.obj);
    }
    this.onPointerOut = function(cbk){
        eventSignal.on('pointerout',cbk,this.obj);
    }
    this.onPointerCancel = function(cbk){
        eventSignal.on('pointercancel',cbk,this.obj);
    }
    this.onEnterStage = function(cbk){
        eventSignal.on('enterstage',cbk,this.obj);
    }
    this.onLeaveStage = function(cbk){
        eventSignal.on('leavestage',cbk,this.obj);
    }

    //keyboard
    this.onKeyDown = function(cbk){
        eventSignal.on('keydown',cbk,this.obj);
    }
    this.onKeyPress = function(cbk){
        eventSignal.on('keypress',cbk,this.obj);
    }
    this.onKeyUp = function(cbk){
        eventSignal.on('keyup',cbk,this.obj);
    }

    //device
    this.onDeviceHov = function(cbk){
        eventSignal.on('hov',cbk,this.obj);
    }
    this.onDeviceTilt = function(cbk){
        eventSignal.on('tilt',cbk,this.obj);
    }
    this.onDeviceMotion = function(cbk){
        eventSignal.on('motion',cbk,this.obj);
    }

    //physics
    this.onCollisionStart = function(cbk){
        eventSignal.on('collisionstart',cbk,this.obj);
    }
    this.onCollisionEnd = function(cbk){
        eventSignal.on('collisionend',cbk,this.obj);
    }
}<|endoftext|>


<commit_before>jQuery(document).ready(function() {
    // Bootstrap date picker fix with jQuery UI component
    jQuery.fn.bootstrapDP = jQuery.fn.datepicker.noConflict();

    /**
     * jQuery function to serialize forms to JSON objects. Usage example:
     *
     * console.log(jQuery('#yourForm').serializeJSON());
     *
     * @returns {{}}
     */
    jQuery.fn.serializeJSON = function() {
        var json = {};

        jQuery.map(jQuery(this).serializeArray(), function(n) {
            var _ = n.name.indexOf('[');

            if (_ > -1) {
                var o = json, _name;

                _name = n.name.replace(/\]/gi, '').split('[');
                for (var i = 0, len = _name.length; i < len; i++) {
                    if (i == len - 1) {
                        if (o[_name[i]]) {
                            if (typeof o[_name[i]] == 'string') {
                                o[_name[i]] = [o[_name[i]]];
                            }
                            o[_name[i]].push(n.value);
                        } else {
                            o[_name[i]] = n.value || '';
                        }
                    } else {
                        o = o[_name[i]] = o[_name[i]] || {};
                    }
                }
            } else {
                if (json[n.name] !== undefined) {
                    if (!json[n.name].push) {
                        json[n.name] = [json[n.name]];
                    }

                    json[n.name].push(n.value || '');
                } else {
                    json[n.name] = n.value || '';
                }
            }
        });

        return json;
    };

    /**
     * Generic html element type getter.
     *
     * @returns {string}
     */
    jQuery.fn.getType = function() {
        return this[0].tagName == "INPUT" ? jQuery(this[0]).attr("type").toLowerCase() : this[0].tagName.toLowerCase();
    };

    // Global tooltip event
    initTooltips(jQuery(document));
});

/**
 * Function initializes default "static" tooltips for specified context.
 *
 * @param   {jQuery}    context
 */
function initTooltips(context) {
    context.on('mouseover', '.tooltipDiv', function() {
        createQtipDiv(jQuery(this));
    });

    context.on('mouseover', '.tooltipTitle', function() {
        createQtipTitle(jQuery(this));
    });
}

/**
 * Generic AJAX error handler.
 *
 * @param   {XMLHttpRequest}    jqXhr
 * @param   {String}            textStatus
 * @param   {String}            error
 */
function handleAjaxError(jqXhr, textStatus, error) {
    var message = '';
    var errorMessage = '';

    try {
        var errorInfo = jQuery.parseJSON(jqXhr.responseText);

        if (errorInfo.errors[0]) {
            errorMessage = errorInfo.errors[0].message;
        }
    } catch (exception) {
        errorMessage = jqXhr.responseText;
    }

    if (jqXhr.status === 0) {
        message = 'Not connect. Verify Network. ' + errorMessage;
    } else if (jqXhr.status == 403) {
        message = 'Forbidden [403]. ' + errorMessage;
    } else if (jqXhr.status == 404) {
        message = 'Requested page not found [404]. ' + errorMessage;
    } else if (jqXhr.status == 500) {
        message = 'Internal Server Error [500]. ' + errorMessage;
    } else if (textStatus === 'parsererror') {
        message = 'Requested JSON parse failed.';
    } else if (textStatus === 'timeout') {
        message = 'Time out error.';
    } else if (textStatus === 'abort') {
        message = 'Ajax request aborted.';
    } else {
        message = 'Uncaught Error.\n' + jqXhr.responseText + textStatus + ', ' + error;
    }

    makeMessage(message, 'error', {});
}

/**
 * Function to trigger noty message.
 *
 * @param   {string}    text    Message to show
 * @param   {string}    type    Type of message
 * @param   {object}    options Custom options for noty
 */
function makeMessage(text, type, options) {
    options = options || {};

    var timeout = 3000;

    switch (type) {
        case 'success':
            timeout = 1500;
    }

    noty(jQuery.extend({}, {
        text: text.nl2br(),
        type: type,
        layout: 'top',
        timeout: timeout
    }, options));
}

/**
 * Generic javascript "job" dispatcher.
 *
 * @returns {*}
 */
function dispatch(fn, args) {
    // Allow fn to be a function object or the name of a global function
    fn = (typeof fn == "function") ? fn : window[fn];

    // args is optional, use an empty array by default
    return fn.apply(this, args || []);
}

/**
 * Function creates a Bootbox modal dialog with specified title, content and buttons.
 *
 * @param   {string}                    title   Modal title
 * @param   {string}                    content Modal content as html
 * @param   {object|array|null}         buttons Button(s) to add dialog
 * @param   {undefined|string|boolean}  trigger Possible body trigger event on close
 *
 * @returns {*}
 */
function createBootboxDialog(title, content, buttons, trigger) {
    trigger = trigger || false;

    var buttonsToShow = [];

    // Every dialog has close button.
    buttonsToShow.push({
        label: "Close",
        class: "btn btn-default pull-left",
        callback: function() {
            if (trigger) {
                jQuery('body').trigger(trigger);
            }
        }
    });

    // We have multiple buttons
    if (buttons instanceof Array) {
        jQuery.each(buttons, function(index, button) {
            buttonsToShow.push(button);
        });
    } else if (buttons !== null) { // Just one button to add
        buttonsToShow.push(buttons);
    }

    // Return bootbox dialog
    return bootbox.dialog(
        content,
        buttonsToShow,
        {
            header: title,
            animate: false,
            show: false
        }
    );
}

/**
 * Function to set specified cookie and value for given days.
 *
 * @param   {string}        name    Name of the cookie
 * @param   {string|number} value   Cookie value
 * @param   {number}        days    How many days cookie is valid
 */
function createCookie(name, value, days) {
    var expires = "";

    if (days) {
        var date = new Date();
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));

        expires = "; expires=" + date.toGMTString();
    }

    document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/";
}

/**
 * Function to read specified cookie.
 *
 * @param   {string}    name     Name of the cookie
 *
 * @returns {*}
 */
function readCookie(name) {
    var nameEQ = encodeURIComponent(name) + "=";
    var ca = document.cookie.split(';');

    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];

        while (c.charAt(0) == ' ') {
            c = c.substring(1, c.length);
        }

        if (c.indexOf(nameEQ) == 0) {
            return decodeURIComponent(c.substring(nameEQ.length, c.length));
        }
    }

    return null;
}

/**
 * Function to erase specified cookie.
 *
 * @param   {string}    name     Name of the cookie
 */
function eraseCookie(name) {
    createCookie(name, "", -1);
}

/**
 * Function makes qTip2 tooltip for specified element. Note that this tooltip can contain
 * desired HTML structure which is shown as tooltip.
 *
 * Below is example HTML structure of usage:
 *
 *  <icon class="icon icon-help tooltipDiv">
 *

 *
Tooltip title

 *

 *              Your tooltip content here
 *

 *

 *  </icon>
 *
 *  And in your CSS definitions add following:
 *
 *  .tooltipDivContainer {
 *      display: none;
 *  }
 *
 * @param   {jQuery}    element
 */
function createQtipDiv(element) {
    var title = element.find('.tooltipDivContainer h1').html();
    var content = element.find('.tooltipDivContainer div');

    createQtip(element, title, content, 'auto', 'top left', 'bottom center', true, 100);
}

/**
 * Function makes simple qTip2 tooltip for specified element.
 *
 * Below is example HTML structure of usage:
 *
 *  click me
 *
 * @param   {jQuery}    element
 */
function createQtipTitle(element) {
    var title = '';
    var content = element.attr('title');

    createQtip(element, title, content, 'auto', 'top left', 'bottom center', false, 50);
}

/**
 * Function creates actual qTip2 for specified element.
 *
 * @param   {jQuery}        element
 * @param   {string}        tipTitle
 * @param   {string}        tipText
 * @param   {string|number} tipWidth
 * @param   {string}        tipMy
 * @param   {string}        tipAt
 * @param   {boolean}       tipFixed
 * @param   {number}        tipDelay
 */
function createQtip(element, tipTitle, tipText, tipWidth, tipMy, tipAt, tipFixed, tipDelay) {
    element.qtip({
        metadata: {
            type: 'html5',      // Use HTML5 data-* attributes
            name: 'qtipopts'    // Grab the metadata from the data-qtipOpts HTML5 attribute
        },
        content: {
            title: tipTitle,
            text: tipText
        },
        style: {
            tip: {
                corner: true
            },
            classes: 'qtip-bootstrap',
            width: tipWidth
        },
        show: {
            ready: true
        },
        hide: {
            fixed: tipFixed,
            delay: tipDelay,
            effect: false
        },
        position: {
            my: tipMy,
            at: tipAt,
            viewport: jQuery(window)
        }
    });
}<commit_msg>Added tooltip init on modal shown event.<commit_after>jQuery(document).ready(function() {
    // Bootstrap date picker fix with jQuery UI component
    jQuery.fn.bootstrapDP = jQuery.fn.datepicker.noConflict();

    /**
     * jQuery function to serialize forms to JSON objects. Usage example:
     *
     * console.log(jQuery('#yourForm').serializeJSON());
     *
     * @returns {{}}
     */
    jQuery.fn.serializeJSON = function() {
        var json = {};

        jQuery.map(jQuery(this).serializeArray(), function(n) {
            var _ = n.name.indexOf('[');

            if (_ > -1) {
                var o = json, _name;

                _name = n.name.replace(/\]/gi, '').split('[');
                for (var i = 0, len = _name.length; i < len; i++) {
                    if (i == len - 1) {
                        if (o[_name[i]]) {
                            if (typeof o[_name[i]] == 'string') {
                                o[_name[i]] = [o[_name[i]]];
                            }
                            o[_name[i]].push(n.value);
                        } else {
                            o[_name[i]] = n.value || '';
                        }
                    } else {
                        o = o[_name[i]] = o[_name[i]] || {};
                    }
                }
            } else {
                if (json[n.name] !== undefined) {
                    if (!json[n.name].push) {
                        json[n.name] = [json[n.name]];
                    }

                    json[n.name].push(n.value || '');
                } else {
                    json[n.name] = n.value || '';
                }
            }
        });

        return json;
    };

    /**
     * Generic html element type getter.
     *
     * @returns {string}
     */
    jQuery.fn.getType = function() {
        return this[0].tagName == "INPUT" ? jQuery(this[0]).attr("type").toLowerCase() : this[0].tagName.toLowerCase();
    };

    // Global tooltip event
    initTooltips(jQuery(document));
});

/**
 * Function initializes default "static" tooltips for specified context.
 *
 * @param   {jQuery}    context
 */
function initTooltips(context) {
    context.on('mouseover', '.tooltipDiv', function() {
        createQtipDiv(jQuery(this));
    });

    context.on('mouseover', '.tooltipTitle', function() {
        createQtipTitle(jQuery(this));
    });
}

/**
 * Generic AJAX error handler.
 *
 * @param   {XMLHttpRequest}    jqXhr
 * @param   {String}            textStatus
 * @param   {String}            error
 */
function handleAjaxError(jqXhr, textStatus, error) {
    var message = '';
    var errorMessage = '';

    try {
        var errorInfo = jQuery.parseJSON(jqXhr.responseText);

        if (errorInfo.errors[0]) {
            errorMessage = errorInfo.errors[0].message;
        }
    } catch (exception) {
        errorMessage = jqXhr.responseText;
    }

    if (jqXhr.status === 0) {
        message = 'Not connect. Verify Network. ' + errorMessage;
    } else if (jqXhr.status == 403) {
        message = 'Forbidden [403]. ' + errorMessage;
    } else if (jqXhr.status == 404) {
        message = 'Requested page not found [404]. ' + errorMessage;
    } else if (jqXhr.status == 500) {
        message = 'Internal Server Error [500]. ' + errorMessage;
    } else if (textStatus === 'parsererror') {
        message = 'Requested JSON parse failed.';
    } else if (textStatus === 'timeout') {
        message = 'Time out error.';
    } else if (textStatus === 'abort') {
        message = 'Ajax request aborted.';
    } else {
        message = 'Uncaught Error.\n' + jqXhr.responseText + textStatus + ', ' + error;
    }

    makeMessage(message, 'error', {});
}

/**
 * Function to trigger noty message.
 *
 * @param   {string}    text    Message to show
 * @param   {string}    type    Type of message
 * @param   {object}    options Custom options for noty
 */
function makeMessage(text, type, options) {
    options = options || {};

    var timeout = 3000;

    switch (type) {
        case 'success':
            timeout = 1500;
    }

    noty(jQuery.extend({}, {
        text: text.nl2br(),
        type: type,
        layout: 'top',
        timeout: timeout
    }, options));
}

/**
 * Generic javascript "job" dispatcher.
 *
 * @returns {*}
 */
function dispatch(fn, args) {
    // Allow fn to be a function object or the name of a global function
    fn = (typeof fn == "function") ? fn : window[fn];

    // args is optional, use an empty array by default
    return fn.apply(this, args || []);
}

/**
 * Function creates a Bootbox modal dialog with specified title, content and buttons.
 *
 * @param   {string}                    title   Modal title
 * @param   {string}                    content Modal content as html
 * @param   {object|array|null}         buttons Button(s) to add dialog
 * @param   {undefined|string|boolean}  trigger Possible body trigger event on close
 *
 * @returns {*}
 */
function createBootboxDialog(title, content, buttons, trigger) {
    trigger = trigger || false;

    var buttonsToShow = [];

    // Every dialog has close button.
    buttonsToShow.push({
        label: "Close",
        class: "btn btn-default pull-left",
        callback: function() {
            if (trigger) {
                jQuery('body').trigger(trigger);
            }
        }
    });

    // We have multiple buttons
    if (buttons instanceof Array) {
        jQuery.each(buttons, function(index, button) {
            buttonsToShow.push(button);
        });
    } else if (buttons !== null) { // Just one button to add
        buttonsToShow.push(buttons);
    }

    // Create bootbox modal
    var modal = bootbox.dialog(
        content,
        buttonsToShow,
        {
            header: title,
            animate: false,
            show: false
        }
    );

    // Initialize possible modal context tooltips
    modal.on('shown.bs.modal', function() {
        initTooltips(modal);
    });

    // Return bootbox dialog
    return modal;
}

/**
 * Function to set specified cookie and value for given days.
 *
 * @param   {string}        name    Name of the cookie
 * @param   {string|number} value   Cookie value
 * @param   {number}        days    How many days cookie is valid
 */
function createCookie(name, value, days) {
    var expires = "";

    if (days) {
        var date = new Date();
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));

        expires = "; expires=" + date.toGMTString();
    }

    document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/";
}

/**
 * Function to read specified cookie.
 *
 * @param   {string}    name     Name of the cookie
 *
 * @returns {*}
 */
function readCookie(name) {
    var nameEQ = encodeURIComponent(name) + "=";
    var ca = document.cookie.split(';');

    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];

        while (c.charAt(0) == ' ') {
            c = c.substring(1, c.length);
        }

        if (c.indexOf(nameEQ) == 0) {
            return decodeURIComponent(c.substring(nameEQ.length, c.length));
        }
    }

    return null;
}

/**
 * Function to erase specified cookie.
 *
 * @param   {string}    name     Name of the cookie
 */
function eraseCookie(name) {
    createCookie(name, "", -1);
}

/**
 * Function makes qTip2 tooltip for specified element. Note that this tooltip can contain
 * desired HTML structure which is shown as tooltip.
 *
 * Below is example HTML structure of usage:
 *
 *  <icon class="icon icon-help tooltipDiv">
 *

 *
Tooltip title

 *

 *              Your tooltip content here
 *

 *

 *  </icon>
 *
 *  And in your CSS definitions add following:
 *
 *  .tooltipDivContainer {
 *      display: none;
 *  }
 *
 * @param   {jQuery}    element
 */
function createQtipDiv(element) {
    var title = element.find('.tooltipDivContainer h1').html();
    var content = element.find('.tooltipDivContainer div');

    createQtip(element, title, content, 'auto', 'top left', 'bottom center', true, 100);
}

/**
 * Function makes simple qTip2 tooltip for specified element.
 *
 * Below is example HTML structure of usage:
 *
 *  click me
 *
 * @param   {jQuery}    element
 */
function createQtipTitle(element) {
    var title = '';
    var content = element.attr('title');

    createQtip(element, title, content, 'auto', 'top left', 'bottom center', false, 50);
}

/**
 * Function creates actual qTip2 for specified element.
 *
 * @param   {jQuery}        element
 * @param   {string}        tipTitle
 * @param   {string}        tipText
 * @param   {string|number} tipWidth
 * @param   {string}        tipMy
 * @param   {string}        tipAt
 * @param   {boolean}       tipFixed
 * @param   {number}        tipDelay
 */
function createQtip(element, tipTitle, tipText, tipWidth, tipMy, tipAt, tipFixed, tipDelay) {
    element.qtip({
        metadata: {
            type: 'html5',      // Use HTML5 data-* attributes
            name: 'qtipopts'    // Grab the metadata from the data-qtipOpts HTML5 attribute
        },
        content: {
            title: tipTitle,
            text: tipText
        },
        style: {
            tip: {
                corner: true
            },
            classes: 'qtip-bootstrap',
            width: tipWidth
        },
        show: {
            ready: true
        },
        hide: {
            fixed: tipFixed,
            delay: tipDelay,
            effect: false
        },
        position: {
            my: tipMy,
            at: tipAt,
            viewport: jQuery(window)
        }
    });
}<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 The Lovefield Project Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
goog.setTestOnly();
goog.require('goog.testing.jsunit');
goog.require('lf.structs.MapSet');

/**
 * Creates a pre-populated MapSet for testing.
 * @return {!lf.structs.MapSet<number, number>}
 */
function getSampleMapSet() {
  var mapSet = new lf.structs.MapSet();
  mapSet.set(10, 11);
  mapSet.set(10, 12);
  mapSet.set(20, 21);
  mapSet.set(20, 25);
  mapSet.set(30, 39);
  return mapSet;
}

function testSet() {
  var mapSet = new lf.structs.MapSet();
  assertFalse(mapSet.has(10));
  mapSet.set(10, 11);
  assertEquals(1, mapSet.size);
  assertTrue(mapSet.has(10));

  // Set the same key/value shall not alter the map.
  mapSet.set(10, 11);
  assertEquals(1, mapSet.size);

  // Set new value of a key shall alter the map.
  mapSet.set(10, 12);
  assertEquals(2, mapSet.size);
}

function testDelete() {
  var mapSet = getSampleMapSet();

  assertTrue(mapSet.delete(10, 12));
  assertEquals(4, mapSet.size);

  // Test that removing a non-existing value does not modify the map.
  assertFalse(mapSet.delete(10, 13));
  assertEquals(4, mapSet.size);

  assertTrue(mapSet.delete(10, 11));
  assertNull(mapSet.get(10));
}

function testGet() {
  var mapSet = getSampleMapSet();

  assertArrayEquals([11, 12], mapSet.get(10));
  assertArrayEquals([21, 25], mapSet.get(20));
  assertArrayEquals([39], mapSet.get(30));
  assertNull(mapSet.get(40));
}

function testSize() {
  var emptyMapSet = new lf.structs.MapSet();
  assertEquals(0, emptyMapSet.size);

  var mapSet = getSampleMapSet();
  assertEquals(5, mapSet.size);
  mapSet.delete(10, 11);
  assertEquals(4, mapSet.size);
  mapSet.delete(10, 12);
  assertEquals(3, mapSet.size);
  mapSet.delete(20, 21);
  assertEquals(2, mapSet.size);
  mapSet.delete(20, 25);
  assertEquals(1, mapSet.size);
  mapSet.delete(30, 39);
  assertEquals(0, mapSet.size);
}

function testClear() {
  var mapSet = getSampleMapSet();
  assertEquals(5, mapSet.size);

  mapSet.clear();
  assertEquals(0, mapSet.size);
}

function testKeys() {
  var emptyMapSet = new lf.structs.MapSet();
  assertArrayEquals([], emptyMapSet.keys());

  var mapSet = getSampleMapSet();
  assertArrayEquals([10, 20, 30], mapSet.keys());
}

function testValues() {
  var emptyMapSet = new lf.structs.MapSet();
  assertArrayEquals([], emptyMapSet.values());

  var mapSet = getSampleMapSet();
  assertArrayEquals([11, 12, 21, 25, 39], mapSet.values());
}

function testMerge_Empty() {
  var m1 = new lf.structs.MapSet();
  var m2 = new lf.structs.MapSet();
  var merged = m1.merge(m2);
  assertEquals(merged, m1);
  assertEquals(0, m1.size);
}

function testMerge() {
  var mapSet1 = getSampleMapSet();
  assertEquals(5, mapSet1.size);
  var mapSet2 = new lf.structs.MapSet();
  mapSet2.set(10, 100);
  mapSet2.set(20, 200);
  mapSet2.set(40, 400);

  mapSet1.merge(mapSet2);
  assertEquals(5 + mapSet2.size, mapSet1.size);
  assertArrayEquals([11, 12, 100], mapSet1.get(10));
  assertArrayEquals([21, 25, 200], mapSet1.get(20));
  assertArrayEquals([39], mapSet1.get(30));
  assertArrayEquals([400], mapSet1.get(40));
}
<commit_msg>Add missing test case for lf.structs.MapSet#delete. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=107640367<commit_after>/**
 * @license
 * Copyright 2015 The Lovefield Project Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
goog.setTestOnly();
goog.require('goog.testing.jsunit');
goog.require('lf.structs.MapSet');

/**
 * Creates a pre-populated MapSet for testing.
 * @return {!lf.structs.MapSet<number, number>}
 */
function getSampleMapSet() {
  var mapSet = new lf.structs.MapSet();
  mapSet.set(10, 11);
  mapSet.set(10, 12);
  mapSet.set(20, 21);
  mapSet.set(20, 25);
  mapSet.set(30, 39);
  return mapSet;
}

function testSet() {
  var mapSet = new lf.structs.MapSet();
  assertFalse(mapSet.has(10));
  mapSet.set(10, 11);
  assertEquals(1, mapSet.size);
  assertTrue(mapSet.has(10));

  // Set the same key/value shall not alter the map.
  mapSet.set(10, 11);
  assertEquals(1, mapSet.size);

  // Set new value of a key shall alter the map.
  mapSet.set(10, 12);
  assertEquals(2, mapSet.size);
}

function testDelete() {
  var mapSet = getSampleMapSet();

  assertTrue(mapSet.delete(10, 12));
  assertEquals(4, mapSet.size);

  // Test that removing a non-existing value for an existing key does not modify
  // the map.
  assertTrue(mapSet.has(10));
  assertFalse(mapSet.delete(10, 13));
  assertEquals(4, mapSet.size);

  // Test that removing a non-existing value for a non-existing key does not
  // modify the map or throw any errors.
  assertFalse(mapSet.has(100));
  assertFalse(mapSet.delete(100, 2000));
  assertEquals(4, mapSet.size);

  assertTrue(mapSet.delete(10, 11));
  assertNull(mapSet.get(10));
}

function testGet() {
  var mapSet = getSampleMapSet();

  assertArrayEquals([11, 12], mapSet.get(10));
  assertArrayEquals([21, 25], mapSet.get(20));
  assertArrayEquals([39], mapSet.get(30));
  assertNull(mapSet.get(40));
}

function testSize() {
  var emptyMapSet = new lf.structs.MapSet();
  assertEquals(0, emptyMapSet.size);

  var mapSet = getSampleMapSet();
  assertEquals(5, mapSet.size);
  mapSet.delete(10, 11);
  assertEquals(4, mapSet.size);
  mapSet.delete(10, 12);
  assertEquals(3, mapSet.size);
  mapSet.delete(20, 21);
  assertEquals(2, mapSet.size);
  mapSet.delete(20, 25);
  assertEquals(1, mapSet.size);
  mapSet.delete(30, 39);
  assertEquals(0, mapSet.size);
}

function testClear() {
  var mapSet = getSampleMapSet();
  assertEquals(5, mapSet.size);

  mapSet.clear();
  assertEquals(0, mapSet.size);
}

function testKeys() {
  var emptyMapSet = new lf.structs.MapSet();
  assertArrayEquals([], emptyMapSet.keys());

  var mapSet = getSampleMapSet();
  assertArrayEquals([10, 20, 30], mapSet.keys());
}

function testValues() {
  var emptyMapSet = new lf.structs.MapSet();
  assertArrayEquals([], emptyMapSet.values());

  var mapSet = getSampleMapSet();
  assertArrayEquals([11, 12, 21, 25, 39], mapSet.values());
}

function testMerge_Empty() {
  var m1 = new lf.structs.MapSet();
  var m2 = new lf.structs.MapSet();
  var merged = m1.merge(m2);
  assertEquals(merged, m1);
  assertEquals(0, m1.size);
}

function testMerge() {
  var mapSet1 = getSampleMapSet();
  assertEquals(5, mapSet1.size);
  var mapSet2 = new lf.structs.MapSet();
  mapSet2.set(10, 100);
  mapSet2.set(20, 200);
  mapSet2.set(40, 400);

  mapSet1.merge(mapSet2);
  assertEquals(5 + mapSet2.size, mapSet1.size);
  assertArrayEquals([11, 12, 100], mapSet1.get(10));
  assertArrayEquals([21, 25, 200], mapSet1.get(20));
  assertArrayEquals([39], mapSet1.get(30));
  assertArrayEquals([400], mapSet1.get(40));
}
<|endoftext|>


<commit_before>/**
* (C) Copyright IBM Corp. 2016. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/

var state = require('../state');

var events = [];

function completeEvent(response) {
	switch (response) {
	case true:
		publish('send', {
			message: 'success',
			silent: true
		});
		break;
	case false:
		publish('send', {
			message: 'failure',
			silent: true
		});
		break;
	default:
		publish('send', {
			message: 'cancel',
			silent: true
		});
	}
}

function destroy() {
	events = [];
}

function unsubscribe(event, handler, context) {
	if (typeof context === undefined)
		context = handler;
}

function currentSubscriptions() {
	return events.slice(0);
}

function hasSubscription(action) {
	var subscriptions = currentSubscriptions();
	for (var i = 0; i < subscriptions.length; i++) {
		var subscription = subscriptions[i];
		if (subscription && subscription.event === action)
			return true;
	}
	return false;
}

function subscribe(event, handler, context, subscribeOnce) {
	if (typeof context === undefined)
		context = handler;
	var index = events.push({ event: event, handler: handler.bind(context), subscribeOnce: (subscribeOnce) ? true : false }) - 1;
	return {
		remove: function() {
			delete events[index];
		}
	};
}

function subscribeMany(event, handler, context) {
	subscribe(event, handler, context, false);
}

function subscribeOnce(event, handler, context) {
	subscribe(event, handler, context, true);
}

function publish(event, data, cb) {
	var current = state.getState();
	var wasSubscription = false;
	for (var i = 0; i < events.length; i++) {
		if (events[i] && events[i].event && events[i].event === event) {
			if (current.DEBUG) {
				wasSubscription = true;
				console.log('Subscription to ' + event + ' was called: ', data);
			}
			events[i].handler.call(undefined, data, cb);
			if (events[i].subscribeOnce === true)
				delete events[i];
		}
	}
	if (current.DEBUG && event.indexOf('layout') == -1 && !wasSubscription)
		console.warn('Nothing is subscribed to ' + event);
}

module.exports = {
	destroy: destroy,
	unsubscribe: unsubscribe,
	currentSubscriptions: currentSubscriptions,
	hasSubscription: hasSubscription,
	subscribe: subscribeMany,
	subscribeOnce: subscribeOnce,
	publish: publish,
	completeEvent: completeEvent
};
<commit_msg>event handling bugs<commit_after>/**
* (C) Copyright IBM Corp. 2016. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/

var state = require('../state');

var events = [];

function completeEvent(response) {
	switch (response) {
	case true:
		publish('send', {
			message: 'success',
			silent: true
		});
		break;
	case false:
		publish('send', {
			message: 'failure',
			silent: true
		});
		break;
	default:
		publish('send', {
			message: 'cancel',
			silent: true
		});
	}
}

function destroy() {
	events = [];
}

function unsubscribe(event, handler, context) {
	if (typeof context === undefined)
		context = handler;
}

function currentSubscriptions() {
	return events.slice(0);
}

function hasSubscription(action) {
	var subscriptions = currentSubscriptions();
	for (var i = 0; i < subscriptions.length; i++) {
		var subscription = subscriptions[i];
		if (subscription && subscription.event === action)
			return true;
	}
	return false;
}

function subscribe(event, handler, context, subscribeOnce) {
	if (typeof context === undefined)
		context = handler;
	var index = events.push({ event: event, handler: handler.bind(context), subscribeOnce: (subscribeOnce) ? true : false }) - 1;
	if (!subscribeOnce) {
		return {
			remove: function() {
				delete events[index];
			}
		};
	}
}

function subscribeMany(event, handler, context) {
	return subscribe(event, handler, context, false);
}

function subscribeOnce(event, handler, context) {
	subscribe(event, handler, context, true);
}

function publish(event, data, cb) {
	var current = state.getState();
	var wasSubscription = false;
	for (var i = 0; i < events.length; i++) {
		if (events[i] && events[i].event && events[i].event === event) {
			if (current.DEBUG) {
				wasSubscription = true;
				console.log('Subscription to ' + event + ' was called: ', data);
			}
			events[i].handler.call(undefined, data, cb);
			if (events[i] && events[i].subscribeOnce === true)
				delete events[i];
		}
	}
	if (current.DEBUG && event.indexOf('layout') == -1 && !wasSubscription)
		console.warn('Nothing is subscribed to ' + event);
}

module.exports = {
	destroy: destroy,
	unsubscribe: unsubscribe,
	currentSubscriptions: currentSubscriptions,
	hasSubscription: hasSubscription,
	subscribe: subscribeMany,
	subscribeOnce: subscribeOnce,
	publish: publish,
	completeEvent: completeEvent
};
<|endoftext|>


<commit_before>
/**
 * @method Events.on
 * @signature Events.on(htmlElement, eventName, [delegatorSelector], functionHandler);
 *
 * Events are always registered to be listened in the bubble phase;
 * this means that the event handler set on the inner element
 * should be executed before other handlers on outer elements, [EP01].
 *
 * In case the same element listens for both directly bound events, both delegate events,
 * the execution of the former is always postponed to the execution of the delegate handlers, [EP03].
 *
 */

QUnit.module( "dom-events.js", {
    beforeEach: function() {

        this.stopPropagationSpy = sinon.spy(Event.prototype, "stopPropagation");
        this.preventDefaultSpy = sinon.spy(Event.prototype, "preventDefault");

        var fakeDOM = "\

\

\

Click the following button:
\
                    <button id='btn'>Child button</button>\

\
                Child link\

";

        setup(fakeDOM);
    },
    afterEach: function() {
        this.stopPropagationSpy.restore();
        this.preventDefaultSpy.restore();
    }
});

test("[EP01] event bubbles up", function (assert) {

    var spyParent = sinon.spy();
    var spyChild1 = sinon.spy();
    var spyChild2 = sinon.spy();
    var parent = $$("#parent");
    var child = $$("#parent button");

    Events.on(child, "click", spyChild1);
    Events.on(parent, "click", spyParent);
    Events.on(child, "click", spyChild2);
    trigger(child, "click");

    assert.ok(spyParent.calledOnce && spyChild1.calledOnce && spyChild2.calledOnce, "Event handlers are fired one time");
    assert.ok(spyChild1.calledBefore(spyChild2), "The handlers of the inner element are executed in the order of registration");
    assert.ok(spyChild1.calledBefore(spyParent) && spyChild2.calledBefore(spyParent), "The handlers of the inner element are executed before the handler of the outer element");

});

test("[EP02] bubbling vs delegation [A]", function (assert) {

    // handlers on the innermost html element are always executed before handlers on the outer elements

    var spyNoDelegate = sinon.spy();
    var spyDelegate = sinon.spy();
    var mainContainer = $$("#test-container");
    var child = $$("#parent button");

    Events.on(mainContainer, "click", "button", spyDelegate);
    Events.on(child, "click", spyNoDelegate);

    trigger(child, "click");

    assert.ok(spyDelegate.calledOnce && spyNoDelegate.calledOnce, "Event handlers are fired one time");
    assert.ok(spyNoDelegate.calledBefore(spyDelegate), "Handlers for the innermost element are always executed before");

});

test("[EP03] bubbling vs delegation [B]", function (assert) {

    // in case the same element has both a directly bound event listener, both a delegated event listener
    // the execution of the former is always postponed to the execution of the latter;
    // the delegated event listener handler could also block the event propagation, so that the
    // directly bound event listener handler is never executed (see [****A]).

    var spyNoDelegate = sinon.spy();
    var spyDelegate = sinon.spy();
    var mainContainer = $$("#test-container")[0];
    var child = $$("#parent button");

    Events.on(mainContainer, "click", spyNoDelegate);
    Events.on(mainContainer, "click", "button", spyDelegate);

    trigger(child, "click");

    assert.ok(spyDelegate.calledOnce && spyNoDelegate.calledOnce, "Event handlers are fired one time");
    assert.ok(spyDelegate.calledBefore(spyNoDelegate), "Delegated handler is executed before");

});

test("[EP04] delegate handlers are executed starting from the innermost element [A]", function (assert) {

    // [A] target element is the same, bound elements are different

    var spyTestContainer = sinon.spy();
    var spyParent = sinon.spy();
    var mainContainer = $$("#test-container");
    var parent = $$("#parent");
    var child = $$("#parent button");

    Events.on(mainContainer, "click", "button", spyTestContainer);
    Events.on(parent, "click", "button", spyParent);

    trigger(child, "click");

    assert.ok(spyParent.calledOnce && spyTestContainer.calledOnce, "Event handlers are fired one time");
    assert.ok(spyParent.calledBefore(spyTestContainer), "The handler of the inner bound element is executed before the handler of the outer bound element");

});

test("[EP05] delegate handlers are executed starting from the innermost element [B]", function (assert) {

    // [B] target elements are different, bound elements is the same

    var spyParent = sinon.spy();
    var spyChild = sinon.spy();
    var mainContainer = $$("#test-container");
    var parent = $$("#parent");
    var child = $$("#parent button");

    Events.on(mainContainer, "click", "#parent", spyParent);
    Events.on(mainContainer, "click", "button", spyChild);

    trigger(child, "click");

    assert.ok(spyParent.calledOnce && spyChild.calledOnce, "Event handlers are fired one time");
    assert.ok(spyChild.calledBefore(spyParent), "The handler of the inner element is executed before the handler of the outer element");

});

test("[EP06] delegate handlers are executed starting from the innermost element [C1]", function (assert) {

    // [C1] different bound elements, and different targets

    var spyParent = sinon.spy();
    var spyChild = sinon.spy();
    var mainContainer = $$("#test-container");
    var parent = $$("#parent");
    var child = $$("#parent button");

    Events.on(mainContainer, "click", "#parent", spyParent);
    Events.on(parent, "click", "button", spyChild);

    trigger(child, "click");

    assert.ok(spyParent.calledOnce && spyChild.calledOnce, "Event handlers are fired one time");
    assert.ok(spyChild.calledBefore(spyParent), "The handler of the inner element is executed before the handler of the outer element");

});

test("[EP07] delegate handlers are executed starting from the innermost element [C2]", function (assert) {

    // [C2] different bound elements, and different targets

    var spyParent = sinon.spy();
    var spyChild = sinon.spy();
    var mainContainer = $$("#test-container");
    var parent = $$("#parent");
    var child = $$("#child");
    var btn = $$("#btn");

    Events.on(mainContainer, "click", "#btn", spyParent);
    Events.on(parent, "click", "#child", spyChild);

    trigger(btn, "click");

    assert.ok(spyParent.calledOnce && spyChild.calledOnce, "Event handlers are fired one time");
    assert.ok(spyChild.calledBefore(spyParent), "The handler of the inner element is executed before the handler of the outer element");

});

// @todo stop propagation should be implemented
xtest("[****A] stop propagation", function (assert) {

    // check [EP03] for reference

    var spyDirect = sinon.spy();
    var mainContainer = $$("#test-container")[0];
    var child = $$("#parent button");

    Events.on(mainContainer, "click", spyDirect);
    Events.on(mainContainer, "click", "button", function(e) {e.stopPropagation(); });

    trigger(child, "click");

    assert.ok(this.stopPropagationSpy.calledOnce, _.one("Event#stopPropagation", this.stopPropagationSpy.callCount));
    assert.ok(!spyDirect.called, _.none("Direct bound event handler", spyDirect.callCount));

});
<commit_msg>event.stopPropagation tests<commit_after>
/**
 * @method Events.on
 * @signature Events.on(htmlElement, eventName, [delegatorSelector], functionHandler);
 *
 * Events are always registered to be listened in the bubble phase;
 * this means that the event handler set on the inner element
 * should be executed before other handlers on outer elements, [EP01].
 *
 * In case the same element listens for both directly bound events, both delegate events,
 * the execution of the former is always postponed to the execution of the delegate handlers, [EP03].
 *
 */

QUnit.module( "dom-events.js", {
    beforeEach: function() {

        this.stopPropagationSpy = sinon.spy(Event.prototype, "stopPropagation");
        this.preventDefaultSpy = sinon.spy(Event.prototype, "preventDefault");

        var fakeDOM = "\

\

\

Click the following button:
\
                    <button id='btn'>Child button</button>\

\
                Child link\

";

        setup(fakeDOM);
    },
    afterEach: function() {
        this.stopPropagationSpy.restore();
        this.preventDefaultSpy.restore();
    }
});

test("[EP01] event bubbles up", function (assert) {

    var spyParent = sinon.spy();
    var spyChild1 = sinon.spy();
    var spyChild2 = sinon.spy();
    var parent = $$("#parent");
    var child = $$("#parent button");

    Events.on(child, "click", spyChild1);
    Events.on(parent, "click", spyParent);
    Events.on(child, "click", spyChild2);
    trigger(child, "click");

    assert.ok(spyParent.calledOnce && spyChild1.calledOnce && spyChild2.calledOnce, "Event handlers are fired one time");
    assert.ok(spyChild1.calledBefore(spyChild2), "The handlers of the inner element are executed in the order of registration");
    assert.ok(spyChild1.calledBefore(spyParent) && spyChild2.calledBefore(spyParent), "The handlers of the inner element are executed before the handler of the outer element");

});

test("[EP02] bubbling vs delegation [A]", function (assert) {

    // handlers on the innermost html element are always executed before handlers on the outer elements

    var spyNoDelegate = sinon.spy();
    var spyDelegate = sinon.spy();
    var mainContainer = $$("#test-container");
    var child = $$("#parent button");

    Events.on(mainContainer, "click", "button", spyDelegate);
    Events.on(child, "click", spyNoDelegate);

    trigger(child, "click");

    assert.ok(spyDelegate.calledOnce && spyNoDelegate.calledOnce, "Event handlers are fired one time");
    assert.ok(spyNoDelegate.calledBefore(spyDelegate), "Handlers for the innermost element are always executed before");

});

test("[EP03] bubbling vs delegation [B]", function (assert) {

    // in case the same element has both a directly bound event listener, both a delegated event listener
    // the execution of the former is always postponed to the execution of the latter;
    // the delegated event listener handler could also block the event propagation, so that the
    // directly bound event listener handler is never executed (see [EP11], [EP12]).

    var spyNoDelegate = sinon.spy();
    var spyDelegate = sinon.spy();
    var mainContainer = $$("#test-container")[0];
    var child = $$("#parent button");

    Events.on(mainContainer, "click", spyNoDelegate);
    Events.on(mainContainer, "click", "button", spyDelegate);

    trigger(child, "click");

    assert.ok(spyDelegate.calledOnce && spyNoDelegate.calledOnce, "Event handlers are fired one time");
    assert.ok(spyDelegate.calledBefore(spyNoDelegate), "Delegated handler is executed before");

});

test("[EP04] delegate handlers are executed starting from the innermost element [A]", function (assert) {

    // [A] target element is the same, bound elements are different

    var spyTestContainer = sinon.spy();
    var spyParent = sinon.spy();
    var mainContainer = $$("#test-container");
    var parent = $$("#parent");
    var child = $$("#parent button");

    Events.on(mainContainer, "click", "button", spyTestContainer);
    Events.on(parent, "click", "button", spyParent);

    trigger(child, "click");

    assert.ok(spyParent.calledOnce && spyTestContainer.calledOnce, "Event handlers are fired one time");
    assert.ok(spyParent.calledBefore(spyTestContainer), "The handler of the inner bound element is executed before the handler of the outer bound element");

});

test("[EP05] delegate handlers are executed starting from the innermost element [B]", function (assert) {

    // [B] target elements are different, bound elements is the same

    var spyParent = sinon.spy();
    var spyChild = sinon.spy();
    var mainContainer = $$("#test-container");
    var parent = $$("#parent");
    var child = $$("#parent button");

    Events.on(mainContainer, "click", "#parent", spyParent);
    Events.on(mainContainer, "click", "button", spyChild);

    trigger(child, "click");

    assert.ok(spyParent.calledOnce && spyChild.calledOnce, "Event handlers are fired one time");
    assert.ok(spyChild.calledBefore(spyParent), "The handler of the inner element is executed before the handler of the outer element");

});

test("[EP06] delegate handlers are executed starting from the innermost element [C1]", function (assert) {

    // [C1] different bound elements, and different targets

    var spyParent = sinon.spy();
    var spyChild = sinon.spy();
    var mainContainer = $$("#test-container");
    var parent = $$("#parent");
    var child = $$("#parent button");

    Events.on(mainContainer, "click", "#parent", spyParent);
    Events.on(parent, "click", "button", spyChild);

    trigger(child, "click");

    assert.ok(spyParent.calledOnce && spyChild.calledOnce, "Event handlers are fired one time");
    assert.ok(spyChild.calledBefore(spyParent), "The handler of the inner element is executed before the handler of the outer element");

});

test("[EP07] delegate handlers are executed starting from the innermost element [C2]", function (assert) {

    // [C2] different bound elements, and different targets

    var spyParent = sinon.spy();
    var spyChild = sinon.spy();
    var mainContainer = $$("#test-container");
    var parent = $$("#parent");
    var child = $$("#child");
    var btn = $$("#btn");

    Events.on(mainContainer, "click", "#btn", spyParent);
    Events.on(parent, "click", "#child", spyChild);

    trigger(btn, "click");

    assert.ok(spyParent.calledOnce && spyChild.calledOnce, "Event handlers are fired one time");
    assert.ok(spyChild.calledBefore(spyParent), "The handler of the inner element is executed before the handler of the outer element");

});

/**
 * Stop event propagation
 */

test("[EP08] stop event propagation (with event.stopPropagation method call)", function (assert) {

    var spyParent = sinon.spy();
    var parent = $$("#parent");
    var child = $$("#parent button");

    Events.on(parent, "click", spyParent);
    Events.on(child, "click", function(evt){
        evt.stopPropagation();
    });
    trigger(child, "click");

    assert.ok(this.stopPropagationSpy.calledOnce, "stopPropagation was called once");
    assert.ok(!spyParent.called, _.none("Event handler", spyParent.callCount));

});

test("[EP09] stop event propagation (with return false)", function (assert) {

    var spyChild = sinon.spy();
    var spyParent = sinon.spy();
    var parent = $$("#parent");
    var child = $$("#parent button");

    Events.on(parent, "click", spyParent);
    Events.on(child, "click", function(){ spyChild(); return false; });
    trigger(child, "click");

    assert.ok(spyChild.calledOnce && !spyParent.called, _.none("Event handler", spyParent.callCount));

});

// @todo It's possible to implement a jQuery style event.stopImmediatePropagation method
// @todo require attention
test("[EP10] stop event propagation has no effect once the event has reached its target", function (assert) {

    var spyOne = sinon.spy();
    var spyTwo = sinon.spy();
    var spyThree = sinon.spy();
    var child = $$("#parent button");

    Events.on(child, "click", function(evt) { spyOne(); evt.stopPropagation(); });
    Events.on(child, "click", function() { spyTwo(); return false; });
    Events.on(child, "click", spyThree);
    trigger(child, "click");

    assert.ok(spyOne.calledOnce && spyTwo.calledOnce && spyThree.calledOnce, "Event handlers are fired one time");
    assert.ok(spyOne.calledBefore(spyTwo) && spyTwo.calledBefore(spyThree), "The handlers are executed in the order of registration");

});

test("[EP11] delegate handler stops propagation (with event.stopPropagation)", function (assert) {

    // in this case there are both a delegate listener, both a directly bound event listener
    // on the same html element
    // check [EP03] for further info

    var spyDirect = sinon.spy();
    var mainContainer = $$("#test-container")[0];
    var child = $$("#parent button");

    Events.on(mainContainer, "click", spyDirect);
    Events.on(mainContainer, "click", "button", function(e) {e.stopPropagation(); });

    trigger(child, "click");

    assert.ok(this.stopPropagationSpy.calledOnce, _.one("Event#stopPropagation", this.stopPropagationSpy.callCount));
    assert.ok(!spyDirect.called, _.none("Direct bound event handler", spyDirect.callCount));

});

test("[EP12] delegate handler stops propagation (with return false)", function (assert) {

    // in this case there are both a delegate listener, both a directly bound event listener
    // on the same html element
    // check [EP03] for further info

    var spyDirect = sinon.spy();
    var mainContainer = $$("#test-container")[0];
    var child = $$("#parent button");

    Events.on(mainContainer, "click", spyDirect);
    Events.on(mainContainer, "click", "button", function() { return false; });

    trigger(child, "click");

    assert.ok(!spyDirect.called, _.none("Direct bound event handler", spyDirect.callCount));

});

test("[EP13] delegate handler stops propagation (with event.stopPropagation)", function (assert) {

    // in this case the delegate listener, and the directly bound event listener
    // are on different dom elements
    // check [EP03] for further info

    var spyNoDelegate = sinon.spy();
    var mainContainer = $$("#test-container");
    var parent = $$("#parent");
    var child = $$("#parent button");

    Events.on(mainContainer, "click", spyNoDelegate);
    Events.on(parent, "click", "button", function(evt) { evt.stopPropagation(); });

    trigger(child, "click");

    assert.ok(this.stopPropagationSpy.calledOnce, "stopPropagation was called once");
    assert.ok(!spyNoDelegate.called, "When delegate handler stops propagation, bound handler is never executed");

});

test("[EP14] delegate handler stops propagation (with return false)", function (assert) {

    // in this case the delegate listener, and the directly bound event listener
    // are on different dom elements
    // check [EP03] for further info

    var spyNoDelegate = sinon.spy();
    var mainContainer = $$("#test-container");
    var parent = $$("#parent");
    var child = $$("#parent button");

    Events.on(mainContainer, "click", spyNoDelegate);
    Events.on(parent, "click", "button", function() { return false; });

    trigger(child, "click");

    assert.ok(!spyNoDelegate.called, "When delegate handler return false, bound handler is never executed");

});

<|endoftext|>