<commit_before>import Ember from 'ember';
import Controller from '@ember/controller';
import app_state from '../utils/app_state';
import session from '../utils/session';
import i18n from '../utils/i18n';
import CoughDrop from '../app';
import {set as emberSet, get as emberGet} from '@ember/object';
import { htmlSafe } from '@ember/string';

export default Controller.extend({
  abort_if_unauthorized: function() {
    if(!session.get('isAuthenticated')) {
      this.transitionToRoute('index');
    } else if(app_state.get('currentUser') && !app_state.get('currentUser.permissions.admin_support_actions')) {
      this.transitionToRoute('index');
    } else if(app_state.get('currentUser') && !this.get('word')) {
      this.load_word();
    }
  }.observes('session.isAuthenticated', 'app_state.currentUser'),
  load_word: function() {
    var _this = this;
    _this.set('status', null);
    _this.set('word', {loading: true});
    _this.set('inflection_options', null);
    var locale = (this.get('locale') || window.navigator.language || 'en').split(/-|_/)[0];
    var opts = {locale: locale, for_review: true};
    if(_this.get('ref')) {
      opts.word = _this.get('ref');
    }
    CoughDrop.store.query('word', opts).then(function(data) {
      var words = data.map(function(r) { return r; });
      _this.set('antonyms', null);
      if(words[0].get('word') != _this.get('ref') || words[0].get('locale') != _this.get('locale')) {
        _this.transitionToRoute('inflections', words[0].get('word'), words[0].get('locale'));
      }
      _this.set('words', words);
      _this.set('word', words[0]);
    }, function(err) {
      _this.set('word', {error: true});
    });
  },
  update_inflection_options: function() {
    if(!this.get('word.word')) { return; }
    var opts = this.get('inflection_options') || {};
    if(this.get('word.word') || emberGet(opts, 'base') != "") {
      emberSet(opts, 'base', emberGet(opts, 'base') || this.get('word.word'));
    }
    if(this.get('word.inflection_overrides')) {
      var overrides = this.get('word.inflection_overrides');
      for(var type in overrides) {
        emberSet(opts, type, emberGet(opts, type) || overrides[type]);
      }
    }
    var type = this.get('word.primary_part_of_speech');
    var word = opts.base || "";
    var write = function(attr, method) {
      if(opts[attr] && opts[attr] == opts[attr + '_fallback']) {
        opts[attr] = null;
      }
      emberSet(opts, attr + '_fallback', method.call(i18n, word));
      emberSet(opts, attr, opts[attr] || opts[attr + '_fallback']);
    }
    var parts = this.get('parts_of_speech');
    if(parts.noun) {
      write('plural', i18n.pluralize);
      write('possessive', i18n.possessive);
    }
    if(parts.verb) {
      write('infinitive', function(word) { return i18n.tense(word, {infinitive: true}); });
      write('present', function(word) { return word; });
      write('simple_present', function(word) { return i18n.tense(word, {simple_present: true}); });
      write('plural_present', function(word) { return word; });
      write('past', function(word) { return i18n.tense(word, {simple_past: true}); });
      write('simple_past', function(word) { return i18n.tense(word, {simple_past: true}); });
      write('present_participle', function(word) { return i18n.tense(word, {present_participle: true}); });
      write('past_participle', function(word) { return i18n.tense(word, {past_participle: true}); });
    }
    if(parts.adjective || parts.adverb) {
      if(parts.adjective) {
        write('plural', i18n.pluralize);
      }
      write('comparative', i18n.comparative);
      write('negative_comparative', function(word) { return i18n.comparative(word, {negative: true}); });
      write('superlative', i18n.superlative);
      write('negation', i18n.negation);
    }
    if(parts.pronoun) {
      write('objective',  function(word) { return i18n.possessive(word, {objective: true}); });
      write('possessive', function(word) { return i18n.possessive(word, {pronoun: true}); });
      write('possessive_adjective', function(word) { return i18n.possessive(word, {}); });
      write('reflexive',  function(word) { return i18n.possessive(word, {reflexive: true}); });
    }
    if(parts.noun || parts.verb || parts.adjective || parts.adverb || parts.pronoun || parts.article || parts.preposition || parts.article || parts.determiner) {
      write('negation', i18n.negation);
    }

    this.set('inflection_options', opts);
    this.set('antonyms', this.get('antonyms') || (this.get('word.antonyms') || []).join(', '));
  }.observes('word.word', 'word.primary_part_of_speech', 'inflection_options.base', 'word.antonyms', 'word.parts_of_speech', 'parts_of_speech'),
  lookup_link: function() {
    return "https://www.google.com/search?q=define:" + encodeURIComponent(this.get('word.word'));
  }.property('word'),
  word_type_style: function() {
    var _this = this;
    var type = this.get('word_types').find(function(t) { return t.id == _this.get('word.primary_part_of_speech'); });
    return type && htmlSafe(type.extra_style + ' padding: 10px; border-radius: 5px;');
  }.property('word.primary_part_of_speech', 'word_types'),
  word_types: function() {
    var res = [
      {name: i18n.t('unspecified', "[ Select Type ]"), id: ''},
      {name: i18n.t('noun', "Noun (dog, window, idea)"), id: 'noun'},
      {name: i18n.t('verb', "Verb (run, jump, cry, think)"), id: 'verb'},
      {name: i18n.t('adjective', "Adjective (red, ugly, humble)"), id: 'adjective'},
      {name: i18n.t('pronoun', "Pronoun (we, I, you, someone, anybody)"), id: 'pronoun'},
      {name: i18n.t('adverb', "Adverb (kindly, often)"), id: 'adverb'},
      {name: i18n.t('question', "Question (why, when)"), id: 'question'},
      {name: i18n.t('conjunction', "Conjunction (and, but, if, because, although)"), id: 'conjunction'},
      {name: i18n.t('negation', "Negation (not, never)"), id: 'negation'},
      // https://www.talkenglish.com/vocabulary/top-50-prepositions.aspx
      {name: i18n.t('preposition', "Preposition (after, in, on, to, with)"), id: 'preposition'},
      {name: i18n.t('interjection', "Interjection (ahem, duh, hey)"), id: 'interjection'},
      {name: i18n.t('article', "Article (a, an, the)"), id: 'article'},
      // https://www.ef.edu/english-resources/english-grammar/determiners/
      {name: i18n.t('determiner', "Determiner (this, that, some, any, other, such, quite)"), id: 'determiner'},
      {name: i18n.t('number', "Number (one, two, three)"), id: 'numeral'},
      {name: i18n.t('social_phrase', "Social Phrase (hello, thank you)"), id: 'social'},
      {name: i18n.t('other', "Other word type"), id: ''},
    ];

    var _this = this;
    var parts = _this.get('word.parts_of_speech') || [];
    res.forEach(function(type) {
      type.checked = !!(parts.indexOf(type.id) != -1) || type.id == _this.get('word.primary_part_of_speech');
      type.style = 'float: left; width: 50%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-radius: 0;';
      CoughDrop.keyed_colors.forEach(function(color) {
        if(color.types.indexOf(type.id) != -1) {
          type.border = color.border;
          type.fill = color.fill;
          type.extra_style = htmlSafe(' border: 1px solid ' + type.border + '; background: ' + type.fill + ';');
          type.style = htmlSafe(type.style + type.extra_style);
        }
      });
    });
    return res;
  }.property('word.parts_of_speech'),
  update_primary_on_single_word_type: function(ref, change) {
    var single_type = null;
    var multiple = false;
    this.get('word_types').forEach(function(t) {
      if(t.checked && single_type) {
        multiple = true;
      } else if(t.checked) {
        single_type = t.id;
      }
    });
    var types = this.get('word_types');
    var pos = this.get('word.primary_part_of_speech');
    var type = types.find(function(t) { return t.id == pos; });
    if(single_type && !multiple) {
      this.set('word.primary_part_of_speech', single_type);
    } else if(change == 'word.primary_part_of_speech' && type) {
      emberSet(type, 'checked', true);
    } else if(multiple && type && !type.checked) {
      type = types.find(function(t) { return t.checked; });
      if(type) { this.set('word.primary_part_of_speech', type.id); }
    }
  }.observes('word_types', 'word_types.@each.checked', 'word.primary_part_of_speech'),
  word_type: function() {
    var res = {};
    if(this.get('word.primary_part_of_speech')) {
      res[this.get('word.primary_part_of_speech')] = true;
    }
    if(['adjective', 'adverb'].indexOf(this.get('word.primary_part_of_speech')) != -1) {
      res['adjective_or_adverb'] = true;
    }
    return res;
  }.property('word.primary_part_of_speech'),
  parts_of_speech: function() {
    var res = {};
    this.get('word_types').forEach(function(type) {
      if(type.checked) { res[type.id] = true; }
    });
    if(res.noun || res.verb || res.adjective || res.pronoun || res.adverb || res.preposition || res.determiner || res.negation) {
      res.oppositable = true;
    }
    return res;
  }.property('word.parts_of_speech', 'word_types', 'word_types.@each.checked'),
  actions: {
    save: function() {
      var _this = this;
      var word = _this.get('word');
      if(this.get('antonyms')) {
        word.set('antonyms', _this.get('antonyms').split(','));
      }
      var types = _this.get('word_types');
      var list = [];
      types.forEach(function(type) {
        if(type.checked) { list.push(type.id); }
      });
      word.set('parts_of_speech', list);
      var overrides = word.get('inflection_overrides');
      var options = _this.get('inflection_options');
      var regulars = [];
      for(var key in options) {
        if(key == 'regulars') {
          regulars = regulars.concat(options[key]).uniq();
        } else if(key == 'base') {
          overrides[key] = options[key]
          if(options[key] == word.get('word')) {
            regulars.push('base');
          }
        } else if(!key.match(/_fallback$/)) {
          overrides[key] = options[key];
          if(options[key] && options[key] == options[key + '_fallback']) {
            regulars.push(key);
          }
        }
      }
      overrides['regulars'] = regulars;
      word.set('inflection_overrides', overrides);
      _this.set('status', {saving: true});
      word.save().then(function() {
        _this.set('status', null);
        var found = false;
        _this.get('words').forEach(function(w) {
          if(w.get('word') != _this.get('word.word')) {
            _this.transitionToRoute('inflections', w.get('word'), w.get('locale'));
          }
        });
      }, function(err) {
        _this.set('status', {error: true});
      });
    },
    skip: function() {
      this.set('word.skip', true);
      this.send('save');
    },
    add_extra: function() {
      var extras = [].concat(this.get('extra_inflections') || []);
      extras.push({});
      this.set('extra_inflections', extras);
    }
  }
});

