<commit_before>---
---
$(document).ready(function() {
  'use strict';
  // Helper definitions
  // =======================================================
  const targetEIN = $('h1.org-name').data('ein');
  const scrollAnchor = $('#grants').offset().top - 64; // 64 is height of profile-nav
  const isPhone = window.matchMedia('only screen and (max-width: 600px)');
  // const isMobile = window.matchMedia('only screen and (max-width: 992px)');
  let gaCheck = window[window['GoogleAnalyticsObject'] || 'ga']; // eslint-disable-line dot-notation
  let gaCount = 0;

  const formatter = new Intl.NumberFormat('en-US', {
    style: 'decimal',
    minimumFractionDigits: 0,
  });
  // Note - fixed grants header handled by profile.js

  // Initialize Materialize components
  // =======================================================
  // Note: if the element is created dynamically via Instantsearch widget,
  // the plugin needs to be initialized in the normal Instantsearch workflow
  // using the render method (e.g. search.once('render'...)
  $('.parallax').parallax();
  $('.sidenav').sidenav();

  // Algolia Instantsearch
  // =======================================================
  // Config
  const searchClient = algoliasearch('QA1231C5W9', '{{ site.algolia_public_key_grants }}');
  const facets = [
    {
      facet: 'tax_year',
      label: 'Tax Year',
    },
    {
      facet: 'grantee_city',
      label: 'City',
    },
    {
      facet: 'grantee_state',
      label: 'State',
    },
    {
      facet: 'grant_amount',
      label: 'Amount',
    },
  ];
  const search = instantsearch({
    'indexName': 'grantmakers_io',
    searchClient,
    'numberLocale': 'en-US',
    'searchParameters': {
      'filters': 'ein:' + targetEIN,
    },
    // routing: true,
    'routing': {
      'stateMapping': {
        stateToRoute({query, refinementList, range, page}) {
          return {
            'query': query,
            'tax_year':
              refinementList &&
              refinementList.tax_year &&
              refinementList.tax_year.join('~'),
            'grantee_city':
              refinementList &&
              refinementList.grantee_city &&
              refinementList.grantee_city.join('~'),
            'grantee_state':
              refinementList &&
              refinementList.grantee_state &&
              refinementList.grantee_state.join('~'),
            'grant_amount':
              range &&
              range.grant_amount &&
              range.grant_amount.replace(':', '~'),
            'page': page,
          };
        },
        /* eslint-disable camelcase */
        routeToState(routeState) {
          return {
            'query': routeState.query,
            'refinementList': {
              'tax_year': routeState.tax_year && routeState.tax_year.split('~'),
              'grantee_city': routeState.grantee_city && routeState.grantee_city.split('~'),
              'grantee_state': routeState.grantee_state && routeState.grantee_state.split('~'),
            },
            'range': {
              'grant_amount': routeState.grant_amount && routeState.grant_amount.replace('~', ':'),
            },
            'page': routeState.page,
          };
        },
      },
    },
  });
  /* eslint-enable camelcase */

  // Define templates
  const templateStats = `{% include algolia/stats.html %}`;

  // Define color palette
  const panelHeaderClasses = ['card-header', 'grey', 'lighten-4'];
  const panelHeaderClassesMobile = ['card-header', 'blue-grey', 'lighten-4'];

  // Construct widgets
  // =======================================================
  search.addWidget(
    instantsearch.widgets.searchBox({
      container: '#ais-widget-search-box',
      placeholder: 'Search by keyword or recipient name',
      autofocus: false,
      reset: true,
      queryHook: function(query, searchInstance) {
        readyToSearchScrollPosition();
        searchInstance(query);
        gaEvents();
      },
    })
  );

  /* Stats */
  search.addWidget(
    instantsearch.widgets.stats({
      container: '#ais-widget-stats',
      templates: {
        text: templateStats,
      },
      cssClasses: {
        root: 'center-align-on-mobile',
        text: ['text-muted', 'hide-on-med-and-down'],
      },
    })
  );

  /* Hits */
  const renderHits = (renderOptions) => {
    const { hits, results, widgetParams } = renderOptions;

    if (!hits.length && results) {
      document.getElementById('ais-widget-pagination').classList.add('hidden');
      widgetParams.container.innerHTML = `

              {% raw %}

We didn't find any results for the search "${ results.query }"

              {% endraw %}

            Find a Different Foundation
            Search all Grants

      `;
    } else {
      document.getElementById('ais-widget-pagination').classList.remove('hidden');
      widgetParams.container.innerHTML = `

            Year
            Name
            Purpose
            Location
            Amount

          ${hits.map(item =>`

              ${ item.tax_year }
              ${ instantsearch.highlight({ attribute: 'grantee_name', hit: item }) }
              ${ instantsearch.highlight({ attribute: 'grant_purpose', hit: item }) }
              ${ item.grantee_city.length ? instantsearch.highlight({ attribute: 'grantee_city', hit: item }) + ', ' + item.grantee_state : item.grantee_state}
              $${ item.grant_amount.toLocaleString() }

          `).join('')}

    `;
    }
  };

  const customHits = instantsearch.connectors.connectHits(
    renderHits
  );

  search.addWidget(
    customHits({
      container: document.querySelector('#ais-widget-hits'),
    })
  );

  /* Refinements */
  facets.forEach((refinement) => {
    if (refinement.facet === 'grant_amount') {
      return;
    }
    /* Desktop */
    const refinementListWithPanel = instantsearch.widgets.panel({
      'templates': {
        'header': refinement.label,
      },
      hidden(options) {
        return options.results.nbHits === 0;
      },
      'cssClasses': {
        'root': 'card',
        'header': panelHeaderClasses,
        'body': 'card-content',
      },
    })(instantsearch.widgets.refinementList);

    search.addWidget(
      refinementListWithPanel({
        'container': `#ais-widget-refinement-list--${refinement.facet}`,
        'attribute': refinement.facet,
        'limit': 8,
        'showMore': false,
        // 'searchable': true,
        'cssClasses': {
          'checkbox': 'filled-in',
          'labelText': 'small',
          'count': ['right', 'small'],
          // 'selectedItem': ['grants-search-text'],
          // 'searchableRoot': 'ais-SearchBox-refinements',
          // 'searchableSubmit': 'hidden',
        },
      })
    );

    /* Mobile */
    const mobileRefinementListWithPanel = instantsearch.widgets.panel({
      'templates': {
        'header': refinement.label,
      },
      hidden(options) {
        return options.results.nbHits === 0;
      },
      'cssClasses': {
        'root': 'card',
        'header': panelHeaderClassesMobile,
        'body': 'card-content',
      },
    })(instantsearch.widgets.refinementList);

    search.addWidget(
      mobileRefinementListWithPanel({
        'container': `#ais-widget-mobile-refinement-list--${refinement.facet}`,
        'attribute': refinement.facet,
        'limit': 8,
        'showMore': false,
        'cssClasses': {
          'checkbox': 'filled-in',
          'count': ['right', 'small'],
          'selectedItem': ['grantmakers-text'],
        },
      })
    );
  });

  const rangeSliderWithPanel = instantsearch.widgets.panel({
    'templates': {
      'header': 'Amount',
    },
    hidden(options) {
      return options.results.nbHits === 0;
    },
    'cssClasses': {
      'root': 'card',
      'header': panelHeaderClasses,
      'body': 'card-content',
    },
  })(instantsearch.widgets.rangeSlider);

  /* Range Slider */
  search.addWidget(
    rangeSliderWithPanel({
      container: '#ais-widget-range-slider',
      attribute: 'grant_amount',
      cssClasses: {
        tooltip: 'small',

      },
      tooltips: {
        format: function(rawValue) {
          return `$${numberHuman(rawValue, 0)}`;
        },
      },
      pips: false,
    })
  );

  /* Current Refinements */
  const createDataAttribtues = refinement =>
    Object.keys(refinement)
      .map(key => `data-${key}="${refinement[key]}"`)
      .join(' ');

  const renderListItem = item => `
    ${item.refinements.map(refinement => `

        <button class="waves-effect btn blue-grey lighten-3 grey-text text-darken-3" ${createDataAttribtues(refinement)}>remove_circle${getLabel(item.label)}: ${formatIfRangeLabel(refinement)} </button>

    `).join('')}
  `;

  const renderCurrentRefinements = (renderOptions) => {
    const { items, refine, widgetParams } = renderOptions;

    widgetParams.container.innerHTML = `<ul class="list list-inline">${items.map(renderListItem).join('')}</ul>`;

    [...widgetParams.container.querySelectorAll('button')].forEach(element => {
      element.addEventListener('click', event => {
        const item = Object.keys(event.currentTarget.dataset).reduce(
          (acc, key) => ({
            ...acc,
            [key]: event.currentTarget.dataset[key],
          }),
          {}
        );

        refine(item);
      });
    });
  };

  const customCurrentRefinements = instantsearch.connectors.connectCurrentRefinements(
    renderCurrentRefinements
  );

  search.addWidget(
    customCurrentRefinements({
      container: document.querySelector('#ais-widget-current-refined-values'),
    })
  );

  /* Clear Refinements */
  search.addWidget(
    instantsearch.widgets.clearRefinements({
      container: '#ais-widget-clear-all',
      templates: {
        resetLabel: 'Clear filters',
      },
      cssClasses: {
        button: ['btn', 'btn-custom', 'waves-effect', 'waves-light', 'white-text'],
      },
    })
  );

  /* Pagination */
  search.addWidget(
    instantsearch.widgets.pagination({
      container: '#ais-widget-pagination',
      scrollTo: '#grants-scroll-anchor', // #grants used in v2
      cssClasses: {
        root: 'pagination',
        page: 'waves-effect',
        selectedItem: ['active', 'grey'],
        disabledItem: 'disabled',
      },
    })
  );

  // Clear refinements button - mobile
  search.addWidget(
    instantsearch.widgets.clearRefinements({
      container: '#ais-widget-mobile-clear-all',
      cssClasses: {
        button: ['waves-effect', 'waves-light', 'btn', 'btn', 'grey', 'lighten-5', 'grey-text', 'text-darken-3'],
      },
      templates: {
        resetLabel: 'Clear',
      },
    })
  );

  // Initialize search
  search.start();

  // Initialize Materialize JS components
  // =======================================================
  search.once('render', function() {
    $('select').formSelect();
    reInitPushpin();
    showTableHeaderToast();
  });

  search.on('error', function(e) {
    if (e.statusCode === 429) {
      renderRateLimit();
      console.log('Rate limit reached');
    }
    if (e.statusCode === 403) {
      renderForbidden();
      console.log('Origin forbidden');
    }
  });

  // Scroll to top upon input change
  // =======================================================
  function readyToSearchScrollPosition() {
    $('html, body').animate({scrollTop: scrollAnchor}, '500', 'swing');
  }

  // Re-init grants header pushpin
  // =======================================================
  function reInitPushpin() {
    const target = $('.pushpin-nav-search');
    if (target.length) {
      target.pushpin({
        top: $('#grants').offset().top,
        bottom: $('#grants').offset().top + $('#grants').height() - target.height(),
        offset: 64,
      });
    }
  }

  // GA Events
  // =======================================================
  function gaEvents() {
    if (typeof gaCheck === 'function' && gaCount === 0) {
      ga('send', 'event', {
        eventCategory: 'Profile Events',
        eventAction: 'Profile Search Focus',
        eventLabel: 'Grants search',
      });
    }
    gaCount++;
  }

  // Temp solution for table header clicks
  // =======================================================
  function showTableHeaderToast() {
    $('.ais-hits th span').click(function() {
      if (typeof gaCheck === 'function') {
        ga('send', 'event', {
          eventCategory: 'Profile Events',
          eventAction: 'Profile Table Attempted Sort Click',
          eventLabel: $(this).find('span').text(),
        });
      }
      let toastHTML;
      if (isPhone.matches) {
        toastHTML = 'Sorting available for current year only <button class="btn-flat toast-action js-toast-action-scroll">GO</button>';
      } else {
        toastHTML = 'Sorting available for current year only <button class="btn-flat toast-action js-toast-action-scroll">Try It</button>';
      }
      M.toast({
        html: toastHTML,
        displayLength: 4000,
      });
      const targetElem = $('#current-year-list-view');
      $('.js-toast-action-scroll').click(function() {
        scrolly(targetElem);
        $('.collapsible-header i').addClass('bounce');
      });
    });
  }

  // Helper functions
  // =======================================================
  function scrolly(elem) {
    let position = $(elem).position().top;
    // animate
    $('html, body').animate({
      scrollTop: position + 100,
    }, 300, function() {
    });
  }

  function renderRateLimit() {
    const message = document.getElementById('rate-limit-message');
    message.classList.remove('hidden');

    const results = document.getElementById('algolia-hits-wrapper');
    results.classList.add('hidden');
  }

  function renderForbidden() {
    const message = document.getElementById('forbidden-message');
    message.classList.remove('hidden');

    const results = document.getElementById('algolia-hits-wrapper');
    results.classList.add('hidden');
  }

  function getLabel(item) {
    const obj = facets.filter(each => each.facet === item);
    return obj[0].label;
  }

  function formatIfRangeLabel(refinement) {
    if (refinement.attribute !== 'grant_amount') {
      return refinement.label;
    } else {
      return `${refinement.operator} $${numberHuman(refinement.value)}`;
    }
  }

  function numberHuman(num, decimals) {
    if (num === null) { return null; } // terminate early
    if (num === 0) { return '0'; } // terminate early
    if (isNaN(num)) { return num; } // terminate early if already a string - handles edge case likely caused by cacheing
    const fixed = !decimals || decimals < 0 ? 0 : decimals; // number of decimal places to show
    const b = num.toPrecision(2).split('e'); // get power
    const k = b.length === 1 ? 0 : Math.floor(Math.min(b[1].slice(1), 14) / 3); // floor at decimals, ceiling at trillions
    const c = k < 1 ? num.toFixed(0 + fixed) : (num / Math.pow(10, k * 3) ).toFixed(1 + fixed); // divide by power
    const d = c < 0 ? c : Math.abs(c); // enforce -0 is 0
    const e = d + ['', 'K', 'M', 'B', 'T'][k]; // append power
    return e;
  }
});
<commit_msg>Fix sort table helper message<commit_after>---
---
$(document).ready(function() {
  'use strict';
  // Helper definitions
  // =======================================================
  const targetEIN = $('h1.org-name').data('ein');
  const scrollAnchor = $('#grants').offset().top - 64; // 64 is height of profile-nav
  const isPhone = window.matchMedia('only screen and (max-width: 600px)');
  // const isMobile = window.matchMedia('only screen and (max-width: 992px)');
  let gaCheck = window[window['GoogleAnalyticsObject'] || 'ga']; // eslint-disable-line dot-notation
  let gaCount = 0;

  const formatter = new Intl.NumberFormat('en-US', {
    style: 'decimal',
    minimumFractionDigits: 0,
  });
  // Note - fixed grants header handled by profile.js

  // Initialize Materialize components
  // =======================================================
  // Note: if the element is created dynamically via Instantsearch widget,
  // the plugin needs to be initialized in the normal Instantsearch workflow
  // using the render method (e.g. search.once('render'...)
  $('.parallax').parallax();
  $('.sidenav').sidenav();

  // Algolia Instantsearch
  // =======================================================
  // Config
  const searchClient = algoliasearch('QA1231C5W9', '{{ site.algolia_public_key_grants }}');
  const facets = [
    {
      facet: 'tax_year',
      label: 'Tax Year',
    },
    {
      facet: 'grantee_city',
      label: 'City',
    },
    {
      facet: 'grantee_state',
      label: 'State',
    },
    {
      facet: 'grant_amount',
      label: 'Amount',
    },
  ];
  const search = instantsearch({
    'indexName': 'grantmakers_io',
    searchClient,
    'numberLocale': 'en-US',
    'searchParameters': {
      'filters': 'ein:' + targetEIN,
    },
    // routing: true,
    'routing': {
      'stateMapping': {
        stateToRoute({query, refinementList, range, page}) {
          return {
            'query': query,
            'tax_year':
              refinementList &&
              refinementList.tax_year &&
              refinementList.tax_year.join('~'),
            'grantee_city':
              refinementList &&
              refinementList.grantee_city &&
              refinementList.grantee_city.join('~'),
            'grantee_state':
              refinementList &&
              refinementList.grantee_state &&
              refinementList.grantee_state.join('~'),
            'grant_amount':
              range &&
              range.grant_amount &&
              range.grant_amount.replace(':', '~'),
            'page': page,
          };
        },
        /* eslint-disable camelcase */
        routeToState(routeState) {
          return {
            'query': routeState.query,
            'refinementList': {
              'tax_year': routeState.tax_year && routeState.tax_year.split('~'),
              'grantee_city': routeState.grantee_city && routeState.grantee_city.split('~'),
              'grantee_state': routeState.grantee_state && routeState.grantee_state.split('~'),
            },
            'range': {
              'grant_amount': routeState.grant_amount && routeState.grant_amount.replace('~', ':'),
            },
            'page': routeState.page,
          };
        },
      },
    },
  });
  /* eslint-enable camelcase */

  // Define templates
  const templateStats = `{% include algolia/stats.html %}`;

  // Define color palette
  const panelHeaderClasses = ['card-header', 'grey', 'lighten-4'];
  const panelHeaderClassesMobile = ['card-header', 'blue-grey', 'lighten-4'];

  // Construct widgets
  // =======================================================
  search.addWidget(
    instantsearch.widgets.searchBox({
      container: '#ais-widget-search-box',
      placeholder: 'Search by keyword or recipient name',
      autofocus: false,
      reset: true,
      queryHook: function(query, searchInstance) {
        readyToSearchScrollPosition();
        searchInstance(query);
        gaEvents();
      },
    })
  );

  /* Stats */
  search.addWidget(
    instantsearch.widgets.stats({
      container: '#ais-widget-stats',
      templates: {
        text: templateStats,
      },
      cssClasses: {
        root: 'center-align-on-mobile',
        text: ['text-muted', 'hide-on-med-and-down'],
      },
    })
  );

  /* Hits */
  const renderHits = (renderOptions) => {
    const { hits, results, widgetParams } = renderOptions;

    if (!hits.length && results) {
      document.getElementById('ais-widget-pagination').classList.add('hidden');
      widgetParams.container.innerHTML = `

              {% raw %}

We didn't find any results for the search "${ results.query }"

              {% endraw %}

            Find a Different Foundation
            Search all Grants

      `;
    } else {
      document.getElementById('ais-widget-pagination').classList.remove('hidden');
      widgetParams.container.innerHTML = `

            Year
            Name
            Purpose
            Location
            Amount

          ${hits.map(item =>`

              ${ item.tax_year }
              ${ instantsearch.highlight({ attribute: 'grantee_name', hit: item }) }
              ${ instantsearch.highlight({ attribute: 'grant_purpose', hit: item }) }
              ${ item.grantee_city.length ? instantsearch.highlight({ attribute: 'grantee_city', hit: item }) + ', ' + item.grantee_state : item.grantee_state}
              $${ item.grant_amount.toLocaleString() }

          `).join('')}

    `;
    }
  };

  const customHits = instantsearch.connectors.connectHits(
    renderHits
  );

  search.addWidget(
    customHits({
      container: document.querySelector('#ais-widget-hits'),
    })
  );

  /* Refinements */
  facets.forEach((refinement) => {
    if (refinement.facet === 'grant_amount') {
      return;
    }
    /* Desktop */
    const refinementListWithPanel = instantsearch.widgets.panel({
      'templates': {
        'header': refinement.label,
      },
      hidden(options) {
        return options.results.nbHits === 0;
      },
      'cssClasses': {
        'root': 'card',
        'header': panelHeaderClasses,
        'body': 'card-content',
      },
    })(instantsearch.widgets.refinementList);

    search.addWidget(
      refinementListWithPanel({
        'container': `#ais-widget-refinement-list--${refinement.facet}`,
        'attribute': refinement.facet,
        'limit': 8,
        'showMore': false,
        // 'searchable': true,
        'cssClasses': {
          'checkbox': 'filled-in',
          'labelText': 'small',
          'count': ['right', 'small'],
          // 'selectedItem': ['grants-search-text'],
          // 'searchableRoot': 'ais-SearchBox-refinements',
          // 'searchableSubmit': 'hidden',
        },
      })
    );

    /* Mobile */
    const mobileRefinementListWithPanel = instantsearch.widgets.panel({
      'templates': {
        'header': refinement.label,
      },
      hidden(options) {
        return options.results.nbHits === 0;
      },
      'cssClasses': {
        'root': 'card',
        'header': panelHeaderClassesMobile,
        'body': 'card-content',
      },
    })(instantsearch.widgets.refinementList);

    search.addWidget(
      mobileRefinementListWithPanel({
        'container': `#ais-widget-mobile-refinement-list--${refinement.facet}`,
        'attribute': refinement.facet,
        'limit': 8,
        'showMore': false,
        'cssClasses': {
          'checkbox': 'filled-in',
          'count': ['right', 'small'],
          'selectedItem': ['grantmakers-text'],
        },
      })
    );
  });

  const rangeSliderWithPanel = instantsearch.widgets.panel({
    'templates': {
      'header': 'Amount',
    },
    hidden(options) {
      return options.results.nbHits === 0;
    },
    'cssClasses': {
      'root': 'card',
      'header': panelHeaderClasses,
      'body': 'card-content',
    },
  })(instantsearch.widgets.rangeSlider);

  /* Range Slider */
  search.addWidget(
    rangeSliderWithPanel({
      container: '#ais-widget-range-slider',
      attribute: 'grant_amount',
      cssClasses: {
        tooltip: 'small',

      },
      tooltips: {
        format: function(rawValue) {
          return `$${numberHuman(rawValue, 0)}`;
        },
      },
      pips: false,
    })
  );

  /* Current Refinements */
  const createDataAttribtues = refinement =>
    Object.keys(refinement)
      .map(key => `data-${key}="${refinement[key]}"`)
      .join(' ');

  const renderListItem = item => `
    ${item.refinements.map(refinement => `

        <button class="waves-effect btn blue-grey lighten-3 grey-text text-darken-3" ${createDataAttribtues(refinement)}>remove_circle${getLabel(item.label)}: ${formatIfRangeLabel(refinement)} </button>

    `).join('')}
  `;

  const renderCurrentRefinements = (renderOptions) => {
    const { items, refine, widgetParams } = renderOptions;

    widgetParams.container.innerHTML = `<ul class="list list-inline">${items.map(renderListItem).join('')}</ul>`;

    [...widgetParams.container.querySelectorAll('button')].forEach(element => {
      element.addEventListener('click', event => {
        const item = Object.keys(event.currentTarget.dataset).reduce(
          (acc, key) => ({
            ...acc,
            [key]: event.currentTarget.dataset[key],
          }),
          {}
        );

        refine(item);
      });
    });
  };

  const customCurrentRefinements = instantsearch.connectors.connectCurrentRefinements(
    renderCurrentRefinements
  );

  search.addWidget(
    customCurrentRefinements({
      container: document.querySelector('#ais-widget-current-refined-values'),
    })
  );

  /* Clear Refinements */
  search.addWidget(
    instantsearch.widgets.clearRefinements({
      container: '#ais-widget-clear-all',
      templates: {
        resetLabel: 'Clear filters',
      },
      cssClasses: {
        button: ['btn', 'btn-custom', 'waves-effect', 'waves-light', 'white-text'],
      },
    })
  );

  /* Pagination */
  search.addWidget(
    instantsearch.widgets.pagination({
      container: '#ais-widget-pagination',
      scrollTo: '#grants-scroll-anchor', // #grants used in v2
      cssClasses: {
        root: 'pagination',
        page: 'waves-effect',
        selectedItem: ['active', 'grey'],
        disabledItem: 'disabled',
      },
    })
  );

  // Clear refinements button - mobile
  search.addWidget(
    instantsearch.widgets.clearRefinements({
      container: '#ais-widget-mobile-clear-all',
      cssClasses: {
        button: ['waves-effect', 'waves-light', 'btn', 'btn', 'grey', 'lighten-5', 'grey-text', 'text-darken-3'],
      },
      templates: {
        resetLabel: 'Clear',
      },
    })
  );

  // Initialize search
  search.start();

  // Initialize Materialize JS components
  // =======================================================
  search.once('render', function() {
    $('select').formSelect();
    reInitPushpin();
    showTableHeaderToast();
  });

  search.on('error', function(e) {
    if (e.statusCode === 429) {
      renderRateLimit();
      console.log('Rate limit reached');
    }
    if (e.statusCode === 403) {
      renderForbidden();
      console.log('Origin forbidden');
    }
  });

  // Scroll to top upon input change
  // =======================================================
  function readyToSearchScrollPosition() {
    $('html, body').animate({scrollTop: scrollAnchor}, '500', 'swing');
  }

  // Re-init grants header pushpin
  // =======================================================
  function reInitPushpin() {
    const target = $('.pushpin-nav-search');
    if (target.length) {
      target.pushpin({
        top: $('#grants').offset().top,
        bottom: $('#grants').offset().top + $('#grants').height() - target.height(),
        offset: 64,
      });
    }
  }

  // GA Events
  // =======================================================
  function gaEvents() {
    if (typeof gaCheck === 'function' && gaCount === 0) {
      ga('send', 'event', {
        eventCategory: 'Profile Events',
        eventAction: 'Profile Search Focus',
        eventLabel: 'Grants search',
      });
    }
    gaCount++;
  }

  // Temp solution for table header clicks
  // =======================================================
  function showTableHeaderToast() {
    $('.ais-hits-table th span').click(function() {
      if (typeof gaCheck === 'function') {
        ga('send', 'event', {
          eventCategory: 'Profile Events',
          eventAction: 'Profile Table Attempted Sort Click',
          eventLabel: $(this).find('span').text(),
        });
      }
      let toastHTML;
      if (isPhone.matches) {
        toastHTML = 'Sorting available for current year only <button class="btn-flat toast-action js-toast-action-scroll">GO</button>';
      } else {
        toastHTML = 'Sorting available for current year only <button class="btn-flat toast-action js-toast-action-scroll">Try It</button>';
      }
      M.toast({
        html: toastHTML,
        displayLength: 4000,
      });
      const targetElem = $('#current-year-list-view');
      $('.js-toast-action-scroll').click(function() {
        scrolly(targetElem);
        $('.collapsible-header i').addClass('bounce');
      });
    });
  }

  // Helper functions
  // =======================================================
  function scrolly(elem) {
    let position = $(elem).position().top;
    // animate
    $('html, body').animate({
      scrollTop: position + 100,
    }, 300, function() {
    });
  }

  function renderRateLimit() {
    const message = document.getElementById('rate-limit-message');
    message.classList.remove('hidden');

    const results = document.getElementById('algolia-hits-wrapper');
    results.classList.add('hidden');
  }

  function renderForbidden() {
    const message = document.getElementById('forbidden-message');
    message.classList.remove('hidden');

    const results = document.getElementById('algolia-hits-wrapper');
    results.classList.add('hidden');
  }

  function getLabel(item) {
    const obj = facets.filter(each => each.facet === item);
    return obj[0].label;
  }

  function formatIfRangeLabel(refinement) {
    if (refinement.attribute !== 'grant_amount') {
      return refinement.label;
    } else {
      return `${refinement.operator} $${numberHuman(refinement.value)}`;
    }
  }

  function numberHuman(num, decimals) {
    if (num === null) { return null; } // terminate early
    if (num === 0) { return '0'; } // terminate early
    if (isNaN(num)) { return num; } // terminate early if already a string - handles edge case likely caused by cacheing
    const fixed = !decimals || decimals < 0 ? 0 : decimals; // number of decimal places to show
    const b = num.toPrecision(2).split('e'); // get power
    const k = b.length === 1 ? 0 : Math.floor(Math.min(b[1].slice(1), 14) / 3); // floor at decimals, ceiling at trillions
    const c = k < 1 ? num.toFixed(0 + fixed) : (num / Math.pow(10, k * 3) ).toFixed(1 + fixed); // divide by power
    const d = c < 0 ? c : Math.abs(c); // enforce -0 is 0
    const e = d + ['', 'K', 'M', 'B', 'T'][k]; // append power
    return e;
  }
});
<|endoftext|>


