pushState based navigation between map-based layouts
authorJohn Firebaugh <john.firebaugh@gmail.com>
Wed, 2 Oct 2013 23:41:44 +0000 (16:41 -0700)
committerJohn Firebaugh <john.firebaugh@gmail.com>
Sun, 13 Oct 2013 21:46:08 +0000 (14:46 -0700)
13 files changed:
app/assets/javascripts/application.js
app/assets/javascripts/changeset.js [deleted file]
app/assets/javascripts/index.js
app/assets/javascripts/index/changeset.js [new file with mode: 0644]
app/assets/javascripts/index/export.js
app/assets/javascripts/router.js [new file with mode: 0644]
app/assets/javascripts/sidebar.js [deleted file]
app/controllers/application_controller.rb
app/controllers/browse_controller.rb
app/controllers/changeset_controller.rb
app/controllers/site_controller.rb
app/views/changeset/list.html.erb
config/environments/production.rb

index 493c03d9161721a1f8eff3184a68e8593e7fd106..c2397664134deb7e8bc432fbcad32dbf7b61a93a 100644 (file)
@@ -17,7 +17,6 @@
 //= require oauth
 //= require piwik
 //= require map
-//= require sidebar
 //= require richtext
 //= require geocoder
 //= require querystring
diff --git a/app/assets/javascripts/changeset.js b/app/assets/javascripts/changeset.js
deleted file mode 100644 (file)
index 0aecced..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-function initializeChangesets(map) {
-  var changesets = [], rects = {};
-
-  var group = L.featureGroup().addTo(map);
-
-  $("[data-changeset]").each(function () {
-    var changeset = $(this).data('changeset');
-    if (changeset.bbox) {
-      changeset.bounds = L.latLngBounds([changeset.bbox.minlat, changeset.bbox.minlon],
-                                        [changeset.bbox.maxlat, changeset.bbox.maxlon]);
-      changesets.push(changeset);
-    }
-  });
-
-  changesets.sort(function (a, b) {
-    return b.bounds.getSize() - a.bounds.getSize();
-  });
-
-  for (var i = 0; i < changesets.length; ++i) {
-    var changeset = changesets[i],
-        rect = L.rectangle(changeset.bounds,
-                           {weight: 2, color: "#ee9900", fillColor: "#ffff55", fillOpacity: 0});
-    rect.id = changeset.id;
-    rects[changeset.id] = rect;
-    rect.addTo(group);
-  }
-
-  function highlightChangeset(id) {
-    rects[id].setStyle({fillOpacity: 0.5});
-    $("#changeset_" + id).addClass("selected");
-  }
-
-  function unHighlightChangeset(id) {
-    rects[id].setStyle({fillOpacity: 0});
-    $("#changeset_" + id).removeClass("selected");
-  }
-
-  group.on({
-    mouseover: function (e) {
-      highlightChangeset(e.layer.id);
-    },
-    mouseout: function (e) {
-      unHighlightChangeset(e.layer.id);
-    }
-  });
-
-  $("[data-changeset]").on({
-    mouseover: function () {
-      highlightChangeset($(this).data("changeset").id);
-    },
-    mouseout: function () {
-      unHighlightChangeset($(this).data("changeset").id);
-    }
-  });
-}
index 525be7e52db1982ef5ed92599641be631c3b5782..62d47a3afc6de7093a51227f2edb6cd810750c1e 100644 (file)
@@ -9,6 +9,8 @@
 //= require index/browse
 //= require index/export
 //= require index/notes