<commit_msg>inflections shouldn't keep last word types on save and load new word<commit_after>import Ember from 'ember';
import Controller from '@ember/controller';
import app_state from '../utils/app_state';
import session from '../utils/session';
import i18n from '../utils/i18n';
import CoughDrop from '../app';
import {set as emberSet, get as emberGet} from '@ember/object';
import { htmlSafe } from '@ember/string';

export default Controller.extend({
  abort_if_unauthorized: function() {
    if(!session.get('isAuthenticated')) {
      this.transitionToRoute('index');
    } else if(app_state.get('currentUser') && !app_state.get('currentUser.permissions.admin_support_actions')) {
      this.transitionToRoute('index');
    } else if(app_state.get('currentUser') && !this.get('word')) {
      this.load_word();
    }
  }.observes('session.isAuthenticated', 'app_state.currentUser'),
  load_word: function() {
    var _this = this;
    _this.set('status', null);
    _this.set('word', {loading: true});
    _this.set('inflection_options', null);
    var types = _this.get('word_types');
    types.forEach(function(type) {
      emberSet(type, 'checked', false);
    });
    var locale = (this.get('locale') || window.navigator.language || 'en').split(/-|_/)[0];
    var opts = {locale: locale, for_review: true};
    if(_this.get('ref')) {
      opts.word = _this.get('ref');
    }
    CoughDrop.store.query('word', opts).then(function(data) {
      var words = data.map(function(r) { return r; });
      _this.set('antonyms', null);
      if(words[0].get('word') != _this.get('ref') || words[0].get('locale') != _this.get('locale')) {
        _this.transitionToRoute('inflections', words[0].get('word'), words[0].get('locale'));
      }
      _this.set('words', words);
      _this.set('word', words[0]);
    }, function(err) {
      _this.set('word', {error: true});
    });
  },
  update_inflection_options: function() {
    if(!this.get('word.word')) { return; }
    var opts = this.get('inflection_options') || {};
    if(this.get('word.word') || emberGet(opts, 'base') != "") {
      emberSet(opts, 'base', emberGet(opts, 'base') || this.get('word.word'));
    }
    if(this.get('word.inflection_overrides')) {
      var overrides = this.get('word.inflection_overrides');
      for(var type in overrides) {
        emberSet(opts, type, emberGet(opts, type) || overrides[type]);
      }
    }
    var type = this.get('word.primary_part_of_speech');
    var word = opts.base || "";
    var write = function(attr, method) {
      if(opts[attr] && opts[attr] == opts[attr + '_fallback']) {
        opts[attr] = null;
      }
      emberSet(opts, attr + '_fallback', method.call(i18n, word));
      emberSet(opts, attr, opts[attr] || opts[attr + '_fallback']);
    }
    var parts = this.get('parts_of_speech');
    if(parts.noun) {
      write('plural', i18n.pluralize);
      write('possessive', i18n.possessive);
    }
    if(parts.verb) {
      write('infinitive', function(word) { return i18n.tense(word, {infinitive: true}); });
      write('present', function(word) { return word; });
      write('simple_present', function(word) { return i18n.tense(word, {simple_present: true}); });
      write('plural_present', function(word) { return word; });
      write('past', function(word) { return i18n.tense(word, {simple_past: true}); });
      write('simple_past', function(word) { return i18n.tense(word, {simple_past: true}); });
      write('present_participle', function(word) { return i18n.tense(word, {present_participle: true}); });
      write('past_participle', function(word) { return i18n.tense(word, {past_participle: true}); });
    }
    if(parts.adjective || parts.adverb) {
      if(parts.adjective) {
        write('plural', i18n.pluralize);
      }
      write('comparative', i18n.comparative);
      write('negative_comparative', function(word) { return i18n.comparative(word, {negative: true}); });
      write('superlative', i18n.superlative);
      write('negation', i18n.negation);
    }
    if(parts.pronoun) {
      write('objective',  function(word) { return i18n.possessive(word, {objective: true}); });
      write('possessive', function(word) { return i18n.possessive(word, {pronoun: true}); });
      write('possessive_adjective', function(word) { return i18n.possessive(word, {}); });
      write('reflexive',  function(word) { return i18n.possessive(word, {reflexive: true}); });
    }
    if(parts.noun || parts.verb || parts.adjective || parts.adverb || parts.pronoun || parts.article || parts.preposition || parts.article || parts.determiner) {
      write('negation', i18n.negation);
    }

    this.set('inflection_options', opts);
    this.set('antonyms', this.get('antonyms') || (this.get('word.antonyms') || []).join(', '));
  }.observes('word.word', 'word.primary_part_of_speech', 'inflection_options.base', 'word.antonyms', 'word.parts_of_speech', 'parts_of_speech'),
  lookup_link: function() {
    return "https://www.google.com/search?q=define:" + encodeURIComponent(this.get('word.word'));
  }.property('word'),
  word_type_style: function() {
    var _this = this;
    var type = this.get('word_types').find(function(t) { return t.id == _this.get('word.primary_part_of_speech'); });
    return type && htmlSafe(type.extra_style + ' padding: 10px; border-radius: 5px;');
  }.property('word.primary_part_of_speech', 'word_types'),
  word_types: function() {
    var res = [
      {name: i18n.t('unspecified', "[ Select Type ]"), id: ''},
      {name: i18n.t('noun', "Noun (dog, window, idea)"), id: 'noun'},
      {name: i18n.t('verb', "Verb (run, jump, cry, think)"), id: 'verb'},
      {name: i18n.t('adjective', "Adjective (red, ugly, humble)"), id: 'adjective'},
      {name: i18n.t('pronoun', "Pronoun (we, I, you, someone, anybody)"), id: 'pronoun'},
      {name: i18n.t('adverb', "Adverb (kindly, often)"), id: 'adverb'},
      {name: i18n.t('question', "Question (why, when)"), id: 'question'},
      {name: i18n.t('conjunction', "Conjunction (and, but, if, because, although)"), id: 'conjunction'},
      {name: i18n.t('negation', "Negation (not, never)"), id: 'negation'},
      // https://www.talkenglish.com/vocabulary/top-50-prepositions.aspx
      {name: i18n.t('preposition', "Preposition (after, in, on, to, with)"), id: 'preposition'},
      {name: i18n.t('interjection', "Interjection (ahem, duh, hey)"), id: 'interjection'},
      {name: i18n.t('article', "Article (a, an, the)"), id: 'article'},
      // https://www.ef.edu/english-resources/english-grammar/determiners/
      {name: i18n.t('determiner', "Determiner (this, that, some, any, other, such, quite)"), id: 'determiner'},
      {name: i18n.t('number', "Number (one, two, three)"), id: 'numeral'},
      {name: i18n.t('social_phrase', "Social Phrase (hello, thank you)"), id: 'social'},
      {name: i18n.t('other', "Other word type"), id: ''},
    ];

    var _this = this;
    var parts = _this.get('word.parts_of_speech') || [];
    res.forEach(function(type) {
      type.checked = !!(parts.indexOf(type.id) != -1) || type.id == _this.get('word.primary_part_of_speech');
      type.style = 'float: left; width: 50%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-radius: 0;';
      CoughDrop.keyed_colors.forEach(function(color) {
        if(color.types.indexOf(type.id) != -1) {
          type.border = color.border;
          type.fill = color.fill;
          type.extra_style = htmlSafe(' border: 1px solid ' + type.border + '; background: ' + type.fill + ';');
          type.style = htmlSafe(type.style + type.extra_style);
        }
      });
    });
    return res;
  }.property('word.parts_of_speech'),
  update_primary_on_single_word_type: function(ref, change) {
    var single_type = null;
    var multiple = false;
    this.get('word_types').forEach(function(t) {
      if(t.checked && single_type) {
        multiple = true;
      } else if(t.checked) {
        single_type = t.id;
      }
    });
    var types = this.get('word_types');
    var pos = this.get('word.primary_part_of_speech');
    var type = types.find(function(t) { return t.id == pos; });
    if(single_type && !multiple) {
      this.set('word.primary_part_of_speech', single_type);
    } else if(change == 'word.primary_part_of_speech' && type) {
      emberSet(type, 'checked', true);
    } else if(multiple && type && !type.checked) {
      type = types.find(function(t) { return t.checked; });
      if(type) { this.set('word.primary_part_of_speech', type.id); }
    }
  }.observes('word_types', 'word_types.@each.checked', 'word.primary_part_of_speech'),
  word_type: function() {
    var res = {};
    if(this.get('word.primary_part_of_speech')) {
      res[this.get('word.primary_part_of_speech')] = true;
    }
    if(['adjective', 'adverb'].indexOf(this.get('word.primary_part_of_speech')) != -1) {
      res['adjective_or_adverb'] = true;
    }
    return res;
  }.property('word.primary_part_of_speech'),
  parts_of_speech: function() {
    var res = {};
    this.get('word_types').forEach(function(type) {
      if(type.checked) { res[type.id] = true; }
    });
    if(res.noun || res.verb || res.adjective || res.pronoun || res.adverb || res.preposition || res.determiner || res.negation) {
      res.oppositable = true;
    }
    return res;
  }.property('word.parts_of_speech', 'word_types', 'word_types.@each.checked'),
  actions: {
    save: function() {
      var _this = this;
      var word = _this.get('word');
      if(this.get('antonyms')) {
        word.set('antonyms', _this.get('antonyms').split(','));
      }
      var types = _this.get('word_types');
      var list = [];
      types.forEach(function(type) {
        if(type.checked) { list.push(type.id); }
      });
      word.set('parts_of_speech', list);
      var overrides = word.get('inflection_overrides');
      var options = _this.get('inflection_options');
      var regulars = [];
      for(var key in options) {
        if(key == 'regulars') {
          regulars = regulars.concat(options[key]).uniq();
        } else if(key == 'base') {
          overrides[key] = options[key]
          if(options[key] == word.get('word')) {
            regulars.push('base');
          }
        } else if(!key.match(/_fallback$/)) {
          overrides[key] = options[key];
          if(options[key] && options[key] == options[key + '_fallback']) {
            regulars.push(key);
          }
        }
      }
      overrides['regulars'] = regulars;
      word.set('inflection_overrides', overrides);
      _this.set('status', {saving: true});
      word.save().then(function() {
        _this.set('status', null);
        _this.get('words').forEach(function(w) {
          if(w.get('word') != _this.get('word.word')) {
            _this.set('inflection_options', null);
            var types = _this.get('word_types');
            types.forEach(function(type) {
              emberSet(type, 'checked', false);
            });
            _this.transitionToRoute('inflections', w.get('word'), w.get('locale'));
          }
        });
      }, function(err) {
        _this.set('status', {error: true});
      });
    },
    skip: function() {
      this.set('word.skip', true);
      this.send('save');
    },
    add_extra: function() {
      var extras = [].concat(this.get('extra_inflections') || []);
      extras.push({});
      this.set('extra_inflections', extras);
    }
  }
});

