<commit_before>/* globals console */

/*
 * Copyright (c) $year, Den Norske Turistforening (DNT)
 *
 * https://github.com/Turistforeningen/turadmin
 */

define(function (require, exports, module) {
    "use strict";

    // Dependencies
    var $ = require('jquery'),
        _ = require('underscore'),
        Backbone = require('backbone'),
        state = require('state');

    var objects = {};
    var objectType;
    var currentOwnerId;
    var $table = $('#objects table tbody');

    var onObjectPatch = function (data, textStatus, jqXhr) {
        var id = this.url.split('/').reverse()[0];
        $('tr[data-id="'+ id +'"]').addClass('success');
    };

    var appendObject = function (obj) {
        var $tr = $('
');
        $tr.html([
            '<input type="checkbox" checked>	',
            '' + obj._id + '	',
            '' + obj.navn + '	',
            '' + obj.privat.opprettet_av.id + '	',
            '' + obj.privat.opprettet_av.navn + '	',
            '' + obj.privat.opprettet_av.epost + '	'
        ].join());

        $tr.find('input').on('change', function (e) {
            var $tr = $(this).parents('tr').first();
            if (this.checked) {
               $tr.attr('data-checked', '');
               $tr.removeClass('active');
            } else {
               $tr.removeAttr('data-checked');
               $tr.addClass('active');
            }
        });

        $table.append($tr);
    };

    $('table thead input[type="checkbox"]').on('click', function () {
        $('table tbody input[type="checkbox"]').prop('checked', this.checked ? true : false).trigger('change');
    });

    $('#current-owner button').on('click', function () {
        currentOwnerId = $('#current-owner input').val();
        objectType = $('#current-owner select').val();

        $.ajax({
            url: '/restProxy/' + objectType + '/',
            data: 'privat.opprettet_av.id=' + encodeURIComponent(currentOwnerId) + '&limit=50&fields=navn,privat',
            processData: false,
            dataType: 'json',
            success: function (data, textStatus, jqXhr) {
                $table.html('');
                objects = {};

                if (data.documents && data.documents.length) {
                    for (var i = 0; i < data.documents.length; i++) {
                        appendObject(data.documents[i]);
                        objects[data.documents[i]['_id']] = data.documents[i];
                    }
                } else {
                    var $emptyLi = '
Fant ingen ' + objectType + ' tilhørende bruker med id ' + currentOwnerId + '
';
                    $table.html($emptyLi);
                }
            }
        });

    });

    $('#new-owner button').on('click', function () {
        var $tableItems = $table.find('tr[data-checked]');
        var $newOwner = $('#new-owner');

        var newOwner = {
            id: $newOwner.find('input[name="id"]').val(),
            navn: $newOwner.find('input[name="navn"]').val(),
            epost: $newOwner.find('input[name="epost"]').val()
        };

        for (var i = 0; i < $tableItems.length; i++) {
            var $item = $tableItems[i];
            var id = $item.dataset.id;
            var privat = objects[id].privat;

            privat.opprettet_av = newOwner;

            $.ajax({
                url: '/restProxy/' + objectType + '/' + id,
                data: {
                    privat: privat,
                    _method: 'PATCH'
                },
                method: 'PUT',
                dataType: 'json',
                success: onObjectPatch
            });
        }
    });
});
<commit_msg>fix(chown): specify contentType and stringify JSON<commit_after>/* globals console */

/*
 * Copyright (c) $year, Den Norske Turistforening (DNT)
 *
 * https://github.com/Turistforeningen/turadmin
 */

define(function (require, exports, module) {
    "use strict";

    // Dependencies
    var $ = require('jquery'),
        _ = require('underscore'),
        Backbone = require('backbone'),
        state = require('state');

    var objects = {};
    var objectType;
    var currentOwnerId;
    var $table = $('#objects table tbody');

    var onObjectPatch = function (data, textStatus, jqXhr) {
        var id = this.url.split('/').reverse()[0];
        $('tr[data-id="'+ id +'"]').addClass('success');
    };

    var appendObject = function (obj) {
        var $tr = $('
');
        $tr.html([
            '<input type="checkbox" checked>	',
            '' + obj._id + '	',
            '' + obj.navn + '	',
            '' + obj.privat.opprettet_av.id + '	',
            '' + obj.privat.opprettet_av.navn + '	',
            '' + obj.privat.opprettet_av.epost + '	'
        ].join());

        $tr.find('input').on('change', function (e) {
            var $tr = $(this).parents('tr').first();
            if (this.checked) {
               $tr.attr('data-checked', '');
               $tr.removeClass('active');
            } else {
               $tr.removeAttr('data-checked');
               $tr.addClass('active');
            }
        });

        $table.append($tr);
    };

    $('table thead input[type="checkbox"]').on('click', function () {
        $('table tbody input[type="checkbox"]').prop('checked', this.checked ? true : false).trigger('change');
    });

    $('#current-owner button').on('click', function () {
        currentOwnerId = $('#current-owner input').val();
        objectType = $('#current-owner select').val();

        $.ajax({
            url: '/restProxy/' + objectType + '/',
            data: 'privat.opprettet_av.id=' + encodeURIComponent(currentOwnerId) + '&limit=50&fields=navn,privat',
            processData: false,
            dataType: 'json',
            success: function (data, textStatus, jqXhr) {
                $table.html('');
                objects = {};

                if (data.documents && data.documents.length) {
                    for (var i = 0; i < data.documents.length; i++) {
                        appendObject(data.documents[i]);
                        objects[data.documents[i]['_id']] = data.documents[i];
                    }
                } else {
                    var $emptyLi = '
Fant ingen ' + objectType + ' tilhørende bruker med id ' + currentOwnerId + '
';
                    $table.html($emptyLi);
                }
            }
        });

    });

    $('#new-owner button').on('click', function () {
        var $tableItems = $table.find('tr[data-checked]');
        var $newOwner = $('#new-owner');

        var newOwner = {
            id: $newOwner.find('input[name="id"]').val(),
            navn: $newOwner.find('input[name="navn"]').val(),
            epost: $newOwner.find('input[name="epost"]').val()
        };

        for (var i = 0; i < $tableItems.length; i++) {
            var $item = $tableItems[i];
            var id = $item.dataset.id;
            var privat = objects[id].privat;

            privat.opprettet_av = newOwner;

            $.ajax({
                url: '/restProxy/' + objectType + '/' + id,
                data: JSON.stringify({
                    privat: privat,
                    _method: 'PATCH'
                }),
                method: 'PUT',
                dataType: 'json',
                contentType: 'application/json',
                success: onObjectPatch
            });
        }
    });
});
<|endoftext|>


<commit_before>// save the initial load hash so we can use it if set
app.hash = window.location.hash;

app.onResize = function(percent) {

  var height = $(window).height() * (percent || 0.855);
  var width = $(window).width();
  // when fullscreen be odd?
  if (height) {
    //if (!app.embeddedMap) {
    if ( width > 767 && !app.embeddedMap ) {
        //$("#map").height(height);
        //$("#map-wrapper").height(height);
        $(".tabs").height(height);
        //$("#legend-wrapper").height(height - 20);
        $("#data-accordion").height(height - (($.browser.msie && $.browser.version < 9)? 130: 96));
        //$("#designs-accordion").height(height - 20 - (($.browser.msie && $.browser.version < 9)? 130: 96));
        //$("#active").height(height + 20 - (($.browser.msie && $.browser.version < 9)? 130: 96));
    }
    app.map.render('map');
  }

  app.viewModel.updateAllScrollBars();

  if (width < 946) {
    app.viewModel.hideTours(true);
  } else {
    app.viewModel.hideTours(false);
  }

};

$(window).on('resize', function() {
    app.onResize();
});

// add indexof for typeahead
if (!Array.prototype.indexOf) {

    Array.prototype.indexOf = function(obj, start) {
         for (var i = (start || 0), j = this.length; i < j; i++) {
             if (this[i] === obj) { return i; }
         }
         return -1;
    };
 }

// state of the app
app.state = {
  //list of active layer ids in order they appear on the map
  activeLayers: [],
  location: {}
};

// property to store the state of the map for restoring
app.restoreState = {};

ko.applyBindings(app.viewModel);
app.viewModel.loadLayersFromServer().done(function() {
  $('#fullscreen').show();
  $('#loading').hide();
  // app.map.updateSize();
  app.onResize();

  // trigger events that depend on the map
  $(document).trigger('map-ready');

  // if we have the hash state go ahead and load it now
  if (app.hash) {
    app.loadStateFromHash(app.hash);
  } else if (app.MPSettings.default_hash) {
    app.loadStateFromDefaultHash(app.MPSettings.default_hash);
  }

  // autocomplete for filter
  $('.search-box').typeahead({
    source: app.typeAheadSource
  });

  if ( ! ($.browser.msie && $.browser.version < 9) && ! app.embeddedMap ) {
    $("#data-accordion").jScrollPane();
  }
    //$("#legend-wrapper").jScrollPane();
  // }
});

// initialize the map
app.init();
// Google.v3 uses EPSG:900913 as projection, so we have to
// transform our coordinates
app.initializeMapLocation = function() {
    var latitude = 40.46,
        longitude = -124.56,
        zoom = 5;

    if (app.MPSettings && app.MPSettings.latitude && app.MPSettings.longitude) {
        latitude = app.MPSettings.latitude;
        longitude = app.MPSettings.longitude;
    }
    if (app.MPSettings && app.MPSettings.zoom) {
        zoom = app.MPSettings.zoom;
    }

    app.map.setCenter(new OpenLayers.LonLat(longitude, latitude).transform(
        new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913")), zoom);

};
app.initializeMapLocation();

setTimeout(function() {
    app.map.mousedrag = false;
}, 800);

$(document).ready(function() {
  app.onResize();
  $(window).resize(app.onResize);

  //Do not display any warning for missing tiles
  OpenLayers.Util.onImageLoadError = function(){
    this.src = 'http://www.openlayers.org/api/img/blank.gif';
  };
  OpenLayers.Tile.Image.useBlankTile=false;

  // if we have the hash state go ahead and load it now
  /*if (app.hash && !app.loginHash) {
    console.log('document ready without #login hash');
    app.loadStateFromHash(app.hash);
  } */
  // handle coordinate indicator on pointer
  $('#map').bind('mouseleave mouseenter', function(e) {
    $('#pos').toggle();
  });
  $('#map').bind('mousemove', function(e) {
    $('#pos').css({
      left: e.pageX + 20,
      top: e.pageY + 20
    });
  });

  $('.form-search').find('.btn').on('click', function(event) {
     $(event.target).closest('form').find('input').val(null).focus();
  });

  if(app.MPSettings.enable_drawing === "True") {
    app.viewModel.enableDrawing(true);
  }

  //fixes a problem in which the data accordion scrollbar was reinitialized before the app switched back to the data tab
  //causing the data tab to appear empty
  //the following appears to fix that problem
  $('#dataTab[data-toggle="tab"]').on('shown', function(e) {
    app.viewModel.showBottomButtons(true);
    app.viewModel.updateScrollBars();
    app.viewModel.showLegend(false);
  });
  $('#activeTab[data-toggle="tab"]').on('shown', function(e) {
    app.viewModel.showBottomButtons(true);
    app.viewModel.updateScrollBars();
    app.viewModel.showLegend(false);
  });
  $('#designsTab[data-toggle="tab"]').on('shown', function(e) {
    app.viewModel.showBottomButtons(false);
    app.viewModel.updateAllScrollBars();
    setTimeout(function() {$('.group-members-popover').popover({html: true, trigger: 'hover'});}, 2000);
  });
  $('#legendTab[data-toggle="tab"]').on('shown', function(e) {
    app.viewModel.showBottomButtons(true);
    app.viewModel.showLegend(true);
    app.viewModel.updateScrollBars();
  });

  //the following appears to handle the bookmark sharing, while the earlier popover activation handles the design sharing
  setTimeout(function() {$('.group-members-popover').popover({html: true, trigger: 'hover'});}, 2000);

  //format the legend scrollbar
  //setTimeout(function() { $('#legend-content').jScrollPane(); }, 500);
  //setTimeout(function() { app.viewModel.updateScrollBars(); }, 500);

  //resizable behavior for overview-overlay
  //might not use the following after all...
  //(having problems setting minHeight, losing resizing ability
  /*
  $("#overview-overlay").resizable({
    handles: 'n',
    containment: 'parent'
    }
  })
  */

  $('#add-layer-modal').on('shown', function () {
    // do something…
    $('#add-layer-modal .icon-info-sign').popover({
      trigger: 'hover',
      container: 'body'
    });
  });

  app.fullscreen = {};
  // fullscreen stuff
  // for security reasons, this event listener must be bound directly
  if ( document.getElementById('btn-fullscreen') ) {
      if ( document.getElementById('btn-fullscreen').addEventListener ) {
          document.getElementById('btn-fullscreen').addEventListener('click', function() {
            if ( BigScreen.enabled ) {
              BigScreen.toggle(document.getElementById('fullscreen'));
              // You could also use .toggle(element)
            } else {
              // fallback for browsers that don't support full screen
              $('#fullscreen-error-overlay').show();
            }
          }, false);
      } else {
          $('#btn-fullscreen').on('click', function() {
            // fallback for browsers that don't support addEventListener
            $('#fullscreen-error-overlay').show();
          });
      }
  }

  // called when entering full screen
  BigScreen.onenter = function() {
    //app.map.updateSize();
    //app.map.render('map');
    //close page guide, hide legend, hide layers
    if ( $.pageguide('isOpen') ) {
        app.fullscreen.pageguide = true;
        //closing the guide here makes it difficult to return to the correct guide...
        //things might be working fine without closing the guide...
        //$.pageguide('close');
    }
    if ( app.viewModel.showLegend() ) {
        app.fullscreen.showLegend = true;
        app.viewModel.showLegend(false);
    }
    if ( app.viewModel.showLayers() ) {
        app.fullscreen.showLayers = true;
        app.viewModel.showLayers(false);
    }
    app.viewModel.isFullScreen(true);
    // make map fullscreen
    setTimeout( app.onResize(0.99), 500);
  };

  BigScreen.onexit = function() {
    // called when exiting full screen
    app.viewModel.isFullScreen(false);
    // return to normal size
    // Not exactly comfortable with the following 2 calls to resize,
    // but ff kept having problems when other strategies were tried...
    //for firefox
    setTimeout( app.onResize(), 300);
    //app.onResize();
    //app.onResize();
    //if applicable, open page guide, show legend, show layers
    if ( app.fullscreen.showLayers ) {
        app.viewModel.showLayers(true);
        app.fullscreen.showLayers = false;
    }
    if ( app.fullscreen.showLegend ) {
        app.viewModel.showLegend(true);
        app.fullscreen.showLegend = false;
    }
    if ( app.fullscreen.pageguide ) {
        app.viewModel.showLayers(true);
        setTimeout( function() {
            $.pageguide('open');
            if ($.pageguide().guide().id === 'default-guide') {
                setTimeout( function() {
                    $.pageguide('showStep', $.pageguide().guide().steps.length-1);
                }, 300 );
            }
        }, 500 );
        app.fullscreen.pageguide = false;
    }
    //for chrome
    setTimeout( app.onResize, 300);
  };

  // Basemaps button and drop-down behavior
  //hide basemaps drop-down on mouseout
  $('#basemaps').mouseleave( function(e) {
    if ( $(e.toElement).hasClass('basey') ) { //handler for chrome and ie
        $('#basemaps').addClass('open');
    } else if ( $(e.relatedTarget).hasClass('basey') ) { //handler for ff
        $('#basemaps').addClass('open');
    } else {
        $('.SimpleLayerSwitcher').hide();
    }
  });

  //hide icon butons on mouseleave
  $('.SimpleLayerSwitcher').mouseleave( function() {
    $('.SimpleLayerSwitcher').hide();
    if (!app.pageguide.preventBasemapsClose) {
        $('#basemaps').removeClass('open');
    }
  });

  //hide basemap list and icon buttons on click/select (and hopefully touchend)
  $('.SimpleLayerSwitcher').mouseup( function() {
    $('.SimpleLayerSwitcher').hide();
    if (!app.pageguide.preventBasemapsClose) {
        $('#basemaps').removeClass('open');
    }
  });

  //hide icon buttons on mousedown
  $('.SimpleLayerSwitcher').mousedown( function() {
      if (!app.pageguide.preventBasemapsClose) {
          $('#basemaps').removeClass('open');
      }
  });

  //show icon buttons on mouseenter
  $('.SimpleLayerSwitcher').mouseenter( function() {
    $('#basemaps').addClass('open');
  });

  $('#overview-overlay-dropdown').mouseleave( function() {
    $('#overview-overlay-dropdown').closest('.btn-group').removeClass('open');
  });

  $('#opacity-popover').mouseleave( function() {
    app.viewModel.hideOpacity();
  });

  $('#registration-modal').on('show', function() {
    $('.empty-input').val("");
  });

  $('#sign-in-modal').on('show', function() {
    $('.empty-input').val("");
  });

  $('#reset-password-modal').on('show', function() {
    $('.empty-input').val("");
  });

  $(document).on('click', 'a[name="start-default-tour"]', function() {
    app.viewModel.startDefaultTour();
  });

  $(document).on('click', '#continue-basic-tour', function() {
    app.viewModel.stepTwoOfBasicTour();
  });

  $(document).on('click', '#start-data-tour', function() {
    app.viewModel.startDataTour();
  });

  $(document).on('click', '#start-active-tour', function() {
    app.viewModel.startActiveTour();
  });

  $(document).on('click', '#share-option', function() {
    app.viewModel.scenarios.initSharingModal();
  });

  // hiding feature attributes on new click events (but ignoring map pan events)
  app.map.events.register('move', app.map, function() {
    app.map.mousedrag = true;
  });
  $('#map').mouseup( function() {
    if ( !app.map.mousedrag ) {
      app.map.clickOutput.attributes = {};
      app.viewModel.closeAttribution();
      app.viewModel.closeDescription();
    }
    app.map.mousedrag = false;
  });

  $('a[data-toggle="tab"]').on('shown', function (e) {
    app.updateUrl();
  });

});

$('#bookmark-form').on('submit', function(event) {
  var inputs = {},
    $form = $(this);
  event.preventDefault();
  $(this).find('input, textarea').each(function(i, input) {
    var $input = $(input);
    inputs[$input.attr('name')] = $input.val();
  });
  $.post('/feedback/bookmark', inputs, function() {
    $form.closest('.modal').modal('hide');
  });
});

$('#feedback-form').on('submit', function (event) {
   var feedback = {}, $form = $(this);
   event.preventDefault();
   $(this).find('input, textarea').each(function (i, input) {
      var $input = $(input);
      feedback[$input.attr('name')] = $input.val();
   });
   feedback.url = window.location.href;
   $.post('/feedback/send', feedback, function () {
      $form.find('textarea').val('');
      $form.closest('.modal').modal('hide');
      //$('#thankyou-modal').modal('show');
   });
   $form.closest('.modal').modal('hide');
});

$(document).mousedown(function(e) {
  //removing bookmark popover from view
  if ($(e.target).closest('a').length && $(e.target).closest('a')[0].id === "bookmarks-button") {
    //do nothing as show/hide behavior is handled in viewModel
  } else if (!$(e.target).closest("#bookmark-popover").length) {
    $('#bookmark-popover').hide();
  }

  //ensure layer switcher is removed
  $('.SimpleLayerSwitcher').hide();

  //removing layer tooltip popover from view
  var layer_pvr_event = $(e.target).closest(".layer-popover").length;
  if (!layer_pvr_event) {
    $("#layer-popover").hide();
  }

  //removing opacity popover from view
  var op_pvr_event = $(e.target).closest("#opacity-popover").length;
  var op_btn_event = $(e.target).closest(".opacity-button").length;
  if (!op_pvr_event && !op_btn_event) {
    //$('#opacity-popover').hide();
    app.viewModel.hideOpacity();
  }
});
<commit_msg>adding touchend event handler to hide simplelayerswitcher<commit_after>// save the initial load hash so we can use it if set
app.hash = window.location.hash;

app.onResize = function(percent) {

  var height = $(window).height() * (percent || 0.855);
  var width = $(window).width();
  // when fullscreen be odd?
  if (height) {
    //if (!app.embeddedMap) {
    if ( width > 767 && !app.embeddedMap ) {
        //$("#map").height(height);
        //$("#map-wrapper").height(height);
        $(".tabs").height(height);
        //$("#legend-wrapper").height(height - 20);
        $("#data-accordion").height(height - (($.browser.msie && $.browser.version < 9)? 130: 96));
        //$("#designs-accordion").height(height - 20 - (($.browser.msie && $.browser.version < 9)? 130: 96));
        //$("#active").height(height + 20 - (($.browser.msie && $.browser.version < 9)? 130: 96));
    }
    app.map.render('map');
  }

  app.viewModel.updateAllScrollBars();

  if (width < 946) {
    app.viewModel.hideTours(true);
  } else {
    app.viewModel.hideTours(false);
  }

};

$(window).on('resize', function() {
    app.onResize();
});

// add indexof for typeahead
if (!Array.prototype.indexOf) {

    Array.prototype.indexOf = function(obj, start) {
         for (var i = (start || 0), j = this.length; i < j; i++) {
             if (this[i] === obj) { return i; }
         }
         return -1;
    };
 }

// state of the app
app.state = {
  //list of active layer ids in order they appear on the map
  activeLayers: [],
  location: {}
};

// property to store the state of the map for restoring
app.restoreState = {};

ko.applyBindings(app.viewModel);
app.viewModel.loadLayersFromServer().done(function() {
  $('#fullscreen').show();
  $('#loading').hide();
  // app.map.updateSize();
  app.onResize();

  // trigger events that depend on the map
  $(document).trigger('map-ready');

  // if we have the hash state go ahead and load it now
  if (app.hash) {
    app.loadStateFromHash(app.hash);
  } else if (app.MPSettings.default_hash) {
    app.loadStateFromDefaultHash(app.MPSettings.default_hash);
  }

  // autocomplete for filter
  $('.search-box').typeahead({
    source: app.typeAheadSource
  });

  if ( ! ($.browser.msie && $.browser.version < 9) && ! app.embeddedMap ) {
    $("#data-accordion").jScrollPane();
  }
    //$("#legend-wrapper").jScrollPane();
  // }
});

// initialize the map
app.init();
// Google.v3 uses EPSG:900913 as projection, so we have to
// transform our coordinates
app.initializeMapLocation = function() {
    var latitude = 40.46,
        longitude = -124.56,
        zoom = 5;

    if (app.MPSettings && app.MPSettings.latitude && app.MPSettings.longitude) {
        latitude = app.MPSettings.latitude;
        longitude = app.MPSettings.longitude;
    }
    if (app.MPSettings && app.MPSettings.zoom) {
        zoom = app.MPSettings.zoom;
    }

    app.map.setCenter(new OpenLayers.LonLat(longitude, latitude).transform(
        new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913")), zoom);

};
app.initializeMapLocation();

setTimeout(function() {
    app.map.mousedrag = false;
}, 800);

$(document).ready(function() {
  app.onResize();
  $(window).resize(app.onResize);

  //Do not display any warning for missing tiles
  OpenLayers.Util.onImageLoadError = function(){
    this.src = 'http://www.openlayers.org/api/img/blank.gif';
  };
  OpenLayers.Tile.Image.useBlankTile=false;

  // if we have the hash state go ahead and load it now
  /*if (app.hash && !app.loginHash) {
    console.log('document ready without #login hash');
    app.loadStateFromHash(app.hash);
  } */
  // handle coordinate indicator on pointer
  $('#map').bind('mouseleave mouseenter', function(e) {
    $('#pos').toggle();
  });
  $('#map').bind('mousemove', function(e) {
    $('#pos').css({
      left: e.pageX + 20,
      top: e.pageY + 20
    });
  });

  $('.form-search').find('.btn').on('click', function(event) {
     $(event.target).closest('form').find('input').val(null).focus();
  });

  if(app.MPSettings.enable_drawing === "True") {
    app.viewModel.enableDrawing(true);
  }

  //fixes a problem in which the data accordion scrollbar was reinitialized before the app switched back to the data tab
  //causing the data tab to appear empty
  //the following appears to fix that problem
  $('#dataTab[data-toggle="tab"]').on('shown', function(e) {
    app.viewModel.showBottomButtons(true);
    app.viewModel.updateScrollBars();
    app.viewModel.showLegend(false);
  });
  $('#activeTab[data-toggle="tab"]').on('shown', function(e) {
    app.viewModel.showBottomButtons(true);
    app.viewModel.updateScrollBars();
    app.viewModel.showLegend(false);
  });
  $('#designsTab[data-toggle="tab"]').on('shown', function(e) {
    app.viewModel.showBottomButtons(false);
    app.viewModel.updateAllScrollBars();
    setTimeout(function() {$('.group-members-popover').popover({html: true, trigger: 'hover'});}, 2000);
  });
  $('#legendTab[data-toggle="tab"]').on('shown', function(e) {
    app.viewModel.showBottomButtons(true);
    app.viewModel.showLegend(true);
    app.viewModel.updateScrollBars();
  });

  //the following appears to handle the bookmark sharing, while the earlier popover activation handles the design sharing
  setTimeout(function() {$('.group-members-popover').popover({html: true, trigger: 'hover'});}, 2000);

  //format the legend scrollbar
  //setTimeout(function() { $('#legend-content').jScrollPane(); }, 500);
  //setTimeout(function() { app.viewModel.updateScrollBars(); }, 500);

  //resizable behavior for overview-overlay
  //might not use the following after all...
  //(having problems setting minHeight, losing resizing ability
  /*
  $("#overview-overlay").resizable({
    handles: 'n',
    containment: 'parent'
    }
  })
  */

  $('#add-layer-modal').on('shown', function () {
    // do something…
    $('#add-layer-modal .icon-info-sign').popover({
      trigger: 'hover',
      container: 'body'
    });
  });

  app.fullscreen = {};
  // fullscreen stuff
  // for security reasons, this event listener must be bound directly
  if ( document.getElementById('btn-fullscreen') ) {
      if ( document.getElementById('btn-fullscreen').addEventListener ) {
          document.getElementById('btn-fullscreen').addEventListener('click', function() {
            if ( BigScreen.enabled ) {
              BigScreen.toggle(document.getElementById('fullscreen'));
              // You could also use .toggle(element)
            } else {
              // fallback for browsers that don't support full screen
              $('#fullscreen-error-overlay').show();
            }
          }, false);
      } else {
          $('#btn-fullscreen').on('click', function() {
            // fallback for browsers that don't support addEventListener
            $('#fullscreen-error-overlay').show();
          });
      }
  }

  // called when entering full screen
  BigScreen.onenter = function() {
    //app.map.updateSize();
    //app.map.render('map');
    //close page guide, hide legend, hide layers
    if ( $.pageguide('isOpen') ) {
        app.fullscreen.pageguide = true;
        //closing the guide here makes it difficult to return to the correct guide...
        //things might be working fine without closing the guide...
        //$.pageguide('close');
    }
    if ( app.viewModel.showLegend() ) {
        app.fullscreen.showLegend = true;
        app.viewModel.showLegend(false);
    }
    if ( app.viewModel.showLayers() ) {
        app.fullscreen.showLayers = true;
        app.viewModel.showLayers(false);
    }
    app.viewModel.isFullScreen(true);
    // make map fullscreen
    setTimeout( app.onResize(0.99), 500);
  };

  BigScreen.onexit = function() {
    // called when exiting full screen
    app.viewModel.isFullScreen(false);
    // return to normal size
    // Not exactly comfortable with the following 2 calls to resize,
    // but ff kept having problems when other strategies were tried...
    //for firefox
    setTimeout( app.onResize(), 300);
    //app.onResize();
    //app.onResize();
    //if applicable, open page guide, show legend, show layers
    if ( app.fullscreen.showLayers ) {
        app.viewModel.showLayers(true);
        app.fullscreen.showLayers = false;
    }
    if ( app.fullscreen.showLegend ) {
        app.viewModel.showLegend(true);
        app.fullscreen.showLegend = false;
    }
    if ( app.fullscreen.pageguide ) {
        app.viewModel.showLayers(true);
        setTimeout( function() {
            $.pageguide('open');
            if ($.pageguide().guide().id === 'default-guide') {
                setTimeout( function() {
                    $.pageguide('showStep', $.pageguide().guide().steps.length-1);
                }, 300 );
            }
        }, 500 );
        app.fullscreen.pageguide = false;
    }
    //for chrome
    setTimeout( app.onResize, 300);
  };

  // Basemaps button and drop-down behavior
  //hide basemaps drop-down on mouseout
  $('#basemaps').mouseleave( function(e) {
    if ( $(e.toElement).hasClass('basey') ) { //handler for chrome and ie
        $('#basemaps').addClass('open');
    } else if ( $(e.relatedTarget).hasClass('basey') ) { //handler for ff
        $('#basemaps').addClass('open');
    } else {
        $('.SimpleLayerSwitcher').hide();
    }
  });

  //hide icon butons on mouseleave
  $('.SimpleLayerSwitcher').mouseleave( function() {
    $('.SimpleLayerSwitcher').hide();
    if (!app.pageguide.preventBasemapsClose) {
        $('#basemaps').removeClass('open');
    }
  });

  //hide basemap list and icon buttons on click/select (and hopefully touchend)
  $('.SimpleLayerSwitcher').on( "touchend mouseup", function() {
    $('.SimpleLayerSwitcher').hide();
    if (!app.pageguide.preventBasemapsClose) {
        $('#basemaps').removeClass('open');
    }
  });

  //hide icon buttons on mousedown
  $('.SimpleLayerSwitcher').mousedown( function() {
      if (!app.pageguide.preventBasemapsClose) {
          $('#basemaps').removeClass('open');
      }
  });

  //show icon buttons on mouseenter
  $('.SimpleLayerSwitcher').mouseenter( function() {
    $('#basemaps').addClass('open');
  });

  $('#overview-overlay-dropdown').mouseleave( function() {
    $('#overview-overlay-dropdown').closest('.btn-group').removeClass('open');
  });

  $('#opacity-popover').mouseleave( function() {
    app.viewModel.hideOpacity();
  });

  $('#registration-modal').on('show', function() {
    $('.empty-input').val("");
  });

  $('#sign-in-modal').on('show', function() {
    $('.empty-input').val("");
  });

  $('#reset-password-modal').on('show', function() {
    $('.empty-input').val("");
  });

  $(document).on('click', 'a[name="start-default-tour"]', function() {
    app.viewModel.startDefaultTour();
  });

  $(document).on('click', '#continue-basic-tour', function() {
    app.viewModel.stepTwoOfBasicTour();
  });

  $(document).on('click', '#start-data-tour', function() {
    app.viewModel.startDataTour();
  });

  $(document).on('click', '#start-active-tour', function() {
    app.viewModel.startActiveTour();
  });

  $(document).on('click', '#share-option', function() {
    app.viewModel.scenarios.initSharingModal();
  });

  // hiding feature attributes on new click events (but ignoring map pan events)
  app.map.events.register('move', app.map, function() {
    app.map.mousedrag = true;
  });
  $('#map').mouseup( function() {
    if ( !app.map.mousedrag ) {
      app.map.clickOutput.attributes = {};
      app.viewModel.closeAttribution();
      app.viewModel.closeDescription();
    }
    app.map.mousedrag = false;
  });

  $('a[data-toggle="tab"]').on('shown', function (e) {
    app.updateUrl();
  });

});

$('#bookmark-form').on('submit', function(event) {
  var inputs = {},
    $form = $(this);
  event.preventDefault();
  $(this).find('input, textarea').each(function(i, input) {
    var $input = $(input);
    inputs[$input.attr('name')] = $input.val();
  });
  $.post('/feedback/bookmark', inputs, function() {
    $form.closest('.modal').modal('hide');
  });
});

$('#feedback-form').on('submit', function (event) {
   var feedback = {}, $form = $(this);
   event.preventDefault();
   $(this).find('input, textarea').each(function (i, input) {
      var $input = $(input);
      feedback[$input.attr('name')] = $input.val();
   });
   feedback.url = window.location.href;
   $.post('/feedback/send', feedback, function () {
      $form.find('textarea').val('');
      $form.closest('.modal').modal('hide');
      //$('#thankyou-modal').modal('show');
   });
   $form.closest('.modal').modal('hide');
});

$(document).mousedown(function(e) {
  //removing bookmark popover from view
  if ($(e.target).closest('a').length && $(e.target).closest('a')[0].id === "bookmarks-button") {
    //do nothing as show/hide behavior is handled in viewModel
  } else if (!$(e.target).closest("#bookmark-popover").length) {
    $('#bookmark-popover').hide();
  }

  //ensure layer switcher is removed
  $('.SimpleLayerSwitcher').hide();

  //removing layer tooltip popover from view
  var layer_pvr_event = $(e.target).closest(".layer-popover").length;
  if (!layer_pvr_event) {
    $("#layer-popover").hide();
  }

  //removing opacity popover from view
  var op_pvr_event = $(e.target).closest("#opacity-popover").length;
  var op_btn_event = $(e.target).closest(".opacity-button").length;
  if (!op_pvr_event && !op_btn_event) {
    //$('#opacity-popover').hide();
    app.viewModel.hideOpacity();
  }
});
<|endoftext|>


<commit_before>import {LitElement, html} from 'lit';
import {openApprovalsDialog} from './chromedash-approvals-dialog';
// eslint-disable-next-line no-unused-vars
import './chromedash-feature';
import {FEATURELIST_CSS} from '../sass/elements/chromedash-featurelist-css.js';

const MAX_FEATURES_SHOWN = 500;

class ChromedashFeaturelist extends LitElement {
  static styles = FEATURELIST_CSS;

  static get properties() {
    return {
      user: {type: Object},
      isSiteEditor: {type: Boolean},
      canApprove: {type: Boolean},
      signedInUser: {type: String},
      editableFeatures: {type: Object},
      features: {attribute: false}, // Directly edited and accessed in template/features.html
      metadataEl: {attribute: false}, // The metadata component element. Directly edited in template/features.html
      searchEl: {attribute: false}, // The search input element. Directly edited in template/features.html
      filtered: {attribute: false},
      openFeatures: {attribute: false},
      starredFeatures: {attribute: false},
    };
  }

  constructor() {
    super();
    this.user = {};
    this.features = [];
    this.editableFeatures = [];
    this.filtered = [];
    this.metadataEl = document.querySelector('chromedash-metadata');
    this.searchEl = document.querySelector('.search input');
    this.isSiteEditor = false;
    this.canApprove = false;
    this.signedInUser = '';
    this._hasInitialized = false; // Used to check initialization code.
    this._hasScrolledByUser = false; // Used to set the app header state.
    /* When scrollTo(), we also expand the feature. This is the id of the feature. */
    this.openFeatures = new Set();
    this.starredFeatures = new Set();

    this._featuresUnveilMetric = new Metric('features_unveil');
    this._featuresFetchMetric = new Metric('features_loaded');
    this._featuresUnveilMetric.start();
    this._featuresFetchMetric.start();

    /* The running context `this` inside `renderItem` is `lit-virtualizer`
     * rather than `chromedash-featurelist`, so all function calls inside need
     * to be bound to `this`. */
    this._onFeatureToggledBound = this._onFeatureToggled.bind(this);
    this._onStarToggledBound = this._onStarToggled.bind(this);
    this._onOpenApprovalsBound = this._onOpenApprovals.bind(this);

    this._loadData();
  }

  async _loadData() {
    const featureUrl = '/features_v2.json';

    try {
      const features = await (await fetch(featureUrl)).json();
      this._featuresFetchMetric.end().log().sendToAnalytics('features', 'loaded');

      features.map((feature) => {
        feature.receivePush = false;
        if (feature.is_released) {
          feature.milestone = feature.browsers.chrome.desktop ||
              feature.browsers.chrome.android ||
              feature.browsers.chrome.webview ||
              feature.browsers.chrome.ios ||
              Infinity;
        }
      });
      this.features = features;

      this.searchEl.disabled = false;
      this.filter(this.searchEl.value);
      this._initialize();
    } catch (error) {
      document.getElementById('content').classList.add('error');
      console.error(error);
      throw new Error('Failed to fetch features');
    };
  }

  _fireEvent(eventName, detail) {
    const event = new CustomEvent(eventName, {detail});
    this.dispatchEvent(event);
  }

  _getSearchStrRegExp(searchStr) {
    return new RegExp(
      // Case-insensitive match on literal string; escape characters that
      // have special meaning in regular expressions.
      searchStr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), 'i');
  }

  _pathToPropertyVal(propPath, feature) {
    return propPath.split('.').reduce((obj, key) => {
      return obj[key];
    }, feature);
  }

  _lt(propPath, value, feature) {
    return this._pathToPropertyVal(propPath, feature) < value;
  }
  _lte(propPath, value, feature) {
    return this._pathToPropertyVal(propPath, feature) <= value;
  }
  _gt(propPath, value, feature) {
    return this._pathToPropertyVal(propPath, feature) > value;
  }
  _gte(propPath, value, feature) {
    return this._pathToPropertyVal(propPath, feature) >= value;
  }
  _eq(propPath, value, feature) {
    return this._pathToPropertyVal(propPath, feature) === value;
  }
  _false() {
    return false;
  }

  _getOperatorFilter(args /* [propPath, operator, valueStr] */) {
    const value = parseFloat(args[2]);
    if (isNaN(value)) return this._false;

    switch (args[1].trim()) {
      case '<':
        return this._lt.bind(this, args[0], value);
        break;
      case '<=':
        return this._lte.bind(this, args[0], value);
        break;
      case '>':
        return this._gt.bind(this, args[0], value);
        break;
      case '>=':
        return this._gte.bind(this, args[0], value);
        break;
      case '=': // Support both '=' and '=='.
      case '==':
        return this._eq.bind(this, args[0], value);
        break;
      default:
        return this._false;
    }
  }

  _setOpenFeatures(featureId, open) {
    const newOpen = new Set(this.openFeatures);
    if (open) {
      newOpen.add(featureId);
    } else {
      newOpen.delete(featureId);
    }
    this.openFeatures = newOpen;
  }

  _onFeatureToggled(e) {
    const feature = e.detail.feature;
    const open = e.detail.open;
    this._setOpenFeatures(feature.id, open);
  }

  _onStarToggled(e) {
    const feature = e.detail.feature;
    const starred = e.detail.starred;

    const newStarredFeatures = new Set(this.starredFeatures);
    if (starred) {
      newStarredFeatures.add(feature.id);
    } else {
      newStarredFeatures.delete(feature.id);
    }
    this.starredFeatures = newStarredFeatures;
  }

  _onOpenApprovals(e) {
    openApprovalsDialog(this.user, e.detail.feature);
  }

  _filterProperty(propPath, regExp, feature) {
    const value = this._pathToPropertyVal(propPath, feature);

    // Null or missing values never match.
    if (value === null || typeof value === 'undefined') {
      return false;
    }

    // Allow for enums that store string in "text" property.
    return (value.text || value).toString().match(regExp) !== null;
  }

  _getPropertyFilter(args /* [propPath, searchStr] */) {
    return this._filterProperty.bind(
      this, args[0], this._getSearchStrRegExp(args[1]));
  }

  _filterKeyword(regExp, feature) {
    return (feature.name + '\n' + feature.summary + '\n' + feature.comments +
    '\n' + feature.tags).match(regExp) !== null;
  }

  _getKeywordFilter(keyword) {
    return this._filterKeyword.bind(
      this, this._getSearchStrRegExp(keyword));
  }

  // Directly called from template/features.html
  filter(val, shouldPushState) {
    this.searchEl.value = val;
    const pushOrReplaceState = (
      history &&
      (shouldPushState ? history.pushState.bind(history) :
        history.replaceState.bind(history)));
    // Clear filter if there's no search or if called directly.
    if (!val) {
      if (pushOrReplaceState) {
        pushOrReplaceState({query: ''}, document.title,
          location.pathname + location.search);
      } else {
        location.hash = '';
      }
      this.filtered = this.features;
    } else {
      val = val.trim();
      if (pushOrReplaceState) {
        pushOrReplaceState({query: val}, document.title,
          '/features#' + encodeURIComponent(val));
      }

      const blinkComponent = val.match(/^component:\s?(.*)/);
      if (blinkComponent) {
        const componentName = blinkComponent[1].trim();
        this.filtered = this.features.filter(feature => (
          feature.browsers.chrome.blink_components.includes(componentName)
        ));
        this._fireEvent('filtered', {count: this.filtered.length});
        return;
      }

      const regExp = /"([^"]*)"/g;
      const start = 0;
      const parts = [];
      let match;
      while ((match = regExp.exec(val)) !== null) {
        if (start - (regExp.lastIndex - match[0].length) > 0) {
          parts.push(val.substring(start, regExp.lastIndex -
                                   match[0].length));
        }
        parts.push(match[1]);
      }
      const matchLen = match ? match[0].length : 0;
      if (start - (regExp.lastIndex - matchLen) > 0) {
        parts.push(val.substring(start, regExp.lastIndex -
                                 matchLen));
      }

      // Match words separated by whitespace and/or ":" and/or ordered
      // comparison operator.
      const wordRegExp = /("([^"]+)"|([^:<>= \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+))([:<>= \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+)?/g;
      // Array of matches, each an array of the form:
      // [ full match, quoted-string-or-word, contents-of-quoted-string,
      //   word, separator ].
      const reMatches = [];
      while ((match = wordRegExp.exec(val)) !== null) {
        reMatches.push(match);
      }

      // Query parts of the form: "name : value".
      const propertyQueries = [];
      // Query parts of the form: "name = value", "name < value", etc.
      const operatorQueries = [];
      // Other words in the query (not matching forms above).
      const keywordQueries = [];

      // Accumulate keyword and property queries.
      for (let i = 0; i < reMatches.length; i++) {
        match = reMatches[i];
        const part = match[2] || match[3];
        const sep = match[4];
        const nextPart = i < reMatches.length - 1 ?
          (reMatches[i + 1][2] || reMatches[i + 1][3]) : null;
        if (sep && sep.trim().match(/^:$/) !== null && nextPart !== null) {
          // Separator is ":", and there exists a right-hand-side.
          // Store property query: "propertyName : propertyValue".
          propertyQueries.push([part, nextPart]);
          i++;
        } else if (sep && sep.trim().match(/^(<=|>=|==|<|>|=)$/) !== null &&
            nextPart !== null) {
          // Separator is an ordered comparison operator and there exists a
          // right-hand-side.
          // Store operator query "name <<operator>> value".
          operatorQueries.push([part, sep, nextPart]);
          i++;
        } else {
          // No special operator found. Store non-separator part as keyword
          // query.
          keywordQueries.push(part);
        }
      }

      // Construct a list filter for each query part, and store them all in
      // a list.
      const filters = propertyQueries.map(this._getPropertyFilter.bind(this))
        .concat(operatorQueries.map(this._getOperatorFilter.bind(this)))
        .concat(keywordQueries.map(this._getKeywordFilter.bind(this)));

      // Apply this.filtered = this.features filtered-by filters.
      if (filters.length === 0) {
        this.filtered = this.features;
      } else {
        let results = this.features;
        for (let i = 0; i < filters.length; i++) {
          results = results.filter(filters[i]);
        }
        this.filtered = results;
      }
    }

    this._fireEvent('filtered', {count: this.filtered.length});
  }

  _firstOfMilestone(milestone) {
    for (let i = 0; i < this.filtered.length; i++) {
      const feature = this.filtered[i];
      if (feature.first_of_milestone &&
          (milestone === feature.browsers.chrome.desktop ||
           milestone === feature.browsers.chrome.status.text)) {
        return feature.id;
      }
    }
    return null;
  }

  // Directly called from template/features.html
  scrollToId(targetId) {
    if (!targetId) return;

    this._setOpenFeatures(targetId, true);

    const targetEl = this.shadowRoot.querySelector('#id-' + targetId);
    if (targetEl) {
      targetEl.scrollIntoView();
      const heightOfHeader = document.querySelector('.main-toolbar').getBoundingClientRect().height;
      window.scrollBy(0, heightOfHeader * -1);
    }
  }

  _initialize() {
    this._featuresUnveilMetric.end().log().sendToAnalytics('features', 'unveil');
    this._fireEvent('app-ready');
    this._hasInitialized = true;
  }

  _computeMilestoneHidden(feature, features, filtered) {
    return filtered.length != features.length || !feature.first_of_milestone;
  }

  _computeMilestoneString(str) {
    return isNaN(parseInt(str)) ? str : 'Chrome ' + str;
  }

  render() {
    // TODO: Avoid computing values in render().
    let filteredWithState = this.filtered.map((feature) => {
      const editable = this.isSiteEditor ||
        (this.editableFeatures && this.editableFeatures.includes(feature.id));
      return {
        feature: feature,
        open: this.openFeatures.has(feature.id),
        starred: this.starredFeatures.has(feature.id),
        canEditFeature: editable,
      };
    });
    let numOverLimit = 0;
    if (filteredWithState.length > MAX_FEATURES_SHOWN) {
      numOverLimit = filteredWithState.length - MAX_FEATURES_SHOWN;
      filteredWithState = filteredWithState.slice(0, MAX_FEATURES_SHOWN);
    }
    return html`
      <link rel="stylesheet" href="/static/css/elements/chromedash-featurelist.css">

      ${filteredWithState.map((item) => html`

${this._computeMilestoneString(item.feature.browsers.chrome.status.milestone_str)}

            <chromedash-feature id="id-${item.feature.id}" tabindex="0"
                 ?open="${item.open}"
                 ?starred="${item.starred}"
                 @feature-toggled="${this._onFeatureToggledBound}"
                 @star-toggled="${this._onStarToggledBound}"
                 @open-approvals-event="${this._onOpenApprovalsBound}"
                 .feature="${item.feature}"
                 ?canEdit="${item.canEditFeature}"
                 ?canApprove="${this.canApprove}"
                 ?signedIn="${this.signedInUser != ''}"
          ></chromedash-feature>

        `)}

      ${numOverLimit > 0 ?
        html`<p>To see ${numOverLimit} earlier features, please enter a more specific query.</p>` :
        ''}
    `;
  }
}

customElements.define('chromedash-featurelist', ChromedashFeaturelist);
<commit_msg>Fix 404 featurelist.css (#2177)<commit_after>import {LitElement, html} from 'lit';
import {openApprovalsDialog} from './chromedash-approvals-dialog';
// eslint-disable-next-line no-unused-vars
import './chromedash-feature';
import {FEATURELIST_CSS} from '../sass/elements/chromedash-featurelist-css.js';

const MAX_FEATURES_SHOWN = 500;

class ChromedashFeaturelist extends LitElement {
  static styles = FEATURELIST_CSS;

  static get properties() {
    return {
      user: {type: Object},
      isSiteEditor: {type: Boolean},
      canApprove: {type: Boolean},
      signedInUser: {type: String},
      editableFeatures: {type: Object},
      features: {attribute: false}, // Directly edited and accessed in template/features.html
      metadataEl: {attribute: false}, // The metadata component element. Directly edited in template/features.html
      searchEl: {attribute: false}, // The search input element. Directly edited in template/features.html
      filtered: {attribute: false},
      openFeatures: {attribute: false},
      starredFeatures: {attribute: false},
    };
  }

  constructor() {
    super();
    this.user = {};
    this.features = [];
    this.editableFeatures = [];
    this.filtered = [];
    this.metadataEl = document.querySelector('chromedash-metadata');
    this.searchEl = document.querySelector('.search input');
    this.isSiteEditor = false;
    this.canApprove = false;
    this.signedInUser = '';
    this._hasInitialized = false; // Used to check initialization code.
    this._hasScrolledByUser = false; // Used to set the app header state.
    /* When scrollTo(), we also expand the feature. This is the id of the feature. */
    this.openFeatures = new Set();
    this.starredFeatures = new Set();

    this._featuresUnveilMetric = new Metric('features_unveil');
    this._featuresFetchMetric = new Metric('features_loaded');
    this._featuresUnveilMetric.start();
    this._featuresFetchMetric.start();

    /* The running context `this` inside `renderItem` is `lit-virtualizer`
     * rather than `chromedash-featurelist`, so all function calls inside need
     * to be bound to `this`. */
    this._onFeatureToggledBound = this._onFeatureToggled.bind(this);
    this._onStarToggledBound = this._onStarToggled.bind(this);
    this._onOpenApprovalsBound = this._onOpenApprovals.bind(this);

    this._loadData();
  }

  async _loadData() {
    const featureUrl = '/features_v2.json';

    try {
      const features = await (await fetch(featureUrl)).json();
      this._featuresFetchMetric.end().log().sendToAnalytics('features', 'loaded');

      features.map((feature) => {
        feature.receivePush = false;
        if (feature.is_released) {
          feature.milestone = feature.browsers.chrome.desktop ||
              feature.browsers.chrome.android ||
              feature.browsers.chrome.webview ||
              feature.browsers.chrome.ios ||
              Infinity;
        }
      });
      this.features = features;

      this.searchEl.disabled = false;
      this.filter(this.searchEl.value);
      this._initialize();
    } catch (error) {
      document.getElementById('content').classList.add('error');
      console.error(error);
      throw new Error('Failed to fetch features');
    };
  }

  _fireEvent(eventName, detail) {
    const event = new CustomEvent(eventName, {detail});
    this.dispatchEvent(event);
  }

  _getSearchStrRegExp(searchStr) {
    return new RegExp(
      // Case-insensitive match on literal string; escape characters that
      // have special meaning in regular expressions.
      searchStr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), 'i');
  }

  _pathToPropertyVal(propPath, feature) {
    return propPath.split('.').reduce((obj, key) => {
      return obj[key];
    }, feature);
  }

  _lt(propPath, value, feature) {
    return this._pathToPropertyVal(propPath, feature) < value;
  }
  _lte(propPath, value, feature) {
    return this._pathToPropertyVal(propPath, feature) <= value;
  }
  _gt(propPath, value, feature) {
    return this._pathToPropertyVal(propPath, feature) > value;
  }
  _gte(propPath, value, feature) {
    return this._pathToPropertyVal(propPath, feature) >= value;
  }
  _eq(propPath, value, feature) {
    return this._pathToPropertyVal(propPath, feature) === value;
  }
  _false() {
    return false;
  }

  _getOperatorFilter(args /* [propPath, operator, valueStr] */) {
    const value = parseFloat(args[2]);
    if (isNaN(value)) return this._false;

    switch (args[1].trim()) {
      case '<':
        return this._lt.bind(this, args[0], value);
        break;
      case '<=':
        return this._lte.bind(this, args[0], value);
        break;
      case '>':
        return this._gt.bind(this, args[0], value);
        break;
      case '>=':
        return this._gte.bind(this, args[0], value);
        break;
      case '=': // Support both '=' and '=='.
      case '==':
        return this._eq.bind(this, args[0], value);
        break;
      default:
        return this._false;
    }
  }

  _setOpenFeatures(featureId, open) {
    const newOpen = new Set(this.openFeatures);
    if (open) {
      newOpen.add(featureId);
    } else {
      newOpen.delete(featureId);
    }
    this.openFeatures = newOpen;
  }

  _onFeatureToggled(e) {
    const feature = e.detail.feature;
    const open = e.detail.open;
    this._setOpenFeatures(feature.id, open);
  }

  _onStarToggled(e) {
    const feature = e.detail.feature;
    const starred = e.detail.starred;

    const newStarredFeatures = new Set(this.starredFeatures);
    if (starred) {
      newStarredFeatures.add(feature.id);
    } else {
      newStarredFeatures.delete(feature.id);
    }
    this.starredFeatures = newStarredFeatures;
  }

  _onOpenApprovals(e) {
    openApprovalsDialog(this.user, e.detail.feature);
  }

  _filterProperty(propPath, regExp, feature) {
    const value = this._pathToPropertyVal(propPath, feature);

    // Null or missing values never match.
    if (value === null || typeof value === 'undefined') {
      return false;
    }

    // Allow for enums that store string in "text" property.
    return (value.text || value).toString().match(regExp) !== null;
  }

  _getPropertyFilter(args /* [propPath, searchStr] */) {
    return this._filterProperty.bind(
      this, args[0], this._getSearchStrRegExp(args[1]));
  }

  _filterKeyword(regExp, feature) {
    return (feature.name + '\n' + feature.summary + '\n' + feature.comments +
    '\n' + feature.tags).match(regExp) !== null;
  }

  _getKeywordFilter(keyword) {
    return this._filterKeyword.bind(
      this, this._getSearchStrRegExp(keyword));
  }

  // Directly called from template/features.html
  filter(val, shouldPushState) {
    this.searchEl.value = val;
    const pushOrReplaceState = (
      history &&
      (shouldPushState ? history.pushState.bind(history) :
        history.replaceState.bind(history)));
    // Clear filter if there's no search or if called directly.
    if (!val) {
      if (pushOrReplaceState) {
        pushOrReplaceState({query: ''}, document.title,
          location.pathname + location.search);
      } else {
        location.hash = '';
      }
      this.filtered = this.features;
    } else {
      val = val.trim();
      if (pushOrReplaceState) {
        pushOrReplaceState({query: val}, document.title,
          '/features#' + encodeURIComponent(val));
      }

      const blinkComponent = val.match(/^component:\s?(.*)/);
      if (blinkComponent) {
        const componentName = blinkComponent[1].trim();
        this.filtered = this.features.filter(feature => (
          feature.browsers.chrome.blink_components.includes(componentName)
        ));
        this._fireEvent('filtered', {count: this.filtered.length});
        return;
      }

      const regExp = /"([^"]*)"/g;
      const start = 0;
      const parts = [];
      let match;
      while ((match = regExp.exec(val)) !== null) {
        if (start - (regExp.lastIndex - match[0].length) > 0) {
          parts.push(val.substring(start, regExp.lastIndex -
                                   match[0].length));
        }
        parts.push(match[1]);
      }
      const matchLen = match ? match[0].length : 0;
      if (start - (regExp.lastIndex - matchLen) > 0) {
        parts.push(val.substring(start, regExp.lastIndex -
                                 matchLen));
      }

      // Match words separated by whitespace and/or ":" and/or ordered
      // comparison operator.
      const wordRegExp = /("([^"]+)"|([^:<>= \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+))([:<>= \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+)?/g;
      // Array of matches, each an array of the form:
      // [ full match, quoted-string-or-word, contents-of-quoted-string,
      //   word, separator ].
      const reMatches = [];
      while ((match = wordRegExp.exec(val)) !== null) {
        reMatches.push(match);
      }

      // Query parts of the form: "name : value".
      const propertyQueries = [];
      // Query parts of the form: "name = value", "name < value", etc.
      const operatorQueries = [];
      // Other words in the query (not matching forms above).
      const keywordQueries = [];

      // Accumulate keyword and property queries.
      for (let i = 0; i < reMatches.length; i++) {
        match = reMatches[i];
        const part = match[2] || match[3];
        const sep = match[4];
        const nextPart = i < reMatches.length - 1 ?
          (reMatches[i + 1][2] || reMatches[i + 1][3]) : null;
        if (sep && sep.trim().match(/^:$/) !== null && nextPart !== null) {
          // Separator is ":", and there exists a right-hand-side.
          // Store property query: "propertyName : propertyValue".
          propertyQueries.push([part, nextPart]);
          i++;
        } else if (sep && sep.trim().match(/^(<=|>=|==|<|>|=)$/) !== null &&
            nextPart !== null) {
          // Separator is an ordered comparison operator and there exists a
          // right-hand-side.
          // Store operator query "name <<operator>> value".
          operatorQueries.push([part, sep, nextPart]);
          i++;
        } else {
          // No special operator found. Store non-separator part as keyword
          // query.
          keywordQueries.push(part);
        }
      }

      // Construct a list filter for each query part, and store them all in
      // a list.
      const filters = propertyQueries.map(this._getPropertyFilter.bind(this))
        .concat(operatorQueries.map(this._getOperatorFilter.bind(this)))
        .concat(keywordQueries.map(this._getKeywordFilter.bind(this)));

      // Apply this.filtered = this.features filtered-by filters.
      if (filters.length === 0) {
        this.filtered = this.features;
      } else {
        let results = this.features;
        for (let i = 0; i < filters.length; i++) {
          results = results.filter(filters[i]);
        }
        this.filtered = results;
      }
    }

    this._fireEvent('filtered', {count: this.filtered.length});
  }

  _firstOfMilestone(milestone) {
    for (let i = 0; i < this.filtered.length; i++) {
      const feature = this.filtered[i];
      if (feature.first_of_milestone &&
          (milestone === feature.browsers.chrome.desktop ||
           milestone === feature.browsers.chrome.status.text)) {
        return feature.id;
      }
    }
    return null;
  }

  // Directly called from template/features.html
  scrollToId(targetId) {
    if (!targetId) return;

    this._setOpenFeatures(targetId, true);

    const targetEl = this.shadowRoot.querySelector('#id-' + targetId);
    if (targetEl) {
      targetEl.scrollIntoView();
      const heightOfHeader = document.querySelector('.main-toolbar').getBoundingClientRect().height;
      window.scrollBy(0, heightOfHeader * -1);
    }
  }

  _initialize() {
    this._featuresUnveilMetric.end().log().sendToAnalytics('features', 'unveil');
    this._fireEvent('app-ready');
    this._hasInitialized = true;
  }

  _computeMilestoneHidden(feature, features, filtered) {
    return filtered.length != features.length || !feature.first_of_milestone;
  }

  _computeMilestoneString(str) {
    return isNaN(parseInt(str)) ? str : 'Chrome ' + str;
  }

  render() {
    // TODO: Avoid computing values in render().
    let filteredWithState = this.filtered.map((feature) => {
      const editable = this.isSiteEditor ||
        (this.editableFeatures && this.editableFeatures.includes(feature.id));
      return {
        feature: feature,
        open: this.openFeatures.has(feature.id),
        starred: this.starredFeatures.has(feature.id),
        canEditFeature: editable,
      };
    });
    let numOverLimit = 0;
    if (filteredWithState.length > MAX_FEATURES_SHOWN) {
      numOverLimit = filteredWithState.length - MAX_FEATURES_SHOWN;
      filteredWithState = filteredWithState.slice(0, MAX_FEATURES_SHOWN);
    }
    return html`
      ${filteredWithState.map((item) => html`

${this._computeMilestoneString(item.feature.browsers.chrome.status.milestone_str)}

            <chromedash-feature id="id-${item.feature.id}" tabindex="0"
                 ?open="${item.open}"
                 ?starred="${item.starred}"
                 @feature-toggled="${this._onFeatureToggledBound}"
                 @star-toggled="${this._onStarToggledBound}"
                 @open-approvals-event="${this._onOpenApprovalsBound}"
                 .feature="${item.feature}"
                 ?canEdit="${item.canEditFeature}"
                 ?canApprove="${this.canApprove}"
                 ?signedIn="${this.signedInUser != ''}"
          ></chromedash-feature>

        `)}

      ${numOverLimit > 0 ?
        html`<p>To see ${numOverLimit} earlier features, please enter a more specific query.</p>` :
        ''}
    `;
  }
}

