<commit_before>/**
 * @license
 * Copyright (c) 2017 Google Inc. All rights reserved.
 * This code may only be used under the BSD style license found at
 * http://polymer.github.io/LICENSE.txt
 * Code distributed by Google as part of this project is also
 * subject to an additional IP rights grant found at
 * http://polymer.github.io/PATENTS.txt
 */

 'use strict';

import {Manifest} from '../manifest.js';
import {Loader} from '../loader.js';
import {assert} from './chai-web.js';
import * as testUtil from '../testing/test-util.js';
import {TestHelper} from '../testing/test-helper.js';

describe('demo flow', function() {
  it('can load the recipe manifest', async function() {
    await Manifest.load('./shell/artifacts/Products/Products.recipes', new Loader());
  });

  it('flows like a demo', async function() {
    let helper = await TestHelper.loadManifestAndPlan('./shell/artifacts/Products/Products.recipes', {
      expectedNumPlans: 2,
      verify: async (plans) => {
        let descriptions = await Promise.all(plans.map(plan => plan.description.getRecipeSuggestion()));
        assert.include(descriptions, 'Show products from your browsing context (Minecraft Book plus 2 other items) ' +
                     'and choose from products recommended based on products from your browsing context ' +
                     'and Claire\'s wishlist (Book: How to Draw plus 2 other items).');
      },
      // Note: options below are useful to debug a failing demo-flow-test.
      // slotComposerStrict: false,
      // logging: true
    });

    // 1. Accept "Show ... and choose ... products" suggestion.
    helper.slotComposer
      .newExpectations()
        .expectRenderSlot('ShowCollection', 'master', {contentTypes: ['template']})
        .expectRenderSlot('ShowCollection', 'master', {contentTypes: ['model'], times: 3})
        .expectRenderSlot('ShowProduct', 'item', {contentTypes: ['template', 'model'], times: 3})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['template', 'model'], hostedParticle: 'ShowProduct'})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['model'], hostedParticle: 'ShowProduct', times: 2, isOptional: true})
        .expectRenderSlot('Chooser', 'action', {contentTypes: ['template', 'model']})
        .expectRenderSlot('AlsoOn', 'annotation', {contentTypes: ['template', 'model'], times: 3})
        .expectRenderSlot('Multiplexer2', 'annotation', {contentTypes: ['template']})
        .expectRenderSlot('Multiplexer2', 'annotation', {verify: helper.slotComposer.expectContentItemsNumber.bind(null, 3)})
        .expectRenderSlot('AlsoOn', 'annotation', {contentTypes: ['model'], times: 3, isOptional: true});
    await helper.acceptSuggestion({particles: ['ShowCollection', 'Multiplexer', 'Chooser', 'Recommend', 'Multiplexer2']});

    assert.equal(2, helper.arc.findStoresByType(helper.arc.context.findSchemaByName('Product').entityClass().type.collectionOf()).length);
    await helper.verifySetSize('ShowCollection', 'collection', 3);
    await helper.verifySetSize('Multiplexer', 'list', 3);
    await helper.verifySetSize('Chooser', 'choices', 3);
    helper.log('----------------------------------------');

    // Replanning.
    let expectedSuggestions = [
      'Check manufacturer information for products from your browsing context ' +
      '(Minecraft Book plus 2 other items).',
      'Buy gifts for Claire\'s Birthday on 2017-08-04, estimate arrival date for ' +
      'products from your browsing context (Minecraft Book plus 2 other items), and estimate ' +
      'arrival date for products recommended based on products from your ' +
      'browsing context and Claire\'s wishlist (Book: How to Draw plus 2 other items).',
      'Recommendations based on Claire\'s wishlist (Book: How to Draw plus 2 other items).',
      'Show Claire\'s wishlist (Book: How to Draw plus 2 other items).',
      'Find alternate shipping for products which won\'t make it on time for products from your browsing context (Minecraft Book plus 2 other items).',
      // TODO: consider whether the 'showList' recipe should resolve to these suggestions?
      // 'Show products from your browsing context (Minecraft Book plus 2 other items).',
      // 'Show products recommended based on products from your browsing context and Claire\'s wishlist (Book: How to Draw plus 2 other items).'
    ];
    await helper.makePlans({expectedNumPlans: 5, expectedSuggestions});
    helper.log('----------------------------------------');

    // 2. Move an element from recommended list to shortlist.
    let verifyShowCollection = (num, content) => {
      assert(content.model, `Content doesn't have model`);
      assert(content.model.items, `Content model doesn't have items, but expected ${num}.`);
      return content.model.items.length == num && content.model.items.every(i => !!i.resolvedImage);
    };
    helper.slotComposer
      .newExpectations()
        .expectRenderSlot('ShowCollection', 'master', {contentTypes: ['model']})
        .expectRenderSlot('Multiplexer', 'annotation', {verify: verifyShowCollection.bind(null, 4), hostedParticle: 'ShowProduct'})
        .expectRenderSlot('ShowProduct', 'item', {contentTypes: ['model']})
        .expectRenderSlot('Chooser', 'action', {verify: helper.slotComposer.expectContentItemsNumber.bind(null, 2)})
        .expectRenderSlot('AlsoOn', 'annotation', {contentTypes: ['model']})
        .expectRenderSlot('Multiplexer2', 'annotation', {verify: helper.slotComposer.expectContentItemsNumber.bind(null, 4)});
    await helper.sendSlotEvent('Chooser', 'action', '_onChooseValue', {key: '1'});
    await helper.verifySetSize('ShowCollection', 'collection', 4);
    await helper.verifySetSize('Multiplexer', 'list', 4);
    await helper.verifySetSize('Chooser', 'choices', 3);
    helper.log('----------------------------------------');

    // Replanning.
    await helper.makePlans({
      expectedNumPlans: 5,
      expectedSuggestions: expectedSuggestions.map(suggestion => {
        return suggestion.replace('Minecraft Book plus 2 other items', 'Minecraft Book plus 3 other items');
      })
    });
    helper.log('----------------------------------------');

    // 3. Select "Buy gift ... and estimate arrival dates ..." suggestion
    helper.slotComposer
      .newExpectations()
        .expectRenderSlot('GiftList', 'preamble', {contentTypes: ['template', 'model']})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['template'], times: 2})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['model'], times: 7})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['model'], times: 7, isOptional: true})
        .expectRenderSlot('Arrivinator', 'annotation', {contentTypes: ['template', 'templateName', 'model'], times: 4})
        .expectRenderSlot('Arrivinator', 'annotation', {contentTypes: ['template'], times: 3, isOptional: true})
        .expectRenderSlot('Arrivinator', 'annotation', {contentTypes: ['templateName', 'model'], times: 3});
    await helper.acceptSuggestion({particles: ['GiftList', 'Multiplexer', 'Multiplexer']});
    await helper.idle();
    helper.log('----------------------------------------');

    // 4. Move another element from recommended list to shortlist.
    helper.slotComposer
      .newExpectations()
        .expectRenderSlot('ShowCollection', 'master', {contentTypes: ['model']})
        .expectRenderSlot('Multiplexer', 'annotation', {hostedParticle: 'ShowProduct', verify: verifyShowCollection.bind(null, 5)})
        .expectRenderSlot('ShowProduct', 'item', {contentTypes: ['model']})
        .expectRenderSlot('Chooser', 'action', {verify: helper.slotComposer.expectContentItemsNumber.bind(null, 1)})
        .expectRenderSlot('AlsoOn', 'annotation', {contentTypes: ['model']})
        .expectRenderSlot('Multiplexer2', 'annotation', {verify: helper.slotComposer.expectContentItemsNumber.bind(null, 5)})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['model'], times: 2, hostedParticle: 'Arrivinator'})
        .expectRenderSlot('Arrivinator', 'annotation', {contentTypes: ['model']});
    await helper.sendSlotEvent('Chooser', 'action', '_onChooseValue', {key: '1'});
    await helper.verifySetSize('ShowCollection', 'collection', 5);
    await helper.verifySetSize('Multiplexer', 'list', 5);
    await helper.verifySetSize('Chooser', 'choices', 3);
    helper.log('----------------------------------------');

    // 5. Select "Check manufacturer information..." suggestion
    await helper.makePlans({expectedNumPlans: 4});
    helper.slotComposer
      .newExpectations()
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['template'], hostedParticle: 'ManufacturerInfo'})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['model'], times: 5, hostedParticle: 'ManufacturerInfo'})
        .expectRenderSlot('ManufacturerInfo', 'annotation', {contentTypes: ['template', 'model'], times: 5});
    await helper.acceptSuggestion({particles: ['Multiplexer'], hostedParticles: ['ManufacturerInfo']});
    helper.log('----------------------------------------');

    // 6. Move the last element to shortlist.
    helper.slotComposer
      .newExpectations()
        .expectRenderSlot('ShowCollection', 'master', {contentTypes: ['model']})
        .expectRenderSlot('Multiplexer', 'annotation', {verify: verifyShowCollection.bind(null, 6), hostedParticle: 'ShowProduct'})
        .expectRenderSlot('ShowProduct', 'item', {contentTypes: ['model']})
        .expectRenderSlot('Chooser', 'action', {verify: (content) => !content.model})
        .expectRenderSlot('Multiplexer2', 'annotation', {verify: helper.slotComposer.expectContentItemsNumber.bind(null, 6)})
        .expectRenderSlot('AlsoOn', 'annotation', {contentTypes: ['model']})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['model'], times: 2, hostedParticle: 'Arrivinator'})
        .expectRenderSlot('Arrivinator', 'annotation', {contentTypes: ['model']})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['model'], times: 2, hostedParticle: 'ManufacturerInfo'})
        .expectRenderSlot('ManufacturerInfo', 'annotation', {contentTypes: ['model']});
    await helper.sendSlotEvent('Chooser', 'action', '_onChooseValue', {key: '0'});
    await helper.verifySetSize('ShowCollection', 'collection', 6);
    await helper.verifySetSize('Multiplexer', 'list', 6);
    await helper.verifySetSize('Chooser', 'choices', 3);
    helper.log('----------------------------------------');

    // 7. Accept 'Recommendations based on...' suggestion
    await helper.makePlans({expectedNumPlans: 3});

    helper.slotComposer
      .newExpectations()
      .expectRenderSlot('Interests', 'postamble', {contentTypes: ['template', 'model']});
    await helper.acceptSuggestion({particles: ['Interests']});
    await helper.makePlans({expectedNumPlans: 2});
    helper.log('----------------------------------------');

    // TODO(mmandlis): Provide methods in helper to verify slot contents (helper.slotComposer._slots[i]._content).
  }).timeout(5000);
});
<commit_msg>Relax demo-flow test slots expectations. (#1481)<commit_after>/**
 * @license
 * Copyright (c) 2017 Google Inc. All rights reserved.
 * This code may only be used under the BSD style license found at
 * http://polymer.github.io/LICENSE.txt
 * Code distributed by Google as part of this project is also
 * subject to an additional IP rights grant found at
 * http://polymer.github.io/PATENTS.txt
 */

 'use strict';

import {Manifest} from '../manifest.js';
import {Loader} from '../loader.js';
import {assert} from './chai-web.js';
import * as testUtil from '../testing/test-util.js';
import {TestHelper} from '../testing/test-helper.js';

describe('demo flow', function() {
  it('can load the recipe manifest', async function() {
    await Manifest.load('./shell/artifacts/Products/Products.recipes', new Loader());
  });

  it('flows like a demo', async function() {
    let helper = await TestHelper.loadManifestAndPlan('./shell/artifacts/Products/Products.recipes', {
      expectedNumPlans: 2,
      verify: async (plans) => {
        let descriptions = await Promise.all(plans.map(plan => plan.description.getRecipeSuggestion()));
        assert.include(descriptions, 'Show products from your browsing context (Minecraft Book plus 2 other items) ' +
                     'and choose from products recommended based on products from your browsing context ' +
                     'and Claire\'s wishlist (Book: How to Draw plus 2 other items).');
      },
      // Note: options below are useful to debug a failing demo-flow-test.
      // slotComposerStrict: false,
      // logging: true
    });

    // 1. Accept "Show ... and choose ... products" suggestion.
    helper.slotComposer
      .newExpectations()
        .expectRenderSlot('ShowCollection', 'master', {contentTypes: ['template']})
        .expectRenderSlot('ShowCollection', 'master', {contentTypes: ['model'], times: 3})
        .expectRenderSlot('ShowProduct', 'item', {contentTypes: ['template', 'model']})
        .expectRenderSlot('ShowProduct', 'item', {contentTypes: ['model'], times: 2})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['template', 'model'], hostedParticle: 'ShowProduct'})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['model'], hostedParticle: 'ShowProduct', times: 2, isOptional: true})
        .expectRenderSlot('Chooser', 'action', {contentTypes: ['template', 'model']})
        // TODO: investigate why this is called - happens if the first Chooser render happens after
        // ShowCollection is fully rendered (if Chooser has the opportunity to render before last the ShowCollection.model
        // it is not called 2nd time).
        .expectRenderSlot('Chooser', 'action', {contentTypes: ['model'], isOptional: true})
        .expectRenderSlot('AlsoOn', 'annotation', {contentTypes: ['template', 'model']})
        .expectRenderSlot('AlsoOn', 'annotation', {contentTypes: ['model'], times: 2})
        .expectRenderSlot('Multiplexer2', 'annotation', {contentTypes: ['template']})
        .expectRenderSlot('Multiplexer2', 'annotation', {verify: helper.slotComposer.expectContentItemsNumber.bind(null, 3)})
        // TODO: the optional Multiplexer2 call only appears if the optional AlsoOn calls happen.
        // But there is no way to currently express this dependency with the mock-slot-composer.
        .expectRenderSlot('AlsoOn', 'annotation', {contentTypes: ['model'], times: 3, isOptional: true})
        .expectRenderSlot('Multiplexer2', 'annotation', {verify: helper.slotComposer.expectContentItemsNumber.bind(null, 3), isOptional: true});
    await helper.acceptSuggestion({particles: ['ShowCollection', 'Multiplexer', 'Chooser', 'Recommend', 'Multiplexer2']});

    assert.equal(2, helper.arc.findStoresByType(helper.arc.context.findSchemaByName('Product').entityClass().type.collectionOf()).length);
    await helper.verifySetSize('ShowCollection', 'collection', 3);
    await helper.verifySetSize('Multiplexer', 'list', 3);
    await helper.verifySetSize('Chooser', 'choices', 3);
    helper.log('----------------------------------------');

    // Replanning.
    let expectedSuggestions = [
      'Check manufacturer information for products from your browsing context ' +
      '(Minecraft Book plus 2 other items).',
      'Buy gifts for Claire\'s Birthday on 2017-08-04, estimate arrival date for ' +
      'products from your browsing context (Minecraft Book plus 2 other items), and estimate ' +
      'arrival date for products recommended based on products from your ' +
      'browsing context and Claire\'s wishlist (Book: How to Draw plus 2 other items).',
      'Recommendations based on Claire\'s wishlist (Book: How to Draw plus 2 other items).',
      'Show Claire\'s wishlist (Book: How to Draw plus 2 other items).',
      'Find alternate shipping for products which won\'t make it on time for products from your browsing context (Minecraft Book plus 2 other items).',
      // TODO: consider whether the 'showList' recipe should resolve to these suggestions?
      // 'Show products from your browsing context (Minecraft Book plus 2 other items).',
      // 'Show products recommended based on products from your browsing context and Claire\'s wishlist (Book: How to Draw plus 2 other items).'
    ];
    await helper.makePlans({expectedNumPlans: 5, expectedSuggestions});
    helper.log('----------------------------------------');

    // 2. Move an element from recommended list to shortlist.
    let verifyShowCollection = (num, content) => {
      assert(content.model, `Content doesn't have model`);
      assert(content.model.items, `Content model doesn't have items, but expected ${num}.`);
      return content.model.items.length == num && content.model.items.every(i => !!i.resolvedImage);
    };
    helper.slotComposer
      .newExpectations()
        .expectRenderSlot('ShowCollection', 'master', {contentTypes: ['model']})
        .expectRenderSlot('Multiplexer', 'annotation', {verify: verifyShowCollection.bind(null, 4), hostedParticle: 'ShowProduct'})
        .expectRenderSlot('ShowProduct', 'item', {contentTypes: ['model']})
        .expectRenderSlot('Chooser', 'action', {verify: helper.slotComposer.expectContentItemsNumber.bind(null, 2)})
        .expectRenderSlot('AlsoOn', 'annotation', {contentTypes: ['model']})
        .expectRenderSlot('Multiplexer2', 'annotation', {verify: helper.slotComposer.expectContentItemsNumber.bind(null, 4)});
    await helper.sendSlotEvent('Chooser', 'action', '_onChooseValue', {key: '1'});
    await helper.verifySetSize('ShowCollection', 'collection', 4);
    await helper.verifySetSize('Multiplexer', 'list', 4);
    await helper.verifySetSize('Chooser', 'choices', 3);
    helper.log('----------------------------------------');

    // Replanning.
    await helper.makePlans({
      expectedNumPlans: 5,
      expectedSuggestions: expectedSuggestions.map(suggestion => {
        return suggestion.replace('Minecraft Book plus 2 other items', 'Minecraft Book plus 3 other items');
      })
    });
    helper.log('----------------------------------------');

    // 3. Select "Buy gift ... and estimate arrival dates ..." suggestion
    helper.slotComposer
      .newExpectations()
        .expectRenderSlot('GiftList', 'preamble', {contentTypes: ['template', 'model']})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['template'], times: 1})
        // TODO: add support in mock-slot-composer for {verify:helper.slotComposer.expectContentItemsNumber.bind(null, N)}
        // for both multiplexers.
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['model'], times: 2})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['model'], times: 7 + 5, isOptional: true})
        .expectRenderSlot('Arrivinator', 'annotation', {contentTypes: ['template', 'templateName', 'model'], times: 4})
        .expectRenderSlot('Arrivinator', 'annotation', {contentTypes: ['template'], times: 3, isOptional: true})
        .expectRenderSlot('Arrivinator', 'annotation', {contentTypes: ['templateName', 'model'], times: 3});
    await helper.acceptSuggestion({particles: ['GiftList', 'Multiplexer', 'Multiplexer']});
    await helper.idle();
    helper.log('----------------------------------------');

    // 4. Move another element from recommended list to shortlist.
    helper.slotComposer
      .newExpectations()
        .expectRenderSlot('ShowCollection', 'master', {contentTypes: ['model']})
        .expectRenderSlot('Multiplexer', 'annotation', {hostedParticle: 'ShowProduct', verify: verifyShowCollection.bind(null, 5)})
        .expectRenderSlot('ShowProduct', 'item', {contentTypes: ['model']})
        .expectRenderSlot('Chooser', 'action', {verify: helper.slotComposer.expectContentItemsNumber.bind(null, 1)})
        .expectRenderSlot('AlsoOn', 'annotation', {contentTypes: ['model']})
        .expectRenderSlot('Multiplexer2', 'annotation', {verify: helper.slotComposer.expectContentItemsNumber.bind(null, 5)})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['model'], times: 2, hostedParticle: 'Arrivinator'})
        .expectRenderSlot('Arrivinator', 'annotation', {contentTypes: ['model']});
    await helper.sendSlotEvent('Chooser', 'action', '_onChooseValue', {key: '1'});
    await helper.verifySetSize('ShowCollection', 'collection', 5);
    await helper.verifySetSize('Multiplexer', 'list', 5);
    await helper.verifySetSize('Chooser', 'choices', 3);
    helper.log('----------------------------------------');

    // 5. Select "Check manufacturer information..." suggestion
    await helper.makePlans({expectedNumPlans: 4});
    helper.slotComposer
      .newExpectations()
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['template'], hostedParticle: 'ManufacturerInfo'})
        .expectRenderSlot('Multiplexer', 'annotation', {hostedParticle: 'ManufacturerInfo', verify: helper.slotComposer.expectContentItemsNumber.bind(null, 5)})
        .expectRenderSlot('ManufacturerInfo', 'annotation', {contentTypes: ['template', 'model'], times: 5});
    await helper.acceptSuggestion({particles: ['Multiplexer'], hostedParticles: ['ManufacturerInfo']});
    helper.log('----------------------------------------');

    // 6. Move the last element to shortlist.
    helper.slotComposer
      .newExpectations()
        .expectRenderSlot('ShowCollection', 'master', {contentTypes: ['model']})
        .expectRenderSlot('Multiplexer', 'annotation', {verify: verifyShowCollection.bind(null, 6), hostedParticle: 'ShowProduct'})
        .expectRenderSlot('ShowProduct', 'item', {contentTypes: ['model']})
        .expectRenderSlot('Chooser', 'action', {verify: (content) => !content.model})
        .expectRenderSlot('Multiplexer2', 'annotation', {verify: helper.slotComposer.expectContentItemsNumber.bind(null, 6)})
        .expectRenderSlot('AlsoOn', 'annotation', {contentTypes: ['model']})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['model'], times: 2, hostedParticle: 'Arrivinator'})
        .expectRenderSlot('Arrivinator', 'annotation', {contentTypes: ['model']})
        .expectRenderSlot('Multiplexer', 'annotation', {contentTypes: ['model'], times: 2, hostedParticle: 'ManufacturerInfo'})
        .expectRenderSlot('ManufacturerInfo', 'annotation', {contentTypes: ['model']});
    await helper.sendSlotEvent('Chooser', 'action', '_onChooseValue', {key: '0'});
    await helper.verifySetSize('ShowCollection', 'collection', 6);
    await helper.verifySetSize('Multiplexer', 'list', 6);
    await helper.verifySetSize('Chooser', 'choices', 3);
    helper.log('----------------------------------------');

    // 7. Accept 'Recommendations based on...' suggestion
    await helper.makePlans({expectedNumPlans: 3});

    helper.slotComposer
      .newExpectations()
      .expectRenderSlot('Interests', 'postamble', {contentTypes: ['template', 'model']});
    await helper.acceptSuggestion({particles: ['Interests']});
    await helper.makePlans({expectedNumPlans: 2});
    helper.log('----------------------------------------');

    // TODO(mmandlis): Provide methods in helper to verify slot contents (helper.slotComposer._slots[i]._content).
  }).timeout(5000);
});
<|endoftext|>