<commit_before>import JSONAPIAdapter from 'ember-data/adapters/json-api';
import EsQuery from 'ember-es-adapter/utils/es-query-builder';
import extend from 'ember-es-adapter/utils/extend';
import Ember from 'ember';
import config from 'ember-get-config';

const {environment} = config;

export default JSONAPIAdapter.extend({
  config: environment,

  urlForCreateRecord(modelName, snapshot) {
    return [this.buildURL(modelName), snapshot.id].join('/');
  },

  query(store, type, params) {
    const url = [this.buildURL(type.modelName), '_search'].join('/');
    let query = Ember.get(params, 'esQuery') || null,
        esParams = Ember.get(params, 'esParams') || null,
        _params = this.filter(params); // Filter out params we don't know how to handle

    // No EsQuery object was supplied, so we'll make a new one. This
    // allows us to build the query outside of the adapter if needed.
    if (Ember.isEmpty(query)) {
      // Initiate new instance with params if supplied
      // Inject _params into params object
      let es = new EsQuery(extend(esParams, _params));

      // Add in query if supplied in params;
      if (params.query) {
        es.addBool({"query_string": {"query": params.query}});
      }
      query = es.buildQuery();
    }

    return fetch(url, {
      method: "POST",
      body: JSON.stringify(query)
    })
    .then(function(resp) {
      return resp.json();
    });
  },

  findAll(store, type) {
    const url = [this.buildURL(type.modelName), '_search'].join('/');

    let es = new EsQuery({ 'size': 10000 });

    return fetch(url, {
      method: "post",
      body: JSON.stringify(es.buildQuery())
    })
    .then(function(resp) {
      return resp.json();
    });
  },

  filter(obj) {
    let allowed = ['size', 'from', 'query', 'page'];

    for (let i in obj) {
      if (allowed.indexOf(i) < 0) {
        delete obj[i];
      }
    }
    return obj;
  }

});
<commit_msg>added updateRecord<commit_after>import JSONAPIAdapter from 'ember-data/adapters/json-api';
import EsQuery from 'ember-es-adapter/utils/es-query-builder';
import extend from 'ember-es-adapter/utils/extend';
import Ember from 'ember';
import config from 'ember-get-config';