<|endoftext|>


<commit_before>describe( 'Subscription Definition Options', function () {

	var monologue = new Monologue();
	var sub;

	describe( 'When calling defer()', function () {

		afterEach( function () {
			sub.unsubscribe();
		} );

		it( 'Should invoke the callback when the event loop is free', function ( done ) {
			sub = monologue.on( "Some.Topic",function ( data ) {
				expect( data ).to.be( "Hai" );
				done();
			} ).defer();
			monologue.emit( "Some.Topic", "Hai" );
		} );
	} );

	describe( 'When calling withContext()', function () {
		var obj = {
			name : "Paul"
		};
		afterEach( function () {
			sub.unsubscribe();
		} );

		it( 'Should invoke the callback with the provided context', function () {
			var nm;
			sub = monologue.on( "Some.Topic",function ( data ) {
				nm = this.name;
			} ).withContext( obj );
			monologue.emit( "Some.Topic", "Hai" );
			expect( nm ).to.be( "Paul" );
		} );
	} );

	describe( 'When calling disposeAfter()', function () {

		describe( 'disposing after 1 invocation', function () {
			var count = 0;
			afterEach( function () {
				sub.unsubscribe();
			} );

			it( 'Should unsubscribe the callback after 1 invocation', function () {
				sub = monologue.on( "Some.Topic",function ( data ) {
					count++;
				} ).disposeAfter( 1 );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				expect( count ).to.be( 1 );
			} );

		} );

		describe( 'disposing after 3 invocations', function () {
			var count = 0;
			afterEach( function () {
				sub.unsubscribe();
			} );

			it( 'Should unsubscribe the callback after 3 invocations', function () {
				sub = monologue.on( "Some.Topic",function ( data ) {
					count++;
				} ).disposeAfter( 3 );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				expect( count ).to.be( 3 );
			} );

		} );

	} );

	describe( 'When calling withContext() and disposeAfter()', function () {
		var obj = {
			name : "Paul"
		};
		afterEach( function () {
			sub.unsubscribe();
		} );

		it( 'Should invoke the callback with the provided context', function () {
			var nm, count = 0;
			sub = monologue.on( "Some.Topic",function ( data ) {
				nm = this.name;
				count++;
			} ).withContext( obj ).disposeAfter( 1 );
			monologue.emit( "Some.Topic", "Hai" );
			monologue.emit( "Some.Topic", "Hai" );
			expect( nm ).to.be( "Paul" );
			expect( count ).to.be( 1 );
		} );
	} );

	describe( 'When calling once()', function () {
		var count = 0;
		afterEach( function () {
			sub.unsubscribe();
		} );

		it( 'Should unsubscribe the callback after 1 invocation', function () {
			sub = monologue.on( "Some.Topic",function ( data ) {
				count++;
			} ).disposeAfter( 1 );
			monologue.emit( "Some.Topic", "Hai" );
			monologue.emit( "Some.Topic", "Hai" );
			expect( count ).to.be( 1 );
		} );
	} );

	describe( 'When calling distinctUntilChanged()', function () {
		var events = [];

		describe( 'When publishing a primitive', function () {
			beforeEach(function () {
				events = [];
				sub = monologue.on( "Some.Topic",function ( data ) {
					events.push(data);
				} ).distinctUntilChanged();
			});

			afterEach( function () {
				sub.unsubscribe();
			} );

			it( 'Should unsubscribe the callback after 1 invocation', function () {
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "I SEZ HAI!" );
				monologue.emit( "Some.Topic", "Hai" );
				expect( events.length ).to.be( 3 );
				expect( events[0] ).to.be( "Hai" );
				expect( events[1] ).to.be( "I SEZ HAI!" );
				expect( events[2] ).to.be( "Hai" );
			} );
		} );

		describe( 'When publishing an object', function () {
			beforeEach(function () {
				events = [];
				sub = monologue.on( "Some.Topic",function ( data ) {
					events.push(data);
				} ).distinctUntilChanged();
			});

			afterEach( function () {
				sub.unsubscribe();
			} );

			it( 'Should unsubscribe the callback after 1 invocation', function () {
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				monologue.emit( "Some.Topic", { name: "John Lennon" } );
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				expect( events.length ).to.be( 3 );
				expect( events[0] ).to.eql( { name: "Paul McCartney" } );
				expect( events[1] ).to.eql( { name: "John Lennon" } );
				expect( events[2] ).to.eql( { name: "Paul McCartney" } );
			} );
		} );

	} );

	describe( 'When calling distinct()', function () {
		var events = [];

		describe( 'When publishing a primitive', function () {
			beforeEach(function () {
				events = [];
				sub = monologue.on( "Some.Topic",function ( data ) {
					events.push(data);
				} ).distinct();
			});

			afterEach( function () {
				sub.unsubscribe();
			} );

			it( 'Should unsubscribe the callback after 1 invocation', function () {
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "I SEZ HAI!" );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "I SEZ HAI!" );
				expect( events.length ).to.be( 2 );
				expect( events[0] ).to.be( "Hai" );
				expect( events[1] ).to.be( "I SEZ HAI!" );
			} );
		} );

		describe( 'When publishing an object', function () {
			beforeEach(function () {
				events = [];
				sub = monologue.on( "Some.Topic",function ( data ) {
					events.push(data);
				} ).distinct();
			});

			afterEach( function () {
				sub.unsubscribe();
			} );

			it( 'Should unsubscribe the callback after 1 invocation', function () {
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				monologue.emit( "Some.Topic", { name: "John Lennon" } );
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				monologue.emit( "Some.Topic", { name: "John Lennon" } );
				expect( events.length ).to.be( 2 );
				expect( events[0] ).to.eql( { name: "Paul McCartney" } );
				expect( events[1] ).to.eql( { name: "John Lennon" } );
			} );
		} );

	} );

});<commit_msg>Added tests around async SubscriptionDefinition options<commit_after>describe( 'Subscription Definition Options', function () {

	var monologue = new Monologue();
	var sub;

	describe( 'When calling defer()', function () {

		afterEach( function () {
			sub.unsubscribe();
		} );

		it( 'Should invoke the callback when the event loop is free', function ( done ) {
			sub = monologue.on( "Some.Topic",function ( data ) {
				expect( data ).to.be( "Hai" );
				done();
			} ).defer();
			monologue.emit( "Some.Topic", "Hai" );
		} );
	} );

	describe( 'When calling withContext()', function () {
		var obj = {
			name : "Paul"
		};
		afterEach( function () {
			sub.unsubscribe();
		} );

		it( 'Should invoke the callback with the provided context', function () {
			var nm;
			sub = monologue.on( "Some.Topic",function ( data ) {
				nm = this.name;
			} ).withContext( obj );
			monologue.emit( "Some.Topic", "Hai" );
			expect( nm ).to.be( "Paul" );
		} );
	} );

	describe( 'When calling disposeAfter()', function () {

		describe( 'disposing after 1 invocation', function () {
			var count = 0;
			afterEach( function () {
				sub.unsubscribe();
			} );

			it( 'Should unsubscribe the callback after 1 invocation', function () {
				sub = monologue.on( "Some.Topic",function ( data ) {
					count++;
				} ).disposeAfter( 1 );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				expect( count ).to.be( 1 );
			} );

		} );

		describe( 'disposing after 3 invocations', function () {
			var count = 0;
			afterEach( function () {
				sub.unsubscribe();
			} );

			it( 'Should unsubscribe the callback after 3 invocations', function () {
				sub = monologue.on( "Some.Topic",function ( data ) {
					count++;
				} ).disposeAfter( 3 );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				expect( count ).to.be( 3 );
			} );

		} );

	} );

	describe( 'When calling withContext() and disposeAfter()', function () {
		var obj = {
			name : "Paul"
		};
		afterEach( function () {
			sub.unsubscribe();
		} );

		it( 'Should invoke the callback with the provided context', function () {
			var nm, count = 0;
			sub = monologue.on( "Some.Topic",function ( data ) {
				nm = this.name;
				count++;
			} ).withContext( obj ).disposeAfter( 1 );
			monologue.emit( "Some.Topic", "Hai" );
			monologue.emit( "Some.Topic", "Hai" );
			expect( nm ).to.be( "Paul" );
			expect( count ).to.be( 1 );
		} );
	} );

	describe( 'When calling once()', function () {
		var count = 0;
		afterEach( function () {
			sub.unsubscribe();
		} );

		it( 'Should unsubscribe the callback after 1 invocation', function () {
			sub = monologue.on( "Some.Topic",function ( data ) {
				count++;
			} ).disposeAfter( 1 );
			monologue.emit( "Some.Topic", "Hai" );
			monologue.emit( "Some.Topic", "Hai" );
			expect( count ).to.be( 1 );
		} );
	} );

	describe( 'When calling distinctUntilChanged()', function () {
		var events = [];

		describe( 'When publishing a primitive', function () {
			beforeEach(function () {
				events = [];
				sub = monologue.on( "Some.Topic",function ( data ) {
					events.push(data);
				} ).distinctUntilChanged();
			});

			afterEach( function () {
				sub.unsubscribe();
			} );

			it( 'Should unsubscribe the callback after 1 invocation', function () {
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "I SEZ HAI!" );
				monologue.emit( "Some.Topic", "Hai" );
				expect( events.length ).to.be( 3 );
				expect( events[0] ).to.be( "Hai" );
				expect( events[1] ).to.be( "I SEZ HAI!" );
				expect( events[2] ).to.be( "Hai" );
			} );
		} );

		describe( 'When publishing an object', function () {
			beforeEach(function () {
				events = [];
				sub = monologue.on( "Some.Topic",function ( data ) {
					events.push(data);
				} ).distinctUntilChanged();
			});

			afterEach( function () {
				sub.unsubscribe();
			} );

			it( 'Should unsubscribe the callback after 1 invocation', function () {
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				monologue.emit( "Some.Topic", { name: "John Lennon" } );
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				expect( events.length ).to.be( 3 );
				expect( events[0] ).to.eql( { name: "Paul McCartney" } );
				expect( events[1] ).to.eql( { name: "John Lennon" } );
				expect( events[2] ).to.eql( { name: "Paul McCartney" } );
			} );
		} );

	} );

	describe( 'When calling distinct()', function () {
		var events = [];

		describe( 'When publishing a primitive', function () {
			beforeEach(function () {
				events = [];
				sub = monologue.on( "Some.Topic",function ( data ) {
					events.push(data);
				} ).distinct();
			});

			afterEach( function () {
				sub.unsubscribe();
			} );

			it( 'Should unsubscribe the callback after 1 invocation', function () {
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "I SEZ HAI!" );
				monologue.emit( "Some.Topic", "Hai" );
				monologue.emit( "Some.Topic", "I SEZ HAI!" );
				expect( events.length ).to.be( 2 );
				expect( events[0] ).to.be( "Hai" );
				expect( events[1] ).to.be( "I SEZ HAI!" );
			} );
		} );

		describe( 'When publishing an object', function () {
			beforeEach(function () {
				events = [];
				sub = monologue.on( "Some.Topic",function ( data ) {
					events.push(data);
				} ).distinct();
			});

			afterEach( function () {
				sub.unsubscribe();
			} );

			it( 'Should unsubscribe the callback after 1 invocation', function () {
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				monologue.emit( "Some.Topic", { name: "John Lennon" } );
				monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
				monologue.emit( "Some.Topic", { name: "John Lennon" } );
				expect( events.length ).to.be( 2 );
				expect( events[0] ).to.eql( { name: "Paul McCartney" } );
				expect( events[1] ).to.eql( { name: "John Lennon" } );
			} );
		} );

	} );

	describe( 'When calling withConstraint()', function () {
		var events = [];
		var count = 0;
		beforeEach(function () {
			events = [];
			sub = monologue.on( "Some.Topic",function ( data ) {
				events.push(data);
			} ).withConstraint(function(data){
					var cnt = count;
					count += 1
					return cnt === 0;
				});
		});

		afterEach( function () {
			sub.unsubscribe();
		} );

		it( 'Should unsubscribe the callback after 1 invocation', function () {
			monologue.emit( "Some.Topic", { name: "Paul McCartney" } );
			monologue.emit( "Some.Topic", { name: "John Lennon" } );
			expect( events.length ).to.be( 1 );
			expect( events[0] ).to.eql( { name: "Paul McCartney" } );
		} );

	} );

	describe( 'When calling withDebounce', function() {
		var events;

		beforeEach(function () {
			events = [];
			sub = monologue.on( "Debounced.Topic",function ( data ) {
				events.push( data );
			} ).withDebounce( 600 );
		});

		it( "should have only invoked debounced callback once", function (done) {
			monologue.emit( "Debounced.Topic", { name: "Help!" } ); // starts the clock on debounce
			setTimeout( function () {
				monologue.emit( "Debounced.Topic", { name: "Paul McCartney" } );
			}, 20 ); // should not invoke callback
			setTimeout( function () {
				monologue.emit( "Debounced.Topic", { name: "John Lennon" } );
			}, 100 ); // should not invoke callback
			setTimeout( function () {
				monologue.emit( "Debounced.Topic", { name: "George Harrison" } );
			}, 150 ); // should not invoke callback
			setTimeout( function () {
				monologue.emit( "Debounced.Topic", { name: "Ringo Starkey" } );
			}, 750 ); // should not invoke callback
			setTimeout( function () {
				expect( events[0] ).to.eql( { name: "Ringo Starkey" } );
				expect( events.length ).to.be( 1 );
				sub.unsubscribe();
				done();
			},1500);
		} );
	});

	describe( 'When calling withDelay', function() {
		var events;

		beforeEach(function () {
			events = [];
			sub = monologue.on( "Delayed.Topic",function ( data ) {
				events.push( data );
			} ).withDelay( 300 );
		});

		it( "should have only invoked debounced callback once", function (done) {
			monologue.emit( "Delayed.Topic", { name: "Help!" } ); // starts the clock on debounce
			setTimeout( function () {
				expect( events[0] ).to.eql( { name: "Help!" } );
				expect( events.length ).to.be( 1 );
				sub.unsubscribe();
				done();
			},600);
		} );
	});

	describe(' When calling withThrottle', function() {
		var events;

		beforeEach(function () {
			events = [];
			sub = monologue.on( "Throttled.Topic",function ( data ) {
				events.push( data );
			} ).withThrottle( 500 );
		});

		it( "should have only invoked throttled callback once", function (done) {
			monologue.emit( "Throttled.Topic", { name: "Hey, Jude." } ); // starts clock on throttle
			events = [];
			for ( var i = 0; i < 10; i++ ) {
				(function ( x ) {
					monologue.emit( "Throttled.Topic", { name: "Hey, Jude." } );
				})( i );
			}
			setTimeout( function () {
				expect( events.length ).to.be( 1 );
				done();
			}, 800 );
		} );
	});

});<|endoftext|>