<commit_before>var Botox = {

  __loading: false,
  __history: {},
  __rootUrl: "",
  __currentState: {},
  __currentTarget: {},
  __currentData: {},
  __previousData: {},

  init: function(){
    Botox.__history = window.History;
    if ( !History.enabled ) {
      // bail if history is not supported
      return false;
    }

    // get the root url of the site
    Botox.__rootUrl = History.getRootUrl();

    // assign the click handler to appropriate links
    $(document).on('click', 'a:not(.no-botox):not([data-remote])', Botox.handleUrl);

    // respond to state changes by loading the requested page through xhr
    $(window).bind('statechange', Botox.stateChange);

    // we're ready
    $(Botox).trigger('BotoxReady');

  },

  handleUrl: function(e) {

    // get the url to load
    var url = $(this).attr('href');
    var title = $(this).attr('title') || null;

    // no xhr if the link is not internal
    if(!(url.substring(0,Botox.__rootUrl.length) === Botox.__rootUrl || url.indexOf(':') === -1)) {
      return true;
    }

    // no xhr if the link was cmd-clicked
    if ( e.which == 2 || e.metaKey ) {
      return true;
    }

	// no xhr if the link points to an item that is served from the systems folder
	if (url.indexOf("/system/") == 0) {
		return true;
	}

    // we're good, xhr this link
    History.pushState(null,title,url);

    // remember the element that triggered this load
    Botox.__currentTarget = e.currentTarget;

    // trigger an event that a state has been pushed
    $(Botox).trigger('BotoxStatePushed', url);

    // prevent the browser from handling the link
    e.preventDefault();
    return false;
  },

  stateChange: function(e) {
    // we'll be loading soon
    $(Botox).trigger('BotoxDataWillLoad', Botox.__currentState.url);
    Botox.setLoading(true);
    Botox.__currentState = Botox.__history.getState();
    Botox.loadRequest(Botox.__currentState.url, Botox.requestLoaded);
  },

  loadRequest: function(url, callback) {
    // perform the request
    $.getJSON(url, callback).error(Botox.requestError);
  },

  requestLoaded: function(response, textStatus, jqXHR) {
    // loading has completed
    Botox.setLoading(false);

    // set new values
    Botox.__previousData = Botox.__currentData;
    Botox.__currentData = response;

    // let the world know we're done, and pass along the current and last responses
    $(Botox).trigger('BotoxDataDidLoad', [Botox.__currentData, Botox.__previousData]);
  },

  requestError: function(jqXHR, textStatus, errorThrown) {
    $(Botox).trigger('BotoxDataLoadError', Botox.__currentState.url);
    return false;
  },

  setLoading: function(loading) {
    loading = (loading==true) ? true : false;
    Botox.__loading = loading;
  }

};
<commit_msg>fire event before pushing the state<commit_after>var Botox = {

  __loading: false,
  __history: {},
  __rootUrl: "",
  __currentState: {},
  __currentTarget: {},
  __currentData: {},
  __previousData: {},

  init: function(){
    Botox.__history = window.History;
    if ( !History.enabled ) {
      // bail if history is not supported
      return false;
    }

    // get the root url of the site
    Botox.__rootUrl = History.getRootUrl();

    // assign the click handler to appropriate links
    $(document).on('click', 'a:not(.no-botox):not([data-remote])', Botox.handleUrl);

    // respond to state changes by loading the requested page through xhr
    $(window).bind('statechange', Botox.stateChange);

    // we're ready
    $(Botox).trigger('BotoxReady');

  },

  handleUrl: function(e) {

    // get the url to load
    var url = $(this).attr('href');
    var title = $(this).attr('title') || null;

    // no xhr if the link is not internal
    if(!(url.substring(0,Botox.__rootUrl.length) === Botox.__rootUrl || url.indexOf(':') === -1)) {
      return true;
    }

    // no xhr if the link was cmd-clicked
    if ( e.which == 2 || e.metaKey ) {
      return true;
    }

	// no xhr if the link points to an item that is served from the systems folder
	if (url.indexOf("/system/") == 0) {
		return true;
	}

    // trigger an event before the state will be pushed
    $(Botox).trigger('BotoxStateWillPush', url);

    // we're good, xhr this link
    History.pushState(null,title,url);

    // remember the element that triggered this load
    Botox.__currentTarget = e.currentTarget;

    // trigger an event that a state has been pushed
    $(Botox).trigger('BotoxStatePushed', url);

    // prevent the browser from handling the link
    e.preventDefault();
    return false;
  },

  stateChange: function(e) {
    // we'll be loading soon
    $(Botox).trigger('BotoxDataWillLoad', Botox.__currentState.url);
    Botox.setLoading(true);
    Botox.__currentState = Botox.__history.getState();
    Botox.loadRequest(Botox.__currentState.url, Botox.requestLoaded);
  },

  loadRequest: function(url, callback) {
    // perform the request
    $.getJSON(url, callback).error(Botox.requestError);
  },

  requestLoaded: function(response, textStatus, jqXHR) {
    // loading has completed
    Botox.setLoading(false);

    // set new values
    Botox.__previousData = Botox.__currentData;
    Botox.__currentData = response;

    // let the world know we're done, and pass along the current and last responses
    $(Botox).trigger('BotoxDataDidLoad', [Botox.__currentData, Botox.__previousData]);
  },

  requestError: function(jqXHR, textStatus, errorThrown) {
    $(Botox).trigger('BotoxDataLoadError', Botox.__currentState.url);
    return false;
  },

  setLoading: function(loading) {
    loading = (loading==true) ? true : false;
    Botox.__loading = loading;
  }

};
<|endoftext|>