const {environment} = config;

export default JSONAPIAdapter.extend({
  config: environment,

  urlForCreateRecord(modelName, snapshot) {
    return [this.buildURL(modelName), snapshot.id].join('/');
  },

  query(store, type, params) {
    const url = [this.buildURL(type.modelName), '_search'].join('/');
    let query = Ember.get(params, 'esQuery') || null,
        esParams = Ember.get(params, 'esParams') || null,
        _params = this.filter(params); // Filter out params we don't know how to handle

    // No EsQuery object was supplied, so we'll make a new one. This
    // allows us to build the query outside of the adapter if needed.
    if (Ember.isEmpty(query)) {
      // Initiate new instance with params if supplied
      // Inject _params into params object
      let es = new EsQuery(extend(esParams, _params));

      // Add in query if supplied in params;
      if (params.query) {
        es.addBool({"query_string": {"query": params.query}});
      }
      query = es.buildQuery();
    }

    return fetch(url, {
      method: "POST",
      body: JSON.stringify(query)
    })
    .then(function(resp) {
      return resp.json();
    });
  },

  findAll(store, type) {
    const url = [this.buildURL(type.modelName), '_search'].join('/');

    let es = new EsQuery({ 'size': 10000 });

    return fetch(url, {
      method: "post",
      body: JSON.stringify(es.buildQuery())
    })
    .then(function(resp) {
      return resp.json();
    });
  },

  filter(obj) {
    let allowed = ['size', 'from', 'query', 'page'];

    for (let i in obj) {
      if (allowed.indexOf(i) < 0) {
        delete obj[i];
      }
    }
    return obj;
  },

  updateRecord(nodelName, type, snapshot) {
    let data = {},
        serializer = this.store.serializerFor(type.modelName);

    serializer.serializeIntoHash(data, type, snapshot, { includeId: true });

    let id = snapshot.id,
        url = this.buildURL(type.modelName, id, snapshot, 'updateRecord');

    return fetch(url, {
      method: "post",
      body: JSON.stringify(data)
    })
    .then(function(resp) {
      return resp.json();
    });

  }

});
<|endoftext|>