customElements.define('chromedash-featurelist', ChromedashFeaturelist);
<|endoftext|>


<commit_before>var map = (function (config) {

    var location = [
        [ 37.387807, -122.082656], // Mountain View
        [-36.866596,  174.777106], // Auckland
        [ 39.909901,  116.434050], // Beijing
        [ 51.510718,   -0.127072], // London
        [ 48.871356,    2.343891], // Paris
        [ 37.789031, -122.389326], // San Francisco
        [ 35.685021,  139.738455], // Tokyo
        [ 43.647523,  -79.394025], // Toronto
        [ 49.282076, -123.107774]  // Vancouver
    ].sort(function() {return 0.5 - Math.random()})[0];

    var months = ['January', 'February', 'March', 'April', 'May', 'June',
            'July', 'August', 'September', 'October', 'November', 'December'],
        days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];

    var default_config = {
        container: 'map-container',
        search: 'input-location',
        latitude: location[0],
        longitude: location[1],
        cluster: false,
        fit: null,
        filter: null,
        full: false,
        zoom: 13,
        controls: false,
        draggable: false,
        target: '/events/near/?lat=${lat}&lng=${lng}',
        countryTarget: '/events/in/${code}/'
    };

    var c = function(property) {
        return config.hasOwnProperty(property) ? config[property] : default_config[property];
    };

    var container = document.getElementById(c('container')),
        search = document.getElementById(c('search')),
        searchWrapper,
        gmap_script,
        gmap_callback,
        gmap,
        geocoder,
        clusterer,
        searchLocation,
        eventStack = [],
        standardMarkerImage,
        officialMarkerImage,
        shadowMarkerImage;

    if (!container) return false;

    if (navigator.geolocation) {
        (function() {
            var button = $(document.createElement('button'));
            button.addClass('button near-me');
            button.html('Near Me ');
            $('.buttons', search.form).append(button);
            button.click(function() {
                button.addClass('loading');
                navigator.geolocation.getCurrentPosition(function(position) {
                    var location = { lat: position.coords.latitude, lng: position.coords.longitude };

                    window.location = c('target').replace(/\${(\w+)}/g, function(match, key) {
                        return location.hasOwnProperty(key) ? location[key] : '';
                    });
                }, function(error) {
                    button.removeClass('loading');
                });
            });
        })();
    }

    if (c('full')) {
        document.getElementById('content').className += ' full';
    }

    gmap_callback = 'gmcb_'+(""+Math.random()).substr(2);
    window[gmap_callback] = function () {
        delete window[gmap_callback];

        InfoBox.init();

        geocoder = new google.maps.Geocoder();

        gmap = new google.maps.Map(container, {
            zoom: c('zoom'),
            center: new google.maps.LatLng(c('latitude'), c('longitude')),
            mapTypeId: google.maps.MapTypeId.ROADMAP,

            scrollwheel: false,
            keyboardShortcuts: false,
            disableDoubleClickZoom: true,
            disableDefaultUI: true,
            draggable: c('draggable'),

            zoomControl: c('controls'),
            zoomControlOptions: {
                style: google.maps.ZoomControlStyle.SMALL,
                position: google.maps.ControlPosition.LEFT_BOTTOM
            }
        });

        if (c('cluster')) {
            clusterer = new MarkerClusterer(gmap, [], {
                styles: [{
                    url: '/media/img/map-cluster.png',
                    height: 50,
                    width: 50,
                    anchor: [19,0],
                    textColor: '#000',
                    textSize: 13
                }]
            });
        }

        if (c('fit')) {
            geocode(c('fit'), function(results) {
                if (results && results.length) {
                    var viewport = results[0].geometry.viewport,
                        center = viewport.getCenter();

                    gmap.fitBounds(viewport);

                    gmap.setZoom(gmap.getZoom() + 1);

                    var overlay = new google.maps.OverlayView();
                    overlay.draw = function() {};
                    overlay.setMap(gmap);

                    var projection = overlay.getProjection();

                    if (projection && projection.fromLatLngToContainerPixel) {
                        var current = projection.fromLatLngToContainerPixel(center),
                            target = new google.maps.Point(
                                current.x,
                                current.y - $(search.form.parentNode).offset().top
                            );

                        gmap.setCenter(projection.fromContainerPixelToLatLng(target));
                    }
                }
            });
        }

        standardMarkerImage = new google.maps.MarkerImage(
            '/media/img/map-marker.png',
            new google.maps.Size(50,75),
            new google.maps.Point(0,0),
            new google.maps.Point(25,75)
        );

        officialMarkerImage = new google.maps.MarkerImage(
            '/media/img/map-marker.png',
            new google.maps.Size(50,75),
            new google.maps.Point(50,0),
            new google.maps.Point(25,75)
        );

        shadowMarkerImage = new google.maps.MarkerImage(
            '/media/img/map-marker.png',
            new google.maps.Size(75,25),
            new google.maps.Point(100,50),
            new google.maps.Point(30,25)
        );

        google.maps.event.addListener(gmap, 'zoom_changed', function () {
            for (var key in venue_cache) {
                if (venue_cache.hasOwnProperty(key)) {
                    if (venue_cache[key].visible) {
                        google.maps.event.trigger(venue_cache[key].marker, 'click');
                    }
                }
            }
        });

        if (c('mapFooter')) {
            var footer = document.createElement('div');
            footer.className = 'footer';
            footer.innerHTML = '
' + c('mapFooter') + '
';

            container.className += ' has-footer';
            container.appendChild(footer);
        }

        if (eventStack.length) {
            add_event.apply(null, eventStack);
            eventStack = [];
        }
    }

    gmap_script = document.createElement("script");
    gmap_script.src = "//maps.google.com/maps/api/js?sensor=false&callback=" + gmap_callback;
    document.body.appendChild(gmap_script);

    if (search) {
        search.form.onsubmit = function () {
            if (searchLocation) {
                window.location = c(searchLocation.targetRef).replace(/\${(\w+)}/g, function(match, key) {
                    return searchLocation.hasOwnProperty(key) ? searchLocation[key] : '';
                });
            }
            return false;
        }

        searchWrapper = document.createElement('span');
        search.parentNode.insertBefore(searchWrapper, search);
        searchWrapper.appendChild(search);

        $(search).keypress(function(e) {
            if ( e.which == 10 ) {
                e.preventDefault();
            }
        });

        $(search).autocomplete({
            appendTo: searchWrapper,
            autoFocus: false,
            minLength: 3,
            source: function (request, response) {
                geocode(request.term, function(results) {
                    var data = [];
                    if (results && results.length) {
                        for (var i = 0, l = Math.min(results.length, 6); i < l; ++i) {
                            var components = results[i].address_components,
                                geometry = results[i].geometry;
                            // console.log(results[i]);

                            var item = {
                                label: results[i].formatted_address,
                                value: results[i].formatted_address,
                            };

                            if (components.length == 2
                                    && components[0].long_name == components[0].short_name
                                    && components[0].types[0] == 'administrative_area_level_1') {
                                components.shift();
                            }

                            if (components.length == 1 && components[0].types[0] == 'country') {
                                item.location = {
                                    targetRef: 'countryTarget',
                                    code: components[0].short_name.toLowerCase()
                                };
                            } else {
                                item.location = {
                                    targetRef: 'target',
                                    lat: geometry.location.lat(),
                                    lng: geometry.location.lng()
                                };
                            }

                            data.push(item);
                        }
                    }
                    response(data);
                });
            },
            open: function () {
                $(this).addClass('with-results');
            },
            close: function () {
                $(this).removeClass('with-results');
            },
            select: function (event, ui) {
                searchLocation = ui.item ? ui.item.location : null;
                if (event.keyCode && event.keyCode == 13 && searchLocation) {
                    $(search.form).submit();
                }
            },
            change: function (event, ui) {
                searchLocation = ui.item ? ui.item.location : null;
            }
        });
    }

    var address_cache = {},
        venue_cache = {};

    function center_map (lat, lng) {
        if (!gmap) return false;
        gmap.setCenter(new google.maps.LatLng(lat,lng));
        return true;
    };

    function geocode (address, callback) {
        if (address_cache.hasOwnProperty(address)) {
            callback(address_cache[address], address);
        } else if (geocoder) {
            request = {
                address: address,
                region: c('filter')
            };
            geocoder.geocode(request, function(results, status) {
                if (status === google.maps.GeocoderStatus.OK) {
                    address_cache[address] = results;
                } else {
                    address_cache[address] = null;
                }
                callback(address_cache[address]);
            });
        } else {
            callback(null, address);
        }
    };

    function add_event (event) {
        if (!gmap) {
            eventStack.push.apply(eventStack, arguments);
        } else {
            for (var i = 0, l = arguments.length; i < l; ++i) {
                (function(event) {
                    var address = event.address,
                        lat = event.latitude,
                        lng = event.longitude,
                        venue_hash = (Math.round(lat * 10000) / 10000) + ':' + (Math.round(lng * 10000) / 10000),
                        venue = venue_cache[venue_hash];

                    if (!venue) {
                        var content = document.createElement("div");

                        var marker = new google.maps.Marker({
                            position: new google.maps.LatLng(lat, lng),
                            map: gmap,
                            icon: standardMarkerImage,
                            shadow: shadowMarkerImage
                        });

                        if (clusterer) {
                            clusterer.addMarker(marker);
                        }

                        var panel = new InfoBox({
                            content: content,
                            alignBottom: true,
                            pixelOffset: new google.maps.Size(-50, -100),
                            infoBoxClearance: new google.maps.Size(40, 0),
                            closeBoxURL: '/media/img/close.png'
                        });

                        google.maps.event.addListener(panel, 'closeclick', function () {
                            venue.visible = false;
                        });

                        google.maps.event.addListener(panel, 'domready', function () {
                            var box = this.getContent().parentNode,
                                projection = this.getProjection(),
                                location = projection.fromLatLngToContainerPixel(marker.position),
                                targetX = location.x + box.offsetWidth / 2 + (this.pixelOffset_.width||0),
                                targetY = location.y - box.offsetHeight + (this.pixelOffset_.height||0) + (container.offsetHeight / 2)
                                    - $(search.form.parentNode).offset().top - search.form.parentNode.offsetHeight - 40
                                    + container.parentNode.offsetTop,
                                target = projection.fromContainerPixelToLatLng(new google.maps.Point(targetX, targetY));

                            gmap.panTo(target);
                        });

                        venue = {
                            name: null,
                            address: address,
                            events: [],
                            latitude: lat,
                            longitude: lng,
                            marker: marker,
                            panel: panel,
                            visible: false
                        };

                        geocode(address, function(results) {
                            if (results && results.length) {
                                venue.address = results[0].formatted_address;
                                if (venue.visible) {
                                    google.maps.event.trigger(marker, 'click');
                                }
                            }
                        });

                        google.maps.event.addListener(marker, 'click', function() {
                            for (var key in venue_cache) {
                                if (venue_cache.hasOwnProperty(key)) {
                                    venue_cache[key].visible = false;
                                    venue_cache[key].panel.close();
                                }
                            }

                            venue.visible = true;

                            var html = [];

                            if (venue.name) {
                                html.push('
'+venue.name+'
');
                                html.push('
'+venue.address+'
');
                            } else {
                                // Shouldn't happen, as venues *should* have names, but just in case...
                                html.push('
'+venue.address+'
');
                            }

                            html.push('
');

                            for (var i = 0, l = venue.events.length; i < l; ++i) {
                                var event = venue.events[i],
                                    from_ts = '',
                                    from_date = months[event.from.getMonth()] + ' ' + event.from.getDate() + ', ' + event.from.getFullYear(),
                                    from_h = event.from.getHours(),
                                    from_m = event.from.getMinutes(),
                                    from_time = (from_h % 12 === 0 ? 12 : from_h % 12) + ':' + (from_m < 10 ? '0' : '') + from_m + ' ' + (from_h < 12 ? 'AM' : 'PM'),
                                    from = from_date + ' ' + from_time,
                                    to_ts = '',
                                    to_date = months[event.to.getMonth()] + ' ' + event.to.getDate() + ', ' + event.to.getFullYear(),
                                    to_h = event.to.getHours(),
                                    to_m = event.to.getMinutes(),
                                    to_time = (to_h % 12 === 0 ? 12 : to_h % 12) + ':' + (to_m < 10 ? '0' : '') + to_m + ' ' + (to_h < 12 ? 'AM' : 'PM'),
                                    to = from_date === to_date ? to_time : to_date + ' ' + to_time;

                                // console.log(event.from, event.to);

                                html.push('
');
                                html.push('' + event.name + '');
                                html.push('<time class="from" datetime="' + from_ts + '">' + from + '</time> to <time class="to" datetime="' + to_ts + '">' + to + '</time>');
                                html.push('' + event.type.name + '');
                                html.push('
');
                            }

                            html.push('
');

                            content.innerHTML = html.join('\n');
                            panel.open(gmap,marker);
                        });

                        venue_cache[venue_hash] = venue;
                    }

                    venue.name = venue.name || event.venue;
                    venue.events.push(event);
                    if (event.official) venue.marker.setIcon(officialMarkerImage);
                    venue.marker.setTitle(venue.name + (venue.events.length > 1 ? ' (' + venue.events.length + ')' : '' ));
                })(arguments[i]);
            }
        }
    }

    return {
        center: center_map,
        geocode: geocode,
        add_event: add_event
    };

})(window.map_config || {});
<commit_msg>[Lighthouse-36] If you don't physically select something from the search dropdown we auto-select the top one when you submit the search form - opposed to currently not doing anything<commit_after>var map = (function (config) {

    var location = [
        [ 37.387807, -122.082656], // Mountain View
        [-36.866596,  174.777106], // Auckland
        [ 39.909901,  116.434050], // Beijing
        [ 51.510718,   -0.127072], // London
        [ 48.871356,    2.343891], // Paris
        [ 37.789031, -122.389326], // San Francisco
        [ 35.685021,  139.738455], // Tokyo
        [ 43.647523,  -79.394025], // Toronto
        [ 49.282076, -123.107774]  // Vancouver
    ].sort(function() {return 0.5 - Math.random()})[0];

    var months = ['January', 'February', 'March', 'April', 'May', 'June',
            'July', 'August', 'September', 'October', 'November', 'December'],
        days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];

    var default_config = {
        container: 'map-container',
        search: 'input-location',
        latitude: location[0],
        longitude: location[1],
        cluster: false,
        fit: null,
        filter: null,
        full: false,
        zoom: 13,
        controls: false,
        draggable: false,
        target: '/events/near/?lat=${lat}&lng=${lng}',
        countryTarget: '/events/in/${code}/'
    };

    var c = function(property) {
        return config.hasOwnProperty(property) ? config[property] : default_config[property];
    };

    var container = document.getElementById(c('container')),
        search = document.getElementById(c('search')),
        searchWrapper,
        gmap_script,
        gmap_callback,
        gmap,
        geocoder,
        clusterer,
        searchLocation,
        eventStack = [],
        standardMarkerImage,
        officialMarkerImage,
        shadowMarkerImage;

    if (!container) return false;

    if (navigator.geolocation) {
        (function() {
            var button = $(document.createElement('button'));
            button.addClass('button near-me');
            button.html('Near Me ');
            $('.buttons', search.form).append(button);
            button.click(function() {
                button.addClass('loading');
                navigator.geolocation.getCurrentPosition(function(position) {
                    var location = { lat: position.coords.latitude, lng: position.coords.longitude };

                    window.location = c('target').replace(/\${(\w+)}/g, function(match, key) {
                        return location.hasOwnProperty(key) ? location[key] : '';
                    });
                }, function(error) {
                    button.removeClass('loading');
                });
            });
        })();
    }

    if (c('full')) {
        document.getElementById('content').className += ' full';
    }

    gmap_callback = 'gmcb_'+(""+Math.random()).substr(2);
    window[gmap_callback] = function () {
        delete window[gmap_callback];

        InfoBox.init();

        geocoder = new google.maps.Geocoder();

        gmap = new google.maps.Map(container, {
            zoom: c('zoom'),
            center: new google.maps.LatLng(c('latitude'), c('longitude')),
            mapTypeId: google.maps.MapTypeId.ROADMAP,

            scrollwheel: false,
            keyboardShortcuts: false,
            disableDoubleClickZoom: true,
            disableDefaultUI: true,
            draggable: c('draggable'),

            zoomControl: c('controls'),
            zoomControlOptions: {
                style: google.maps.ZoomControlStyle.SMALL,
                position: google.maps.ControlPosition.LEFT_BOTTOM
            }
        });

        if (c('cluster')) {
            clusterer = new MarkerClusterer(gmap, [], {
                styles: [{
                    url: '/media/img/map-cluster.png',
                    height: 50,
                    width: 50,
                    anchor: [19,0],
                    textColor: '#000',
                    textSize: 13
                }]
            });
        }

        if (c('fit')) {
            geocode(c('fit'), function(results) {
                if (results && results.length) {
                    var viewport = results[0].geometry.viewport,
                        center = viewport.getCenter();

                    gmap.fitBounds(viewport);

                    gmap.setZoom(gmap.getZoom() + 1);

                    var overlay = new google.maps.OverlayView();
                    overlay.draw = function() {};
                    overlay.setMap(gmap);

                    var projection = overlay.getProjection();

                    if (projection && projection.fromLatLngToContainerPixel) {
                        var current = projection.fromLatLngToContainerPixel(center),
                            target = new google.maps.Point(
                                current.x,
                                current.y - $(search.form.parentNode).offset().top
                            );

                        gmap.setCenter(projection.fromContainerPixelToLatLng(target));
                    }
                }
            });
        }

        standardMarkerImage = new google.maps.MarkerImage(
            '/media/img/map-marker.png',
            new google.maps.Size(50,75),
            new google.maps.Point(0,0),
            new google.maps.Point(25,75)
        );

        officialMarkerImage = new google.maps.MarkerImage(
            '/media/img/map-marker.png',
            new google.maps.Size(50,75),
            new google.maps.Point(50,0),
            new google.maps.Point(25,75)
        );

        shadowMarkerImage = new google.maps.MarkerImage(
            '/media/img/map-marker.png',
            new google.maps.Size(75,25),
            new google.maps.Point(100,50),
            new google.maps.Point(30,25)
        );

        google.maps.event.addListener(gmap, 'zoom_changed', function () {
            for (var key in venue_cache) {
                if (venue_cache.hasOwnProperty(key)) {
                    if (venue_cache[key].visible) {
                        google.maps.event.trigger(venue_cache[key].marker, 'click');
                    }
                }
            }
        });

        if (c('mapFooter')) {
            var footer = document.createElement('div');
            footer.className = 'footer';
            footer.innerHTML = '
' + c('mapFooter') + '
';

            container.className += ' has-footer';
            container.appendChild(footer);
        }

        if (eventStack.length) {
            add_event.apply(null, eventStack);
            eventStack = [];
        }
    }

    gmap_script = document.createElement("script");
    gmap_script.src = "//maps.google.com/maps/api/js?sensor=false&callback=" + gmap_callback;
    document.body.appendChild(gmap_script);

    if (search) {
        search.form.onsubmit = function () {
            if (searchLocation) {
                window.location = c(searchLocation.targetRef).replace(/\${(\w+)}/g, function(match, key) {
                    return searchLocation.hasOwnProperty(key) ? searchLocation[key] : '';
                });
            }
            return false;
        }

        searchWrapper = document.createElement('span');
        search.parentNode.insertBefore(searchWrapper, search);
        searchWrapper.appendChild(search);

        $(search).keypress(function(e) {
            if ( e.which == 10 ) {
                e.preventDefault();
            }
        });

        $(search).autocomplete({
            appendTo: searchWrapper,
            autoFocus: false,
            minLength: 3,
            source: function (request, response) {
                geocode(request.term, function(results) {
                    var data = [];
                    if (results && results.length) {
                        for (var i = 0, l = Math.min(results.length, 6); i < l; ++i) {
                            var components = results[i].address_components,
                                geometry = results[i].geometry;
                            // console.log(results[i]);

                            var item = {
                                label: results[i].formatted_address,
                                value: results[i].formatted_address,
                            };

                            if (components.length == 2
                                    && components[0].long_name == components[0].short_name
                                    && components[0].types[0] == 'administrative_area_level_1') {
                                components.shift();
                            }

                            if (components.length == 1 && components[0].types[0] == 'country') {
                                item.location = {
                                    targetRef: 'countryTarget',
                                    code: components[0].short_name.toLowerCase()
                                };
                            } else {
                                item.location = {
                                    targetRef: 'target',
                                    lat: geometry.location.lat(),
                                    lng: geometry.location.lng()
                                };
                            }

                            data.push(item);
                        }
                    }
                    $(search).data('results', data);
                    response(data);
                });
            },
            open: function () {
                var results = $(search).data('results');
                searchLocation = (results &&results.length) ? results[0].location : null;
                $(this).addClass('with-results');
            },
            close: function () {
                $(this).removeClass('with-results');
            },
            select: function (event, ui) {
                searchLocation = ui.item ? ui.item.location : null;
                if (event.keyCode && event.keyCode == 13 && searchLocation) {
                    $(search.form).submit();
                }
            },
            change: function (event, ui) {
                searchLocation = ui.item ? ui.item.location : null;
            }
        });
    }

    var address_cache = {},
        venue_cache = {};

    function center_map (lat, lng) {
        if (!gmap) return false;
        gmap.setCenter(new google.maps.LatLng(lat,lng));
        return true;
    };

    function geocode (address, callback) {
        if (address_cache.hasOwnProperty(address)) {
            callback(address_cache[address], address);
        } else if (geocoder) {
            request = {
                address: address,
                region: c('filter')
            };
            geocoder.geocode(request, function(results, status) {
                if (status === google.maps.GeocoderStatus.OK) {
                    address_cache[address] = results;
                } else {
                    address_cache[address] = null;
                }
                callback(address_cache[address]);
            });
        } else {
            callback(null, address);
        }
    };

    function add_event (event) {
        if (!gmap) {
            eventStack.push.apply(eventStack, arguments);
        } else {
            for (var i = 0, l = arguments.length; i < l; ++i) {
                (function(event) {
                    var address = event.address,
                        lat = event.latitude,
                        lng = event.longitude,
                        venue_hash = (Math.round(lat * 10000) / 10000) + ':' + (Math.round(lng * 10000) / 10000),
                        venue = venue_cache[venue_hash];

                    if (!venue) {
                        var content = document.createElement("div");

                        var marker = new google.maps.Marker({
                            position: new google.maps.LatLng(lat, lng),
                            map: gmap,
                            icon: standardMarkerImage,
                            shadow: shadowMarkerImage
                        });

                        if (clusterer) {
                            clusterer.addMarker(marker);
                        }

                        var panel = new InfoBox({
                            content: content,
                            alignBottom: true,
                            pixelOffset: new google.maps.Size(-50, -100),
                            infoBoxClearance: new google.maps.Size(40, 0),
                            closeBoxURL: '/media/img/close.png'
                        });

                        google.maps.event.addListener(panel, 'closeclick', function () {
                            venue.visible = false;
                        });

                        google.maps.event.addListener(panel, 'domready', function () {
                            var box = this.getContent().parentNode,
                                projection = this.getProjection(),
                                location = projection.fromLatLngToContainerPixel(marker.position),
                                targetX = location.x + box.offsetWidth / 2 + (this.pixelOffset_.width||0),
                                targetY = location.y - box.offsetHeight + (this.pixelOffset_.height||0) + (container.offsetHeight / 2)
                                    - $(search.form.parentNode).offset().top - search.form.parentNode.offsetHeight - 40
                                    + container.parentNode.offsetTop,
                                target = projection.fromContainerPixelToLatLng(new google.maps.Point(targetX, targetY));

                            gmap.panTo(target);
                        });

                        venue = {
                            name: null,
                            address: address,
                            events: [],
                            latitude: lat,
                            longitude: lng,
                            marker: marker,
                            panel: panel,
                            visible: false
                        };

                        geocode(address, function(results) {
                            if (results && results.length) {
                                venue.address = results[0].formatted_address;
                                if (venue.visible) {
                                    google.maps.event.trigger(marker, 'click');
                                }
                            }
                        });

                        google.maps.event.addListener(marker, 'click', function() {
                            for (var key in venue_cache) {
                                if (venue_cache.hasOwnProperty(key)) {
                                    venue_cache[key].visible = false;
                                    venue_cache[key].panel.close();
                                }
                            }

                            venue.visible = true;

                            var html = [];

                            if (venue.name) {
                                html.push('
'+venue.name+'
');
                                html.push('
'+venue.address+'
');
                            } else {
                                // Shouldn't happen, as venues *should* have names, but just in case...
                                html.push('
'+venue.address+'
');
                            }

                            html.push('
');

                            for (var i = 0, l = venue.events.length; i < l; ++i) {
                                var event = venue.events[i],
                                    from_ts = '',
                                    from_date = months[event.from.getMonth()] + ' ' + event.from.getDate() + ', ' + event.from.getFullYear(),
                                    from_h = event.from.getHours(),
                                    from_m = event.from.getMinutes(),
                                    from_time = (from_h % 12 === 0 ? 12 : from_h % 12) + ':' + (from_m < 10 ? '0' : '') + from_m + ' ' + (from_h < 12 ? 'AM' : 'PM'),
                                    from = from_date + ' ' + from_time,
                                    to_ts = '',
                                    to_date = months[event.to.getMonth()] + ' ' + event.to.getDate() + ', ' + event.to.getFullYear(),
                                    to_h = event.to.getHours(),
                                    to_m = event.to.getMinutes(),
                                    to_time = (to_h % 12 === 0 ? 12 : to_h % 12) + ':' + (to_m < 10 ? '0' : '') + to_m + ' ' + (to_h < 12 ? 'AM' : 'PM'),
                                    to = from_date === to_date ? to_time : to_date + ' ' + to_time;

                                // console.log(event.from, event.to);

                                html.push('
');
                                html.push('' + event.name + '');
                                html.push('<time class="from" datetime="' + from_ts + '">' + from + '</time> to <time class="to" datetime="' + to_ts + '">' + to + '</time>');
                                html.push('' + event.type.name + '');
                                html.push('
');
                            }

                            html.push('
');

                            content.innerHTML = html.join('\n');
                            panel.open(gmap,marker);
                        });

                        venue_cache[venue_hash] = venue;
                    }

                    venue.name = venue.name || event.venue;
                    venue.events.push(event);
                    if (event.official) venue.marker.setIcon(officialMarkerImage);
                    venue.marker.setTitle(venue.name + (venue.events.length > 1 ? ' (' + venue.events.length + ')' : '' ));
                })(arguments[i]);
            }
        }
    }

    return {
        center: center_map,
        geocode: geocode,
        add_event: add_event
    };

})(window.map_config || {});
<|endoftext|>