<commit_before>/**
 * @fileoverview Contains code defining the IncidentList class.
 * @requires jQuery
 */

/**
 * Creates a list of CHP Incidents from CHP Incident data.
 * @class Represents a set of CHP Incidents.
 * @this IncidentList
 * @param {Array} data An array of CHP Incidents from SacTraffic.org.
 */
var IncidentList = function (element) {
  this.element = element;
  this.incidents = {};
};

/**
 * Gets an Incident by it's ID.
 * @param {String} id The Incident's ID.
 * @returns {Incident}
 */
IncidentList.prototype.getIncident = function(id) {
  return this.incidents[id];
};

/**
 * Add an Incident to the IncidentList.
 * @param {Incident} incident The Incident to add.
 * @fires IncidentList#st_new_incident
 * @fires IncidentList#st_update_incident
 */
IncidentList.prototype.addIncident = function (incident) {
  if (typeof this.incidents[incident.ID] === 'undefined') {
    /**
     * A new Incident event.
     * @event IncidentList#st_new_incident
     * @param {Incident} incident The incident created.
     */
    $(this).trigger('st_new_incident', [incident]);
  } else {
    /**
     * An updated Incident event.
     * @event IncidentList#st_update_incident
     * @param {Incident} incident The incident updated.
     */
    $(this).trigger('st_update_incident', [incident]);
  }

  this.incidents[incident.ID] = incident;
};

/**
 * Remove an Incident from the IncidentList.
 * @param {String} id The Incident id to remove.
 * @fires IncidentList#st_delete_incident
 */
IncidentList.prototype.removeIncident = function (id) {
  delete this.incidents[id];

  /**
   * An Incident delete event.
   * @event IncidentList#st_delete_incident
   * @param {String} id The incident ID deleted.
   */
  $(this).trigger('st_delete_incident', [id]);
};

/**
 * Test is a given ID is in the IncidentList.
 * @param {String} id The Incident id to remove.
 * @returns {boolean}
 */
IncidentList.prototype.containsId = function (id) {
  return (Object.keys(this.incidents).indexOf(id) === -1) ? false : true;
};

/**
 * Returns the number of incidents in the IncidentList.
 * @returns {number}
 */
IncidentList.prototype.size = function () {
  var size = 0;
  for (var incident_id in this.incidents) {
    size++;
  }
  return size;
};

/**
 * Gets the bounds of the Incidents in the IncidentList.
 * @returns {Object}
 */
IncidentList.prototype.getBounds = function () {
  var lats = [];
  var lons = [];

  for (var incident_id in this.incidents) {
    var incident = this.incidents[incident_id];

    if (incident.geolocation) {
      lats.push(incident.geolocation.lat);
      lons.push(incident.geolocation.lon);
    }
  }

  return {
    sw: {
      lat: lats.min(),
      lon: lons.min()
    },
    ne: {
      lat: lats.max(),
      lon: lons.max()
    }
  };
};

/**
 * Udpates the IncidentList with new data from the server.
 * @param {Object} incident_data the server data.
 */
IncidentList.prototype.update = function (incident_data) {
  var update_ids = [];

  // Incidents arrive in decending chronological order. Reverse them so we
  // can prepend them in the DOM.
  incident_data.reverse();

  // Add or update existing incidents
  for (var x = 0; x < incident_data.length; x++) {
    var incident = new Incident(incident_data[x]);

    // Skip Silver and Amber Alerts...
    var alert_re = /(SILVER|AMBER) Alert/i;
    if (alert_re.test(incident.LogType)) continue;

    update_ids.push(incident.ID);
    this.addIncident(incident);
  }

  // Remove incidents we no longer have
  for (var id in this.incidents) {
    if (update_ids.indexOf(id) === -1) {
      this.removeIncident(id);
    }
  }
};
<commit_msg>Match JSDoc with reality.<commit_after>/**
 * @fileoverview Contains code defining the IncidentList class.
 * @requires jQuery
 */

/**
 * Creates a list of CHP Incidents from CHP Incident data.
 * @class Represents a set of CHP Incidents.
 * @this IncidentList
 * @param {jQuery} [element] An optional jQuery element to build the list in.
 */
var IncidentList = function (element) {
  this.element = element;
  this.incidents = {};
};

/**
 * Gets an Incident by it's ID.
 * @param {String} id The Incident's ID.
 * @returns {Incident}
 */
IncidentList.prototype.getIncident = function(id) {
  return this.incidents[id];
};

/**
 * Add an Incident to the IncidentList.
 * @param {Incident} incident The Incident to add.
 * @fires IncidentList#st_new_incident
 * @fires IncidentList#st_update_incident
 */
