<commit_before>// Framework7 app object
var myApp = null;
var viewModel = null;
var LogError = null;

// The Office initialize function must be run each time a new page is loaded
$(document).ready(function () {
    try {
        viewModel = new HeaderModel();
        initializeFramework7();
        updateStatus(ImportedStrings.mha_loading);
        LogError = window.parent.LogError;
        window.parent.SetShowErrorEvent(showError);
        window.parent.SetUpdateStatusEvent(updateStatus);
        window.parent.SetRenderItemEvent(renderItemEvent);
    }
    catch (e) {
        updateStatus(e);
    }
});

function initializeFramework7() {
    myApp = new Framework7();

    myApp.addView("#summary-view");
    myApp.addView("#received-view");
    myApp.addView("#antispam-view");
    myApp.addView("#other-view");
}

function renderItemEvent(headers) {
    // Empty data
    $("#summary-content").empty();
    $("#received-content").empty();
    $("#antispam-content").empty();
    $("#other-content").empty();

    updateStatus(ImportedStrings.mha_loading);

    viewModel = new HeaderModel(headers);
    buildViews();
    hideStatus();
}

function buildViews() {
    // Build summary view
    var summaryContent = $("#summary-content");
    var contentBlock;
    var headerVal;
    var pre;

    for (var i = 0; i < viewModel.summary.summaryRows.length; i++) {
        if (viewModel.summary.summaryRows[i].get()) {
            $("
")
                .addClass("content-block-title")
                .text(viewModel.summary.summaryRows[i].label)
                .appendTo(summaryContent);

            contentBlock = $("
")
                .addClass("content-block")
                .appendTo(summaryContent);

            headerVal = $("
")
                .addClass("code-box")
                .appendTo(contentBlock);

            pre = $("
").appendTo(headerVal);

            $("")
                .text(viewModel.summary.summaryRows[i].get())
                .appendTo(pre);
        }
    }

    if (viewModel.originalHeaders) {
        $("#original-headers").text(viewModel.originalHeaders);
        $("#orig-headers-ui").show();
    }

    // Build received view
    var receivedContent = $("#received-content");

    if (viewModel.receivedHeaders.receivedRows.length > 0) {
        var timeline = $("
")
            .addClass("timeline")
            .appendTo(receivedContent);

        var currentTime = null;
        var currentTimeEntry = null;
        var timelineItem;
        var timelineDate;
        var timelineInner;

        for (i = 0; i < viewModel.receivedHeaders.receivedRows.length; i++) {

            if (i === 0) {
                currentTime = moment(viewModel.receivedHeaders.receivedRows[i].dateNum).local();

                timelineItem = $("
")
                    .addClass("timeline-item")
                    .appendTo(timeline);

                timelineDate = currentTime.format("h:mm") + "" + currentTime.format("A") + "";

                $("
")
                    .addClass("timeline-item-date")
                    .html(timelineDate)
                    .appendTo(timelineItem);

                $("
")
                    .addClass("timeline-item-divider")
                    .appendTo(timelineItem);

                currentTimeEntry = $("
")
                    .addClass("timeline-item-content")
                    .appendTo(timelineItem);

                // Add initial otherRows
                timelineInner = $("
")
                    .addClass("timeline-item-inner")
                    .addClass("link")
                    .addClass("open-popover")
                    .attr("data-popover", ".popover-" + i)
                    .appendTo(currentTimeEntry);

                $("
")
                    .addClass("timeline-item-time")
                    .text(currentTime.format("h:mm:ss"))
                    .appendTo(timelineInner);

                $("
")
                    .addClass("timeline-item-subtitle")
                    .html("From: " + viewModel.receivedHeaders.receivedRows[i].from)
                    .appendTo(timelineInner);

                $("
")
                    .addClass("timeline-item-text")
                    .html("To: " + viewModel.receivedHeaders.receivedRows[i].by)
                    .appendTo(timelineInner);
            } else {
                // Determine if new timeline item is needed
                var entryTime = moment(viewModel.receivedHeaders.receivedRows[i].dateNum).local();

                if (entryTime.minute() > currentTime.minute()) {
                    // Into a new minute, create a new timeline item
                    currentTime = entryTime;

                    timelineItem = $("
")
                        .addClass("timeline-item")
                        .appendTo(timeline);

                    timelineDate = currentTime.format("h:mm") + "" + currentTime.format("A") + "";
                    $("
")
                        .addClass("timeline-item-date")
                        .html(timelineDate)
                        .appendTo(timelineItem);

                    $("
")
                        .addClass("timeline-item-divider")
                        .appendTo(timelineItem);

                    currentTimeEntry = $("
")
                        .addClass("timeline-item-content")
                        .appendTo(timelineItem);

                }

                // Add additional rows
                timelineInner = $("
")
                    .addClass("timeline-item-inner")
                    .addClass("link")
                    .addClass("open-popover")
                    .attr("data-popover", ".popover-" + i)
                    .appendTo(currentTimeEntry);

                $("
")
                    .addClass("timeline-item-time")
                    .text(entryTime.format("h:mm:ss"))
                    .appendTo(timelineInner);

                $("
")
                    .addClass("timeline-item-subtitle")
                    .html("To: " + viewModel.receivedHeaders.receivedRows[i].by)
                    .appendTo(timelineInner);

                var progress = $("
")
                    .addClass("timeline-item-text")
                    .appendTo(timelineInner);

                $("
")
                    .text(viewModel.receivedHeaders.receivedRows[i].delay)
                    .appendTo(progress);

                var progressWrap = $("
")
                    .addClass("progress-wrap-" + i)
                    .appendTo(progress);

                try {
                    myApp.showProgressbar(".progress-wrap-" + i, viewModel.receivedHeaders.receivedRows[i].percent);
                } catch (e) {
                    $("#original-headers").text(JSON.stringify(e));
                    return;
                }
            }

            // popover
            var popover = $("
")
                .addClass("popover")
                .addClass("popover-" + i)
                .appendTo(receivedContent);

            $("
")
                .addClass("popover-angle")
                .appendTo(popover);

            var popoverInner = $("
")
                .addClass("popover-inner")
                .appendTo(popover);

            var popoverContent = $("
")
                .addClass("content-block")
                .appendTo(popoverInner);

            addCalloutEntry("From", viewModel.receivedHeaders.receivedRows[i].from, popoverContent);
            addCalloutEntry("To", viewModel.receivedHeaders.receivedRows[i].by, popoverContent);
            addCalloutEntry("Time", viewModel.receivedHeaders.receivedRows[i].date, popoverContent);
            addCalloutEntry("Type", viewModel.receivedHeaders.receivedRows[i].with, popoverContent);
            addCalloutEntry("ID", viewModel.receivedHeaders.receivedRows[i].id, popoverContent);
            addCalloutEntry("For", viewModel.receivedHeaders.receivedRows[i].for, popoverContent);
            addCalloutEntry("Via", viewModel.receivedHeaders.receivedRows[i].via, popoverContent);
        }

        // Add a final empty timeline item to extend
        // timeline
        var endTimelineItem = $("
")
            .addClass("timeline-item")
            .appendTo(timeline);

        currentTime.add(1, "m");
        var endTimelineDate = currentTime.format("h:mm") + "" + currentTime.format("A") + "";
        $("
")
            .addClass("timeline-item-date")
            .html(endTimelineDate)
            .appendTo(endTimelineItem);

        $("
")
            .addClass("timeline-item-divider")
            .appendTo(endTimelineItem);
    }

    // Build antispam view
    var antispamContent = $("#antispam-content");
    var list;
    var ul;

    // Forefront
    if (viewModel.forefrontAntiSpamReport.forefrontAntiSpamRows.length > 0) {
        $("
")
            .addClass("content-block-title")
            .text("Forefront Antispam Report")
            .appendTo(antispamContent);

        list = $("
")
            .addClass("list-block")
            .addClass("accordion-list")
            .appendTo(antispamContent);

        ul = $("
")
            .appendTo(list);

        for (i = 0; i < viewModel.forefrontAntiSpamReport.forefrontAntiSpamRows.length; i++) {
            addSpamReportRow(viewModel.forefrontAntiSpamReport.forefrontAntiSpamRows[i], ul);
        }
    }

    // Microsoft
    if (viewModel.antiSpamReport.antiSpamRows.length > 0) {
        $("
")
            .addClass("content-block-title")
            .text("Microsoft Antispam Report")
            .appendTo(antispamContent);

        list = $("
")
            .addClass("list-block")
            .addClass("accordion-list")
            .appendTo(antispamContent);

        ul = $("
")
            .appendTo(list);

        for (i = 0; i < viewModel.antiSpamReport.antiSpamRows.length; i++) {
            addSpamReportRow(viewModel.antiSpamReport.antiSpamRows[i], ul);
        }
    }

    // Build other view
    var otherContent = $("#other-content");

    for (i = 0; i < viewModel.otherHeaders.otherRows.length; i++) {
        if (viewModel.otherHeaders.otherRows[i].value) {
            var headerName = $("
")
                .addClass("content-block-title")
                .text(viewModel.otherHeaders.otherRows[i].header)
                .appendTo(otherContent);

            if (viewModel.otherHeaders.otherRows[i].url) {
                headerName.empty();

                $($.parseHTML(viewModel.otherHeaders.otherRows[i].url))
                    .addClass("external")
                    .appendTo(headerName);
            }

            contentBlock = $("
")
                .addClass("content-block")
                .appendTo(otherContent);

            headerVal = $("
")
                .addClass("code-box")
                .appendTo(contentBlock);

            pre = $("
").appendTo(headerVal);

            $("")
                .text(viewModel.otherHeaders.otherRows[i].value)
                .appendTo(pre);
        }
    }
}

function addSpamReportRow(spamRow, parent) {
    if (spamRow.get()) {
        var item = $("
")
            .addClass("accordion-item")
            .appendTo(parent);

        var link = $("")
            .addClass("item-content")
            .addClass("item-link")
            .attr("href", "#")
            .appendTo(item);

        var innerItem = $("
")
            .addClass("item-inner")
            .appendTo(link);

        $("
")
            .addClass("item-title")
            .text(spamRow.label)
            .appendTo(innerItem);

        var itemContent = $("
")
            .addClass("accordion-item-content")
            .appendTo(item);

        contentBlock = $("
")
            .addClass("content-block")
            .appendTo(itemContent);

        var linkWrap = $("
")
            .appendTo(contentBlock);

        var linkVal = mapHeaderToURL(spamRow.url, spamRow.get());

        $($.parseHTML(linkVal))
            .addClass("external")
            .appendTo(linkWrap);
    }
}

function addCalloutEntry(name, value, parent) {
    if (value) {
        $("
")
            .addClass("wrap-line")
            .html("" + name + ": " + value)
            .appendTo(parent);
    }
}

function updateStatus(message) {
    myApp.hidePreloader();
    myApp.showPreloader(message);
}

function hideStatus() {
    myApp.hidePreloader();
}

function showError(error, message) {
    LogError(error, message);
    myApp.hidePreloader();
    myApp.alert(message, "An Error Occurred");
}<commit_msg>Remove bum comment<commit_after>// Framework7 app object
var myApp = null;
var viewModel = null;
var LogError = null;

$(document).ready(function () {
    try {
        viewModel = new HeaderModel();
        initializeFramework7();
        updateStatus(ImportedStrings.mha_loading);
        LogError = window.parent.LogError;
        window.parent.SetShowErrorEvent(showError);
        window.parent.SetUpdateStatusEvent(updateStatus);
        window.parent.SetRenderItemEvent(renderItemEvent);
    }
    catch (e) {
        updateStatus(e);
    }
});

function initializeFramework7() {
    myApp = new Framework7();

    myApp.addView("#summary-view");
    myApp.addView("#received-view");
    myApp.addView("#antispam-view");
    myApp.addView("#other-view");
}

function renderItemEvent(headers) {
    // Empty data
    $("#summary-content").empty();
    $("#received-content").empty();
    $("#antispam-content").empty();
    $("#other-content").empty();

    updateStatus(ImportedStrings.mha_loading);

    viewModel = new HeaderModel(headers);
    buildViews();
    hideStatus();
}

function buildViews() {
    // Build summary view
    var summaryContent = $("#summary-content");
    var contentBlock;
    var headerVal;
    var pre;

    for (var i = 0; i < viewModel.summary.summaryRows.length; i++) {
        if (viewModel.summary.summaryRows[i].get()) {
            $("
")
                .addClass("content-block-title")
                .text(viewModel.summary.summaryRows[i].label)
                .appendTo(summaryContent);

            contentBlock = $("
")
                .addClass("content-block")
                .appendTo(summaryContent);

            headerVal = $("
")
                .addClass("code-box")
                .appendTo(contentBlock);

            pre = $("
").appendTo(headerVal);

            $("")
                .text(viewModel.summary.summaryRows[i].get())
                .appendTo(pre);
        }
    }

    if (viewModel.originalHeaders) {
        $("#original-headers").text(viewModel.originalHeaders);
        $("#orig-headers-ui").show();
    }

    // Build received view
    var receivedContent = $("#received-content");

    if (viewModel.receivedHeaders.receivedRows.length > 0) {
        var timeline = $("
")
            .addClass("timeline")
            .appendTo(receivedContent);

        var currentTime = null;
        var currentTimeEntry = null;
        var timelineItem;
        var timelineDate;
        var timelineInner;

        for (i = 0; i < viewModel.receivedHeaders.receivedRows.length; i++) {

            if (i === 0) {
                currentTime = moment(viewModel.receivedHeaders.receivedRows[i].dateNum).local();

                timelineItem = $("
")
                    .addClass("timeline-item")
                    .appendTo(timeline);

                timelineDate = currentTime.format("h:mm") + "" + currentTime.format("A") + "";

                $("
")
                    .addClass("timeline-item-date")
                    .html(timelineDate)
                    .appendTo(timelineItem);

                $("
")
                    .addClass("timeline-item-divider")
                    .appendTo(timelineItem);

                currentTimeEntry = $("
")
                    .addClass("timeline-item-content")
                    .appendTo(timelineItem);

                // Add initial otherRows
                timelineInner = $("
")
                    .addClass("timeline-item-inner")
                    .addClass("link")
                    .addClass("open-popover")
                    .attr("data-popover", ".popover-" + i)
                    .appendTo(currentTimeEntry);

                $("
")
                    .addClass("timeline-item-time")
                    .text(currentTime.format("h:mm:ss"))
                    .appendTo(timelineInner);

                $("
")
                    .addClass("timeline-item-subtitle")
                    .html("From: " + viewModel.receivedHeaders.receivedRows[i].from)
                    .appendTo(timelineInner);

                $("
")
                    .addClass("timeline-item-text")
                    .html("To: " + viewModel.receivedHeaders.receivedRows[i].by)
                    .appendTo(timelineInner);
            } else {
                // Determine if new timeline item is needed
                var entryTime = moment(viewModel.receivedHeaders.receivedRows[i].dateNum).local();

                if (entryTime.minute() > currentTime.minute()) {
                    // Into a new minute, create a new timeline item
                    currentTime = entryTime;

                    timelineItem = $("
")
                        .addClass("timeline-item")
                        .appendTo(timeline);

                    timelineDate = currentTime.format("h:mm") + "" + currentTime.format("A") + "";
                    $("
")
                        .addClass("timeline-item-date")
                        .html(timelineDate)
                        .appendTo(timelineItem);

                    $("
")
                        .addClass("timeline-item-divider")
                        .appendTo(timelineItem);

                    currentTimeEntry = $("
")
                        .addClass("timeline-item-content")
                        .appendTo(timelineItem);

                }

                // Add additional rows
                timelineInner = $("
")
                    .addClass("timeline-item-inner")
                    .addClass("link")
                    .addClass("open-popover")
                    .attr("data-popover", ".popover-" + i)
                    .appendTo(currentTimeEntry);

                $("
")
                    .addClass("timeline-item-time")
                    .text(entryTime.format("h:mm:ss"))
                    .appendTo(timelineInner);

                $("
")
                    .addClass("timeline-item-subtitle")
                    .html("To: " + viewModel.receivedHeaders.receivedRows[i].by)
                    .appendTo(timelineInner);

                var progress = $("
")
                    .addClass("timeline-item-text")
                    .appendTo(timelineInner);

                $("
")
                    .text(viewModel.receivedHeaders.receivedRows[i].delay)
                    .appendTo(progress);

                var progressWrap = $("
")
                    .addClass("progress-wrap-" + i)
                    .appendTo(progress);

                try {
                    myApp.showProgressbar(".progress-wrap-" + i, viewModel.receivedHeaders.receivedRows[i].percent);
                } catch (e) {
                    $("#original-headers").text(JSON.stringify(e));
                    return;
                }
            }

            // popover
            var popover = $("
")
                .addClass("popover")
                .addClass("popover-" + i)
                .appendTo(receivedContent);

            $("
")
                .addClass("popover-angle")
                .appendTo(popover);

            var popoverInner = $("
")
                .addClass("popover-inner")
                .appendTo(popover);

            var popoverContent = $("
")
                .addClass("content-block")
                .appendTo(popoverInner);

            addCalloutEntry("From", viewModel.receivedHeaders.receivedRows[i].from, popoverContent);
            addCalloutEntry("To", viewModel.receivedHeaders.receivedRows[i].by, popoverContent);
            addCalloutEntry("Time", viewModel.receivedHeaders.receivedRows[i].date, popoverContent);
            addCalloutEntry("Type", viewModel.receivedHeaders.receivedRows[i].with, popoverContent);
            addCalloutEntry("ID", viewModel.receivedHeaders.receivedRows[i].id, popoverContent);
            addCalloutEntry("For", viewModel.receivedHeaders.receivedRows[i].for, popoverContent);
            addCalloutEntry("Via", viewModel.receivedHeaders.receivedRows[i].via, popoverContent);
        }

        // Add a final empty timeline item to extend
        // timeline
        var endTimelineItem = $("
")
            .addClass("timeline-item")
            .appendTo(timeline);

        currentTime.add(1, "m");
        var endTimelineDate = currentTime.format("h:mm") + "" + currentTime.format("A") + "";
        $("
")
            .addClass("timeline-item-date")
            .html(endTimelineDate)
            .appendTo(endTimelineItem);

        $("
")
            .addClass("timeline-item-divider")
            .appendTo(endTimelineItem);
    }

    // Build antispam view
    var antispamContent = $("#antispam-content");
    var list;
    var ul;

    // Forefront
    if (viewModel.forefrontAntiSpamReport.forefrontAntiSpamRows.length > 0) {
        $("
")
            .addClass("content-block-title")
            .text("Forefront Antispam Report")
            .appendTo(antispamContent);

        list = $("
")
            .addClass("list-block")
            .addClass("accordion-list")
            .appendTo(antispamContent);

        ul = $("
")
            .appendTo(list);

        for (i = 0; i < viewModel.forefrontAntiSpamReport.forefrontAntiSpamRows.length; i++) {
            addSpamReportRow(viewModel.forefrontAntiSpamReport.forefrontAntiSpamRows[i], ul);
        }
    }

    // Microsoft
    if (viewModel.antiSpamReport.antiSpamRows.length > 0) {
        $("
")
            .addClass("content-block-title")
            .text("Microsoft Antispam Report")
            .appendTo(antispamContent);

        list = $("
")
            .addClass("list-block")
            .addClass("accordion-list")
            .appendTo(antispamContent);

        ul = $("
")
            .appendTo(list);

        for (i = 0; i < viewModel.antiSpamReport.antiSpamRows.length; i++) {
            addSpamReportRow(viewModel.antiSpamReport.antiSpamRows[i], ul);
        }
    }

    // Build other view
    var otherContent = $("#other-content");

    for (i = 0; i < viewModel.otherHeaders.otherRows.length; i++) {
        if (viewModel.otherHeaders.otherRows[i].value) {
            var headerName = $("
")
                .addClass("content-block-title")
                .text(viewModel.otherHeaders.otherRows[i].header)
                .appendTo(otherContent);

            if (viewModel.otherHeaders.otherRows[i].url) {
                headerName.empty();

                $($.parseHTML(viewModel.otherHeaders.otherRows[i].url))
                    .addClass("external")
                    .appendTo(headerName);
            }

            contentBlock = $("
")
                .addClass("content-block")
                .appendTo(otherContent);

            headerVal = $("
")
                .addClass("code-box")
                .appendTo(contentBlock);

            pre = $("
").appendTo(headerVal);

            $("")
                .text(viewModel.otherHeaders.otherRows[i].value)
                .appendTo(pre);
        }
    }
}

function addSpamReportRow(spamRow, parent) {
    if (spamRow.get()) {
        var item = $("
")
            .addClass("accordion-item")
            .appendTo(parent);

        var link = $("")
            .addClass("item-content")
            .addClass("item-link")
            .attr("href", "#")
            .appendTo(item);

        var innerItem = $("
")
            .addClass("item-inner")
            .appendTo(link);

        $("
")
            .addClass("item-title")
            .text(spamRow.label)
            .appendTo(innerItem);

        var itemContent = $("
")
            .addClass("accordion-item-content")
            .appendTo(item);

        contentBlock = $("
")
            .addClass("content-block")
            .appendTo(itemContent);

        var linkWrap = $("
")
            .appendTo(contentBlock);

        var linkVal = mapHeaderToURL(spamRow.url, spamRow.get());

        $($.parseHTML(linkVal))
            .addClass("external")
            .appendTo(linkWrap);
    }
}

function addCalloutEntry(name, value, parent) {
    if (value) {
        $("
")
            .addClass("wrap-line")
            .html("" + name + ": " + value)
            .appendTo(parent);
    }
}

function updateStatus(message) {
    myApp.hidePreloader();
    myApp.showPreloader(message);
}

function hideStatus() {
    myApp.hidePreloader();
}

function showError(error, message) {
    LogError(error, message);
    myApp.hidePreloader();
    myApp.alert(message, "An Error Occurred");
}<|endoftext|>


<commit_before>// Copyright 2017, University of Colorado Boulder

/**
 * QUnit tests for the KeyStateTracker.
 *
 * @author Jesse Greenberg
 * @author Michael Barlow
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  // const KeyboardFuzzer = require( 'SCENERY/accessibility/KeyboardFuzzer' ); // TODO: can we use this in testing? https://github.com/phetsims/scenery/issues/850
  const KeyboardUtil = require( 'SCENERY/accessibility/KeyboardUtil' );
  const KeyStateTracker = require( 'SCENERY/accessibility/KeyStateTracker' );
  const timer = require( 'PHET_CORE/timer' );

  // mock scenery Event, type has a domEvent field for the dom event we
  const tabKeyEvent = { domEvent: { keyCode: KeyboardUtil.KEY_TAB } };
  const spaceKeyEvent = { domEvent: { keyCode: KeyboardUtil.KEY_SPACE } };
  const shiftTabKeyEvent = { domEvent: { keyCode: KeyboardUtil.KEY_TAB, shiftKey: true } };
  const shiftKeyEvent = { domEvent: { keyCode: KeyboardUtil.KEY_SHIFT } };

  const testTracker = new KeyStateTracker();

  let intervalID = null;

  QUnit.module( 'KeyStateTracker', {

    before() {

      // step the timer, because utteranceQueue runs on timer
      intervalID = setInterval( () => {
        timer.emit1( 1 / 100 ); // step timer in seconds, every millisecond
      }, 10 );

    },
    after() {
      clearInterval( intervalID );
    },

    beforeEach() {
      testTracker.clearState();
    }
  } );

  QUnit.test( 'basic state tracking of keys', assert => {

    // mock sending "keydown" events to the tracker
    testTracker.keydownUpdate( tabKeyEvent );
    assert.ok( testTracker.isKeyDown( tabKeyEvent.domEvent.keyCode ), 'tab key should be down in tracker' );

    testTracker.keyupUpdate( tabKeyEvent );
    assert.ok( !testTracker.isKeyDown( tabKeyEvent.domEvent.keyCode ), 'tab key should be up in tracker' );

    testTracker.keydownUpdate( spaceKeyEvent );
    assert.ok( testTracker.isAnyKeyInListDown( [ tabKeyEvent.domEvent.keyCode, spaceKeyEvent.domEvent.keyCode ] ), 'tab or space are down' );
    assert.ok( !testTracker.areKeysDown( [ tabKeyEvent.domEvent.keyCode, spaceKeyEvent.domEvent.keyCode ] ), 'tab and space are not down' );

    testTracker.keydownUpdate( tabKeyEvent );
    assert.ok( testTracker.isAnyKeyInListDown( [ tabKeyEvent.domEvent.keyCode, spaceKeyEvent.domEvent.keyCode ] ), 'tab and/or space are down' );
    assert.ok( testTracker.areKeysDown( [ tabKeyEvent.domEvent.keyCode, spaceKeyEvent.domEvent.keyCode ] ), 'tab and space are down' );

    testTracker.keydownUpdate( spaceKeyEvent );
  } );

  QUnit.test( 'tracking of shift key', assert => {

    // mock sending "keydown" events to the tracker
    testTracker.keydownUpdate( shiftTabKeyEvent );
    assert.ok( testTracker.shiftKeyDown, 'tab key with shift modifier should produce a keystate with shift key down' );

    testTracker.keydownUpdate( shiftKeyEvent );
    testTracker.keydownUpdate( shiftTabKeyEvent );
    assert.ok( testTracker.isKeyDown( tabKeyEvent.domEvent.keyCode ), 'tab key should be down in tracker' );
    assert.ok( testTracker.isKeyDown( shiftKeyEvent.domEvent.keyCode ), 'shift key should be down in tracker' );
    assert.ok( testTracker.shiftKeyDown, 'shift key should be down in tracker getter' );

    testTracker.keyupUpdate( shiftKeyEvent );
    testTracker.keyupUpdate( tabKeyEvent );

    assert.ok( !testTracker.isKeyDown( shiftKeyEvent.domEvent.keyCode ), 'shift key should not be down in tracker' );
    assert.ok( !testTracker.shiftKeyDown, 'shift key should not be down in tracker getter' );
    assert.ok( !testTracker.isKeyDown( tabKeyEvent.domEvent.keyCode ), 'tab key should not be down in tracker' );

    assert.ok( !testTracker.keysAreDown(), 'no keys should be down' );

    testTracker.keydownUpdate( tabKeyEvent );
    testTracker.keyupUpdate( shiftTabKeyEvent );
    assert.ok( !testTracker.isKeyDown( tabKeyEvent.domEvent.keyCode ), 'tab key should not be down in tracker' );

    // This test originally came before a global key state tracker, when changing focus might begin to update a new
    // key state tracker and we need to make sure the new tracker was initialized with the state of the previous
    // tracker. With a global tracker, this isn't necessary.
    assert.ok( !testTracker.isKeyDown( shiftKeyEvent.domEvent.keyCode ), 'shift key should not be down in tracker just because of tab up with shift key code' );
    assert.ok( !testTracker.shiftKeyDown, 'shift key should not be down in tracker getter' );
  } );

  QUnit.test( 'test tracking with time', async assert => {

    const done = assert.async();

    let previousTime = Date.now();
    const intervalID = window.setInterval( () => {
      const currentTime = Date.now();
      testTracker.step( ( currentTime - previousTime ) / 1000 ); // convert to seconds
      previousTime = currentTime;
    }, 10 );

    testTracker.keydownUpdate( spaceKeyEvent );
    let currentTimeDown = testTracker.timeDownForKey( spaceKeyEvent.domEvent.keyCode );
    assert.ok( currentTimeDown === 0, 'should be zero, has not been down any time' );

    timer.setTimeout( () => {
      currentTimeDown = testTracker.timeDownForKey( spaceKeyEvent.domEvent.keyCode );

      assert.ok( currentTimeDown >= 480 && currentTimeDown <= 505, 'key pressed for 100ms' );

      timer.setTimeout( () => {
        currentTimeDown = testTracker.timeDownForKey( spaceKeyEvent.domEvent.keyCode );

        assert.ok( currentTimeDown >= 560 && currentTimeDown <= 590, 'key pressed for 51 more ms.' );

        testTracker.keyupUpdate( spaceKeyEvent );

        window.clearInterval( intervalID );
        done();
      }, 71 );
    }, 500 );
  } );
} );<commit_msg>change how KeyStateTrackerTests use<commit_after>// Copyright 2017, University of Colorado Boulder

/**
 * QUnit tests for the KeyStateTracker.
 *
 * @author Jesse Greenberg
 * @author Michael Barlow
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  // const KeyboardFuzzer = require( 'SCENERY/accessibility/KeyboardFuzzer' ); // TODO: can we use this in testing? https://github.com/phetsims/scenery/issues/850
  const KeyboardUtil = require( 'SCENERY/accessibility/KeyboardUtil' );
  const KeyStateTracker = require( 'SCENERY/accessibility/KeyStateTracker' );
  const timer = require( 'PHET_CORE/timer' );

  // mock scenery Event, type has a domEvent field for the dom event we
  const tabKeyEvent = { domEvent: { keyCode: KeyboardUtil.KEY_TAB } };
  const spaceKeyEvent = { domEvent: { keyCode: KeyboardUtil.KEY_SPACE } };
  const shiftTabKeyEvent = { domEvent: { keyCode: KeyboardUtil.KEY_TAB, shiftKey: true } };
  const shiftKeyEvent = { domEvent: { keyCode: KeyboardUtil.KEY_SHIFT } };

  const testTracker = new KeyStateTracker();

  let intervalID = null;

  QUnit.module( 'KeyStateTracker', {

    before() {

      // step the timer, because utteranceQueue runs on timer
      let previousTime = Date.now();
      intervalID = setInterval( () => {
        const currentTime = Date.now();
        const timeStep = ( currentTime - previousTime ) / 1000; // convert to seconds
        previousTime = currentTime;

        // step timer
        timer.emit1( timeStep );
      }, 10 );

    },
    after() {
      clearInterval( intervalID );
    },

    beforeEach() {
      testTracker.clearState();
    }
  } );

  QUnit.test( 'basic state tracking of keys', assert => {

    // mock sending "keydown" events to the tracker
    testTracker.keydownUpdate( tabKeyEvent );
    assert.ok( testTracker.isKeyDown( tabKeyEvent.domEvent.keyCode ), 'tab key should be down in tracker' );

    testTracker.keyupUpdate( tabKeyEvent );
    assert.ok( !testTracker.isKeyDown( tabKeyEvent.domEvent.keyCode ), 'tab key should be up in tracker' );

    testTracker.keydownUpdate( spaceKeyEvent );
    assert.ok( testTracker.isAnyKeyInListDown( [ tabKeyEvent.domEvent.keyCode, spaceKeyEvent.domEvent.keyCode ] ), 'tab or space are down' );
    assert.ok( !testTracker.areKeysDown( [ tabKeyEvent.domEvent.keyCode, spaceKeyEvent.domEvent.keyCode ] ), 'tab and space are not down' );

    testTracker.keydownUpdate( tabKeyEvent );
    assert.ok( testTracker.isAnyKeyInListDown( [ tabKeyEvent.domEvent.keyCode, spaceKeyEvent.domEvent.keyCode ] ), 'tab and/or space are down' );
    assert.ok( testTracker.areKeysDown( [ tabKeyEvent.domEvent.keyCode, spaceKeyEvent.domEvent.keyCode ] ), 'tab and space are down' );

    testTracker.keydownUpdate( spaceKeyEvent );
  } );

  QUnit.test( 'tracking of shift key', assert => {

    // mock sending "keydown" events to the tracker
    testTracker.keydownUpdate( shiftTabKeyEvent );
    assert.ok( testTracker.shiftKeyDown, 'tab key with shift modifier should produce a keystate with shift key down' );

    testTracker.keydownUpdate( shiftKeyEvent );
    testTracker.keydownUpdate( shiftTabKeyEvent );
    assert.ok( testTracker.isKeyDown( tabKeyEvent.domEvent.keyCode ), 'tab key should be down in tracker' );
    assert.ok( testTracker.isKeyDown( shiftKeyEvent.domEvent.keyCode ), 'shift key should be down in tracker' );
    assert.ok( testTracker.shiftKeyDown, 'shift key should be down in tracker getter' );

    testTracker.keyupUpdate( shiftKeyEvent );
    testTracker.keyupUpdate( tabKeyEvent );

    assert.ok( !testTracker.isKeyDown( shiftKeyEvent.domEvent.keyCode ), 'shift key should not be down in tracker' );
    assert.ok( !testTracker.shiftKeyDown, 'shift key should not be down in tracker getter' );
    assert.ok( !testTracker.isKeyDown( tabKeyEvent.domEvent.keyCode ), 'tab key should not be down in tracker' );

    assert.ok( !testTracker.keysAreDown(), 'no keys should be down' );

    testTracker.keydownUpdate( tabKeyEvent );
    testTracker.keyupUpdate( shiftTabKeyEvent );
    assert.ok( !testTracker.isKeyDown( tabKeyEvent.domEvent.keyCode ), 'tab key should not be down in tracker' );

    // This test originally came before a global key state tracker, when changing focus might begin to update a new
    // key state tracker and we need to make sure the new tracker was initialized with the state of the previous
    // tracker. With a global tracker, this isn't necessary.
    assert.ok( !testTracker.isKeyDown( shiftKeyEvent.domEvent.keyCode ), 'shift key should not be down in tracker just because of tab up with shift key code' );
    assert.ok( !testTracker.shiftKeyDown, 'shift key should not be down in tracker getter' );
  } );

  QUnit.test( 'test tracking with time', async assert => {

    const done = assert.async();

    const intervalListener = ( time ) => {
      testTracker.step( time );
    };
    timer.addListener( intervalListener );

    // we will test holding down a key for these lenghts of time in ms
    const firstPressTime = 500;
    const secondPressTime = 71;
    const totalPressTime = firstPressTime + secondPressTime;

    testTracker.keydownUpdate( spaceKeyEvent );
    let currentTimeDown = testTracker.timeDownForKey( spaceKeyEvent.domEvent.keyCode );
    assert.ok( currentTimeDown === 0, 'should be zero, has not been down any time' );

    timer.setTimeout( () => {
      currentTimeDown = testTracker.timeDownForKey( spaceKeyEvent.domEvent.keyCode );
      console.log( currentTimeDown );

      assert.ok( currentTimeDown >= firstPressTime && currentTimeDown <= totalPressTime, 'key pressed for ' + firstPressTime + ' ms' );

      timer.setTimeout( () => {
        currentTimeDown = testTracker.timeDownForKey( spaceKeyEvent.domEvent.keyCode );
        console.log( currentTimeDown );

        assert.ok( currentTimeDown >= totalPressTime, 'key pressed for ' + secondPressTime + ' more ms.' );

        testTracker.keyupUpdate( spaceKeyEvent );

        window.clearInterval( intervalID );
        done();
      }, secondPressTime );
    }, firstPressTime );
  } );
} );<|endoftext|>


<commit_before>'use strict';

import $ from 'jquery';
import { MediaQuery } from './foundation.util.mediaQuery';
import { GetYoDigits } from './foundation.core.utils';
import { Plugin }from './foundation.core.plugin';

import { Accordion } from './foundation.accordion';
import { Tabs } from './foundation.tabs';

// The plugin matches the plugin classes with these plugin instances.
var MenuPlugins = {
  tabs: {
    cssClass: 'tabs',
    plugin:   Tabs,
    open:     (plugin, target) => plugin.selectTab(target),
    close:    null /* not supported */,
    toggle:   null /* not supported */,
  },
  accordion: {
    cssClass: 'accordion',
    plugin:   Accordion,
    open:     (plugin, target) => plugin.down($(target)),
    close:    (plugin, target) => plugin.up($(target)),
    toggle:   (plugin, target) => plugin.toggle($(target)),
  }
};

/**
 * ResponsiveAccordionTabs module.
 * @module foundation.responsiveAccordionTabs
 * @requires foundation.util.motion
 * @requires foundation.accordion
 * @requires foundation.tabs
 */

class ResponsiveAccordionTabs extends Plugin{
  constructor(element, options) {
    super(element, options);
    return this.options.reflow && this.storezfData || this;
  }

  /**
   * Creates a new instance of a responsive accordion tabs.
   * @class
   * @name ResponsiveAccordionTabs
   * @fires ResponsiveAccordionTabs#init
   * @param {jQuery} element - jQuery object to make into Responsive Accordion Tabs.
   * @param {Object} options - Overrides to the default plugin settings.
   */
  _setup(element, options) {
    this.$element = $(element);
    this.options = $.extend({}, ResponsiveAccordionTabs.defaults, this.$element.data(), options);

    this.rules = this.$element.data('responsive-accordion-tabs');
    this.currentMq = null;
    this.currentRule = null;
    this.currentPlugin = null;
    this.className = 'ResponsiveAccordionTabs'; // ie9 back compat
    if (!this.$element.attr('id')) {
      this.$element.attr('id',GetYoDigits(6, 'responsiveaccordiontabs'));
    };

    this._init();
    this._events();
  }

  /**
   * Initializes the Menu by parsing the classes from the 'data-responsive-accordion-tabs' attribute on the element.
   * @function
   * @private
   */
  _init() {
    MediaQuery._init();

    // The first time an Interchange plugin is initialized, this.rules is converted from a string of "classes" to an object of rules
    if (typeof this.rules === 'string') {
      let rulesTree = {};

      // Parse rules from "classes" pulled from data attribute
      let rules = this.rules.split(' ');

      // Iterate through every rule found
      for (let i = 0; i < rules.length; i++) {
        let rule = rules[i].split('-');
        let ruleSize = rule.length > 1 ? rule[0] : 'small';
        let rulePlugin = rule.length > 1 ? rule[1] : rule[0];

        if (MenuPlugins[rulePlugin] !== null) {
          rulesTree[ruleSize] = MenuPlugins[rulePlugin];
        }
      }

      this.rules = rulesTree;
    }

    this._getAllOptions();

    if (!$.isEmptyObject(this.rules)) {
      this._checkMediaQueries();
    }
  }

  _getAllOptions() {
    //get all defaults and options
    var _this = this;
    _this.allOptions = {};
    for (var key in MenuPlugins) {
      if (MenuPlugins.hasOwnProperty(key)) {
        var obj = MenuPlugins[key];
        try {
          var dummyPlugin = $('

');
          var tmpPlugin = new obj.plugin(dummyPlugin,_this.options);
          for (var keyKey in tmpPlugin.options) {
            if (tmpPlugin.options.hasOwnProperty(keyKey) && keyKey !== 'zfPlugin') {
              var objObj = tmpPlugin.options[keyKey];
              _this.allOptions[keyKey] = objObj;
            }
          }
          tmpPlugin.destroy();
        }
        catch(e) {
        }
      }
    }
  }

  /**
   * Initializes events for the Menu.
   * @function
   * @private
   */
  _events() {
    this._changedZfMediaQueryHandler = this._checkMediaQueries.bind(this);
    $(window).on('changed.zf.mediaquery', this._changedZfMediaQueryHandler);
  }

  /**
   * Checks the current screen width against available media queries. If the media query has changed, and the plugin needed has changed, the plugins will swap out.
   * @function
   * @private
   */
  _checkMediaQueries() {
    var matchedMq, _this = this;
    // Iterate through each rule and find the last matching rule
    $.each(this.rules, function(key) {
      if (MediaQuery.atLeast(key)) {
        matchedMq = key;
      }
    });

    // No match? No dice
    if (!matchedMq) return;

    // Plugin already initialized? We good
    if (this.currentPlugin instanceof this.rules[matchedMq].plugin) return;

    // Remove existing plugin-specific CSS classes
    $.each(MenuPlugins, function(key, value) {
      _this.$element.removeClass(value.cssClass);
    });

    // Add the CSS class for the new plugin
    this.$element.addClass(this.rules[matchedMq].cssClass);

    // Create an instance of the new plugin
    if (this.currentPlugin) {
      //don't know why but on nested elements data zfPlugin get's lost
      if (!this.currentPlugin.$element.data('zfPlugin') && this.storezfData) this.currentPlugin.$element.data('zfPlugin',this.storezfData);
      this.currentPlugin.destroy();
    }
    this._handleMarkup(this.rules[matchedMq].cssClass);
    this.currentRule = this.rules[matchedMq];
    this.currentPlugin = new this.currentRule.plugin(this.$element, this.options);
    this.storezfData = this.currentPlugin.$element.data('zfPlugin');

  }

  _handleMarkup(toSet){
    var _this = this, fromString = 'accordion';
    var $panels = $('[data-tabs-content='+this.$element.attr('id')+']');
    if ($panels.length) fromString = 'tabs';
    if (fromString === toSet) {
      return;
    };

    var tabsTitle = _this.allOptions.linkClass?_this.allOptions.linkClass:'tabs-title';
    var tabsPanel = _this.allOptions.panelClass?_this.allOptions.panelClass:'tabs-panel';

    this.$element.removeAttr('role');
    var $liHeads = this.$element.children('.'+tabsTitle+',[data-accordion-item]').removeClass(tabsTitle).removeClass('accordion-item').removeAttr('data-accordion-item');
    var $liHeadsA = $liHeads.children('a').removeClass('accordion-title');

    if (fromString === 'tabs') {
      $panels = $panels.children('.'+tabsPanel).removeClass(tabsPanel).removeAttr('role').removeAttr('aria-hidden').removeAttr('aria-labelledby');
      $panels.children('a').removeAttr('role').removeAttr('aria-controls').removeAttr('aria-selected');
    }else{
      $panels = $liHeads.children('[data-tab-content]').removeClass('accordion-content');
    };

    $panels.css({display:'',visibility:''});
    $liHeads.css({display:'',visibility:''});
    if (toSet === 'accordion') {
      $panels.each(function(key,value){
        $(value).appendTo($liHeads.get(key)).addClass('accordion-content').attr('data-tab-content','').removeClass('is-active').css({height:''});
        $('[data-tabs-content='+_this.$element.attr('id')+']').after('

').detach();
        $liHeads.addClass('accordion-item').attr('data-accordion-item','');
        $liHeadsA.addClass('accordion-title');
      });
    }else if (toSet === 'tabs'){
      var $tabsContent = $('[data-tabs-content='+_this.$element.attr('id')+']');
      var $placeholder = $('#tabs-placeholder-'+_this.$element.attr('id'));
      if ($placeholder.length) {
        $tabsContent = $('

').insertAfter($placeholder).attr('data-tabs-content',_this.$element.attr('id'));
        $placeholder.remove();
      }else{
        $tabsContent = $('

').insertAfter(_this.$element).attr('data-tabs-content',_this.$element.attr('id'));
      };
      $panels.each(function(key,value){
        var tempValue = $(value).appendTo($tabsContent).addClass(tabsPanel);
        var hash = $liHeadsA.get(key).hash.slice(1);
        var id = $(value).attr('id') || GetYoDigits(6, 'accordion');
        if (hash !== id) {
          if (hash !== '') {
            $(value).attr('id',hash);
          }else{
            hash = id;
            $(value).attr('id',hash);
            $($liHeadsA.get(key)).attr('href',$($liHeadsA.get(key)).attr('href').replace('#','')+'#'+hash);
          };
        };
        var isActive = $($liHeads.get(key)).hasClass('is-active');
        if (isActive) {
          tempValue.addClass('is-active');
        };
      });
      $liHeads.addClass(tabsTitle);
    };
  }

  /**
   * Opens the plugin pane defined by `target`.
   * @param {jQuery | String} target - jQuery object or string of the id of the pane to open.
   * @see Accordion.down
   * @see Tabs.selectTab
   * @function
   */
  open(target) {
    if (this.currentRule && typeof this.currentRule.open === 'function') {
      return this.currentRule.open(this.currentPlugin, ...arguments);
    }
  }

  /**
   * Closes the plugin pane defined by `target`. Not availaible for Tabs.
   * @param {jQuery | String} target - jQuery object or string of the id of the pane to close.
   * @see Accordion.up
   * @function
   */
  close(target) {
    if (this.currentRule && typeof this.currentRule.close === 'function') {
      return this.currentRule.close(this.currentPlugin, ...arguments);
    }
  }

  /**
   * Toggles the plugin pane defined by `target`. Not availaible for Tabs.
   * @param {jQuery | String} target - jQuery object or string of the id of the pane to toggle.
   * @see Accordion.toggle
   * @function
   */
  toggle(target) {
    if (this.currentRule && typeof this.currentRule.toggle === 'function') {
      return this.currentRule.toggle(this.currentPlugin, ...arguments);
    }
  }

  /**
   * Destroys the instance of the current plugin on this element, as well as the window resize handler that switches the plugins out.
   * @function
   */
  _destroy() {
    if (this.currentPlugin) this.currentPlugin.destroy();
    $(window).off('changed.zf.mediaquery', this._changedZfMediaQueryHandler);
  }
}

ResponsiveAccordionTabs.defaults = {};

export {ResponsiveAccordionTabs};
<commit_msg>feat: add zfPluginBase attr to provide access to real instance<commit_after>'use strict';

import $ from 'jquery';
import { MediaQuery } from './foundation.util.mediaQuery';
import { GetYoDigits } from './foundation.core.utils';
import { Plugin }from './foundation.core.plugin';

import { Accordion } from './foundation.accordion';
import { Tabs } from './foundation.tabs';

// The plugin matches the plugin classes with these plugin instances.
var MenuPlugins = {
  tabs: {
    cssClass: 'tabs',
    plugin:   Tabs,
    open:     (plugin, target) => plugin.selectTab(target),
    close:    null /* not supported */,
    toggle:   null /* not supported */,
  },
  accordion: {
    cssClass: 'accordion',
    plugin:   Accordion,
    open:     (plugin, target) => plugin.down($(target)),
    close:    (plugin, target) => plugin.up($(target)),
    toggle:   (plugin, target) => plugin.toggle($(target)),
  }
};

/**
 * ResponsiveAccordionTabs module.
 * @module foundation.responsiveAccordionTabs
 * @requires foundation.util.motion
 * @requires foundation.accordion
 * @requires foundation.tabs
 */

class ResponsiveAccordionTabs extends Plugin{
  constructor(element, options) {
    super(element, options);
    return this.options.reflow && this.storezfData || this;
  }

  /**
   * Creates a new instance of a responsive accordion tabs.
   * @class
   * @name ResponsiveAccordionTabs
   * @fires ResponsiveAccordionTabs#init
   * @param {jQuery} element - jQuery object to make into Responsive Accordion Tabs.
   * @param {Object} options - Overrides to the default plugin settings.
   */
  _setup(element, options) {
    this.$element = $(element);
    this.$element.data('zfPluginBase');
    this.options = $.extend({}, ResponsiveAccordionTabs.defaults, this.$element.data(), options);

    this.rules = this.$element.data('responsive-accordion-tabs');
    this.currentMq = null;
    this.currentRule = null;
    this.currentPlugin = null;
    this.className = 'ResponsiveAccordionTabs'; // ie9 back compat
    if (!this.$element.attr('id')) {
      this.$element.attr('id',GetYoDigits(6, 'responsiveaccordiontabs'));
    };

    this._init();
    this._events();
  }

  /**
   * Initializes the Menu by parsing the classes from the 'data-responsive-accordion-tabs' attribute on the element.
   * @function
   * @private
   */
  _init() {
    MediaQuery._init();

    // The first time an Interchange plugin is initialized, this.rules is converted from a string of "classes" to an object of rules
    if (typeof this.rules === 'string') {
      let rulesTree = {};

      // Parse rules from "classes" pulled from data attribute
      let rules = this.rules.split(' ');

      // Iterate through every rule found
      for (let i = 0; i < rules.length; i++) {
        let rule = rules[i].split('-');
        let ruleSize = rule.length > 1 ? rule[0] : 'small';
        let rulePlugin = rule.length > 1 ? rule[1] : rule[0];

        if (MenuPlugins[rulePlugin] !== null) {
          rulesTree[ruleSize] = MenuPlugins[rulePlugin];
        }
      }

      this.rules = rulesTree;
    }

    this._getAllOptions();

    if (!$.isEmptyObject(this.rules)) {
      this._checkMediaQueries();
    }
  }

  _getAllOptions() {
    //get all defaults and options
    var _this = this;
    _this.allOptions = {};
    for (var key in MenuPlugins) {
      if (MenuPlugins.hasOwnProperty(key)) {
        var obj = MenuPlugins[key];
        try {
          var dummyPlugin = $('

');
          var tmpPlugin = new obj.plugin(dummyPlugin,_this.options);
          for (var keyKey in tmpPlugin.options) {
            if (tmpPlugin.options.hasOwnProperty(keyKey) && keyKey !== 'zfPlugin') {
              var objObj = tmpPlugin.options[keyKey];
              _this.allOptions[keyKey] = objObj;
            }
          }
          tmpPlugin.destroy();
        }
        catch(e) {
        }
      }
    }
  }

  /**
   * Initializes events for the Menu.
   * @function
   * @private
   */
  _events() {
    this._changedZfMediaQueryHandler = this._checkMediaQueries.bind(this);
    $(window).on('changed.zf.mediaquery', this._changedZfMediaQueryHandler);
  }

  /**
   * Checks the current screen width against available media queries. If the media query has changed, and the plugin needed has changed, the plugins will swap out.
   * @function
   * @private
   */
  _checkMediaQueries() {
    var matchedMq, _this = this;
    // Iterate through each rule and find the last matching rule
    $.each(this.rules, function(key) {
      if (MediaQuery.atLeast(key)) {
        matchedMq = key;
      }
    });

    // No match? No dice
    if (!matchedMq) return;

    // Plugin already initialized? We good
    if (this.currentPlugin instanceof this.rules[matchedMq].plugin) return;

    // Remove existing plugin-specific CSS classes
    $.each(MenuPlugins, function(key, value) {
      _this.$element.removeClass(value.cssClass);
    });

    // Add the CSS class for the new plugin
    this.$element.addClass(this.rules[matchedMq].cssClass);

    // Create an instance of the new plugin
    if (this.currentPlugin) {
      //don't know why but on nested elements data zfPlugin get's lost
      if (!this.currentPlugin.$element.data('zfPlugin') && this.storezfData) this.currentPlugin.$element.data('zfPlugin',this.storezfData);
      this.currentPlugin.destroy();
    }
    this._handleMarkup(this.rules[matchedMq].cssClass);
    this.currentRule = this.rules[matchedMq];
    this.currentPlugin = new this.currentRule.plugin(this.$element, this.options);
    this.storezfData = this.currentPlugin.$element.data('zfPlugin');

  }

  _handleMarkup(toSet){
    var _this = this, fromString = 'accordion';
    var $panels = $('[data-tabs-content='+this.$element.attr('id')+']');
    if ($panels.length) fromString = 'tabs';
    if (fromString === toSet) {
      return;
    };

    var tabsTitle = _this.allOptions.linkClass?_this.allOptions.linkClass:'tabs-title';
    var tabsPanel = _this.allOptions.panelClass?_this.allOptions.panelClass:'tabs-panel';

    this.$element.removeAttr('role');
    var $liHeads = this.$element.children('.'+tabsTitle+',[data-accordion-item]').removeClass(tabsTitle).removeClass('accordion-item').removeAttr('data-accordion-item');
    var $liHeadsA = $liHeads.children('a').removeClass('accordion-title');

    if (fromString === 'tabs') {
      $panels = $panels.children('.'+tabsPanel).removeClass(tabsPanel).removeAttr('role').removeAttr('aria-hidden').removeAttr('aria-labelledby');
      $panels.children('a').removeAttr('role').removeAttr('aria-controls').removeAttr('aria-selected');
    }else{
      $panels = $liHeads.children('[data-tab-content]').removeClass('accordion-content');
    };

    $panels.css({display:'',visibility:''});
    $liHeads.css({display:'',visibility:''});
    if (toSet === 'accordion') {
      $panels.each(function(key,value){
        $(value).appendTo($liHeads.get(key)).addClass('accordion-content').attr('data-tab-content','').removeClass('is-active').css({height:''});
        $('[data-tabs-content='+_this.$element.attr('id')+']').after('

').detach();
        $liHeads.addClass('accordion-item').attr('data-accordion-item','');
        $liHeadsA.addClass('accordion-title');
      });
    }else if (toSet === 'tabs'){
      var $tabsContent = $('[data-tabs-content='+_this.$element.attr('id')+']');
      var $placeholder = $('#tabs-placeholder-'+_this.$element.attr('id'));
      if ($placeholder.length) {
        $tabsContent = $('

').insertAfter($placeholder).attr('data-tabs-content',_this.$element.attr('id'));
        $placeholder.remove();
      }else{
        $tabsContent = $('

').insertAfter(_this.$element).attr('data-tabs-content',_this.$element.attr('id'));
      };
      $panels.each(function(key,value){
        var tempValue = $(value).appendTo($tabsContent).addClass(tabsPanel);
        var hash = $liHeadsA.get(key).hash.slice(1);
        var id = $(value).attr('id') || GetYoDigits(6, 'accordion');
        if (hash !== id) {
          if (hash !== '') {
            $(value).attr('id',hash);
          }else{
            hash = id;
            $(value).attr('id',hash);
            $($liHeadsA.get(key)).attr('href',$($liHeadsA.get(key)).attr('href').replace('#','')+'#'+hash);
          };
        };
        var isActive = $($liHeads.get(key)).hasClass('is-active');
        if (isActive) {
          tempValue.addClass('is-active');
        };
      });
      $liHeads.addClass(tabsTitle);
    };
  }

  /**
   * Opens the plugin pane defined by `target`.
   * @param {jQuery | String} target - jQuery object or string of the id of the pane to open.
   * @see Accordion.down
   * @see Tabs.selectTab
   * @function
   */
  open(target) {
    if (this.currentRule && typeof this.currentRule.open === 'function') {
      return this.currentRule.open(this.currentPlugin, ...arguments);
    }
  }

  /**
   * Closes the plugin pane defined by `target`. Not availaible for Tabs.
   * @param {jQuery | String} target - jQuery object or string of the id of the pane to close.
   * @see Accordion.up
   * @function
   */
  close(target) {
    if (this.currentRule && typeof this.currentRule.close === 'function') {
      return this.currentRule.close(this.currentPlugin, ...arguments);
    }
  }

  /**
   * Toggles the plugin pane defined by `target`. Not availaible for Tabs.
   * @param {jQuery | String} target - jQuery object or string of the id of the pane to toggle.
   * @see Accordion.toggle
   * @function
   */
  toggle(target) {
    if (this.currentRule && typeof this.currentRule.toggle === 'function') {
      return this.currentRule.toggle(this.currentPlugin, ...arguments);
    }
  }

  /**
   * Destroys the instance of the current plugin on this element, as well as the window resize handler that switches the plugins out.
   * @function
   */
  _destroy() {
    if (this.currentPlugin) this.currentPlugin.destroy();
    $(window).off('changed.zf.mediaquery', this._changedZfMediaQueryHandler);
  }
}

ResponsiveAccordionTabs.defaults = {};

export {ResponsiveAccordionTabs};
<|endoftext|>


<commit_before>/*
* jQuery Mobile Framework : history.pushState support, layered on top of hashchange
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
( function( $, window ) {
	// For now, let's Monkeypatch this onto the end of $.mobile._registerInternalEvents
	// Scope self to pushStateHandler so we can reference it sanely within the
	// methods handed off as event handlers
	var	pushStateHandler = {},
		self = pushStateHandler,
		$win = $( window ),
		url = $.mobile.path.parseUrl( location.href );

	$.extend( pushStateHandler, {
		// TODO move to a path helper, this is rather common functionality
		initialFilePath: (function() {
			return url.pathname + url.search;
		})(),

		initialHref: url.hrefNoHash,

		// Flag for tracking if a Hashchange naturally occurs after each popstate + replace
		hashchangeFired: false,

		state: function() {
			return {
				hash: location.hash || "#" + self.initialFilePath,
				title: document.title,

				// persist across refresh
				initialHref: self.initialHref
			};
		},

		isSubHashPage: function( page ) {
			// if the page is a dialog, a subpage, or an embedded page
			// then the hash will have been maintained
			return page.is( "[role='dialog']" ) ||
				page.jqmData("url").indexOf( $.mobile.subPageUrlKey ) >= 0 ||
				page.jqmData("url") == page.attr( "id" );
		},

		resetUIKeys: function( url ) {
			var dialog = $.mobile.dialogHashKey,
				subkey = "&" + $.mobile.subPageUrlKey,
				dialogIndex = url.indexOf( dialog );

			if( dialogIndex > -1 ) {
				url = url.slice( 0, dialogIndex ) + "#" + url.slice( dialogIndex );
			} else if( url.indexOf( subkey ) > -1 ) {
				url = url.split( subkey ).join( "#" + subkey );
			}

			return url;
		},

		// on hash change we want to clean up the url
		// NOTE this takes place *after* the vanilla navigation hash change
		// handling has taken place and set the state of the DOM
		onHashChange: function( e ) {
			var href, state;

			self.hashchangeFired = true;

			// only replaceState when the hash doesn't represent an embeded page
			if( $.mobile.path.isPath(location.hash) ) {

				// propulate the hash when its not available
				state = self.state();

				// make the hash abolute with the current href
				href = $.mobile.path.makeUrlAbsolute( state.hash.replace("#", ""), location.href );

				href = self.resetUIKeys( href );

				// replace the current url with the new href and store the state
				history.replaceState( state, document.title, href );
			}
		},

		// on popstate (ie back or forward) we need to replace the hash that was there previously
		// cleaned up by the additional hash handling
		onPopState: function( e ) {
			var poppedState = e.originalEvent.state, holdnexthashchange = false;

			// if there's no state its not a popstate we care about, ie chrome's initial popstate
			// or forward popstate
			if( poppedState ) {
				// can't test the hash directly because the url has already been altered, possibly to
				// one without a hash, so we check if the page on display is one that would have
				// generated a hash. Generally speaking a crappy solution.
				if( self.isSubHashPage( $.mobile.activePage ) ){
					holdnexthashchange = true;
				}

				// change the page based on the hash
				$.mobile._handleHashChange( poppedState.hash );

				// disable the next hash change if the page we came from has a hash
				$.mobile.urlHistory.ignoreNextHashChange = holdnexthashchange;
			}
		},

		init: function() {
			$win.bind( "hashchange", self.onHashChange );

			// Handle popstate events the occur through history changes
			$win.bind( "popstate", self.onPopState );

			// if there's no hash, we need to replacestate for returning to home
			if ( location.hash === "" ) {
				history.replaceState( self.state(), document.title, location.href );
			}
		}
	});

	$( function() {
		if( $.mobile.pushStateEnabled && $.support.pushState ){
			pushStateHandler.init();
		}
	});
})( jQuery, this );<commit_msg>minor refactor and check for defined attr<commit_after>/*
* jQuery Mobile Framework : history.pushState support, layered on top of hashchange
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
( function( $, window ) {
	// For now, let's Monkeypatch this onto the end of $.mobile._registerInternalEvents
	// Scope self to pushStateHandler so we can reference it sanely within the
	// methods handed off as event handlers
	var	pushStateHandler = {},
		self = pushStateHandler,
		$win = $( window ),
		url = $.mobile.path.parseUrl( location.href );

	$.extend( pushStateHandler, {
		// TODO move to a path helper, this is rather common functionality
		initialFilePath: (function() {
			return url.pathname + url.search;
		})(),

		initialHref: url.hrefNoHash,

		// Flag for tracking if a Hashchange naturally occurs after each popstate + replace
		hashchangeFired: false,

		state: function() {
			return {
				hash: location.hash || "#" + self.initialFilePath,
				title: document.title,

				// persist across refresh
				initialHref: self.initialHref
			};
		},

		isSubHashPage: function( page ) {
			var pageUrl = page.jqmData("url") || "";

			// if the page is a dialog, a subpage, or an embedded page
			// then the hash will have been maintained
			return page.is( "[role='dialog']" ) ||
				pageUrl.indexOf( $.mobile.subPageUrlKey ) >= 0 ||
				pageUrl === page.attr( "id" );
		},

		resetUIKeys: function( url ) {
			var dialog = $.mobile.dialogHashKey,
				subkey = "&" + $.mobile.subPageUrlKey,
				dialogIndex = url.indexOf( dialog );

			if( dialogIndex > -1 ) {
				url = url.slice( 0, dialogIndex ) + "#" + url.slice( dialogIndex );
			} else if( url.indexOf( subkey ) > -1 ) {
				url = url.split( subkey ).join( "#" + subkey );
			}

			return url;
		},

		// on hash change we want to clean up the url
		// NOTE this takes place *after* the vanilla navigation hash change
		// handling has taken place and set the state of the DOM
		onHashChange: function( e ) {
			var href, state;

			self.hashchangeFired = true;

			// only replaceState when the hash doesn't represent an embeded page
			if( $.mobile.path.isPath(location.hash) ) {

				// propulate the hash when its not available
				state = self.state();

				// make the hash abolute with the current href
				href = $.mobile.path.makeUrlAbsolute( state.hash.replace("#", ""), location.href );

				href = self.resetUIKeys( href );

				// replace the current url with the new href and store the state
				history.replaceState( state, document.title, href );
			}
		},

		// on popstate (ie back or forward) we need to replace the hash that was there previously
		// cleaned up by the additional hash handling
		onPopState: function( e ) {
			var poppedState = e.originalEvent.state, holdnexthashchange = false;

			// if there's no state its not a popstate we care about, ie chrome's initial popstate
			// or forward popstate
			if( poppedState ) {
				// can't test the hash directly because the url has already been altered, possibly to
				// one without a hash, so we check if the page on display is one that would have
				// generated a hash. Generally speaking a crappy solution.
				if( self.isSubHashPage( $.mobile.activePage ) ){
					holdnexthashchange = true;
				}

				// change the page based on the hash
				$.mobile._handleHashChange( poppedState.hash );

				// disable the next hash change if the page we came from has a hash
				$.mobile.urlHistory.ignoreNextHashChange = holdnexthashchange;
			}
		},

		init: function() {
			$win.bind( "hashchange", self.onHashChange );

			// Handle popstate events the occur through history changes
			$win.bind( "popstate", self.onPopState );

			// if there's no hash, we need to replacestate for returning to home
			if ( location.hash === "" ) {
				history.replaceState( self.state(), document.title, location.href );
			}
		}
	});

	$( function() {
		if( $.mobile.pushStateEnabled && $.support.pushState ){
			pushStateHandler.init();
		}
	});
})( jQuery, this );<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import reactMixin from 'react-mixin';
import autoBind from 'react-autobind';
import Reflux from 'reflux';
import {actions} from '../actions';
import {stores} from '../stores';
import {bem} from '../bem';
import {searches} from '../searches';
import ui from '../ui';
import mixins from '../mixins';

import { t } from '../utils';

import {
  ListSearch,
  ListTagFilter,
  ListCollectionFilter,
  ListExpandToggle
} from '../components/list';

class AssetNavigatorListView extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
    autoBind(this);
  }
  componentDidMount () {
    this.searchClear();
    this.listenTo(this.searchStore, this.searchStoreChanged);
  }
  searchStoreChanged (searchStoreState) {
    this.setState(searchStoreState);
  }
  activateSortable() {
    if (!this.refs.liblist) {
      return;
    }

    var $el = $(ReactDOM.findDOMNode(this.refs.liblist));
    if ($el.hasClass('ui-sortable')) {
      $el.sortable('destroy');
    }
    $el.sortable({
      helper: 'clone',
      cursor: 'move',
      distance: 5,
      items: '> li',
      connectWith: [
        '.survey-editor__list',
        '.group__rows'
      ],
      opacity: 0.9,
      scroll: false,
      deactivate: ()=> {
        $el.sortable('cancel');
      }
    });
  }
  render () {
    var list,
        count,
        status,
        isSearch = this.state.searchResultsDisplayed;

    if (isSearch) {
      status = this.state.searchState;
      list = this.state.searchResultsList;
      count = this.state.searchResultsCount;
    } else {
      status = this.state.defaultQueryState;
      list = this.state.defaultQueryResultsList;
      count = this.state.defaultQueryCount;
    }

    if (status !== 'done') {
      return (
          <bem.LibList m={'empty'}>
            <bem.LibList__item m={'message'}>
              {t('loading')}
            </bem.LibList__item>
          </bem.LibList>
        );
    } else if (count === 0) {
      return (
          <bem.LibList m={'empty'}>
            <bem.LibList__item m={'message'}>
              {t('no search results found')}
            </bem.LibList__item>
          </bem.LibList>
        );
    } else {

      window.setTimeout(()=>{
        this.activateSortable();
      }, 1);
      return (
        <bem.LibList m={['done', isSearch ? 'search' : 'default']} ref='liblist'>
          {list.map((item)=> {
            var modifiers = [item.asset_type];
            var summ = item.summary;
            // Library questions can only be of `question` or `block` types
            // Reject `survey` types to not include current survey on save
            if (summ.row_count == undefined || item.asset_type == 'survey') {
              return false;
            }
            return (
              <bem.LibList__item m={modifiers} key={item.uid} data-uid={item.uid}>
                <bem.LibList__dragbox />
                <bem.LibList__label m={'name'}>
                  <ui.AssetName {...item} />
                </bem.LibList__label>

                { item.asset_type === 'block' &&
                  <bem.LibList__qtype>
                    {t('block of ___ questions').replace('___', summ.row_count)}
                  </bem.LibList__qtype>
                }

                { (stores.pageState.state.assetNavExpanded && item.asset_type === 'block') &&

                    {summ.labels.map(function(lbl, i){
                      return
{lbl}
;
                    })}

                }

                { stores.pageState.state.assetNavExpanded &&
                  <bem.LibList__tags>
                    {(item.tags || []).map((tg, i)=>{
                      return <bem.LibList__tag key={i}>{tg}</bem.LibList__tag>;
                    })}
                  </bem.LibList__tags>
                }
              </bem.LibList__item>
            );
          })}
        </bem.LibList>
      );
    }
  }
};

reactMixin(AssetNavigatorListView.prototype, searches.common);
reactMixin(AssetNavigatorListView.prototype, Reflux.ListenerMixin);

class AssetNavigator extends Reflux.Component {
  constructor(props) {
    super(props);
    this.state = {
      searchResults: {},
      imports: [],
      searchContext: searches.getSearchContext('library', {
        filterParams: {
          assetType: 'asset_type:question OR asset_type:block OR asset_type:template'
        }
      }),
      selectedTags: []
    };
    this.store = stores.tags;
    autoBind(this);
  }
  componentDidMount() {
    this.listenTo(stores.pageState, this.handlePageStateStore);
    this.state.searchContext.mixin.searchDefault();
  }
  filterSearchResults (response) {
    if (this.searchFieldValue() === response.query) {
      return response.results;
    }
  }
  handlePageStateStore (state) {
    this.setState(state);
  }
  getImportsByStatus (n) {
    return this.imports.filter((i)=> i.status === n );
  }
  searchFieldValue () {
    return this.refs.navigatorSearchBox.getValue();
  }
  toggleTagSelected (tag) {
    var tags = this.state.selectedTags,
        _ti = tags.indexOf(tag);
    if (_ti === -1) {
      tags.push(tag);
    } else {
      tags.splice(tags.indexOf(_ti), 1);
    }
    this.setState({
      selectedTags: tags
    });
  }

  render () {
    return (
      <bem.LibNav>
        <bem.LibNav__header>
          <bem.LibNav__search>
            <ListSearch
              ref='navigatorSearchBox'
              placeholder={t('search library')}
              searchContext={this.state.searchContext}
            />
          </bem.LibNav__search>
          <ListTagFilter searchContext={this.state.searchContext} />
          <ListCollectionFilter searchContext={this.state.searchContext} />
          <ListExpandToggle searchContext={this.state.searchContext} />
        </bem.LibNav__header>

        <bem.LibNav__content>
          <AssetNavigatorListView searchContext={this.state.searchContext} />
        </bem.LibNav__content>

        <bem.LibNav__footer />
      </bem.LibNav>
    );
  }
};

reactMixin(AssetNavigator.prototype, Reflux.connectFilter(stores.assetSearch, 'searchResults', AssetNavigator.prototype.filterSearchResults));

export default AssetNavigator;
<commit_msg>PR changes<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import reactMixin from 'react-mixin';
import autoBind from 'react-autobind';
import Reflux from 'reflux';
import {actions} from '../actions';
import {stores} from '../stores';
import {bem} from '../bem';
import {searches} from '../searches';
import ui from '../ui';
import mixins from '../mixins';

import { ASSET_TYPES } from '../constants';

import { t } from '../utils';

import {
  ListSearch,
  ListTagFilter,
  ListCollectionFilter,
  ListExpandToggle
} from '../components/list';

class AssetNavigatorListView extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
    autoBind(this);
  }
  componentDidMount () {
    this.searchClear();
    this.listenTo(this.searchStore, this.searchStoreChanged);
  }
  searchStoreChanged (searchStoreState) {
    this.setState(searchStoreState);
  }
  activateSortable() {
    if (!this.refs.liblist) {
      return;
    }

    var $el = $(ReactDOM.findDOMNode(this.refs.liblist));
    if ($el.hasClass('ui-sortable')) {
      $el.sortable('destroy');
    }
    $el.sortable({
      helper: 'clone',
      cursor: 'move',
      distance: 5,
      items: '> li',
      connectWith: [
        '.survey-editor__list',
        '.group__rows'
      ],
      opacity: 0.9,
      scroll: false,
      deactivate: ()=> {
        $el.sortable('cancel');
      }
    });
  }
  render () {
    var list,
        count,
        status,
        isSearch = this.state.searchResultsDisplayed;

    if (isSearch) {
      status = this.state.searchState;
      list = this.state.searchResultsList;
      count = this.state.searchResultsCount;
    } else {
      status = this.state.defaultQueryState;
      list = this.state.defaultQueryResultsList;
      count = this.state.defaultQueryCount;
    }

    if (status !== 'done') {
      return (
          <bem.LibList m={'empty'}>
            <bem.LibList__item m={'message'}>
              {t('loading')}
            </bem.LibList__item>
          </bem.LibList>
        );
    } else if (count === 0) {
      return (
          <bem.LibList m={'empty'}>
            <bem.LibList__item m={'message'}>
              {t('no search results found')}
            </bem.LibList__item>
          </bem.LibList>
        );
    } else {

      window.setTimeout(()=>{
        this.activateSortable();
      }, 1);
      return (
        <bem.LibList m={['done', isSearch ? 'search' : 'default']} ref='liblist'>
          {list.map((item)=> {
            var modifiers = [item.asset_type];
            var summ = item.summary;
            // HACK FIX: (ideally `survey`s would not be searched)
            // Library questions can only be of `question` or `block` types
            // Reject `survey` types to not include current survey on save
            if (summ.row_count == undefined
              || item.asset_type === ASSET_TYPES.survey.id) {
              return false;
            }
            return (
              <bem.LibList__item m={modifiers} key={item.uid} data-uid={item.uid}>
                <bem.LibList__dragbox />
                <bem.LibList__label m={'name'}>
                  <ui.AssetName {...item} />
                </bem.LibList__label>

                { item.asset_type === 'block' &&
                  <bem.LibList__qtype>
                    {t('block of ___ questions').replace('___', summ.row_count)}
                  </bem.LibList__qtype>
                }

                { (stores.pageState.state.assetNavExpanded && item.asset_type === 'block') &&

                    {summ.labels.map(function(lbl, i){
                      return
{lbl}
;
                    })}

                }

                { stores.pageState.state.assetNavExpanded &&
                  <bem.LibList__tags>
                    {(item.tags || []).map((tg, i)=>{
                      return <bem.LibList__tag key={i}>{tg}</bem.LibList__tag>;
                    })}
                  </bem.LibList__tags>
                }
              </bem.LibList__item>
            );
          })}
        </bem.LibList>
      );
    }
  }
};

reactMixin(AssetNavigatorListView.prototype, searches.common);
reactMixin(AssetNavigatorListView.prototype, Reflux.ListenerMixin);

class AssetNavigator extends Reflux.Component {
  constructor(props) {
    super(props);
    this.state = {
      searchResults: {},
      imports: [],
      searchContext: searches.getSearchContext('library', {
        filterParams: {
          assetType: 'asset_type:question OR asset_type:block OR asset_type:template'
        }
      }),
      selectedTags: []
    };
    this.store = stores.tags;
    autoBind(this);
  }
  componentDidMount() {
    this.listenTo(stores.pageState, this.handlePageStateStore);
    this.state.searchContext.mixin.searchDefault();
  }
  filterSearchResults (response) {
    if (this.searchFieldValue() === response.query) {
      return response.results;
    }
  }
  handlePageStateStore (state) {
    this.setState(state);
  }
  getImportsByStatus (n) {
    return this.imports.filter((i)=> i.status === n );
  }
  searchFieldValue () {
    return this.refs.navigatorSearchBox.getValue();
  }
  toggleTagSelected (tag) {
    var tags = this.state.selectedTags,
        _ti = tags.indexOf(tag);
    if (_ti === -1) {
      tags.push(tag);
    } else {
      tags.splice(tags.indexOf(_ti), 1);
    }
    this.setState({
      selectedTags: tags
    });
  }

  render () {
    return (
      <bem.LibNav>
        <bem.LibNav__header>
          <bem.LibNav__search>
            <ListSearch
              ref='navigatorSearchBox'
              placeholder={t('search library')}
              searchContext={this.state.searchContext}
            />
          </bem.LibNav__search>
          <ListTagFilter searchContext={this.state.searchContext} />
          <ListCollectionFilter searchContext={this.state.searchContext} />
          <ListExpandToggle searchContext={this.state.searchContext} />
        </bem.LibNav__header>

        <bem.LibNav__content>
          <AssetNavigatorListView searchContext={this.state.searchContext} />
        </bem.LibNav__content>

        <bem.LibNav__footer />
      </bem.LibNav>
    );
  }
};

reactMixin(AssetNavigator.prototype, Reflux.connectFilter(stores.assetSearch, 'searchResults', AssetNavigator.prototype.filterSearchResults));

export default AssetNavigator;
<|endoftext|>


<commit_before>/*global define*/
define([
        './buildModuleUrl',
        './Color',
        './defined',
        './DeveloperError',
        './loadImage',
        './writeTextToCanvas'
    ], function(
        buildModuleUrl,
        Color,
        defined,
        DeveloperError,
        loadImage,
        writeTextToCanvas) {
    'use strict';

    /**
     * A utility class for generating custom map pins as canvas elements.
     *

     *

     *

     * Example pins generated using both the maki icon set, which ships with Cesium, and single character text.
     *

     *
     * @alias PinBuilder
     * @constructor
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Map%20Pins.html|Cesium Sandcastle PinBuilder Demo}
     */
    function PinBuilder() {
        this._cache = {};
    }

    /**
     * Creates an empty pin of the specified color and size.
     *
     * @param {Color} color The color of the pin.
     * @param {Number} size The size of the pin, in pixels.
     * @returns {Canvas} The canvas element that represents the generated pin.
     */
    PinBuilder.prototype.fromColor = function(color, size) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(color)) {
            throw new DeveloperError('color is required');
        }
        if (!defined(size)) {
            throw new DeveloperError('size is required');
        }
        //>>includeEnd('debug');
        return createPin(undefined, undefined, color, size, this._cache);
    };

    /**
     * Creates a pin with the specified icon, color, and size.
     *
     * @param {String} url The url of the image to be stamped onto the pin.
     * @param {Color} color The color of the pin.
     * @param {Number} size The size of the pin, in pixels.
     * @returns {Canvas|Promise.<Canvas>} The canvas element or a Promise to the canvas element that represents the generated pin.
     */
    PinBuilder.prototype.fromUrl = function(url, color, size) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(url)) {
            throw new DeveloperError('url is required');
        }
        if (!defined(color)) {
            throw new DeveloperError('color is required');
        }
        if (!defined(size)) {
            throw new DeveloperError('size is required');
        }
        //>>includeEnd('debug');
        return createPin(url, undefined, color, size, this._cache);
    };

    /**
     * Creates a pin with the specified {@link https://www.mapbox.com/maki/|maki} icon identifier, color, and size.
     *
     * @param {String} id The id of the maki icon to be stamped onto the pin.
     * @param {Color} color The color of the pin.
     * @param {Number} size The size of the pin, in pixels.
     * @returns {Canvas|Promise.<Canvas>} The canvas element or a Promise to the canvas element that represents the generated pin.
     */
    PinBuilder.prototype.fromMakiIconId = function(id, color, size) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(id)) {
            throw new DeveloperError('id is required');
        }
        if (!defined(color)) {
            throw new DeveloperError('color is required');
        }
        if (!defined(size)) {
            throw new DeveloperError('size is required');
        }
        //>>includeEnd('debug');
        return createPin(buildModuleUrl('Assets/Textures/maki/' + encodeURIComponent(id) + '.png'), undefined, color, size, this._cache);
    };

    /**
     * Creates a pin with the specified text, color, and size.  The text will be sized to be as large as possible
     * while still being contained completely within the pin.
     *
     * @param {String} text The text to be stamped onto the pin.
     * @param {Color} color The color of the pin.
     * @param {Number} size The size of the pin, in pixels.
     * @returns {Canvas} The canvas element that represents the generated pin.
     */
    PinBuilder.prototype.fromText = function(text, color, size) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(text)) {
            throw new DeveloperError('text is required');
        }
        if (!defined(color)) {
            throw new DeveloperError('color is required');
        }
        if (!defined(size)) {
            throw new DeveloperError('size is required');
        }
        //>>includeEnd('debug');

        return createPin(undefined, text, color, size, this._cache);
    };

    var colorScratch = new Color();

    //This function (except for the 3 commented lines) was auto-generated from an online tool,
    //http://www.professorcloud.com/svg-to-canvas/, using Assets/Textures/pin.svg as input.
    //The reason we simply can't load and draw the SVG directly to the canvas is because
    //it taints the canvas in Internet Explorer (and possibly some other browsers); making
    //it impossible to create a WebGL texture from the result.
    function drawPin(context2D, color, size) {
        context2D.save();
        context2D.scale(size / 24, size / 24); //Added to auto-generated code to scale up to desired size.
        context2D.fillStyle = color.toCssColorString(); //Modified from auto-generated code.
        context2D.strokeStyle = color.brighten(0.6, colorScratch).toCssColorString(); //Modified from auto-generated code.
        context2D.lineWidth = 0.846;
        context2D.beginPath();
        context2D.moveTo(6.72, 0.422);
        context2D.lineTo(17.28, 0.422);
        context2D.bezierCurveTo(18.553, 0.422, 19.577, 1.758, 19.577, 3.415);
        context2D.lineTo(19.577, 10.973);
        context2D.bezierCurveTo(19.577, 12.63, 18.553, 13.966, 17.282, 13.966);
        context2D.lineTo(14.386, 14.008);
        context2D.lineTo(11.826, 23.578);
        context2D.lineTo(9.614, 14.008);
        context2D.lineTo(6.719, 13.965);
        context2D.bezierCurveTo(5.446, 13.983, 4.422, 12.629, 4.422, 10.972);
        context2D.lineTo(4.422, 3.416);
        context2D.bezierCurveTo(4.423, 1.76, 5.447, 0.423, 6.718, 0.423);
        context2D.closePath();
        context2D.fill();
        context2D.stroke();
        context2D.restore();
    }

    //This function takes an image or canvas and uses it as a template
    //to "stamp" the pin with a white image outlined in black.  The color
    //values of the input image are ignored completely and only the alpha
    //values are used.
    function drawIcon(context2D, image, size) {
        //Size is the largest image that looks good inside of pin box.
        var imageSize = size / 2.5;
        var sizeX = imageSize;
        var sizeY = imageSize;

        if (image.width > image.height) {
            sizeY = imageSize * (image.height / image.width);
        } else if (image.width < image.height) {
            sizeX = imageSize * (image.width / image.height);
        }

        //x and y are the center of the pin box
        var x = (size - sizeX) / 2;
        var y = ((7 / 24) * size) - (sizeY / 2);

        context2D.globalCompositeOperation = 'destination-out';
        context2D.drawImage(image, x - 1, y, sizeX, sizeY);
        context2D.drawImage(image, x, y - 1, sizeX, sizeY);
        context2D.drawImage(image, x + 1, y, sizeX, sizeY);
        context2D.drawImage(image, x, y + 1, sizeX, sizeY);

        context2D.globalCompositeOperation = 'destination-over';
        context2D.fillStyle = Color.BLACK.toCssColorString();
        context2D.fillRect(x - 1, y - 1, sizeX + 1, sizeY + 1);

        context2D.globalCompositeOperation = 'destination-out';
        context2D.drawImage(image, x, y, sizeX, sizeY);

        context2D.globalCompositeOperation = 'destination-over';
        context2D.fillStyle = Color.WHITE.toCssColorString();
        context2D.fillRect(x, y, sizeX, sizeY);
    }

    var stringifyScratch = new Array(4);
    function createPin(url, label, color, size, cache) {
        //Use the parameters as a unique ID for caching.
        stringifyScratch[0] = url;
        stringifyScratch[1] = label;
        stringifyScratch[2] = color;
        stringifyScratch[3] = size;
        var id = JSON.stringify(stringifyScratch);

        var item = cache[id];
        if (defined(item)) {
            return item;
        }

        var canvas = document.createElement('canvas');
        canvas.width = size;
        canvas.height = size;

        var context2D = canvas.getContext("2d");
        drawPin(context2D, color, size);

        if (defined(url)) {
            //If we have an image url, load it and then stamp the pin.
            var promise = loadImage(url).then(function(image) {
                drawIcon(context2D, image, size);
                cache[id] = canvas;
                return canvas;
            });
            cache[id] = promise;
            return promise;
        } else if (defined(label)) {
            //If we have a label, write it to a canvas and then stamp the pin.
            var image = writeTextToCanvas(label, {
                font : 'bold ' + size + 'px sans-serif'
            });
            drawIcon(context2D, image, size);
        }

        cache[id] = canvas;
        return canvas;
    }

    return PinBuilder;
});
<commit_msg>Fix leaky alpha values coming from pinBuilder.<commit_after>/*global define*/
define([
        './buildModuleUrl',
        './Color',
        './defined',
        './DeveloperError',
        './loadImage',
        './writeTextToCanvas'
    ], function(
        buildModuleUrl,
        Color,
        defined,
        DeveloperError,
        loadImage,
        writeTextToCanvas) {
    'use strict';

    /**
     * A utility class for generating custom map pins as canvas elements.
     *

     *

     *

     * Example pins generated using both the maki icon set, which ships with Cesium, and single character text.
     *

     *
     * @alias PinBuilder
     * @constructor
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Map%20Pins.html|Cesium Sandcastle PinBuilder Demo}
     */
    function PinBuilder() {
        this._cache = {};
    }

    /**
     * Creates an empty pin of the specified color and size.
     *
     * @param {Color} color The color of the pin.
     * @param {Number} size The size of the pin, in pixels.
     * @returns {Canvas} The canvas element that represents the generated pin.
     */
    PinBuilder.prototype.fromColor = function(color, size) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(color)) {
            throw new DeveloperError('color is required');
        }
        if (!defined(size)) {
            throw new DeveloperError('size is required');
        }
        //>>includeEnd('debug');
        return createPin(undefined, undefined, color, size, this._cache);
    };

    /**
     * Creates a pin with the specified icon, color, and size.
     *
     * @param {String} url The url of the image to be stamped onto the pin.
     * @param {Color} color The color of the pin.
     * @param {Number} size The size of the pin, in pixels.
     * @returns {Canvas|Promise.<Canvas>} The canvas element or a Promise to the canvas element that represents the generated pin.
     */
    PinBuilder.prototype.fromUrl = function(url, color, size) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(url)) {
            throw new DeveloperError('url is required');
        }
        if (!defined(color)) {
            throw new DeveloperError('color is required');
        }
        if (!defined(size)) {
            throw new DeveloperError('size is required');
        }
        //>>includeEnd('debug');
        return createPin(url, undefined, color, size, this._cache);
    };

    /**
     * Creates a pin with the specified {@link https://www.mapbox.com/maki/|maki} icon identifier, color, and size.
     *
     * @param {String} id The id of the maki icon to be stamped onto the pin.
     * @param {Color} color The color of the pin.
     * @param {Number} size The size of the pin, in pixels.
     * @returns {Canvas|Promise.<Canvas>} The canvas element or a Promise to the canvas element that represents the generated pin.
     */
    PinBuilder.prototype.fromMakiIconId = function(id, color, size) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(id)) {
            throw new DeveloperError('id is required');
        }
        if (!defined(color)) {
            throw new DeveloperError('color is required');
        }
        if (!defined(size)) {
            throw new DeveloperError('size is required');
        }
        //>>includeEnd('debug');
        return createPin(buildModuleUrl('Assets/Textures/maki/' + encodeURIComponent(id) + '.png'), undefined, color, size, this._cache);
    };

    /**
     * Creates a pin with the specified text, color, and size.  The text will be sized to be as large as possible
     * while still being contained completely within the pin.
     *
     * @param {String} text The text to be stamped onto the pin.
     * @param {Color} color The color of the pin.
     * @param {Number} size The size of the pin, in pixels.
     * @returns {Canvas} The canvas element that represents the generated pin.
     */
    PinBuilder.prototype.fromText = function(text, color, size) {
        //>>includeStart('debug', pragmas.debug);
        if (!defined(text)) {
            throw new DeveloperError('text is required');
        }
        if (!defined(color)) {
            throw new DeveloperError('color is required');
        }
        if (!defined(size)) {
            throw new DeveloperError('size is required');
        }
        //>>includeEnd('debug');

        return createPin(undefined, text, color, size, this._cache);
    };

    var colorScratch = new Color();

    //This function (except for the 3 commented lines) was auto-generated from an online tool,
    //http://www.professorcloud.com/svg-to-canvas/, using Assets/Textures/pin.svg as input.
    //The reason we simply can't load and draw the SVG directly to the canvas is because
    //it taints the canvas in Internet Explorer (and possibly some other browsers); making
    //it impossible to create a WebGL texture from the result.
    function drawPin(context2D, color, size) {
        context2D.save();
        context2D.scale(size / 24, size / 24); //Added to auto-generated code to scale up to desired size.
        context2D.fillStyle = color.toCssColorString(); //Modified from auto-generated code.
        context2D.strokeStyle = color.brighten(0.6, colorScratch).toCssColorString(); //Modified from auto-generated code.
        context2D.lineWidth = 0.846;
        context2D.beginPath();
        context2D.moveTo(6.72, 0.422);
        context2D.lineTo(17.28, 0.422);
        context2D.bezierCurveTo(18.553, 0.422, 19.577, 1.758, 19.577, 3.415);
        context2D.lineTo(19.577, 10.973);
        context2D.bezierCurveTo(19.577, 12.63, 18.553, 13.966, 17.282, 13.966);
        context2D.lineTo(14.386, 14.008);
        context2D.lineTo(11.826, 23.578);
        context2D.lineTo(9.614, 14.008);
        context2D.lineTo(6.719, 13.965);
        context2D.bezierCurveTo(5.446, 13.983, 4.422, 12.629, 4.422, 10.972);
        context2D.lineTo(4.422, 3.416);
        context2D.bezierCurveTo(4.423, 1.76, 5.447, 0.423, 6.718, 0.423);
        context2D.closePath();
        context2D.fill();
        context2D.stroke();
        context2D.restore();
    }

    //This function takes an image or canvas and uses it as a template
    //to "stamp" the pin with a white image outlined in black.  The color
    //values of the input image are ignored completely and only the alpha
    //values are used.
    function drawIcon(context2D, image, size) {
        //Size is the largest image that looks good inside of pin box.
        var imageSize = size / 2.5;
        var sizeX = imageSize;
        var sizeY = imageSize;

        if (image.width > image.height) {
            sizeY = imageSize * (image.height / image.width);
        } else if (image.width < image.height) {
            sizeX = imageSize * (image.width / image.height);
        }

        //x and y are the center of the pin box
        var x = Math.round((size - sizeX) / 2);
        var y = Math.round(((7 / 24) * size) - (sizeY / 2));

        context2D.globalCompositeOperation = 'destination-out';
        context2D.drawImage(image, x - 1, y, sizeX, sizeY);
        context2D.drawImage(image, x, y - 1, sizeX, sizeY);
        context2D.drawImage(image, x + 1, y, sizeX, sizeY);
        context2D.drawImage(image, x, y + 1, sizeX, sizeY);

        context2D.globalCompositeOperation = 'destination-over';
        context2D.fillStyle = Color.BLACK.toCssColorString();
        context2D.fillRect(x - 1, y - 1, sizeX + 2, sizeY + 2);

        context2D.globalCompositeOperation = 'destination-out';
        context2D.drawImage(image, x, y, sizeX, sizeY);

        context2D.globalCompositeOperation = 'destination-over';
        context2D.fillStyle = Color.WHITE.toCssColorString();
        context2D.fillRect(x - 1, y - 2, sizeX + 2, sizeY + 2);
    }

    var stringifyScratch = new Array(4);
    function createPin(url, label, color, size, cache) {
        //Use the parameters as a unique ID for caching.
        stringifyScratch[0] = url;
        stringifyScratch[1] = label;
        stringifyScratch[2] = color;
        stringifyScratch[3] = size;
        var id = JSON.stringify(stringifyScratch);

        var item = cache[id];
        if (defined(item)) {
            return item;
        }

        var canvas = document.createElement('canvas');
        canvas.width = size;
        canvas.height = size;

        var context2D = canvas.getContext("2d");
        drawPin(context2D, color, size);

        if (defined(url)) {
            //If we have an image url, load it and then stamp the pin.
            var promise = loadImage(url).then(function(image) {
                drawIcon(context2D, image, size);
                cache[id] = canvas;
                return canvas;
            });
            cache[id] = promise;
            return promise;
        } else if (defined(label)) {
            //If we have a label, write it to a canvas and then stamp the pin.
            var image = writeTextToCanvas(label, {
                font : 'bold ' + size + 'px sans-serif'
            });
            drawIcon(context2D, image, size);
        }

        cache[id] = canvas;
        return canvas;
    }

    return PinBuilder;
});
<|endoftext|>