<commit_before>/* ***********************************************************************************************

    Unify Project

    Homepage: unify-project.org
    License: MIT + Apache (V2)
    Copyright: 2009-2011 Deutsche Telekom AG, Germany, http://telekom.com
               2012 Sebastian Fastner, Mainz, Germany, http://unify-training.com

*********************************************************************************************** */

/**
 * Handles pop over stacking (and blocking elements)
 */

qx.Class.define("unify.ui.core.PopOverManager",
{
  extend : qx.core.Object,
  type : "singleton",

  /*
  *****************************************************************************
     CONSTRUCTOR
  *****************************************************************************
  */

  construct : function() {
    this.base(arguments);

    var root = this.__root = qx.core.Init.getApplication().getRoot();
    this.__visibleOverlays = [];
    this.__overlays={};
    this.__styleRegistry = {};

    var Style = qx.bom.element.Style;

    var pblocker = this.__pblocker = document.createElement("div");
    var pstyle = qx.theme.manager.Appearance.getInstance().styleFrom("POPOVER-BLOCKER");
    pstyle.display = "none";
    Style.setStyles(pblocker, pstyle);
    pblocker.id = "popover-blocker";

    var mblocker = this.__mblocker = document.createElement("div");
    var mstyle = qx.theme.manager.Appearance.getInstance().styleFrom("MODAL-BLOCKER");
    mstyle.display = "none";
    Style.setStyles(mblocker, mstyle);
    mblocker.id = "modal-blocker";

    qx.event.Registration.addListener(pblocker,'tap',this.__onTapBlocker,this);

    var rootElement = root.getElement();
    rootElement.appendChild(pblocker);
    rootElement.appendChild(mblocker);
  },

  events : {
    /** Show popup event */
    "show" : "qx.event.type.Data",

    /** Hide popup event */
    "hide" : "qx.event.type.Data"
  },

  /*
  *****************************************************************************
     MEMBERS
  *****************************************************************************
  */

  members :
  {
    __root : null,
    __visibleOverlays : null,
    __pblocker : null,
    __mblocker : null,

    /**
     * Applies correct zIndex to all visible pop-overs
     * and positions blocker below the topmost visible popover.
     */
    __sortPopOvers : function()
    {
      var zIndexBase = 100; //TODO read base value from config or some other global strategy to play nice with other zIndex dependant features like transition animations
      var visible = this.__visibleOverlays;
      var pblocker = this.__pblocker;
      var mblocker = this.__mblocker;

      var numVisible = visible.length;
      for (var i=0; i<numVisible; i++) {
        var widget = visible[i];
        if (widget.setOuterStyle) {
          widget.setOuterStyle({zIndex: zIndexBase + 2*i});
        } else {
          widget.setStyle({zIndex: zIndexBase + 2*i});
        }
      }

      if (numVisible > 0) {
        var mSet=false;
        var pSet=false;
        for(var i=numVisible-1;i>=0;i--){
          var modal = visible[i].getModal();

          if(!mSet && modal){
            mblocker.style.zIndex = (zIndexBase-1)+2*i;
            mblocker.style.display = 'block';

            var styleState = visible[i].getUserData("blockerState");
            if (styleState) {
              var val = styleState;
              styleState = {};
              styleState[val] = true;
            }

            var style = qx.theme.manager.Appearance.getInstance().styleFrom("MODAL-BLOCKER", styleState);
            qx.bom.element.Style.setStyles(mblocker, style);

            mSet = true;
          } else if (!pSet && !modal){
            pblocker.style.zIndex = (zIndexBase-1)+2*i;
            pblocker.style.display = 'block';

            var styleState = visible[i].getUserData("blockerState");
            if (styleState) {
              var val = styleState;
              styleState = {};
              styleState[val] = true;
            }

            var style = qx.theme.manager.Appearance.getInstance().styleFrom("POPOVER-BLOCKER", styleState);
            qx.bom.element.Style.setStyles(pblocker, style);

            pSet = true;
          }
          if (mSet&&pSet) {
            break;
          }
        }
        if(!mSet){
          mblocker.style.zIndex = undefined;
          mblocker.style.display = 'none';
        }
        if(!pSet){
          pblocker.style.zIndex = undefined;
          pblocker.style.display = 'none';
        }
      } else {
        pblocker.style.zIndex = undefined;
        mblocker.style.zIndex = undefined;
        pblocker.style.display = 'none';
        mblocker.style.display = 'none';
      }
    },

    /**
     * Closes topmost popover
     */
    __onTapBlocker : function(){
      var numVisible = this.__visibleOverlays.length;

      if (numVisible > 0) {
        var topMost = this.__visibleOverlays[numVisible-1];

        this.hide(topMost);
      } else {
        this.error("tapped on blocker without visible viewmanager");
        //sort popovers again to make sure the blocker is gone
        this.__sortPopOvers();
      }
    },

    /**
     * Shows the view manager with the given ID.
     *
     * @param widget {unify.ui.core.IPopOver} Widget to show
     * @param position {String|Map|unify.ui.Widget?"center"} Position of widget ("center", "window", {left:50,top:50}) or trigger widget
     */
    show : function(widget, position) {
      if (qx.core.Environment.get("qx.debug")) {
        this.debug("Show: " + widget);
        if (!qx.Class.implementsInterface(widget, unify.ui.core.IPopOver)) {
          this.error(widget + " don't implement IPopOver");
        }
      }

      var pos = position || "center";
      if (pos instanceof unify.ui.core.Widget) {
        if (widget.setTrigger) {
          widget.setTrigger(pos);
          pos = widget.getPositionHint();
        } else {
          pos = null;
        }
      } else if (pos == "center" || pos == "window") {
        pos = {left: "50%", top: "50%"};
      } else if (pos == "full") {
        pos = {left: 0, top: 0, right: 0, bottom: 0};
      }
      this.__root.add(widget, pos);

      this.__visibleOverlays.push(widget);
      this.__sortPopOvers();

      widget.show();

      this.fireDataEvent("show", widget);
    },

    /**
     * Hides the view manager with the given ID.
     *
     * @param widget {unify.ui.core.IPopOver} Widget to hide
     */
    hide : function(widget) {
      var self = this;

      var hideCallback=function(){
        qx.lang.Array.remove(self.__visibleOverlays, widget);
        self.__sortPopOvers();

        self.fireDataEvent("hide", widget);
      };

      widget.addListenerOnce("changeVisibility", hideCallback, this);
      widget.hide();
    }
  },

  /*
  *****************************************************************************
     DESTRUCTOR
  *****************************************************************************
  */

  destruct : function() {
    qx.event.Registration.removeListener(this.__pblocker, 'tap', this.__onTapBlocker, this);

    var rootElement = this.__root.getElement();
    rootElement.removeChild(this.__pblocker);
    rootElement.removeChild(this.__mblocker);
    this.__root = this.__pblocker = this.__mblocker = this.__overlays = this.__styleRegistry = null;
  }
});
<commit_msg>Fix #98: Pop over blocker not shown<commit_after>/* ***********************************************************************************************

    Unify Project

    Homepage: unify-project.org
    License: MIT + Apache (V2)
    Copyright: 2009-2011 Deutsche Telekom AG, Germany, http://telekom.com
               2012 Sebastian Fastner, Mainz, Germany, http://unify-training.com