IncidentList.prototype.addIncident = function (incident) {
  if (typeof this.incidents[incident.ID] === 'undefined') {
    /**
     * A new Incident event.
     * @event IncidentList#st_new_incident
     * @param {Incident} incident The incident created.
     */
    $(this).trigger('st_new_incident', [incident]);
  } else {
    /**
     * An updated Incident event.
     * @event IncidentList#st_update_incident
     * @param {Incident} incident The incident updated.
     */
    $(this).trigger('st_update_incident', [incident]);
  }

  this.incidents[incident.ID] = incident;
};

/**
 * Remove an Incident from the IncidentList.
 * @param {String} id The Incident id to remove.
 * @fires IncidentList#st_delete_incident
 */
IncidentList.prototype.removeIncident = function (id) {
  delete this.incidents[id];

  /**
   * An Incident delete event.
   * @event IncidentList#st_delete_incident
   * @param {String} id The incident ID deleted.
   */
  $(this).trigger('st_delete_incident', [id]);
};

/**
 * Test is a given ID is in the IncidentList.
 * @param {String} id The Incident id to remove.
 * @returns {boolean}
 */
IncidentList.prototype.containsId = function (id) {
  return (Object.keys(this.incidents).indexOf(id) === -1) ? false : true;
};

/**
 * Returns the number of incidents in the IncidentList.
 * @returns {number}
 */
IncidentList.prototype.size = function () {
  var size = 0;
  for (var incident_id in this.incidents) {
    size++;
  }
  return size;
};

/**
 * Gets the bounds of the Incidents in the IncidentList.
 * @returns {Object}
 */
IncidentList.prototype.getBounds = function () {
  var lats = [];
  var lons = [];

  for (var incident_id in this.incidents) {
    var incident = this.incidents[incident_id];

    if (incident.geolocation) {
      lats.push(incident.geolocation.lat);
      lons.push(incident.geolocation.lon);
    }
  }

  return {
    sw: {
      lat: lats.min(),
      lon: lons.min()
    },
    ne: {
      lat: lats.max(),
      lon: lons.max()
    }
  };
};

/**
 * Udpates the IncidentList with new data from the server.
 * @param {Object} incident_data the server data.
 */
IncidentList.prototype.update = function (incident_data) {
  var update_ids = [];

  // Incidents arrive in decending chronological order. Reverse them so we
  // can prepend them in the DOM.
  incident_data.reverse();

  // Add or update existing incidents
  for (var x = 0; x < incident_data.length; x++) {
    var incident = new Incident(incident_data[x]);

    // Skip Silver and Amber Alerts...
    var alert_re = /(SILVER|AMBER) Alert/i;
    if (alert_re.test(incident.LogType)) continue;

    update_ids.push(incident.ID);
    this.addIncident(incident);
  }

  // Remove incidents we no longer have
  for (var id in this.incidents) {
    if (update_ids.indexOf(id) === -1) {
      this.removeIncident(id);
    }
  }
};
<|endoftext|>


<commit_before>import EX from "../ex.js";
import ModeMenu from "./mode_menu.js";
import Sidebar from "./sidebar.js";
import Post from "../post.js";
import Posts from "./posts.js";
import Tag from "../tag.js";

import _ from "lodash";
import moment from "moment";

export default class PreviewPanel {
  static initialize() {
    // This is the main content panel that comes before the preview panel.
    let $content = $(`
      #c-posts #content,
      #c-post-appeals #a-index,
      #c-post-flags #a-index,
      #c-post-versions #a-index,
      #c-explore-posts > div,
      #c-notes #a-index,
      #c-pools #a-gallery,
      #c-pools #a-show,
      #c-comments #a-index,
      #c-moderator-post-queues #a-show,
      #c-users #a-show,
      #c-wiki-pages > div > #content,
      #c-wiki-page-versions > div > #content
    `);

    if ($content.length === 0) {
      return;
    }

    $content.parent().addClass("ex-panel-container");
    $content.addClass("ex-content-panel ex-panel");
    $content.after(`

            No image selected. Click a thumbnail to open image preview.

    `);

    // XXX: sometimes a huge offset is calculated. don't know why.
    // PreviewPanel.origTop = $("#ex-preview-panel > div").offset().top;
    PreviewPanel.origTop = 127;

    const width = _.defaultTo(EX.config.previewPanelState[EX.config.pageKey()], EX.config.defaultPreviewPanelWidth);
    PreviewPanel.setWidth(width);
    PreviewPanel.setHeight();
    PreviewPanel.save();

    if (ModeMenu.getMode() === "view") {
      PreviewPanel.$panel.hide();
    }

    if (PreviewPanel.opened()) {
      Sidebar.close();
    }

    $(document).scroll(_.throttle(PreviewPanel.setHeight, 16));
    $('.ex-mode-menu select[name="mode"]').change(PreviewPanel.switchMode);
    $("#ex-preview-panel-resizer").draggable({
      axis: "x",
      helper: "clone",
      drag: _.throttle(PreviewPanel.resize, 16),
      stop: _.debounce(PreviewPanel.save, 100),
    });
  }

  static async update($post) {
    const postId = $post.data("id");
    const post = await Post.get(postId);
    const html = PreviewPanel.renderPost(post);

    $("#ex-preview-panel > div").children().first().replaceWith(html);
  }

  static get $panel() {
    return $("#ex-preview-panel");
  }

  static resize(e, ui) {
    // XXX magic number
    PreviewPanel.setWidth($("body").innerWidth() - ui.position.left - 28);
  }

  static save() {
    let state = EX.config.previewPanelState;
    state[EX.config.pageKey()] = PreviewPanel.$panel.width();
    EX.config.previewPanelState = state;
  }

  static opened() {
    return PreviewPanel.$panel.is(":visible") && PreviewPanel.$panel.width() > 0;
  }

  static open() {
    if (PreviewPanel.$panel.width() === 0) {
      PreviewPanel.setWidth(EX.config.defaultPreviewPanelWidth);
    }

    PreviewPanel.$panel.show({ effect: "slide", direction: "left" }).promise().then(PreviewPanel.save);
    Sidebar.close();
  }

  static close() {
    PreviewPanel.$panel.hide({ effect: "slide", direction: "right" }).promise().then(PreviewPanel.save);
    Sidebar.open();
  }

  static switchMode() {
    if (ModeMenu.getMode() === "view") {
      PreviewPanel.close();
    } else {
      PreviewPanel.open();
    }
  }

  static setWidth(width) {
    PreviewPanel.$panel.width(width);
    PreviewPanel.$panel.css({ flex: `0 0 ${width}px` });
    $("#ex-preview-panel > div").width(width);
  }

  static setHeight() {
    const headerHeight = $("#ex-header").outerHeight(true);
    const footerHeight = $("footer").outerHeight(true);

    let height;
    if (window.scrollY + headerHeight >= PreviewPanel.origTop) {
      $("#ex-preview-panel > div").addClass("ex-fixed").css({ top: headerHeight });
      height = `calc(100vh - ${headerHeight}px)`;
    } else {
      $("#ex-preview-panel > div").removeClass("ex-fixed");
      height = `calc(100vh - ${PreviewPanel.origTop - window.scrollY}px)`;
    }

    const diff = window.scrollY + window.innerHeight + footerHeight - $("body").height();
    if (diff >= 0) {
      height = `calc(100vh - ${headerHeight}px - ${diff}px)`;
    }

    $("#ex-preview-panel > div").css({ height });
    $("#ex-preview-panel > div > article.post-preview .post-media").css({ "max-height": height });
  }

  static renderPost(post) {
    return `

Score

                ${post.fav_count}

                ${post.score}

User

              ${_.escape(post.uploader_name)}

              <time class="created-at ex-short-relative-time" datetime="${post.created_at}" title="${moment(post.created_at).format()}">
                ${moment(post.created_at).locale("en-short").fromNow()}
              </time>

Rating

              ${post.pretty_rating}

Source

              ${post.source_link}

Size

              ${post.image_width}x${post.image_height}

            ${Tag.renderTagList(post.tags, "ex-tag-list-inline")}

          ${Posts.preview(post, { size: "large", classes: [ "ex-no-tooltip" ] })}

    `;
  }
}
<commit_msg>[fix] Use compact rating in preview panel.<commit_after>import EX from "../ex.js";
import ModeMenu from "./mode_menu.js";
import Sidebar from "./sidebar.js";
import Post from "../post.js";
import Posts from "./posts.js";
import Tag from "../tag.js";

import _ from "lodash";
import moment from "moment";

export default class PreviewPanel {
  static initialize() {
    // This is the main content panel that comes before the preview panel.
    let $content = $(`
      #c-posts #content,
      #c-post-appeals #a-index,
      #c-post-flags #a-index,
      #c-post-versions #a-index,
      #c-explore-posts > div,
      #c-notes #a-index,
      #c-pools #a-gallery,
      #c-pools #a-show,
      #c-comments #a-index,
      #c-moderator-post-queues #a-show,
      #c-users #a-show,
      #c-wiki-pages > div > #content,
      #c-wiki-page-versions > div > #content
    `);

    if ($content.length === 0) {
      return;
    }

    $content.parent().addClass("ex-panel-container");
    $content.addClass("ex-content-panel ex-panel");
    $content.after(`

            No image selected. Click a thumbnail to open image preview.

    `);

    // XXX: sometimes a huge offset is calculated. don't know why.
    // PreviewPanel.origTop = $("#ex-preview-panel > div").offset().top;
    PreviewPanel.origTop = 127;

    const width = _.defaultTo(EX.config.previewPanelState[EX.config.pageKey()], EX.config.defaultPreviewPanelWidth);
    PreviewPanel.setWidth(width);
    PreviewPanel.setHeight();
    PreviewPanel.save();

    if (ModeMenu.getMode() === "view") {
      PreviewPanel.$panel.hide();
    }

    if (PreviewPanel.opened()) {
      Sidebar.close();
    }

    $(document).scroll(_.throttle(PreviewPanel.setHeight, 16));
    $('.ex-mode-menu select[name="mode"]').change(PreviewPanel.switchMode);
    $("#ex-preview-panel-resizer").draggable({
      axis: "x",
      helper: "clone",
      drag: _.throttle(PreviewPanel.resize, 16),
      stop: _.debounce(PreviewPanel.save, 100),
    });
  }

  static async update($post) {
    const postId = $post.data("id");
    const post = await Post.get(postId);
    const html = PreviewPanel.renderPost(post);

    $("#ex-preview-panel > div").children().first().replaceWith(html);
  }

  static get $panel() {
    return $("#ex-preview-panel");
  }

  static resize(e, ui) {
    // XXX magic number
    PreviewPanel.setWidth($("body").innerWidth() - ui.position.left - 28);
  }

  static save() {
    let state = EX.config.previewPanelState;
    state[EX.config.pageKey()] = PreviewPanel.$panel.width();
    EX.config.previewPanelState = state;
  }