+//= require index/changeset
+//= require router
 
 $(document).ready(function () {
   var params = OSM.mapParams();
@@ -141,12 +143,12 @@ $(document).ready(function () {
       map.getLayersCode(),
       map._object);
 
-      var expiry = new Date();
-      expiry.setYear(expiry.getFullYear() + 10);
-      $.cookie("_osm_location", cookieContent(map), { expires: expiry });
+    var expiry = new Date();
+    expiry.setYear(expiry.getFullYear() + 10);
+    $.cookie("_osm_location", cookieContent(map), { expires: expiry });
 
-      // Trigger hash update on layer changes.
-      map.hash.onMapMove();
+    // Trigger hash update on layer changes.
+    map.hash.onMapMove();
   });
 
   if (OSM.PIWIK) {
@@ -175,10 +177,6 @@ $(document).ready(function () {
     marker.setLatLng([params.mlat, params.mlon]).addTo(map);
   }
 
-  if (params.object) {
-    map.addObject(params.object, { zoom: params.object_zoom });
-  }
-
   $("#homeanchor").on("click", function(e) {
     e.preventDefault();
 
@@ -213,9 +211,50 @@ $(document).ready(function () {
   }
 
   initializeSearch(map);
-  initializeExport(map);
   initializeBrowse(map, params);
   initializeNotes(map, params);
 
-  if ('undefined' !== typeof initializeChangesets) initializeChangesets(map);
+  OSM.Index = function(map) {
+    var page = {};
+
+    page.pushstate = page.popstate = function(path) {
+      $("#view_tab").addClass("current");
+      $('#sidebar_content').load(path);
+    };
+
+    page.unload = function() {
+      $("#view_tab").removeClass("current");
+    };
+
+    return page;
+  };
+
+  OSM.Browse = function(map) {
+    var page = {};
+
+    page.pushstate = page.popstate = function(path) {
+      $('#sidebar_content').load(path, page.load);
+    };
+
+    page.load = function() {
+      map.addObject(OSM.mapParams().object, {zoom: true});
+    };
+
+    page.unload = function() {
+      map.removeObject();
+    };
+
+    return page;
+  };
+
+  var router = OSM.Router({
+    "/":                           OSM.Index(map),
+    "/export":                     OSM.Export(map),
+    "/browse/changesets":          OSM.ChangesetList(map),
+    "/browse/:type/:id(/history)": OSM.Browse(map)
+  });
+
+  $(document).on("click", "a", function(e) {
+    if (router(this.pathname + this.search + this.hash)) e.preventDefault();
+  });
 });
diff --git a/app/assets/javascripts/index/changeset.js b/app/assets/javascripts/index/changeset.js
new file mode 100644 (file)
index 0000000..d87bf63
--- /dev/null
@@ -0,0 +1,75 @@
+OSM.ChangesetList = function(map) {
+  var page = {};
+
+  var group = L.featureGroup()
+    .on({
+      mouseover: function (e) {
+        highlightChangeset(e.layer.id);
+      },
+      mouseout: function (e) {
+        unHighlightChangeset(e.layer.id);
+      }
+    });
+
+  group.getLayerId = function(layer) {
+    return layer.id;
+  };
+
+  function highlightChangeset(id) {
+    group.getLayer(id).setStyle({fillOpacity: 0.5});
+    $("#changeset_" + id).addClass("selected");
+  }
+
+  function unHighlightChangeset(id) {
+    group.getLayer(id).setStyle({fillOpacity: 0});
+    $("#changeset_" + id).removeClass("selected");
+  }
+
+  page.pushstate = page.popstate = function(path) {
+    $("#history_tab").addClass("current");
+    $('#sidebar_content').load(path, page.load);
+  };
+
+  page.load = function() {
+    map.addLayer(group);
+
+    var changesets = [];
+    $("[data-changeset]").each(function () {
+      var changeset = $(this).data('changeset');
+      if (changeset.bbox) {
+        changeset.bounds = L.latLngBounds([changeset.bbox.minlat, changeset.bbox.minlon],
+          [changeset.bbox.maxlat, changeset.bbox.maxlon]);
+        changesets.push(changeset);
+      }
+    });
+
+    changesets.sort(function (a, b) {
+      return b.bounds.getSize() - a.bounds.getSize();
+    });
+
+    for (var i = 0; i < changesets.length; ++i) {
+      var changeset = changesets[i],
+        rect = L.rectangle(changeset.bounds,
+          {weight: 2, color: "#ee9900", fillColor: "#ffff55", fillOpacity: 0});
+      rect.id = changeset.id;
+      rect.addTo(group);
+    }
+
+    $("[data-changeset]").on({
+      mouseover: function () {
+        highlightChangeset($(this).data("changeset").id);
+      },
+      mouseout: function () {
+        unHighlightChangeset($(this).data("changeset").id);
+      }
+    });
+  };
+
+  page.unload = function() {
+    map.removeLayer(group);
+    group.clearLayers();
+    $("#history_tab").removeClass("current");
+  };
+
+  return page;
+};
index 01d21cea255d52830d7e1ad091710aa67369a8e9..70684eca9dac0dd70b26f8ebb57c636aa023f9a0 100644 (file)
@@ -1,71 +1,76 @@
-function initializeExport(map) {
-  if (window.location.pathname == "/export") {
-    startExport();
+OSM.Export = function(map) {
+  var page = {};
+
+  var locationFilter = new L.LocationFilter({
+    enableButton: false,
+    adjustButton: false
+  }).on("change", update);
+
+  function getBounds() {
+    return L.latLngBounds(
+      L.latLng($("#minlat").val(), $("#minlon").val()),
+      L.latLng($("#maxlat").val(), $("#maxlon").val()));
   }
 
-  function startExport() {
-    var locationFilter = new L.LocationFilter({
-      enableButton: false,
-      adjustButton: false
-    }).addTo(map);
+  function boundsChanged() {
+    var bounds = getBounds();
 
-    update();
-
-    locationFilter.on("change", update);
-
-    map.on("moveend", update);
+    map.fitBounds(bounds);
+    locationFilter.setBounds(bounds);
 
-    $("#maxlat,#minlon,#maxlon,#minlat").change(boundsChanged);
+    enableFilter();
+    validateControls();
+  }
 
-    $("#drag_box").click(enableFilter);
+  function enableFilter() {
+    if (!locationFilter.getBounds().isValid()) {
+      locationFilter.setBounds(map.getBounds().pad(-0.2));
+    }
 
-    setBounds(map.getBounds());
+    $("#drag_box").hide();
+    locationFilter.enable();
+  }
 
-    $("#sidebar").one("closed", function () {
-      map.removeLayer(locationFilter);
-      map.off("moveend", update);
-      locationFilter.off("change", update);
-    });
+  function update() {
+    setBounds(locationFilter.isEnabled() ? locationFilter.getBounds() : map.getBounds());
+    validateControls();
+  }
 
-    function getBounds() {
-      return L.latLngBounds(L.latLng($("#minlat").val(), $("#minlon").val()),
-                            L.latLng($("#maxlat").val(), $("#maxlon").val()));
-    }
+  function setBounds(bounds) {
+    var precision = zoomPrecision(map.getZoom());
+    $("#minlon").val(bounds.getWest().toFixed(precision));
+    $("#minlat").val(bounds.getSouth().toFixed(precision));
+    $("#maxlon").val(bounds.getEast().toFixed(precision));
+    $("#maxlat").val(bounds.getNorth().toFixed(precision));
+  }
 
-    function boundsChanged() {
-      var bounds = getBounds();
+  function validateControls() {
+    $("#export_osm_too_large").toggle(getBounds().getSize() > OSM.MAX_REQUEST_AREA);
+  }
 
-      map.fitBounds(bounds);
-      locationFilter.setBounds(bounds);
+  page.pushstate = page.popstate = function(path) {
+    $("#export_tab").addClass("current");
+    $('#sidebar_content').load(path, page.load);
+  };
 
-      enableFilter();
-      validateControls();
-    }
+  page.load = function() {
+    map
+      .addLayer(locationFilter)
+      .on("moveend", update);
 
-    function enableFilter() {
-      if (!locationFilter.getBounds().isValid()) {
-        locationFilter.setBounds(map.getBounds().pad(-0.2));
-      }
+    $("#maxlat, #minlon, #maxlon, #minlat").change(boundsChanged);
+    $("#drag_box").click(enableFilter);
 
-      $("#drag_box").hide();
-      locationFilter.enable();
-    }
+    update();
+  };
 
-    function update() {
-      setBounds(locationFilter.isEnabled() ? locationFilter.getBounds() : map.getBounds());
-      validateControls();
-    }
+  page.unload = function() {
+    map
+      .removeLayer(locationFilter)
+      .off("moveend", update);
 
-    function setBounds(bounds) {
-      var precision = zoomPrecision(map.getZoom());
-      $("#minlon").val(bounds.getWest().toFixed(precision));
-      $("#minlat").val(bounds.getSouth().toFixed(precision));
-      $("#maxlon").val(bounds.getEast().toFixed(precision));
-      $("#maxlat").val(bounds.getNorth().toFixed(precision));
-    }
+    $("#export_tab").removeClass("current");
+  };
 
-    function validateControls() {
-      $("#export_osm_too_large").toggle(getBounds().getSize() > OSM.MAX_REQUEST_AREA);
-    }
-  }
-}
+  return page;
+};
diff --git a/app/assets/javascripts/router.js b/app/assets/javascripts/router.js
new file mode 100644 (file)
index 0000000..7b2e995
--- /dev/null
@@ -0,0 +1,78 @@
+OSM.Router = function(rts) {
+  var escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#\s]/g;
+  var optionalParam = /\((.*?)\)/g;
+  var namedParam    = /(\(\?)?:\w+/g;
+  var splatParam    = /\*\w+/g;
+
+  function Route(path, controller) {
+    var regexp = new RegExp('^' +
+      path.replace(escapeRegExp, '\\$&')
+        .replace(optionalParam, '(?:$1)?')
+        .replace(namedParam, function(match, optional){
+          return optional ? match : '([^\/]+)';
+        })
+        .replace(splatParam, '(.*?)') + '(?:$|[?#])');
+
+    var route = {};
+
+    route.match = function(path) {
+      return regexp.test(path);
+    };
+
+    route.run = function(action, path) {
+      var params = [];
+
+      if (path) {
+        params = regexp.exec(path).map(function(param, i) {
+          return (i > 0 && param) ? decodeURIComponent(param) : param;
+        });
+      }
+
+      (controller[action] || $.noop).apply(controller, params);
+    };
+
+    return route;
+  }
+
+  var routes = [];
+  for (var r in rts)
+    routes.push(Route(r, rts[r]));
+
+  routes.recognize = function(path) {
+    for (var i = 0; i < this.length; i++) {
+      if (this[i].match(path)) return this[i];
+    }
+  };
+
+  var currentPath = window.location.pathname,
+    currentRoute = routes.recognize(currentPath);
+
+  currentRoute.run('load', currentPath);
+
+  if (window.history && window.history.pushState) {
+    $(window).on('popstate', function() {
+      var path = window.location.pathname;
+      if (path === currentPath) return;
+      currentRoute.run('unload');
+      currentPath = path;
+      currentRoute = routes.recognize(currentPath);
+      currentRoute.run('popstate', currentPath);
+    });
+
+    return function (url) {
+      var path = url.replace(/#.*/, ''),
+        route = routes.recognize(path);
+      if (!route) return false;
+      window.history.pushState({}, document.title, url);
+      currentRoute.run('unload');
+      currentPath = path;
+      currentRoute = route;
+      currentRoute.run('pushstate', currentPath);
+      return true;
+    }
+  } else {
+    return function (url) {
+      window.location.assign(url);
+    }
+  }
+};
diff --git a/app/assets/javascripts/sidebar.js b/app/assets/javascripts/sidebar.js
deleted file mode 100644 (file)
index 21a4380..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-function closeSidebar() {
-  $("#sidebar")
-    .trigger("closed");
-}
-
-$(document).ready(function () {
-  $(".sidebar_close").click(function (e) {
-    closeSidebar();
-    e.preventDefault();
-  });
-});
index 12cdb15d4ebbc85c19bdcc353df7900044ec0427..97ab5abfc397036304e6e3f73311f80dea2ffa84 100644 (file)
@@ -422,6 +422,10 @@ class ApplicationController < ActionController::Base
     request.body.rewind
   end
 
+  def map_layout
+    request.xhr? ? false : 'map'
+  end
+
   def preferred_editor
     editor = if params[:editor]
       params[:editor]
index 638cb7302edbe628466afb8df6fa4c66c480410c..9f0683aef539cbb138558ca7c6d4bf174f311f0c 100644 (file)
@@ -1,5 +1,5 @@
 class BrowseController < ApplicationController
-  layout 'map'
+  layout :map_layout
 
   before_filter :authorize_web  
   before_filter :set_locale 
index 727522510f7563b9a47060dc5aa9ffd73117fb55..89d244907d7db35b6c21388dc22c3468c1c15149 100644 (file)
@@ -321,7 +321,7 @@ class ChangesetController < ApplicationController
 
       @edits = changesets.order("changesets.created_at DESC").offset((@page - 1) * @page_size).limit(@page_size).preload(:user, :changeset_tags)
 
-      render :action => :list, :layout => 'map'
+      render :action => :list, :layout => map_layout
     end
   end
 
index cb57ddc3a3f20b683d9de7c22982a114655af6ec..eb8749f6f659da21af7c69252c570728fbd829c0 100644 (file)
@@ -1,6 +1,6 @@
 class SiteController < ApplicationController
   layout 'site'
-  layout 'map', :only => [:index, :export]
+  layout :map_layout, :only => [:index, :export]
 
   before_filter :authorize_web
   before_filter :set_locale
index fc135c6ca0ab477dea7de724f4987ed293af74a2..42b5d3e1e9f17d45baa04144a27ac77ada4f4853 100644 (file)
@@ -1,6 +1,4 @@
 <% content_for :head do -%>
-  <%= javascript_include_tag "changeset" %>
-
   <% unless params[:friends] or params[:nearby] -%>
     <%= auto_discovery_link_tag :atom, params.merge({ :page => nil, :action => :feed }) %>
   <% end -%>
index bbb1c55ef1d7a984b853a033906b3c4fa8a7775e..3eb08ac6c6268fa6da8f7b1a20b225325f3d1880 100644 (file)
@@ -69,7 +69,7 @@ OpenStreetMap::Application.configure do
 
   # Precompile additional assets.
   # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
-  config.assets.precompile += %w( index.js browse.js changeset.js welcome.js )
+  config.assets.precompile += %w( index.js browse.js welcome.js )
   config.assets.precompile += %w( user.js diary_entry.js pngfix.js swfobject.js )
   config.assets.precompile += %w( large-ltr.css small-ltr.css print-ltr.css )
   config.assets.precompile += %w( large-rtl.css small-rtl.css print-rtl.css )