<commit_before>if(!this.JSON){this.JSON={};}
(function(){function f(n){return n<10?'0'+n:n;}
if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+
f(this.getUTCMonth()+1)+'-'+
f(this.getUTCDate())+'T'+
f(this.getUTCHours())+':'+
f(this.getUTCMinutes())+':'+
f(this.getUTCSeconds())+'Z':null;};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf();};}
var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);})+'"':'"'+string+'"';}
function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key);}
if(typeof rep==='function'){value=rep.call(holder,key,value);}
switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';}
v=partial.length===0?'[]':gap?'[\n'+gap+
partial.join(',\n'+gap)+'\n'+
mind+']':'['+partial.join(',')+']';gap=mind;return v;}
if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==='string'){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}
v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+
mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}}
if(typeof JSON.stringify!=='function'){JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;}
rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}
return str('',{'':value});};}
if(typeof JSON.parse!=='function'){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}}
return reviver.call(holder,key,value);}
cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+
('0000'+a.charCodeAt(0).toString(16)).slice(-4);});}
if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;}
throw new SyntaxError('JSON.parse');};}}());
<commit_msg>Update json2.min.js<commit_after>/***
if(!this.JSON){this.JSON={};}
(function(){function f(n){return n<10?'0'+n:n;}
if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+
f(this.getUTCMonth()+1)+'-'+
f(this.getUTCDate())+'T'+
f(this.getUTCHours())+':'+
f(this.getUTCMinutes())+':'+
f(this.getUTCSeconds())+'Z':null;};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf();};}
var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);})+'"':'"'+string+'"';}
function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key);}
if(typeof rep==='function'){value=rep.call(holder,key,value);}
switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';}
v=partial.length===0?'[]':gap?'[\n'+gap+
partial.join(',\n'+gap)+'\n'+
mind+']':'['+partial.join(',')+']';gap=mind;return v;}
if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==='string'){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}
v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+
mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}}
if(typeof JSON.stringify!=='function'){JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;}
rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}
return str('',{'':value});};}
if(typeof JSON.parse!=='function'){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}}
return reviver.call(holder,key,value);}
cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+
('0000'+a.charCodeAt(0).toString(16)).slice(-4);});}
if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;}
throw new SyntaxError('JSON.parse');};}}());
**/
var __slice = Array.prototype.slice;
(function($) {
  var Sketch;
  $.fn.sketch = function() {
    var args, key, sketch;
    key = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
    if (this.length > 1) {
      $.error('Sketch.js can only be called on one element at a time.');
    }
    sketch = this.data('sketch');
    if (typeof key === 'string' && sketch) {
      if (sketch[key]) {
        if (typeof sketch[key] === 'function') {
          return sketch[key].apply(sketch, args);
        } else if (args.length === 0) {
          return sketch[key];
        } else if (args.length === 1) {
          return sketch[key] = args[0];
        }
      } else {
        return $.error('Sketch.js did not recognize the given command.');
      }
    } else if (sketch) {
      return sketch;
    } else {
      this.data('sketch', new Sketch(this.get(0), key));
      return this;
    }
  };
  Sketch = (function() {
    function Sketch(el, opts) {
      this.el = el;
      this.canvas = $(el);
      this.context = el.getContext('2d');
      this.options = $.extend({
        toolLinks: true,
        defaultTool: 'marker',
        defaultColor: '#000000',
        defaultSize: 5
      }, opts);
      this.painting = false;
      this.color = this.options.defaultColor;
      this.size = this.options.defaultSize;
      this.tool = this.options.defaultTool;
      this.actions = [];
      this.action = [];
      this.canvas.bind('click mousedown mouseup mousemove mouseleave mouseout touchstart touchmove touchend touchcancel', this.onEvent);
      if (this.options.toolLinks) {
        $('body').delegate("a[href=\"#" + (this.canvas.attr('id')) + "\"]", 'click', function(e) {
          var $canvas, $this, key, sketch, _i, _len, _ref;
          $this = $(this);
          $canvas = $($this.attr('href'));
          sketch = $canvas.data('sketch');
          _ref = ['color', 'size', 'tool'];
          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
            key = _ref[_i];
            if ($this.attr("data-" + key)) {
              sketch.set(key, $(this).attr("data-" + key));
            }
          }
          if ($(this).attr('data-download')) {
            sketch.download($(this).attr('data-download'));
          }
          return false;
        });
      }
    }
    Sketch.prototype.download = function(format) {
      var mime;
      format || (format = "png");
      if (format === "jpg") {
        format = "jpeg";
      }
      mime = "image/" + format;
      return window.open(this.el.toDataURL(mime));
    };
    Sketch.prototype.set = function(key, value) {
      this[key] = value;
      return this.canvas.trigger("sketch.change" + key, value);
    };
    Sketch.prototype.startPainting = function() {
      this.painting = true;
      return this.action = {
        tool: this.tool,
        color: this.color,
        size: parseFloat(this.size),
        events: []
      };
    };
    Sketch.prototype.stopPainting = function() {
      if (this.action) {
        this.actions.push(this.action);
      }
      this.painting = false;
      this.action = null;
      return this.redraw();
    };
    Sketch.prototype.onEvent = function(e) {
      if (e.originalEvent && e.originalEvent.targetTouches) {
        e.pageX = e.originalEvent.targetTouches[0].pageX;
        e.pageY = e.originalEvent.targetTouches[0].pageY;
      }
      $.sketch.tools[$(this).data('sketch').tool].onEvent.call($(this).data('sketch'), e);
      e.preventDefault();
      return false;
    };
    Sketch.prototype.redraw = function() {
      var sketch;
      this.el.width = this.canvas.width();
      this.context = this.el.getContext('2d');
      sketch = this;
      $.each(this.actions, function() {
        if (this.tool) {
          return $.sketch.tools[this.tool].draw.call(sketch, this);
        }
      });
      if (this.painting && this.action) {
        return $.sketch.tools[this.action.tool].draw.call(sketch, this.action);
      }
    };
    return Sketch;
  })();
  $.sketch = {
    tools: {}
  };
  $.sketch.tools.marker = {
    onEvent: function(e) {
      switch (e.type) {
        case 'mousedown':
        case 'touchstart':
          this.startPainting();
          break;
        case 'mouseup':
        case 'mouseout':
        case 'mouseleave':
        case 'touchend':
        case 'touchcancel':
          this.stopPainting();
      }
      if (this.painting) {
        this.action.events.push({
          x: e.pageX - this.canvas.offset().left,
          y: e.pageY - this.canvas.offset().top,
          event: e.type
        });
        return this.redraw();
      }
    },
    draw: function(action) {
      var event, previous, _i, _len, _ref;
      this.context.lineJoin = "round";
      this.context.lineCap = "round";
      this.context.beginPath();
      this.context.moveTo(action.events[0].x, action.events[0].y);
      _ref = action.events;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        event = _ref[_i];
        this.context.lineTo(event.x, event.y);
        previous = event;
      }
      this.context.strokeStyle = action.color;
      this.context.lineWidth = action.size;
      return this.context.stroke();
    }
  };
  return $.sketch.tools.eraser = {
    onEvent: function(e) {
      return $.sketch.tools.marker.onEvent.call(this, e);
    },
    draw: function(action) {
      var oldcomposite;
      oldcomposite = this.context.globalCompositeOperation;
      this.context.globalCompositeOperation = "destination-out";
      action.color = "#000000";
      $.sketch.tools.marker.draw.call(this, action);
      return this.context.globalCompositeOperation = oldcomposite;
    }
  };
})(jQuery);
<|endoftext|>