  static opened() {
    return PreviewPanel.$panel.is(":visible") && PreviewPanel.$panel.width() > 0;
  }

  static open() {
    if (PreviewPanel.$panel.width() === 0) {
      PreviewPanel.setWidth(EX.config.defaultPreviewPanelWidth);
    }

    PreviewPanel.$panel.show({ effect: "slide", direction: "left" }).promise().then(PreviewPanel.save);
    Sidebar.close();
  }

  static close() {
    PreviewPanel.$panel.hide({ effect: "slide", direction: "right" }).promise().then(PreviewPanel.save);
    Sidebar.open();
  }

  static switchMode() {
    if (ModeMenu.getMode() === "view") {
      PreviewPanel.close();
    } else {
      PreviewPanel.open();
    }
  }

  static setWidth(width) {
    PreviewPanel.$panel.width(width);
    PreviewPanel.$panel.css({ flex: `0 0 ${width}px` });
    $("#ex-preview-panel > div").width(width);
  }

  static setHeight() {
    const headerHeight = $("#ex-header").outerHeight(true);
    const footerHeight = $("footer").outerHeight(true);

    let height;
    if (window.scrollY + headerHeight >= PreviewPanel.origTop) {
      $("#ex-preview-panel > div").addClass("ex-fixed").css({ top: headerHeight });
      height = `calc(100vh - ${headerHeight}px)`;
    } else {
      $("#ex-preview-panel > div").removeClass("ex-fixed");
      height = `calc(100vh - ${PreviewPanel.origTop - window.scrollY}px)`;
    }

    const diff = window.scrollY + window.innerHeight + footerHeight - $("body").height();
    if (diff >= 0) {
      height = `calc(100vh - ${headerHeight}px - ${diff}px)`;
    }

    $("#ex-preview-panel > div").css({ height });
    $("#ex-preview-panel > div > article.post-preview .post-media").css({ "max-height": height });
  }