*********************************************************************************************** */

/**
 * Handles pop over stacking (and blocking elements)
 */

qx.Class.define("unify.ui.core.PopOverManager",
{
  extend : qx.core.Object,
  type : "singleton",

  /*
  *****************************************************************************
     CONSTRUCTOR
  *****************************************************************************
  */

  construct : function() {
    this.base(arguments);

    var root = this.__root = qx.core.Init.getApplication().getRoot();
    this.__visibleOverlays = [];
    this.__overlays={};
    this.__styleRegistry = {};

    var Style = qx.bom.element.Style;

    var pblocker = this.__pblocker = document.createElement("div");
    var pstyle = qx.theme.manager.Appearance.getInstance().styleFrom("POPOVER-BLOCKER");
    pstyle.display = "none";
    Style.setStyles(pblocker, pstyle);
    pblocker.id = "popover-blocker";

    var mblocker = this.__mblocker = document.createElement("div");
    var mstyle = qx.theme.manager.Appearance.getInstance().styleFrom("MODAL-BLOCKER");
    mstyle.display = "none";
    Style.setStyles(mblocker, mstyle);
    mblocker.id = "modal-blocker";

    qx.event.Registration.addListener(pblocker,'tap',this.__onTapBlocker,this);

    var rootElement = root.getElement();
    rootElement.appendChild(pblocker);
    rootElement.appendChild(mblocker);
  },

  events : {
    /** Show popup event */
    "show" : "qx.event.type.Data",

    /** Hide popup event */
    "hide" : "qx.event.type.Data"
  },

  /*
  *****************************************************************************
     MEMBERS
  *****************************************************************************
  */

  members :
  {
    __root : null,
    __visibleOverlays : null,
    __pblocker : null,
    __mblocker : null,

    /**
     * Applies correct zIndex to all visible pop-overs
     * and positions blocker below the topmost visible popover.
     */
    __sortPopOvers : function()
    {
      var zIndexBase = 100; //TODO read base value from config or some other global strategy to play nice with other zIndex dependant features like transition animations
      var visible = this.__visibleOverlays;
      var pblocker = this.__pblocker;
      var mblocker = this.__mblocker;

      var numVisible = visible.length;
      for (var i=0; i<numVisible; i++) {
        var widget = visible[i];
        if (widget.setOuterStyle) {
          widget.setOuterStyle({zIndex: zIndexBase + 2*i});
        } else {
          widget.setStyle({zIndex: zIndexBase + 2*i});
        }
      }

      if (numVisible > 0) {
        var mSet=false;
        var pSet=false;
        for(var i=numVisible-1;i>=0;i--){
          var modal = visible[i].getModal();

          if(!mSet && modal){
            var styleState = visible[i].getUserData("blockerState");
            if (styleState) {
              var val = styleState;
              styleState = {};
              styleState[val] = true;
            }

            var style = qx.theme.manager.Appearance.getInstance().styleFrom("MODAL-BLOCKER", styleState) || {};
            style.zIndex = (zIndexBase-1)+2*i;
            style.display = 'block';
            qx.bom.element.Style.setStyles(mblocker, style);

            mSet = true;
          } else if (!pSet && !modal){
            var styleState = visible[i].getUserData("blockerState");
            if (styleState) {
              var val = styleState;
              styleState = {};
              styleState[val] = true;
            }

            var style = qx.theme.manager.Appearance.getInstance().styleFrom("POPOVER-BLOCKER", styleState) || {};
            style.zIndex = (zIndexBase-1)+2*i;
            style.display = 'block';
            qx.bom.element.Style.setStyles(pblocker, style);

            pSet = true;
          }
          if (mSet&&pSet) {
            break;
          }
        }
        if(!mSet){
          mblocker.style.zIndex = undefined;
          mblocker.style.display = 'none';
        }
        if(!pSet){
          pblocker.style.zIndex = undefined;
          pblocker.style.display = 'none';
        }
      } else {
        pblocker.style.zIndex = undefined;
        mblocker.style.zIndex = undefined;
        pblocker.style.display = 'none';
        mblocker.style.display = 'none';
      }
    },

    /**
     * Closes topmost popover
     */
    __onTapBlocker : function(){
      var numVisible = this.__visibleOverlays.length;

      if (numVisible > 0) {
        var topMost = this.__visibleOverlays[numVisible-1];

        this.hide(topMost);
      } else {
        this.error("tapped on blocker without visible viewmanager");
        //sort popovers again to make sure the blocker is gone
        this.__sortPopOvers();
      }
    },

    /**
     * Shows the view manager with the given ID.
     *
     * @param widget {unify.ui.core.IPopOver} Widget to show
     * @param position {String|Map|unify.ui.Widget?"center"} Position of widget ("center", "window", {left:50,top:50}) or trigger widget
     */
    show : function(widget, position) {
      if (qx.core.Environment.get("qx.debug")) {
        this.debug("Show: " + widget);
        if (!qx.Class.implementsInterface(widget, unify.ui.core.IPopOver)) {
          this.error(widget + " don't implement IPopOver");
        }
      }

      var pos = position || "center";
      if (pos instanceof unify.ui.core.Widget) {
        if (widget.setTrigger) {
          widget.setTrigger(pos);
          pos = widget.getPositionHint();
        } else {
          pos = null;
        }
      } else if (pos == "center" || pos == "window") {
        pos = {left: "50%", top: "50%"};
      } else if (pos == "full") {
        pos = {left: 0, top: 0, right: 0, bottom: 0};
      }
      this.__root.add(widget, pos);

      this.__visibleOverlays.push(widget);
      this.__sortPopOvers();

      widget.show();

      this.fireDataEvent("show", widget);
    },

    /**
     * Hides the view manager with the given ID.
     *
     * @param widget {unify.ui.core.IPopOver} Widget to hide
     */
    hide : function(widget) {
      var self = this;

      var hideCallback=function(){
        qx.lang.Array.remove(self.__visibleOverlays, widget);
        self.__sortPopOvers();

        self.fireDataEvent("hide", widget);
      };

      widget.addListenerOnce("changeVisibility", hideCallback, this);
      widget.hide();
    }
  },

  /*
  *****************************************************************************
     DESTRUCTOR
  *****************************************************************************
  */

  destruct : function() {
    qx.event.Registration.removeListener(this.__pblocker, 'tap', this.__onTapBlocker, this);

    var rootElement = this.__root.getElement();
    rootElement.removeChild(this.__pblocker);
    rootElement.removeChild(this.__mblocker);
    this.__root = this.__pblocker = this.__mblocker = this.__overlays = this.__styleRegistry = null;
  }
});
<|endoftext|>