<commit_before>/* eslint-disable quote-props, no-magic-numbers */
import Ember from 'ember';
import { hasTransitions, onTransitionEnd } from 'ember-modal-service/utils/css-transitions';

const {
	Component,
	computed,
	inject: { service },
	on,
	String: { camelize },
	RSVP,
	run
} = Ember;

/**
 * Component to wrap modal objects.
 *
 * @extends Ember.Component
 */
export default Component.extend({

	/**
	 * Modal service inject.
	 *
	 * @property modal
	 * @type Object
	 */
	scheduler: service('scheduler'),

	/**
	 * Modal service inject.
	 *
	 * @property modal
	 * @type Object
	 */
	modal: service('modal'),

	/**
	 * HTML class name bindings.
	 *
	 * @property classNameBindings
	 * @type Array
	 */
	classNameBindings: ['model.options.hasOverlay:modal-overlay:modal-view'],

	/**
	 * HTML attributes bindings.
	 *
	 * @property attributeBindings
	 * @type Array
	 */
	attributeBindings: ['data-modal-show', 'role', 'data-id'],

	/**
	 * HTML role.
	 *
	 * @property role
	 * @type String
	 */
	role: 'dialog',

	/**
	 * Modal is visible/hidden.
	 *
	 * @property visible
	 * @type Boolean
	 */
	visible: false,

	/**
	 * `data-id` attribute of wrapper element
	 *
	 * @property data-id
	 * @type {String}
	 */
	'data-id': computed('model.fullname', function() {
		return camelize(this.get('model.fullname'));
	}),

	/**
	 * Modal is visible/hidden. This property is read from CSS.
	 *
	 * @property data-modal-show
	 * @type Boolean
	 */
	'data-modal-show': computed('visible', function() {
		return String(this.get('visible'));
	}),

	/**
	 * On did insert element, set element as visible and set data-id.
	 *
	 * @event onDidInsertElement
	 */
	onDidInsertElement: on('didInsertElement', function() {
		const scheduler = this.get('scheduler');

		run.next(scheduler.scheduleOnce.bind(scheduler, this, '_open'));
	}),

	/**
	 * Resolve current promise and close modal.
	 *
	 * @method resolve
	 */
	resolve(data, label = `Component '${this.get('model.fullname')}': fulfillment`) {
		this.get('model.deferred').resolve(data, label);
	},

	/**
	 * Reject current promise and close modal.
	 *
	 * @method reject
	 */
	reject(data, label = `Component '${this.get('model.fullname')}': rejection`) {
		this.get('model.deferred').reject(data, label);
	},

	/**
	 * Action to know when modal is fully opened.
	 *
	 * @method didOpen
	 */
	didOpen() {},

	/**
	 * Safe call to didOpen method.
	 *
	 * @method _safeDidOpen
	 */
	_safeDidOpen() {
		if (this.isDestroyed) {
			return;
		}

		this.didOpen();
	},

	/**
	 * Turn on visibility and send didOpen event.
	 *
	 * @method _open
	 * @private
	 */
	_open() {
		if (this.isDestroyed) {
			return;
		}

		const scheduler = this.get('scheduler');
		const element = this.element;

		this.set('visible', true);

		if (hasTransitions(element)) {
			onTransitionEnd(element, scheduler.scheduleOnce.bind(scheduler, this, '_safeDidOpen'), 'all', true);
		} else {
			this.didOpen();
		}
	},

	/**
	 * Set modal as not visible and remove modal from array later.
	 *
	 * @method _close
	 * @private
	 */
	_close() {
		if (this.isDestroyed) {
			return;
		}

		const scheduler = this.get('scheduler');
		const element = this.$().get(0);

		// Close modal.
		this.set('visible', false);

		// Remove modal from array when transition ends.
		if (hasTransitions(element)) {
			onTransitionEnd(element, scheduler.scheduleOnce.bind(scheduler, this, '_remove'), 'all', true);
		} else {
			this._remove();
		}
	},

	/**
	 * Remove itself from service.
	 *
	 * @method _remove
	 * @private
	 */
	_remove() {
		if (this.isDestroyed) {
			return;
		}

		this.get('modal.content').removeObject(this.get('model'));
	},

	/**
	 * When the promise has been settled, close the view.
	 *
	 * @method hasBeenSettled
	 * @private
	 */
	_hasBeenSettled: on('init', function() {
		// Prevent triggering Ember.onerror on promise resolution.
		this.get('model.promise').catch((e) => {
			if (e instanceof Error) {
				return RSVP.reject(e, `Component '${this.get('model.fullname')}': bubble error`);
			}

			// Ignore rejections due to not being real errors here.
			return e;
		}, `Component '${this.get('model.fullname')}': catch real errors or ignore`).finally(
			this._close.bind(this),
			`Component '${this.get('model.fullname')}': close modal`
		);
	}),

	actions: {

		/**
		 * Action to resolve the underlying modal promise directly from the
		 * template, using the passed arguments as resolution values
		 *
		 * @method resolve
		 */
		resolve() {
			this.resolve(...arguments);
		},

		/**
		 * Action to reject the underlying modal promise directly from the
		 * template, using the passed arguments as rejection values
		 *
		 * @method reject
		 */
		reject() {
			this.reject(...arguments);
		}

	}
});
<commit_msg>Use standard ember ariaRole<commit_after>/* eslint-disable quote-props, no-magic-numbers */
import Ember from 'ember';
import { hasTransitions, onTransitionEnd } from 'ember-modal-service/utils/css-transitions';