  static renderPost(post) {
    return `

Score

                ${post.fav_count}

                ${post.score}

User

              ${_.escape(post.uploader_name)}

              <time class="created-at ex-short-relative-time" datetime="${post.created_at}" title="${moment(post.created_at).format()}">
                ${moment(post.created_at).locale("en-short").fromNow()}
              </time>

Rating

              ${post.rating.toUpperCase()}

Source

              ${post.source_link}

Size

              ${post.image_width}x${post.image_height}

            ${Tag.renderTagList(post.tags, "ex-tag-list-inline")}

          ${Posts.preview(post, { size: "large", classes: [ "ex-no-tooltip" ] })}

    `;
  }
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 Jeeva Kandasamy (jkandasa@gmail.com)
 *
 * 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.
 */
myControllerModule.controller('VariableMapperController', function(alertService, $scope, $filter, $uibModal, displayRestError, TypesFactory) {

  $scope.filteredList=[];
  $scope.orgList=[];
  $scope.config = {
    itemsPerPage: 10,
    maxPages:10,
    fillLastPage: false
  }

  //Filter
  $scope.updateFilteredList = function() {
    $scope.filteredList = $filter("filter")($scope.orgList, $scope.query);
  };

  $scope.orgList = TypesFactory.getSensorVariableMapper(function(response) {
                    },function(error){
                      displayRestError.display(error);
                    });
  $scope.filteredList = $scope.orgList;

  //Update
  $scope.edit = function (keyValue, size) {
    var editModalInstance = $uibModal.open({
    templateUrl: 'partials/variableMapper/editModal.html',
    controller: 'VMPeditController',
    size: size,
    resolve: {keyValue: function () {return keyValue;}}
    });

    editModalInstance.result.then(function (keyValue) {
      TypesFactory.updateSensorVariableMapper(keyValue, function(response) {
        alertService.success("Updated "+keyValue.key);
        //Update display table
        $scope.orgList = TypesFactory.getSensorVariableMapper(function(response) {
        },function(error){
          displayRestError.display(error);
        });
        $scope.filteredList = $scope.orgList;
      },function(error){
        displayRestError.display(error);
      });
    }),
    function () {
      //console.log('Modal dismissed at: ' + new Date());
    }
  };

});
myControllerModule.controller('VMPeditController', function ($scope, $modalInstance, keyValue, TypesFactory) {
  $scope.keyValue = keyValue;
  //$scope.keyValue.id  = keyValueOrg.id;
  //$scope.keyValue.key = keyValueOrg.key;

  $scope.sensorVariableTypes = TypesFactory.getSensorVariableTypesAll({id: keyValue.id});

  $scope.updateVariableTypes= function(){
    var variableTypesArray=[];
    if($scope.variableTypes.length >0){
        angular.forEach($scope.variableTypes, function(key, value) {
          variableTypesArray.push(key.displayName);
        });
      }
      $scope.keyValue.value = variableTypesArray.join(', ');
  }

  $scope.header = "Update '"+$scope.keyValue.key+"'";
  $scope.update = function() {$modalInstance.close($scope.keyValue); }
  $scope.cancel = function () { $modalInstance.dismiss('cancel'); }
});
<commit_msg>Update variableMapper.js<commit_after>/*
 * Copyright (C) 2015 Jeeva Kandasamy (jkandasa@gmail.com)
 *
 * 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.
 */
myControllerModule.controller('VariableMapperController', function(alertService, $scope, $filter, $uibModal, displayRestError, TypesFactory, $filter) {

  $scope.filteredList=[];
  $scope.orgList=[];
  $scope.config = {
    itemsPerPage: 10,
    maxPages:10,
    fillLastPage: false
  }

  //Filter
  $scope.updateFilteredList = function() {
    $scope.filteredList = $filter("filter")($scope.orgList, $scope.query);
  };

  $scope.orgList = TypesFactory.getSensorVariableMapper(function(response) {
                    },function(error){
                      displayRestError.display(error);
                    });
  $scope.filteredList = $scope.orgList;

  //Update
  $scope.edit = function (keyValue, size) {
    var editModalInstance = $uibModal.open({
    templateUrl: 'partials/variableMapper/editModal.html',
    controller: 'VMPeditController',
    size: size,
    resolve: {keyValue: function () {return keyValue;}}
    });

    editModalInstance.result.then(function (keyValue) {
      TypesFactory.updateSensorVariableMapper(keyValue, function(response) {
		alertService.success($filter('translate')('VARIABLEMAPPER.NOTIFY_UPDATED', keyValue));
        //Update display table
        $scope.orgList = TypesFactory.getSensorVariableMapper(function(response) {
        },function(error){
          displayRestError.display(error);
        });
        $scope.filteredList = $scope.orgList;
      },function(error){
        displayRestError.display(error);
      });
    }),
    function () {
      //console.log('Modal dismissed at: ' + new Date());
    }
  };

});
myControllerModule.controller('VMPeditController', function ($scope, $modalInstance, keyValue, TypesFactory, $filter) {
  $scope.keyValue = keyValue;
  //$scope.keyValue.id  = keyValueOrg.id;
  //$scope.keyValue.key = keyValueOrg.key;

  $scope.sensorVariableTypes = TypesFactory.getSensorVariableTypesAll({id: keyValue.id});

  $scope.updateVariableTypes= function(){
    var variableTypesArray=[];
    if($scope.variableTypes.length >0){
        angular.forEach($scope.variableTypes, function(key, value) {
          variableTypesArray.push(key.displayName);
        });
      }
      $scope.keyValue.value = variableTypesArray.join(', ');
  }
  $scope.header = $filter('translate')('VARIABLEMAPPER.TITLE_UPDATED',keyValue);
  $scope.update = function() {$modalInstance.close($scope.keyValue); }
  $scope.cancel = function () { $modalInstance.dismiss('cancel'); }
});
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var glimpse = require('glimpse');

var _strategies = [];

var getContentTypeCategory = (function() {
    var listing = {
        'text/html':                   {'document': true},
        'text/plain':                  {'document': true},
        'application/xhtml+xml':       {'document': true},
        'text/xml':                    {'data': true},
        'application/json':            {'data': true},
        'text/css':                    {'stylesheet': true},
        'text/xsl':                    {'stylesheet': true},
        'image/jpg':                   {'image': true},
        'image/jpeg':                  {'image': true},
        'image/pjpeg':                 {'image': true},
        'image/png':                   {'image': true},
        'image/gif':                   {'image': true},
        'image/bmp':                   {'image': true},
        'image/svg+xml':               {'image': true, 'font': true, 'document': true},
        'image/vnd.microsoft.icon':    {'image': true},
        'image/webp':                  {'image': true},
        'image/x-icon':                {'image': true},
        'image/x-xbitmap':             {'image': true},
        'font/ttf':                    {'font': true},
        'font/otf':                    {'font': true},
        'font/woff':                   {'font': true},
        'font/woff2':                  {'font': true},
        'font/truetype':               {'font': true},
        'font/opentype':               {'font': true},
        'application/octet-stream':    {'font': true, 'image': true},
        'application/font-woff':       {'font': true},
        'application/font-woff2':      {'font': true},
        'application/x-font-woff':     {'font': true},
        'application/x-font-type1':    {'font': true},
        'application/x-font-ttf':      {'font': true},
        'application/x-truetype-font': {'font': true},
        'text/javascript':             {'script': true},
        'text/ecmascript':             {'script': true},
        'application/javascript':      {'script': true},
        'application/ecmascript':      {'script': true},
        'application/x-javascript':    {'script': true},
        'text/javascript1.1':          {'script': true},
        'text/javascript1.2':          {'script': true},
        'text/javascript1.3':          {'script': true},
        'text/jscript':                {'script': true},
        'text/livescript':             {'script': true},
        'text/vtt':                    {'texttrack': true},
    };

    return function(contentType) {
        var target = '';
        for (var i = 0; i < contentType.length; i++) {
            var char = contentType[i];
            if (char == ';') {
                break;
            }
            target += char
        }

        return listing[target];
    };
})();

var getStatusCodeText = (function() {
    var listing = {
        100: 'Continue',
        101: 'Switching Protocols',
        102: 'Processing',
        200: 'OK',
        201: 'Created',
        202: 'Accepted',
        203: 'Non-Authoritative Information',
        204: 'No Content',
        205: 'Reset Content',
        206: 'Partial Content',
        207: 'Multi-Status',
        208: 'Already Reported',
        226: 'IM Used',
        300: 'Multiple Choices',
        301: 'Moved Permanentl',
        302: 'Found',
        303: 'See Other',
        304: 'Not Modified',
        305: 'Use Proxy',
        307: 'Temporary Redirect',
        308: 'Permanent Redirect',
        400: 'Bad Request',
        401: 'Unauthorized',
        402: 'Payment Required',
        403: 'Forbidden',
        404: 'Not Found',
        405: 'Method Not Allowed',
        406: 'Not Acceptable',
        407: 'Proxy Authentication Required',
        408: 'Request Timeout',
        409: 'Conflict',
        410: 'Gone',
        411: 'Length Required',
        412: 'Precondition Failed',
        413: 'Payload Too Large',
        414: 'URI Too Long',
        415: 'Unsupported Media Type',
        416: 'Range Not Satisfiable',
        417: 'Expectation Failed',
        421: 'Misdirected Request',
        422: 'Unprocessable Entity',
        423: 'Locked',
        424: 'Failed Dependency',
        425: 'Unassigned',
        426: 'Upgrade Required',
        427: 'Unassigned',
        428: 'Precondition Required',
        429: 'Too Many Requests',
        430: 'Unassigned',
        431: 'Request Header Fields Too Large',
        500: 'Internal Server Error',
        501: 'Not Implemented',
        502: 'Bad Gateway',
        503: 'Service Unavailable',
        504: 'Gateway Timeout',
        505: 'HTTP Version Not Supported',
        506: 'Variant Also Negotiates',
        507: 'Insufficient Storage',
        508: 'Loop Detected',
        509: 'Unassigned',
        510: 'Not Extended',
        511: 'Network Authentication Required'
    };

    return function(statusCode) {
        return listing[statusCode];
    };
})();
var setupIndex = function(request, type, payload) {
    if (type == 'begin-request') {
        request._requestStartTime = payload.requestStartTime;
        request._requestMethod = payload.requestMethod;
        request._requestUrl = (payload.requestPath || '') + (payload.requestQueryString || '');
    }
    if (type == 'end-request') {
        request._responseStatusCode = payload.responseStatusCode;
        request._responseStatusText = getStatusCodeText(payload.responseStatusCode);
        request._responseContentCategory = getContentTypeCategory(payload.responseContentType);

        payload.responseStatusText = request._responseStatusText;
        payload.responseContentCategory = request._responseContentCategory;
    }
}

module.exports = {
    registerStrategy: function(strategy) {
        _strategies.push(strategy);

        glimpse.emit('system.request.converter.strategy.added', { strategy: strategy });
    },
    createRequest: function(requestId) {
        return {
            id: requestId,
            messages: {}
        };
    },
    convertMessages: function(request, messages) {
        var didUpdate = false;

        // setup the type index
        if (!request.types) {
            request.types = {};
        }

        _.forEach(messages, function(message) {
            if (!request.messages[message.id]) {
                // copy across the messages into the message index
                request.messages[message.id] = message;

                // copy across the messages into the type index
                _.forEach(message.types, function(type) {
                    if (!request.types[type]) {
                        request.types[type] = [];

                        // hack because we commonly use datatime in sorting and its expensive to get
                        setupIndex(request, type, message.payload);
                    }

                    request.types[type].push(message.id);
                });

                // run through the registered providers
                _.forEach(_strategies, function(action) {
                    action(request, message) || didUpdate;
                });

                didUpdate = true;
            }
        });

        return didUpdate;
    }
};

// TODO: Need to come up with a better self registration process
(function() {
    var registerStrategy = module.exports.registerStrategy;

    // NOTE: Removing for the time being... doesn't looks like this will be needed
    // TODO: Check if this can be removed
    registerStrategy(require('./request-converter-strategy-tab___TEMP'));
    registerStrategy(require('./request-converter-strategy-tab-message___TEMP'));
})();
<commit_msg>Make sure getContentTypeCategory checks that a contentType is present before we process it<commit_after>'use strict';

var _ = require('lodash');
var glimpse = require('glimpse');

var _strategies = [];

var getContentTypeCategory = (function() {
    var listing = {
        'text/html':                   {'document': true},
        'text/plain':                  {'document': true},
        'application/xhtml+xml':       {'document': true},
        'text/xml':                    {'data': true},
        'application/json':            {'data': true},
        'text/css':                    {'stylesheet': true},
        'text/xsl':                    {'stylesheet': true},
        'image/jpg':                   {'image': true},
        'image/jpeg':                  {'image': true},
        'image/pjpeg':                 {'image': true},
        'image/png':                   {'image': true},
        'image/gif':                   {'image': true},
        'image/bmp':                   {'image': true},
        'image/svg+xml':               {'image': true, 'font': true, 'document': true},
        'image/vnd.microsoft.icon':    {'image': true},
        'image/webp':                  {'image': true},
        'image/x-icon':                {'image': true},
        'image/x-xbitmap':             {'image': true},
        'font/ttf':                    {'font': true},
        'font/otf':                    {'font': true},
        'font/woff':                   {'font': true},
        'font/woff2':                  {'font': true},
        'font/truetype':               {'font': true},
        'font/opentype':               {'font': true},
        'application/octet-stream':    {'font': true, 'image': true},
        'application/font-woff':       {'font': true},
        'application/font-woff2':      {'font': true},
        'application/x-font-woff':     {'font': true},
        'application/x-font-type1':    {'font': true},
        'application/x-font-ttf':      {'font': true},
        'application/x-truetype-font': {'font': true},
        'text/javascript':             {'script': true},
        'text/ecmascript':             {'script': true},
        'application/javascript':      {'script': true},
        'application/ecmascript':      {'script': true},
        'application/x-javascript':    {'script': true},
        'text/javascript1.1':          {'script': true},
        'text/javascript1.2':          {'script': true},
        'text/javascript1.3':          {'script': true},
        'text/jscript':                {'script': true},
        'text/livescript':             {'script': true},
        'text/vtt':                    {'texttrack': true},
    };

    return function(contentType) {
        if (contentType) {
            var target = '';
            for (var i = 0; i < contentType.length; i++) {
                var char = contentType[i];
                if (char == ';') {
                    break;
                }
                target += char
            }

            return listing[target];
        }
    };
})();

var getStatusCodeText = (function() {
    var listing = {
        100: 'Continue',
        101: 'Switching Protocols',
        102: 'Processing',
        200: 'OK',
        201: 'Created',
        202: 'Accepted',
        203: 'Non-Authoritative Information',
        204: 'No Content',
        205: 'Reset Content',
        206: 'Partial Content',
        207: 'Multi-Status',
        208: 'Already Reported',
        226: 'IM Used',
        300: 'Multiple Choices',
        301: 'Moved Permanentl',
        302: 'Found',
        303: 'See Other',
        304: 'Not Modified',
        305: 'Use Proxy',
        307: 'Temporary Redirect',
        308: 'Permanent Redirect',
        400: 'Bad Request',
        401: 'Unauthorized',
        402: 'Payment Required',
        403: 'Forbidden',
        404: 'Not Found',
        405: 'Method Not Allowed',
        406: 'Not Acceptable',
        407: 'Proxy Authentication Required',
        408: 'Request Timeout',
        409: 'Conflict',
        410: 'Gone',
        411: 'Length Required',
        412: 'Precondition Failed',
        413: 'Payload Too Large',
        414: 'URI Too Long',
        415: 'Unsupported Media Type',
        416: 'Range Not Satisfiable',
        417: 'Expectation Failed',
        421: 'Misdirected Request',
        422: 'Unprocessable Entity',
        423: 'Locked',
        424: 'Failed Dependency',
        425: 'Unassigned',
        426: 'Upgrade Required',
        427: 'Unassigned',
        428: 'Precondition Required',
        429: 'Too Many Requests',
        430: 'Unassigned',
        431: 'Request Header Fields Too Large',
        500: 'Internal Server Error',
        501: 'Not Implemented',
        502: 'Bad Gateway',
        503: 'Service Unavailable',
        504: 'Gateway Timeout',
        505: 'HTTP Version Not Supported',
        506: 'Variant Also Negotiates',
        507: 'Insufficient Storage',
        508: 'Loop Detected',
        509: 'Unassigned',
        510: 'Not Extended',
        511: 'Network Authentication Required'
    };

    return function(statusCode) {
        return listing[statusCode];
    };
})();
var setupIndex = function(request, type, payload) {
    if (type == 'begin-request') {
        request._requestStartTime = payload.requestStartTime;
        request._requestMethod = payload.requestMethod;
        request._requestUrl = (payload.requestPath || '') + (payload.requestQueryString || '');
    }
    if (type == 'end-request') {
        request._responseStatusCode = payload.responseStatusCode;
        request._responseStatusText = getStatusCodeText(payload.responseStatusCode);
        request._responseContentCategory = getContentTypeCategory(payload.responseContentType);

        payload.responseStatusText = request._responseStatusText;
        payload.responseContentCategory = request._responseContentCategory;
    }
}

module.exports = {
    registerStrategy: function(strategy) {
        _strategies.push(strategy);

        glimpse.emit('system.request.converter.strategy.added', { strategy: strategy });
    },
    createRequest: function(requestId) {
        return {
            id: requestId,
            messages: {}
        };
    },
    convertMessages: function(request, messages) {
        var didUpdate = false;

        // setup the type index
        if (!request.types) {
            request.types = {};
        }

        _.forEach(messages, function(message) {
            if (!request.messages[message.id]) {
                // copy across the messages into the message index
                request.messages[message.id] = message;

                // copy across the messages into the type index
                _.forEach(message.types, function(type) {
                    if (!request.types[type]) {
                        request.types[type] = [];

                        // hack because we commonly use datatime in sorting and its expensive to get
                        setupIndex(request, type, message.payload);
                    }

                    request.types[type].push(message.id);
                });

                // run through the registered providers
                _.forEach(_strategies, function(action) {
                    action(request, message) || didUpdate;
                });

                didUpdate = true;
            }
        });

        return didUpdate;
    }
};

// TODO: Need to come up with a better self registration process
(function() {
    var registerStrategy = module.exports.registerStrategy;

    // NOTE: Removing for the time being... doesn't looks like this will be needed
    // TODO: Check if this can be removed
    registerStrategy(require('./request-converter-strategy-tab___TEMP'));
    registerStrategy(require('./request-converter-strategy-tab-message___TEMP'));
})();
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Link } from 'react-router';

import { shownMessage, removeMessage } from 'actions/App/AppActions';

import _ from 'lodash';

window.$ = require('jquery');

export default class AppMessage extends Component {
  getBodyMessage(){
    let body;
    if(this.props.message.type === 'error'){
      //Handle Errors that are passed as objects;
      if(_.isObject(this.props.message.body)){
        if(_.values(this.props.message.body).length > 1){
          body = [];
          _.forOwn(this.props.message.body, function(value, key){
            body.push(
{'Error in ' + _.capitalize(key) + ': ' + value}
);
          });
        }
        else{
          _.forOwn(this.props.message.body, function(value, key) {
            body = 'Error in ' + _.capitalize(key) + ': ' + value;
          });
        }
      }
      else{
        body = 'Error: ' + this.props.message.body;
      }
    }
    else{
      body = this.props.message.body;
    }
    return body;
  }