<commit_before>$(document).ready(function(){
    // variables
    var pymChild = null;
    var aspect_ratio = (1 / 3); // H / W
    var mobile_threshold = 500;
    var c10 = d3.scale.category10();
    var typeOrder = [
        "2000",
        "2005-2009",
        "2010-2014"
    ];

    var yearColors = {};
    typeOrder.forEach(function(y) {
        yearColors[y] = c10(y);
    });
    console.log(yearColors);
    // we need this in a few places
    var dateFormat = d3.time.format("%Y");
    var numberFormat = d3.format(",d");
    var decimalFormat = d3.format(",.0f");
    var percentFormat = d3.format('0%');
    //var percentFormat = function(v) {
    //    return decimalFormat(v) + "%";
    //};

    /** START container vars **/
    var chartContainer = d3.select("div#chart");
    var legendContainer = d3.select("div#legend");
    var labelContainer = d3.select("div#value-label")
    /** END container vars **/

    d3.csv("static/data/ct-income-distribution.csv", function(data) {
        const DATA = d3.nest()
            .key(function(o) { return o.IncomeGroup; })
            .sortKeys(function(a, b)  { return +a - +b})
            .key(function(o) { return o.Year; })
            .sortKeys(function(a, b)  { return +a - +b})
            .rollup(function(leaf) { return +leaf[0].Share; })
            .entries(data);

        function drawChart(container_width) {
            if (isNaN(container_width)) {
                container_width = $(window).width();
            }
            var tip = d3.tip()
                .attr('class', 'd3-tip')
                .html(function(d) {
                    var text = "Year: " + d.key + "
Share: " + percentFormat(d.values);
                    return text;
                });
            // d3.select("span.legend-title").text(function(){
            //     return container_width + "px : " + d3.format("0.2f")(container_width/mobile_threshold)
            // })

            // remove existing chart and legend entries
            chartContainer.selectAll("svg").remove();

            console.log(DATA);

            // vars
            var height = container_width * aspect_ratio;
            var width = container_width;

            var margin = {
                top: 0.09 * height,
                right: 0.12 * width,
                bottom: 0.15 * height,
                left: 0.08 * width
            }

            // check bottom/left for minimums
            if (container_width < mobile_threshold) {
                margin = {
                    top: 0.15 * height,
                    right: 0.15 * width,
                    bottom: 0.35 * height,
                    left: 0.18 * width
                }
            } else if (container_width < mobile_threshold * 1.5) {
                margin = {
                    top: 0.09 * height,
                    right: 0.12 * width,
                    bottom: 0.2 * height,
                    left: 0.1 * width
                }
            }

            typeOrder.forEach(function(d) {
                var itemClass = ".legend-item.y" + d + "> span.legend-box";
                var item = d3.select(itemClass);
                var itemColor = yearColors[d];
                var style = 'background-color:' + itemColor;
                item.attr('style', style);
            });
            //var legend = legendContainer.selectAll('span')
            //    .data(typeOrder)
            //    .enter()
            //    .append('span')
            //    .html(function(d) {
            //        return d;
            //    })
            //    .append('span')
            //    .attr("height", '10')
            //    .attr("width", '10')
            //    .html(" ")
            //    .attr('background-color', function(d) {
            //        return c10(d);
            //    });
            //
            // svg containers
            var svg = chartContainer.append("svg")
                .classed("chart", true)
                .attr("height", height)
                .attr("width", width)
                .attr("transform", "translate("+ margin.left + ", " + margin.top + ")");

            svg.call(tip);
            // take margins off of height and width
            height = (height-(margin.top + margin.bottom));
            width = (width-(margin.left + margin.right));

            // Scales
            var groupX = d3.scale.ordinal()
                .rangeRoundBands([0, width], 0.1);

            var barX = d3.scale.ordinal()
                .domain(typeOrder);

            var y = d3.scale.linear()
                .range([height, 0])
                .domain([0, 1]);

            // update x scale data
            groupX.domain(
                DATA.map(function(d) {
                    return d.key;
                })
            );

            barX.rangeRoundBands([0, groupX.rangeBand()], 0.1);
            hoverX = groupX.copy()
                .rangeRoundBands([0, width], 0);

            var chart = svg.append("g")
                .classed("chart", true)
                .attr("height", height)
                .attr("width", width)
                .attr("transform", "translate("+ margin.left + ", " + margin.top + ")");

            // axes
            var xAxis = d3.svg.axis()
                .scale(groupX)
                .orient("bottom");

            var yAxis = d3.svg.axis()
                .scale(y)
                .orient("left")
                .tickFormat(percentFormat);

            if (container_width < mobile_threshold) {
                yAxis.ticks(4);
            }

            if (container_width < mobile_threshold * 1.5) {
                xAxis.tickFormat(function(t) {
                    return "'" + t.slice(-2);
                })
            }

            var xAxis = chart.append("g")
                .attr("class", "axis xaxis")
                .attr("transform", "translate(0," + height + ")")
                .call(xAxis);

            if (container_width < mobile_threshold) {
                xAxis.call(function(g) {
                    g.selectAll("g text")
                        .attr("dy", function(d, i){
                            if (i % 2 == 1) {
                                // ODD ticks
                                return "1.75em";
                            } else {
                                // EVEN ticks
                                return "0.75em"
                            }
                        })
                })
            }

            var xAxisLabel = xAxis.append("text")
                .attr("dx", "2em")
                .attr("dy", (-1.01 * width))
                .attr("transform", "rotate(90)")
                .style("text-anchor", "end")
                .text("Income Group");

            // under certain size, move x axis label ?

            var yAxis = chart.append("g")
                .attr("class", "axis yaxis")
                .call(yAxis);

            var yAxisLabel = yAxis.append("text")
                .attr("dy", "-0.25em")
                .attr("dx", "0.5em")
                .html("Percent");

            var barGroups = chart.selectAll("g.bar-group")
                .data(DATA)
                .enter()
                .append("g")
                .attr("class", function(d) {
                    return [
                        "bar-group",
                        "_"+d.key
                    ].join(" ");
                })
                .attr("data-year", function(d) {
                    return "_"+d.key;
                })
                .attr("transform", function(d) {
                    return "translate(" + groupX(d.key) + ", 0)";
                })

            barGroups.each(function(groupData) {
                // make value labels
                var valueData = [
                    {key : "Year", values: groupData.key}
                ].concat(groupData.values);

                var incomeGroup = groupData.key;
                valueData.forEach(function(value) {
                    labelContainer.append("p")
                        .attr("class", function() {
                            return [
                                "value",
                                "_"+incomeGroup
                            ].join(" ");
                        })
                        .selectAll("span")
                        .data(function() {
                            if (value.key in typeOrder) {
                                return [value.key, percentFormat(value.values)]
                            } else {
                                return [value.key, value.values]
                            }
                        })
                        .enter()
                        .append("span")
                        .text(function(v) { return v; })
                })
            });

            //// bars within groups
            barGroups.selectAll("rect.bar")
                .data(function(d) { return d.values; })
                .enter()
                .append("rect")
                .attr("class", function(d) {
                    return [
                        "bar",
                        d.key.toLowerCase()
                    ].join(" ");
                })
                .attr("data-type", function(d) {
                    return d.key.toLowerCase();
                })
                .attr("width", barX.rangeBand())
                .attr("fill", function(d) {
                    return c10(d.key);
                })
                .attr("x", function(d) {return barX(d.key);})
                .attr("y", function(d) { return y(d.values); })
                .attr("height", function(d) { return height - y(d.values); })
                .on("mouseover",tip.show)
                .on("mouseout", tip.hide);

        }
        pymChild = new pym.Child({ renderCallback: drawChart });
    });
});<commit_msg>added more precise percent format for tooltip<commit_after>$(document).ready(function(){
    // variables
    var pymChild = null;
    var aspect_ratio = (1 / 3); // H / W
    var mobile_threshold = 500;
    var c10 = d3.scale.category10();
    var typeOrder = [
        "2000",
        "2005-2009",
        "2010-2014"
    ];

    var yearColors = {};
    typeOrder.forEach(function(y) {
        yearColors[y] = c10(y);
    });
    console.log(yearColors);
    // we need this in a few places
    var dateFormat = d3.time.format("%Y");
    var numberFormat = d3.format(",d");
    var decimalFormat = d3.format(",.0f");
    var percentFormat = d3.format('0%');
    var precisePercentFormat = d3.format('.1%');
    //var percentFormat = function(v) {
    //    return decimalFormat(v) + "%";
    //};

    /** START container vars **/
    var chartContainer = d3.select("div#chart");
    var legendContainer = d3.select("div#legend");
    var labelContainer = d3.select("div#value-label")
    /** END container vars **/

    d3.csv("static/data/ct-income-distribution.csv", function(data) {
        const DATA = d3.nest()
            .key(function(o) { return o.IncomeGroup; })
            .sortKeys(function(a, b)  { return +a - +b})
            .key(function(o) { return o.Year; })
            .sortKeys(function(a, b)  { return +a - +b})
            .rollup(function(leaf) { return +leaf[0].Share; })
            .entries(data);

        function drawChart(container_width) {
            if (isNaN(container_width)) {
                container_width = $(window).width();
            }
            var tip = d3.tip()
                .attr('class', 'd3-tip')
                .html(function(d) {
                    var text = "Year: " + d.key + "
Share: " + precisePercentFormat(d.values);
                    return text;
                });
            // d3.select("span.legend-title").text(function(){
            //     return container_width + "px : " + d3.format("0.2f")(container_width/mobile_threshold)
            // })

            // remove existing chart and legend entries
            chartContainer.selectAll("svg").remove();

            console.log(DATA);

            // vars
            var height = container_width * aspect_ratio;
            var width = container_width;

            var margin = {
                top: 0.09 * height,
                right: 0.12 * width,
                bottom: 0.15 * height,
                left: 0.08 * width
            }

            // check bottom/left for minimums
            if (container_width < mobile_threshold) {
                margin = {
                    top: 0.15 * height,
                    right: 0.15 * width,
                    bottom: 0.35 * height,
                    left: 0.18 * width
                }
            } else if (container_width < mobile_threshold * 1.5) {
                margin = {
                    top: 0.09 * height,
                    right: 0.12 * width,
                    bottom: 0.2 * height,
                    left: 0.1 * width
                }
            }

            typeOrder.forEach(function(d) {
                var itemClass = ".legend-item.y" + d + "> span.legend-box";
                var item = d3.select(itemClass);
                var itemColor = yearColors[d];
                var style = 'background-color:' + itemColor;
                item.attr('style', style);
            });
            //var legend = legendContainer.selectAll('span')
            //    .data(typeOrder)
            //    .enter()
            //    .append('span')
            //    .html(function(d) {
            //        return d;
            //    })
            //    .append('span')
            //    .attr("height", '10')
            //    .attr("width", '10')
            //    .html(" ")
            //    .attr('background-color', function(d) {
            //        return c10(d);
            //    });
            //
            // svg containers
            var svg = chartContainer.append("svg")
                .classed("chart", true)
                .attr("height", height)
                .attr("width", width)
                .attr("transform", "translate("+ margin.left + ", " + margin.top + ")");

            svg.call(tip);
            // take margins off of height and width
            height = (height-(margin.top + margin.bottom));
            width = (width-(margin.left + margin.right));

            // Scales
            var groupX = d3.scale.ordinal()
                .rangeRoundBands([0, width], 0.1);

            var barX = d3.scale.ordinal()
                .domain(typeOrder);

            var y = d3.scale.linear()
                .range([height, 0])
                .domain([0, 1]);

            // update x scale data
            groupX.domain(
                DATA.map(function(d) {
                    return d.key;
                })
            );

            barX.rangeRoundBands([0, groupX.rangeBand()], 0.1);
            hoverX = groupX.copy()
                .rangeRoundBands([0, width], 0);

            var chart = svg.append("g")
                .classed("chart", true)
                .attr("height", height)
                .attr("width", width)
                .attr("transform", "translate("+ margin.left + ", " + margin.top + ")");

            // axes
            var xAxis = d3.svg.axis()
                .scale(groupX)
                .orient("bottom");

            var yAxis = d3.svg.axis()
                .scale(y)
                .orient("left")
                .tickFormat(percentFormat);

            if (container_width < mobile_threshold) {
                yAxis.ticks(4);
            }

            if (container_width < mobile_threshold * 1.5) {
                xAxis.tickFormat(function(t) {
                    return "'" + t.slice(-2);
                })
            }

            var xAxis = chart.append("g")
                .attr("class", "axis xaxis")
                .attr("transform", "translate(0," + height + ")")
                .call(xAxis);

            if (container_width < mobile_threshold) {
                xAxis.call(function(g) {
                    g.selectAll("g text")
                        .attr("dy", function(d, i){
                            if (i % 2 == 1) {
                                // ODD ticks
                                return "1.75em";
                            } else {
                                // EVEN ticks
                                return "0.75em"
                            }
                        })
                })
            }

            var xAxisLabel = xAxis.append("text")
                .attr("dx", "2em")
                .attr("dy", (-1.01 * width))
                .attr("transform", "rotate(90)")
                .style("text-anchor", "end")
                .text("Income Group");

            // under certain size, move x axis label ?

            var yAxis = chart.append("g")
                .attr("class", "axis yaxis")
                .call(yAxis);

            var yAxisLabel = yAxis.append("text")
                .attr("dy", "-0.25em")
                .attr("dx", "0.5em")
                .html("Percent");

            var barGroups = chart.selectAll("g.bar-group")
                .data(DATA)
                .enter()
                .append("g")
                .attr("class", function(d) {
                    return [
                        "bar-group",
                        "_"+d.key
                    ].join(" ");
                })
                .attr("data-year", function(d) {
                    return "_"+d.key;
                })
                .attr("transform", function(d) {
                    return "translate(" + groupX(d.key) + ", 0)";
                })

            barGroups.each(function(groupData) {
                // make value labels
                var valueData = [
                    {key : "Year", values: groupData.key}
                ].concat(groupData.values);

                var incomeGroup = groupData.key;
                valueData.forEach(function(value) {
                    labelContainer.append("p")
                        .attr("class", function() {
                            return [
                                "value",
                                "_"+incomeGroup
                            ].join(" ");
                        })
                        .selectAll("span")
                        .data(function() {
                            if (value.key in typeOrder) {
                                return [value.key, percentFormat(value.values)]
                            } else {
                                return [value.key, value.values]
                            }
                        })
                        .enter()
                        .append("span")
                        .text(function(v) { return v; })
                })
            });

            //// bars within groups
            barGroups.selectAll("rect.bar")
                .data(function(d) { return d.values; })
                .enter()
                .append("rect")
                .attr("class", function(d) {
                    return [
                        "bar",
                        d.key.toLowerCase()
                    ].join(" ");
                })
                .attr("data-type", function(d) {
                    return d.key.toLowerCase();
                })
                .attr("width", barX.rangeBand())
                .attr("fill", function(d) {
                    return c10(d.key);
                })
                .attr("x", function(d) {return barX(d.key);})
                .attr("y", function(d) { return y(d.values); })
                .attr("height", function(d) { return height - y(d.values); })
                .on("mouseover",tip.show)
                .on("mouseout", tip.hide);

        }
        pymChild = new pym.Child({ renderCallback: drawChart });
    });
});<|endoftext|>