const {
	Component,
	computed,
	inject: { service },
	on,
	String: { camelize },
	RSVP,
	run
} = Ember;

/**
 * Component to wrap modal objects.
 *
 * @extends Ember.Component
 */
export default Component.extend({

	/**
	 * Modal service inject.
	 *
	 * @property modal
	 * @type Object
	 */
	scheduler: service('scheduler'),

	/**
	 * Modal service inject.
	 *
	 * @property modal
	 * @type Object
	 */
	modal: service('modal'),

	/**
	 * HTML class name bindings.
	 *
	 * @property classNameBindings
	 * @type Array
	 */
	classNameBindings: ['model.options.hasOverlay:modal-overlay:modal-view'],

	/**
	 * HTML attributes bindings.
	 *
	 * @property attributeBindings
	 * @type Array
	 */
	attributeBindings: ['data-modal-show', 'data-id'],

	/**
	 * HTML role.
	 *
	 * @property ariaRole
	 * @type String
	 */
	ariaRole: 'dialog',

	/**
	 * Modal is visible/hidden.
	 *
	 * @property visible
	 * @type Boolean
	 */
	visible: false,

	/**
	 * `data-id` attribute of wrapper element
	 *
	 * @property data-id
	 * @type {String}
	 */
	'data-id': computed('model.fullname', function() {
		return camelize(this.get('model.fullname'));
	}),

	/**
	 * Modal is visible/hidden. This property is read from CSS.
	 *
	 * @property data-modal-show
	 * @type Boolean
	 */
	'data-modal-show': computed('visible', function() {
		return String(this.get('visible'));
	}),

	/**
	 * On did insert element, set element as visible and set data-id.
	 *
	 * @event onDidInsertElement
	 */
	onDidInsertElement: on('didInsertElement', function() {
		const scheduler = this.get('scheduler');

		run.next(scheduler.scheduleOnce.bind(scheduler, this, '_open'));
	}),

	/**
	 * Resolve current promise and close modal.
	 *
	 * @method resolve
	 */
	resolve(data, label = `Component '${this.get('model.fullname')}': fulfillment`) {
		this.get('model.deferred').resolve(data, label);
	},

	/**
	 * Reject current promise and close modal.
	 *
	 * @method reject
	 */
	reject(data, label = `Component '${this.get('model.fullname')}': rejection`) {
		this.get('model.deferred').reject(data, label);
	},

	/**
	 * Action to know when modal is fully opened.
	 *
	 * @method didOpen
	 */
	didOpen() {},

	/**
	 * Safe call to didOpen method.
	 *
	 * @method _safeDidOpen
	 */
	_safeDidOpen() {
		if (this.isDestroyed) {
			return;
		}

		this.didOpen();
	},

	/**
	 * Turn on visibility and send didOpen event.
	 *
	 * @method _open
	 * @private
	 */
	_open() {
		if (this.isDestroyed) {
			return;
		}

		const scheduler = this.get('scheduler');
		const element = this.element;

		this.set('visible', true);

		if (hasTransitions(element)) {
			onTransitionEnd(element, scheduler.scheduleOnce.bind(scheduler, this, '_safeDidOpen'), 'all', true);
		} else {
			this.didOpen();
		}
	},

	/**
	 * Set modal as not visible and remove modal from array later.
	 *
	 * @method _close
	 * @private
	 */
	_close() {
		if (this.isDestroyed) {
			return;
		}

		const scheduler = this.get('scheduler');
		const element = this.$().get(0);

		// Close modal.
		this.set('visible', false);

		// Remove modal from array when transition ends.
		if (hasTransitions(element)) {
			onTransitionEnd(element, scheduler.scheduleOnce.bind(scheduler, this, '_remove'), 'all', true);
		} else {
			this._remove();
		}
	},

	/**
	 * Remove itself from service.
	 *
	 * @method _remove
	 * @private
	 */
	_remove() {
		if (this.isDestroyed) {
			return;
		}

		this.get('modal.content').removeObject(this.get('model'));
	},

	/**
	 * When the promise has been settled, close the view.
	 *
	 * @method hasBeenSettled
	 * @private
	 */
	_hasBeenSettled: on('init', function() {
		// Prevent triggering Ember.onerror on promise resolution.
		this.get('model.promise').catch((e) => {
			if (e instanceof Error) {
				return RSVP.reject(e, `Component '${this.get('model.fullname')}': bubble error`);
			}

			// Ignore rejections due to not being real errors here.
			return e;
		}, `Component '${this.get('model.fullname')}': catch real errors or ignore`).finally(
			this._close.bind(this),
			`Component '${this.get('model.fullname')}': close modal`
		);
	}),

	actions: {

		/**
		 * Action to resolve the underlying modal promise directly from the
		 * template, using the passed arguments as resolution values
		 *
		 * @method resolve
		 */
		resolve() {
			this.resolve(...arguments);
		},

		/**
		 * Action to reject the underlying modal promise directly from the
		 * template, using the passed arguments as rejection values
		 *
		 * @method reject
		 */
		reject() {
			this.reject(...arguments);
		}

	}
});
<|endoftext|>


<commit_before>import Ember from 'ember'
const {Service} = Ember
import Poller from '../classes/poller'

/**
 * @callback {Function} PollboyCallback
 * @returns {Ember.RSVP.Promise} Resolved when next polling interval should begin
 */