  render() {
    const ReactCSSTransitionGroup = require('react-addons-css-transition-group');

    let message;
    if(this.props.message.display){
      let bgClass;
      let textClass;
      switch(this.props.message.type){
        case 'success':
          bgClass = 'bg-success';
          textClass = 'text-success';
          break;
        case 'error':
          bgClass = 'bg-danger';
          textClass = 'text-danger';
          break;
        default:
          bgClass = 'bg-warning';
          textClass = 'text-warning';
          break;
      }

      message = (

              {this.getBodyMessage()}

               this.handleCloseClick(e)} className={textClass}>

      );
    }

    return (
      <ReactCSSTransitionGroup transitionName="fade" transitionAppearTimeout={300} transitionEnterTimeout={300} transitionLeaveTimeout={300}>
        {message}
      </ReactCSSTransitionGroup>
    );
  }

  handleCloseClick(e){
    e.preventDefault();
    this.props.dispatch(removeMessage());
  }
}
<commit_msg>AppMessage should be wrapped in bootstrap row.<commit_after>import React, { Component } from 'react';
import { Link } from 'react-router';

import { shownMessage, removeMessage } from 'actions/App/AppActions';

import _ from 'lodash';

window.$ = require('jquery');

export default class AppMessage extends Component {
  getBodyMessage(){
    let body;
    if(this.props.message.type === 'error'){
      //Handle Errors that are passed as objects;
      if(_.isObject(this.props.message.body)){
        if(_.values(this.props.message.body).length > 1){
          body = [];
          _.forOwn(this.props.message.body, function(value, key){
            body.push(
{'Error in ' + _.capitalize(key) + ': ' + value}
);
          });
        }
        else{
          _.forOwn(this.props.message.body, function(value, key) {
            body = 'Error in ' + _.capitalize(key) + ': ' + value;
          });
        }
      }
      else{
        body = 'Error: ' + this.props.message.body;
      }
    }
    else{
      body = this.props.message.body;
    }
    return body;
  }

  render() {
    const ReactCSSTransitionGroup = require('react-addons-css-transition-group');

    let message;
    if(this.props.message.display){
      let bgClass;
      let textClass;
      switch(this.props.message.type){
        case 'success':
          bgClass = 'bg-success';
          textClass = 'text-success';
          break;
        case 'error':
          bgClass = 'bg-danger';
          textClass = 'text-danger';
          break;
        default:
          bgClass = 'bg-warning';
          textClass = 'text-warning';
          break;
      }

      message = (

                {this.getBodyMessage()}

                 this.handleCloseClick(e)} className={textClass}>

      );
    }

    return (
      <ReactCSSTransitionGroup transitionName="fade" transitionAppearTimeout={300} transitionEnterTimeout={300} transitionLeaveTimeout={300}>
        {message}
      </ReactCSSTransitionGroup>
    );
  }

  handleCloseClick(e){
    e.preventDefault();
    this.props.dispatch(removeMessage());
  }
}
<|endoftext|>