export default Service.extend({
  pollers: [],

  init () {
    this._super()
    this._onVisibilityChangeHandler = this.onVisibilityChange.bind(this)
    document.addEventListener('visibilitychange', this._onVisibilityChangeHandler, false)
  },

  willDestroy () {
    document.removeEventListener('visibilitychange', this._onVisibilityChangeHandler)
  },

  /**
   * Pause/resume polling based on whether or not page is currently visible to user.
   * When user switches browser tabs polling will stop.
   * When user switches applications polling will stop.
   * When page comes back into focus any pollers that were stopped due to loss of page focus will resume.
   * @param {Event} event - visibilitychange event data
   */
  onVisibilityChange (event) {
    const pollers = this.get('pollers')

    // If page is no longer visible pause pollers
    if (event.target.hidden) {
      pollers.forEach((poller) => {
        poller.pause()
      })

      return
    }

    // If page has become visible make sure to resume pollers that were previously paused
    pollers.forEach((poller) => {
      poller.resume()
    })
  },

  /**
   * Add new polling item
   * @param {Object} context - context to be provided to callback
   * @param {PollboyCallback} callback - callback function for each poll interval
   * @param {Number} interval - number of milliseconds between polls
   * @returns {Poller} poller instance
   */
  add (context, callback, interval) {
    const poller = Poller.create({
      callback,
      context,
      interval
    })

    poller.start()

    this.get('pollers').push(poller)

    return poller
  },

  /**
   * Remove poller
   * @param {Poller} poller - Poller to remove
   */
  remove (poller) {
    poller.stop() // Make sure poller is no longer polling

    const pollers = this.get('pollers')
    const index = pollers.indexOf(poller)

    if (index !== -1) {
      pollers.splice(index, 1)
    }
  }
})
<commit_msg>FastBoot Compatibility<commit_after>import Ember from 'ember'
const {Service} = Ember
import Poller from '../classes/poller'

/**
 * @callback {Function} PollboyCallback
 * @returns {Ember.RSVP.Promise} Resolved when next polling interval should begin
 */

export default Service.extend({
  pollers: [],

  init () {
    this._super()
    this._onVisibilityChangeHandler = this.onVisibilityChange.bind(this)
    if (typeof document !== 'undefined') {
      document.addEventListener('visibilitychange', this._onVisibilityChangeHandler, false)
    }
  },

  willDestroy () {
    if (typeof document !== 'undefined') {
      document.removeEventListener('visibilitychange', this._onVisibilityChangeHandler)
    }
  },

  /**
   * Pause/resume polling based on whether or not page is currently visible to user.
   * When user switches browser tabs polling will stop.
   * When user switches applications polling will stop.
   * When page comes back into focus any pollers that were stopped due to loss of page focus will resume.
   * @param {Event} event - visibilitychange event data
   */
  onVisibilityChange (event) {
    const pollers = this.get('pollers')

    // If page is no longer visible pause pollers
    if (event.target.hidden) {
      pollers.forEach((poller) => {
        poller.pause()
      })

      return
    }

    // If page has become visible make sure to resume pollers that were previously paused
    pollers.forEach((poller) => {
      poller.resume()
    })
  },

  /**
   * Add new polling item
   * @param {Object} context - context to be provided to callback
   * @param {PollboyCallback} callback - callback function for each poll interval
   * @param {Number} interval - number of milliseconds between polls
   * @returns {Poller} poller instance
   */
  add (context, callback, interval) {
    const poller = Poller.create({
      callback,
      context,
      interval
    })

    poller.start()

    this.get('pollers').push(poller)

    return poller
  },

  /**
   * Remove poller
   * @param {Poller} poller - Poller to remove
   */
  remove (poller) {
    poller.stop() // Make sure poller is no longer polling

    const pollers = this.get('pollers')
    const index = pollers.indexOf(poller)

    if (index !== -1) {
      pollers.splice(index, 1)
    }
  }
})
<|endoftext|>


<commit_before>Ext.onReady(function() {

	Ext.direct.Manager.addProvider(Ext.app.REMOTING_API);

	var listStore = Ext.create('Ext.data.ArrayStore', {
		fields: [ {
			name: 'list'
		}, {
			name: 'id'
		} ],
		data: [ [ 'Persons with FullName', 'Persons with FullName' ],
				[ 'Persons with FullName and City', 'Persons with FullName and City' ],
				[ 'Persons with everything', 'Persons with everything' ] ]
	});

	var directStore = new Ext.data.DirectStore({
		autoLoad: true,
		fields: [],
		directFn: personAction.loadPersonFullName
	});

	var pagingToolbar = Ext.create('Ext.toolbar.Paging', {
		store: directStore,
		displayInfo: true
	});

	var combo = Ext.create('Ext.form.field.ComboBox', {
		fieldLabel: 'List',
		mode: 'local',
		store: listStore,
		displayField: 'list',
		forceSelection: true,
		triggerAction: 'all',
		width: 300,
		valueField: 'list',
		listeners: {
			select: function(cmb, record, eOpts) {
				var ix = record[0].index;
				if (ix === 0) {
					directStore.remoteSort = false;
					directStore.getProxy().directFn = personAction.loadPersonFullName;
				} else if (ix === 1) {
					directStore.remoteSort = false;
					directStore.getProxy().directFn = personAction.loadPersonFullNameCity;
				} else if (ix === 2) {
					directStore.remoteSort = true;
					directStore.getProxy().directFn = personAction.loadPersonEverything;
				}
				directStore.sortInfo = null;
				pagingToolbar.moveFirst();
			}
		}
	});

	var grid = Ext.create('Ext.grid.Panel', {
		store: directStore,
		height: 350,
		width: 700,
		bbar: pagingToolbar,
		stripeRows: true,
		columns: [],
		forceFit: true,
		title: 'Grid with Metadata'
	});

	directStore.on({
		metachange: function(store, meta) {

			console.log('Metadata Change');

			var columns = [];
			var fields = meta.fields;
			for (var i = 0; i < fields.length; i++) {
				columns.push(Ext.apply({}, fields[i], {
					dataIndex: fields[i].name
				}));
			}
			if (Ext.isDefined(meta.limit)) {
				directStore.pageSize = meta.limit;
			}

			grid.reconfigure(directStore, columns);
		}
	});

	new Ext.Panel({
		renderTo: Ext.getBody(),
		border: false,
		frame: false,
		items: [ combo, grid ]
	});

	combo.setValue(listStore.first());

});
<commit_msg>Make the combobox wider so we see the whole content on one line<commit_after>Ext.onReady(function() {

	Ext.direct.Manager.addProvider(Ext.app.REMOTING_API);

	var listStore = Ext.create('Ext.data.ArrayStore', {
		fields: [ {
			name: 'list'
		}, {
			name: 'id'
		} ],
		data: [ [ 'Persons with FullName', 'Persons with FullName' ],
				[ 'Persons with FullName and City', 'Persons with FullName and City' ],
				[ 'Persons with everything', 'Persons with everything' ] ]
	});

	var directStore = new Ext.data.DirectStore({
		autoLoad: true,
		fields: [],
		directFn: personAction.loadPersonFullName
	});

	var pagingToolbar = Ext.create('Ext.toolbar.Paging', {
		store: directStore,
		displayInfo: true
	});

	var combo = Ext.create('Ext.form.field.ComboBox', {
		fieldLabel: 'List',
		mode: 'local',
		store: listStore,
		displayField: 'list',
		forceSelection: true,
		triggerAction: 'all',
		width: 400,
		valueField: 'list',
		listeners: {
			select: function(cmb, record, eOpts) {
				var ix = record[0].index;
				if (ix === 0) {
					directStore.remoteSort = false;
					directStore.getProxy().directFn = personAction.loadPersonFullName;
				} else if (ix === 1) {
					directStore.remoteSort = false;
					directStore.getProxy().directFn = personAction.loadPersonFullNameCity;
				} else if (ix === 2) {
					directStore.remoteSort = true;
					directStore.getProxy().directFn = personAction.loadPersonEverything;
				}
				directStore.sortInfo = null;
				pagingToolbar.moveFirst();
			}
		}
	});

	var grid = Ext.create('Ext.grid.Panel', {
		store: directStore,
		height: 350,
		width: 700,
		bbar: pagingToolbar,
		stripeRows: true,
		columns: [],
		forceFit: true,
		title: 'Grid with Metadata'
	});

	directStore.on({
		metachange: function(store, meta) {

			console.log('Metadata Change');

			var columns = [];
			var fields = meta.fields;
			for (var i = 0; i < fields.length; i++) {
				columns.push(Ext.apply({}, fields[i], {
					dataIndex: fields[i].name
				}));
			}
			if (Ext.isDefined(meta.limit)) {
				directStore.pageSize = meta.limit;
			}

			grid.reconfigure(directStore, columns);
		}
	});

	new Ext.Panel({
		renderTo: Ext.getBody(),
		border: false,
		frame: false,
		items: [ combo, grid ]
	});

	combo.setValue(listStore.first());

});
<|endoftext|>


<commit_before>/*
 * Copyright 2016 Lime - HighTech Solutions s.r.o.
 *
 * 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.
 */
import React from "react";
import {connect} from "react-redux";
// Actions
import {authenticate, init} from "../actions/tokenActions";
// Components
// i18n
import {FormattedMessage} from "react-intl";

const stompClient = require('../websocket-client');

/**
 * Operation component displays the operation data to the user.
 */
@connect((store) => {
    return {
        context: store.dispatching.context
    }
})
export default class Token extends React.Component {

    constructor() {
        super();
        this.update = this.update.bind(this);
        this.init = this.init.bind(this);
        this.onRegister = this.onRegister.bind(this);
        this.onAuthorize = this.onAuthorize.bind(this);
        this.setAuthorizationByWebSocketInProgress = this.setAuthorizationByWebSocketInProgress.bind(this);
        this.isAuthorizationByWebSocketInProgress = this.isAuthorizationByWebSocketInProgress.bind(this);
        this.setAuthorizedByWebSocket = this.setAuthorizedByWebSocket.bind(this);
        this.isAuthorizedByWebSocket = this.isAuthorizedByWebSocket.bind(this);
        this.state = {authorizationByWebSocketInProgress: false, authorizedByWebSocket: false};
    }

    componentWillMount() {
        this.init();
        this.update();
    }

    init() {
        this.props.dispatch(init());
    }

    update() {
        const update = this.update;
        const isAuthorizationByWebSocketInProgress = this.isAuthorizationByWebSocketInProgress;
        const isAuthorizedByWebSocket = this.isAuthorizedByWebSocket;
        if (!isAuthorizedByWebSocket()) {
            // Authorization by WebSockets wasn't completed, we need to keep calling update() every 3s.
            if (isAuthorizationByWebSocketInProgress()) {
                // If the WebSocket authorization is in progress, calling authenticate() method is temporarily paused,
                // however update() method is called in 3s to check for possible change of state in case WebSocket
                // authenticate() method fails (in this case the 3s polling will be resumed).
                setTimeout(function () {
                    update();
                }, 3000);
            } else {
                // Otherwise keep trying to authenticate every 3s using polling. This is a fallback mechanism in case
                // authorization by WebSockets fails completely (e.g. network issues).
                this.props.dispatch(authenticate(function (b) {
                    if (b) {
                        setTimeout(function () {
                            update();
                        }, 3000);
                    }
                }));
            }
        }
    }

    setAuthorizedByWebSocket(authorized) {
        this.setState({authorizedByWebSocket: authorized});
    }

    isAuthorizedByWebSocket() {
        return this.state.authorizedByWebSocket;
    }

    setAuthorizationByWebSocketInProgress(inProgress) {
        this.setState({authorizationByWebSocketInProgress: inProgress});
    }

    isAuthorizationByWebSocketInProgress() {
        return this.state.authorizationByWebSocketInProgress;
    }

    onRegister() {
        console.log('WebSocket has been registered.');
    }

    onAuthorize() {
        const setAuthorizedByWebSocket = this.setAuthorizedByWebSocket;
        const setAuthorizationByWebSocketInProgress = this.setAuthorizationByWebSocketInProgress;
        // WebSocket authorization is marked as in progress so that regular 3s polling does not call the authenticate() method.
        // This mechanism avoids calling authenticate() methods twice at the same time in the rather rare case of a race condition.
        setAuthorizationByWebSocketInProgress(true);
        console.log('Authorization received from WebSocket.');
        this.props.dispatch(authenticate(function (b) {
            if (!b) {
                setAuthorizedByWebSocket(true);
            }
            // End of attempt to authorize by WebSockets - 3s polling can be resumed in case authorization is not done yet.
            setAuthorizationByWebSocketInProgress(false);
        }));
    }

    componentWillReceiveProps(props) {
        const webSocketId = props.context.webSocketId;
        if (webSocketId != null) {
            stompClient.register([
                {route: '/user/topic/registration', callback: this.onRegister},
                {route: '/user/topic/authorization', callback: this.onAuthorize}
            ], webSocketId);
        }
    }

    render() {
        return (

                    <FormattedMessage id="message.token.confirm"/>

                    <FormattedMessage id="message.token.offline"/>

                    <FormattedMessage id="message.token.offline.link"/>

        )
    }
}<commit_msg>Fixed locking which prevents calling the authorize() method twice<commit_after>/*
 * Copyright 2016 Lime - HighTech Solutions s.r.o.
 *
 * 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.
 */
import React from "react";
import {connect} from "react-redux";
// Actions
import {authenticate, init} from "../actions/tokenActions";
// Components
// i18n
import {FormattedMessage} from "react-intl";

const stompClient = require('../websocket-client');

/**
 * Operation component displays the operation data to the user.
 */
@connect((store) => {
    return {
        context: store.dispatching.context
    }
})
export default class Token extends React.Component {

    constructor() {
        super();
        this.update = this.update.bind(this);
        this.init = this.init.bind(this);
        this.onRegister = this.onRegister.bind(this);
        this.onAuthorize = this.onAuthorize.bind(this);
        this.setAuthorizationInProgress = this.setAuthorizationInProgress.bind(this);
        this.isAuthorizationInProgress = this.isAuthorizationInProgress.bind(this);
        this.setAuthorized = this.setAuthorized.bind(this);
        this.isAuthorized = this.isAuthorized.bind(this);
        this.state = {authorizationInProgress: false, authorized: false};
    }

    componentWillMount() {
        this.init();
        this.update();
    }

    init() {
        this.props.dispatch(init());
    }

    update() {
        // Save references to the methods for the calls from anonymous functions.
        const setAuthorized = this.setAuthorized;
        const setAuthorizationInProgress = this.setAuthorizationInProgress;
        const update = this.update;
        if (this.isAuthorized()) {
            // Authorization was already done, there is nothing to do.
            return;
        }
        // Authorization is in progress and wasn't completed yet (this state happens when authorization is initiated by WebSockets).
        if (this.isAuthorizationInProgress()) {
            // If the WebSocket authorization is in progress, calling authenticate() method is temporarily paused,
            // however update() method is called in 3s to check for possible change of state in case WebSocket
            // authenticate() method fails. In this case the 3s polling with call of the authenticate() method will be resumed.
            setTimeout(function () {
                update();
            }, 3000);
        } else {
            // Mark authorization in progress to lock calling of the authenticate() method. This prevents duplicate calls
            // of the authenticate() method.
            setAuthorizationInProgress(true);
            // Keep trying to authenticate every 3s using polling. This is a fallback mechanism in case
            // authorization by WebSockets fails completely (e.g. network issues).
            this.props.dispatch(authenticate(function (b) {
                if (b) {
                    setTimeout(function () {
                        update();
                    }, 3000);
                } else {
                    // Authorization was completed successfully.
                    setAuthorized(true);
                }
                // End of attempt to authorize by 3s polling.
                setAuthorizationInProgress(false);
            }));
        }
    }

    setAuthorized(authorized) {
        this.setState({authorized: authorized});
    }

    isAuthorized() {
        return this.state.authorized;
    }

    setAuthorizationInProgress(inProgress) {
        this.setState({authorizationInProgress: inProgress});
    }

    isAuthorizationInProgress() {
        return this.state.authorizationInProgress;
    }

    onRegister() {
        console.log('WebSocket has been registered.');
    }

    onAuthorize() {
        console.log('Authorization request received from WebSocket.');
        // Save references to the methods for the calls from anonymous functions.
        const setAuthorized = this.setAuthorized;
        const setAuthorizationInProgress = this.setAuthorizationInProgress;
        if (this.isAuthorized()) {
            // Authorization was already done, there is nothing to do.
            return;
        }
        if (this.isAuthorizationInProgress()) {
            // Authorization is already in progress, do not dispatch authenticate(), it is already handled by polling.
            return;
        }
        // Mark authorization in progress to lock calling of the authenticate() method. This prevents duplicate calls
        // of the authenticate() method.
        setAuthorizationInProgress(true);
        this.props.dispatch(authenticate(function (b) {
            if (!b) {
                // Authorization was completed successfully.
                setAuthorized(true);
            }
            // End of attempt to authorize by WebSockets - 3s polling can be resumed in case authorization is not done yet.
            setAuthorizationInProgress(false);
        }));
    }

    componentWillReceiveProps(props) {
        const webSocketId = props.context.webSocketId;
        if (webSocketId !== undefined) {
            stompClient.register([
                {route: '/user/topic/registration', callback: this.onRegister},
                {route: '/user/topic/authorization', callback: this.onAuthorize}
            ], webSocketId);
        }
    }

    render() {
        return (

                    <FormattedMessage id="message.token.confirm"/>

                    <FormattedMessage id="message.token.offline"/>

                    <FormattedMessage id="message.token.offline.link"/>

        )
    }
}<|endoftext|>