<commit_before>(function(){
    'use strict';
    angular
        .module('owh')
        .service('xlsService', xlsService);

    xlsService.$inject = [];

    //service to interface with js-xlsx library
    function xlsService() {
        var service = {
            getSheetFromArray: getSheetFromArray,
            getSheetArrayFromMixedTable: getSheetArrayFromMixedTable,
            getCSVFromSheet: getCSVFromSheet,
            exportCSVFromMixedTable: exportCSVFromMixedTable,
            exportXLSFromMixedTable: exportXLSFromMixedTable,
            getFilename: getFilename
        };
        return service;

        function s2ab(s) {
            var buf = new ArrayBuffer(s.length);
            var view = new Uint8Array(buf);
            for (var i=0; i!==s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
            return buf;
        }

        //data should have rowHeaders array for csv exporting
        function exportCSVFromMixedTable(data, filename) {
            var sheetArray = getSheetArrayFromMixedTable(data);
            var sheet = getSheetFromArray(sheetArray);
            var csv = getCSVFromSheet(sheet, data.headers, data.rowHeaders);
            saveAs(new Blob([s2ab(csv)], {type:"application/octet-stream"}), filename + ".csv");
        }

        function exportXLSFromMixedTable(data, filename) {
            var wb = {SheetNames: [], Sheets: {}};
            var ws_name = filename;
            var sheetJson = getSheetArrayFromMixedTable(data);
            var ws = getSheetFromArray(sheetJson, true);
            wb.SheetNames.push(ws_name);
            wb.Sheets[ws_name] = ws;
            var wbout = XLSX.write(wb, {bookType:'xlsx', bookSST:true, type: 'binary'});
            saveAs(new Blob([s2ab(wbout)],{type:"application/octet-stream"}), filename + ".xlsx");
        }

        //returns xls worksheet from array of json rows
        //ex: [[{title: 'test', colspan: 1, rowspan: 1}, {...}, {...}], [{...}, null, {...}] ]
        function getSheetFromArray(data, convertNumbers) {
            var ws = {'!merges': []};
            var range = {s: {c:10000000, r:10000000}, e: {c:0, r:0 }};
            //keep track of offsets caused by merged cells
            var cOffsets = {};
            for(var R = 0; R !== data.length; ++R) {
                var rOffset = 0;
                if(!cOffsets[R]) {
                    cOffsets[R] = 0;
                }
                for(var C = 0; C !== data[R].length; ++C) {
                    var cellJson = data[R][C];
                    var newR = R + rOffset;
                    var newC = C + cOffsets[R];
                    if(range.s.r > newR) range.s.r = newR;
                    if(range.s.c > newC) range.s.c = newC;
                    if(range.e.r < newR) range.e.r = newR;
                    if(range.e.c < newC) range.e.c = newC;

                    if(cellJson === null) continue;
                    var cell = getCellFromJson(cellJson, convertNumbers);
                    var cell_ref = XLSX.utils.encode_cell({c:newC,r:newR});

                    if(cellJson.colspan > 1 || cellJson.rowspan > 1) {
                        cOffsets[R] += cellJson.colspan - 1;
                        var mergeCell = {s: {c: newC, r: newR}, e: {c: (newC + cellJson.colspan - 1), r: (newR + cellJson.rowspan - 1)}};
                        ws['!merges'].push(mergeCell);

                    }
                    ws[cell_ref] = cell;
                }
            }
          	if(range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
          	return ws;
        }

        function getCellFromJson(cellJson, convertNumbers) {
            var cell = {v: cellJson.title };

            if(typeof cell.v === 'number') cell.t = 'n';
            else if(typeof cell.v === 'boolean') cell.t = 'b';
            else if(cell.v instanceof Date) {
                cell.t = 'n'; cell.z = XLSX.SSF._table[14];
                cell.v = datenum(cell.v);
            }
            else {
                cell.t = 's';
                if(convertNumbers) {
                    //check if string is parsable as integer and make sure doesn't contain letters
                    var numberValue = parseFloat(cell.v.replace(',', ''));

                    if(!isNaN(numberValue) && !cell.v.match(/[a-z]/i)) {
                        //if (numberValue >= 1000)
                            cell.v =  XLSX.SSF._table[3];
                        //cell.v = numberValue;
                        cell.t = 'n';
                    }
                }
            }
            return cell;
        }

        //gets json representation of sheet
        function getSheetArrayFromMixedTable(table) {
            var sheet = [];
            var numOfPercentageColumns = 0;
            angular.forEach(table.headers, function(headerRow, idx) {
                var headers = [];
                if(idx > 0 && table.headers.length > 1) {
                    //add cell to account for row headers
                    headers.push({title: "", colspan: table.rowHeaders.length, rowspan: 1});
                }
                angular.forEach(headerRow, function(cell, innerIdx) {
                    var colspan = cell.colspan;
                    //check is column header for data column, else add header as normal
                    if(table.calculatePercentage && ((innerIdx >= table.rowHeaders.length && innerIdx < headerRow.length - 1) || idx > 0)) {
                        //for the bottom row just add an extra column for every existing one, else double the length
                        if(idx === table.headers.length - 1) {
                            headers.push({title: cell.title, colspan: colspan, rowspan: cell.rowspan});
                            headers.push({title: "% of " +cell.title+" Deaths", colspan: colspan, rowspan: cell.rowspan});
                            numOfPercentageColumns++;
                        } else {
                            headers.push({title: cell.title, colspan: colspan * 2, rowspan: cell.rowspan});
                        }
                    } else {
                        headers.push({title: cell.title, colspan: colspan, rowspan: cell.rowspan});
                    }

                });
                sheet.push(headers);
            });

            //keep track of column offsets so we know how much padding to add to each row
            var colOffsets = {};
            angular.forEach(table.data, function(row, idx) {
                var rowArray = [];

                function getPadding(colOffsets) {
                    var padding = 0;
                    for(var i = 0; i < table.rowHeaders.length - 1; i++) {
                        if(colOffsets[i]) {
                            padding++;
                            colOffsets[i]--;
                        }
                    }
                    return padding;
                }

                function replacePadding(colOffsets) {
                    var paddingIndex = 0;
                    for(var i = 0; i < table.rowHeaders.length; i++) {
                        if(!colOffsets[i]) {
                            var rowspan = row[paddingIndex] ? row[paddingIndex].rowspan : 0;
                            if(rowspan > 0) {
                                colOffsets[i] = rowspan - 1;
                            }
                            paddingIndex++;
                        }
                    }
                    return colOffsets;
                }

                //get padding
                var padding = getPadding(colOffsets);

                //add padding as needed to row
                if(table.rowHeaders.length > 1) {
                    if(padding > 0) {
                        rowArray.push({title: "", colspan: padding, rowspan: 1});
                    }
                }

                //replace zero/empty offsets
                replacePadding(colOffsets);

                angular.forEach(row, function(cell, innerIdx) {
                    var colspan = cell.colspan;
                    if(cell.title === 'Total') {
                        colspan += numOfPercentageColumns;
                    }
                    rowArray.push({title: cell.title, colspan: colspan, rowspan: cell.rowspan});
                    //if we have a percentage then add an extra column to display it
                    if(table.calculatePercentage && cell.percentage !== undefined && innerIdx < row.length - 1 ) {
                        rowArray.push({title: cell.percentage, colspan: colspan, rowspan: cell.rowspan});
                    }
                });
                sheet.push(rowArray);
            });
            return sheet;
        }

        //helper function to repeat merge cells in header for CSV output
        function padSheetForCSV(sheet, colHeaders, rowHeaders) {
            var headerMerges = [];
            angular.forEach(sheet['!merges'], function(merge, idx) {
                //only repeat if merge cell is part of headers, use rowHeaders.length - 1 because of Total row
                if(merge.s.r < colHeaders.length || merge.s.c < rowHeaders.length - 1) {
                    var start = sheet[XLSX.utils.encode_cell(merge.s)];
                    for(var r = merge.s.r; r <= merge.e.r; r++) {
                        for(var c = merge.s.c; c <= merge.e.c; c++) {
                            //replace with value from starting cell in range
                            sheet[XLSX.utils.encode_cell({c: c, r: r})] = {v: start.v, t: 's'};
                        }
                    }
                    headerMerges.push(idx);
                }
            });
            angular.forEach(headerMerges, function(el) {
                sheet['!merges'].splice(el, 1);
            });
            return sheet;
        }

        function getCSVFromSheet(sheet, colHeaders, rowHeaders) {
            var csv = XLSX.utils.sheet_to_csv(padSheetForCSV(sheet, colHeaders, rowHeaders));
            return csv;
        }

        function getFilename(selectedFilter) {
            //get year range
            var yearRange = '';
            angular.forEach(selectedFilter.allFilters, function(filter) {
                if(filter.key === 'year') {
                    if(filter.value.length > 1) {
                        var minYear = parseInt(filter.value[0], 10);
                        var maxYear = parseInt(filter.value[0], 10);
                        angular.forEach(filter.value, function(year) {
                            var yearInt = parseInt(year, 10);
                            if(yearInt < minYear) {
                                minYear = yearInt;
                            }
                            if(yearInt > maxYear) {
                                maxYear = yearInt;
                            }
                        });
                        yearRange = minYear + '-' + maxYear;
                    } else if(filter.value.length === 1) {
                        //only one year selected
                        yearRange = filter.value[0];
                    } else {
                        //use all if none selected
                        yearRange = 'All';
                    }

                }
            });
            return selectedFilter.header + '_' + yearRange + '_Filtered';
        }

    }
}());
<commit_msg>Trying to fix XLS and CSV<commit_after>(function(){
    'use strict';
    angular
        .module('owh')
        .service('xlsService', xlsService);

    xlsService.$inject = [];

    //service to interface with js-xlsx library
    function xlsService() {
        var service = {
            getSheetFromArray: getSheetFromArray,
            getSheetArrayFromMixedTable: getSheetArrayFromMixedTable,
            getCSVFromSheet: getCSVFromSheet,
            exportCSVFromMixedTable: exportCSVFromMixedTable,
            exportXLSFromMixedTable: exportXLSFromMixedTable,
            getFilename: getFilename
        };
        return service;

        function s2ab(s) {
            var buf = new ArrayBuffer(s.length);
            var view = new Uint8Array(buf);
            for (var i=0; i!==s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
            return buf;
        }

        //data should have rowHeaders array for csv exporting
        function exportCSVFromMixedTable(data, filename) {
            var sheetArray = getSheetArrayFromMixedTable(data);
            var sheet = getSheetFromArray(sheetArray);
            var csv = getCSVFromSheet(sheet, data.headers, data.rowHeaders);
            saveAs(new Blob([s2ab(csv)], {type:"application/octet-stream"}), filename + ".csv");
        }

        function exportXLSFromMixedTable(data, filename) {
            var wb = {SheetNames: [], Sheets: {}};
            var ws_name = filename;
            var sheetJson = getSheetArrayFromMixedTable(data);
            var ws = getSheetFromArray(sheetJson, true);
            wb.SheetNames.push(ws_name);
            wb.Sheets[ws_name] = ws;
            var wbout = XLSX.write(wb, {bookType:'xlsx', bookSST:true, type: 'binary'});
            saveAs(new Blob([s2ab(wbout)],{type:"application/octet-stream"}), filename + ".xlsx");
        }

        //returns xls worksheet from array of json rows
        //ex: [[{title: 'test', colspan: 1, rowspan: 1}, {...}, {...}], [{...}, null, {...}] ]
        function getSheetFromArray(data, convertNumbers) {
            var ws = {'!merges': []};
            var range = {s: {c:10000000, r:10000000}, e: {c:0, r:0 }};
            //keep track of offsets caused by merged cells
            var cOffsets = {};
            for(var R = 0; R !== data.length; ++R) {
                var rOffset = 0;
                if(!cOffsets[R]) {
                    cOffsets[R] = 0;
                }
                for(var C = 0; C !== data[R].length; ++C) {
                    var cellJson = data[R][C];
                    var newR = R + rOffset;
                    var newC = C + cOffsets[R];
                    if(range.s.r > newR) range.s.r = newR;
                    if(range.s.c > newC) range.s.c = newC;
                    if(range.e.r < newR) range.e.r = newR;
                    if(range.e.c < newC) range.e.c = newC;

                    if(cellJson === null) continue;
                    var cell = getCellFromJson(cellJson, convertNumbers);
                    var cell_ref = XLSX.utils.encode_cell({c:newC,r:newR});

                    if(cellJson.colspan > 1 || cellJson.rowspan > 1) {
                        cOffsets[R] += cellJson.colspan - 1;
                        var mergeCell = {s: {c: newC, r: newR}, e: {c: (newC + cellJson.colspan - 1), r: (newR + cellJson.rowspan - 1)}};
                        ws['!merges'].push(mergeCell);

                    }
                    ws[cell_ref] = cell;
                }
            }
          	if(range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
          	return ws;
        }

        function getCellFromJson(cellJson, convertNumbers) {
            var cell = {v: cellJson.title };

            if(typeof cell.v === 'number') cell.t = 'n';
            else if(typeof cell.v === 'boolean') cell.t = 'b';
            else if(cell.v instanceof Date) {
                cell.t = 'n'; cell.z = XLSX.SSF._table[14];
                cell.v = datenum(cell.v);
            }
            else {
                cell.t = 's';
                if(convertNumbers) {
                    //check if string is parsable as integer and make sure doesn't contain letters
                    var numberValue = parseFloat(cell.v.replace(',', ''));

                    if(!isNaN(numberValue) && !cell.v.match(/[a-z]/i)) {
                        if (numberValue >= 1000) {
                            cell.z = XLSX.SSF._table[3];
                        }
                        cell.v = numberValue;
                        cell.t = 'n';
                    }
                }
            }
            return cell;
        }

        //gets json representation of sheet
        function getSheetArrayFromMixedTable(table) {
            var sheet = [];
            var numOfPercentageColumns = 0;
            angular.forEach(table.headers, function(headerRow, idx) {
                var headers = [];
                if(idx > 0 && table.headers.length > 1) {
                    //add cell to account for row headers
                    headers.push({title: "", colspan: table.rowHeaders.length, rowspan: 1});
                }
                angular.forEach(headerRow, function(cell, innerIdx) {
                    var colspan = cell.colspan;
                    //check is column header for data column, else add header as normal
                    if(table.calculatePercentage && ((innerIdx >= table.rowHeaders.length && innerIdx < headerRow.length - 1) || idx > 0)) {
                        //for the bottom row just add an extra column for every existing one, else double the length
                        if(idx === table.headers.length - 1) {
                            headers.push({title: cell.title, colspan: colspan, rowspan: cell.rowspan});
                            headers.push({title: "% of " +cell.title+" Deaths", colspan: colspan, rowspan: cell.rowspan});
                            numOfPercentageColumns++;
                        } else {
                            headers.push({title: cell.title, colspan: colspan * 2, rowspan: cell.rowspan});
                        }
                    } else {
                        headers.push({title: cell.title, colspan: colspan, rowspan: cell.rowspan});
                    }

                });
                sheet.push(headers);
            });

            //keep track of column offsets so we know how much padding to add to each row
            var colOffsets = {};
            angular.forEach(table.data, function(row, idx) {
                var rowArray = [];

                function getPadding(colOffsets) {
                    var padding = 0;
                    for(var i = 0; i < table.rowHeaders.length - 1; i++) {
                        if(colOffsets[i]) {
                            padding++;
                            colOffsets[i]--;
                        }
                    }
                    return padding;
                }

                function replacePadding(colOffsets) {
                    var paddingIndex = 0;
                    for(var i = 0; i < table.rowHeaders.length; i++) {
                        if(!colOffsets[i]) {
                            var rowspan = row[paddingIndex] ? row[paddingIndex].rowspan : 0;
                            if(rowspan > 0) {
                                colOffsets[i] = rowspan - 1;
                            }
                            paddingIndex++;
                        }
                    }
                    return colOffsets;
                }

                //get padding
                var padding = getPadding(colOffsets);

                //add padding as needed to row
                if(table.rowHeaders.length > 1) {
                    if(padding > 0) {
                        rowArray.push({title: "", colspan: padding, rowspan: 1});
                    }
                }

                //replace zero/empty offsets
                replacePadding(colOffsets);

                angular.forEach(row, function(cell, innerIdx) {
                    var colspan = cell.colspan;
                    if(cell.title === 'Total') {
                        colspan += numOfPercentageColumns;
                    }
                    rowArray.push({title: cell.title, colspan: colspan, rowspan: cell.rowspan});
                    //if we have a percentage then add an extra column to display it
                    if(table.calculatePercentage && cell.percentage !== undefined && innerIdx < row.length - 1 ) {
                        rowArray.push({title: cell.percentage, colspan: colspan, rowspan: cell.rowspan});
                    }
                });
                sheet.push(rowArray);
            });
            return sheet;
        }

        //helper function to repeat merge cells in header for CSV output
        function padSheetForCSV(sheet, colHeaders, rowHeaders) {
            var headerMerges = [];
            angular.forEach(sheet['!merges'], function(merge, idx) {
                //only repeat if merge cell is part of headers, use rowHeaders.length - 1 because of Total row
                if(merge.s.r < colHeaders.length || merge.s.c < rowHeaders.length - 1) {
                    var start = sheet[XLSX.utils.encode_cell(merge.s)];
                    for(var r = merge.s.r; r <= merge.e.r; r++) {
                        for(var c = merge.s.c; c <= merge.e.c; c++) {
                            //replace with value from starting cell in range
                            sheet[XLSX.utils.encode_cell({c: c, r: r})] = {v: start.v, t: 's'};
                        }
                    }
                    headerMerges.push(idx);
                }
            });
            angular.forEach(headerMerges, function(el) {
                sheet['!merges'].splice(el, 1);
            });
            return sheet;
        }

        function getCSVFromSheet(sheet, colHeaders, rowHeaders) {
            var csv = XLSX.utils.sheet_to_csv(padSheetForCSV(sheet, colHeaders, rowHeaders));
            return csv;
        }

        function getFilename(selectedFilter) {
            //get year range
            var yearRange = '';
            angular.forEach(selectedFilter.allFilters, function(filter) {
                if(filter.key === 'year') {
                    if(filter.value.length > 1) {
                        var minYear = parseInt(filter.value[0], 10);
                        var maxYear = parseInt(filter.value[0], 10);
                        angular.forEach(filter.value, function(year) {
                            var yearInt = parseInt(year, 10);
                            if(yearInt < minYear) {
                                minYear = yearInt;
                            }
                            if(yearInt > maxYear) {
                                maxYear = yearInt;
                            }
                        });
                        yearRange = minYear + '-' + maxYear;
                    } else if(filter.value.length === 1) {
                        //only one year selected
                        yearRange = filter.value[0];
                    } else {
                        //use all if none selected
                        yearRange = 'All';
                    }

                }
            });
            return selectedFilter.header + '_' + yearRange + '_Filtered';
        }

    }
}());
<|endoftext|>