Add a mini map to the changeset view in the data browser.
[rails.git] / app / views / browse / start.rjs
index 12a5f070c97482f1b3cd445895019f3ac22bba25..f38b1dc80937cb0c9f6355236fd87a2b50b01e49 100644 (file)
 page.replace_html :sidebar_title, 'Data'
 page.replace_html :sidebar_content, :partial => 'start'
 page << <<EOJ
-    
-    var gml, sf, objList, currentFeature, featureList, mode = "auto", currentBounds;
-    OpenLayers.Feature.Vector.style['default'].strokeWidth = 3;
-    OpenLayers.Feature.Vector.style['default'].cursor = "pointer";
-    
-    function start() {
-        openSidebar({ onclose: stopBrowse });
-        var vectors = new OpenLayers.Layer.Vector();
-    
-        box = new OpenLayers.Control.DrawFeature(vectors, OpenLayers.Handler.RegularPolygon, { 
-          handlerOptions: {
-            sides: 4,
-            snapAngle: 90,
-            irregular: true,
-            persist: true,
-            callbacks: { done: endDrag }
-          }
-        });
-        map.addControl(box);
-        map.events.register("moveend", map, showData);
-        map.events.triggerEvent("moveend");
-    }
+  var browseBoxControl;
+  var browseActive;
+  var browseMode = "auto";
+  var browseBounds;
+  var browseFeatureList;
+  var browseActiveFeature;
+  var browseDataLayer;
+  var browseSelectControl;
+  var browseObjectList;
 
-    function showData() {
-        if (mode == "manual") { return; }
-        if (map.getZoom() >= 15) {
-            useMap();
-        } else {
-            $("status").innerHTML = "Zoom in or Select an area of the map to view.";
-        }    
-    }
-    
-    function stopBrowse() {
-        if (gml) {
-            gml.destroy();
-            gml = null; 
-        } 
-        if (sf) {   
-            sf.destroy();  
-            sf = null;
-        } 
-        if (currentFeature) {
-            currentFeature.destroy(); 
-            currentFeature = null; 
-        } 
-        map.events.unregister("moveend", map, showData);
-    }
-    
-    function startDrag() {
-      $("drag_box").innerHTML='Drag a box on the map to select an area';
-       box.activate(); 
-       return false;
-    };
-    $("drag_box").onclick = startDrag;
-    
-    function useMap() {
-        var bounds = map.getExtent();
-        var projected = bounds.clone().transform(map.getProjectionObject(), epsg4326);
-        if (!currentBounds || !currentBounds.containsBounds(projected)) {
-            var center = bounds.getCenterLonLat();
-            var tileWidth = bounds.getWidth() * 1.2;
-            var tileHeight = bounds.getHeight() * 1.2;
-            var tileBounds =
-                new OpenLayers.Bounds(center.lon - (tileWidth / 2),
-                                      center.lat - (tileHeight / 2),
-                                      center.lon + (tileWidth / 2),
-                                      center.lat + (tileHeight / 2));
-
-            currentBounds = tileBounds;
-            getData(tileBounds);
-            mode = "auto";
-            $("drag_box").style.display="inline";
-            $("use_map").style.display="none";
-        }
-        return false;
-    }
-    $("use_map").onclick = useMap;
+  OpenLayers.Feature.Vector.style['default'].strokeWidth = 3;
+  OpenLayers.Feature.Vector.style['default'].cursor = "pointer";
     
-    function endDrag(bbox) {
-        var bounds = bbox.getBounds();
-        box.deactivate();
-        getData(bounds);
-        $("drag_box").innerHTML = "Manually select a different area";
-        mode = "manual";  
-        $("use_map").style.display="inline";
-        $("drag_box").style.display="none";
-    }
+  function startBrowse() {
+    browseActive = true;
+
+    openSidebar({ onclose: stopBrowse });
+
+    var vectors = new OpenLayers.Layer.Vector();
     
-    function displayFeatureWarning() {
-        $("status").innerHTML = "";
-        var div = document.createElement("div");
-        var p = document.createElement("p");
-        p.appendChild(document.createTextNode("You have loaded an area which contains " + featureList.length + " features. In general, some browsers may not cope well with displaying this quantity of data. Generally, browsers work best at displaying less than 100 features at a time: doing anything else may make your browser slow/unresponsive. If you are sure you want to display this data, you may do so by clicking the button below.")); 
-        div.appendChild(p);
-        var input = document.createElement("input");
-        input.type = "submit";
-        input.value = "Load Data";
-        input.onclick = loadFeatureList;
-        div.appendChild(input); 
-        $("object").innerHTML="";
-        $("object").appendChild(div);
+    browseBoxControl = new OpenLayers.Control.DrawFeature(vectors, OpenLayers.Handler.RegularPolygon, { 
+      handlerOptions: {
+        sides: 4,
+        snapAngle: 90,
+        irregular: true,
+        persist: true,
+        callbacks: { done: endDrag }
+      }
+    });
+    map.addControl(browseBoxControl);
+
+    map.events.register("moveend", map, showData);
+    map.events.triggerEvent("moveend");
+  }
+
+  function showData() {
+    if (browseMode == "auto") {
+      if (map.getZoom() >= 15) {
+          useMap();
+      } else {
+          setStatus("Zoom in or select an area of the map to view");
+      }    
     }
-    
-    function loadFeatureList() {
-        gml.addFeatures(featureList);
-        gml.events.triggerEvent("loadend");
-        return false;
+  }
+
+  function stopBrowse() {
+    if (browseActive) {
+      browseActive = false;
+
+      if (browseDataLayer) {
+        browseDataLayer.destroy();
+        browseDataLayer = null; 
+      } 
+
+      if (browseSelectControl) {   
+        browseSelectControl.destroy();  
+        browseSelectControl = null;
+      } 
+
+      if (browseBoxControl) {
+       browseBoxControl.destroy();
+       browseBoxControl = null;
+      }                
+
+      if (browseActiveFeature) {
+        browseActiveFeature.destroy(); 
+        browseActiveFeature = null; 
+      }
+
+      map.dataLayer.setVisibility(false);
+      map.events.unregister("moveend", map, showData);
     }    
+  }
 
-    function customDataLoader(request) { 
-        var doc = request.responseXML;
-        
-        if (!doc || !doc.documentElement) {
-            doc = request.responseText;
-        }
-        
-        var options = {};
-        
-        OpenLayers.Util.extend(options, this.formatOptions);
-        if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
-            options.externalProjection = this.projection;
-            options.internalProjection = this.map.getProjectionObject();
-        }    
-        
-        var gml = this.format ? new this.format(options) : new OpenLayers.Format.GML(options);
-        var features = gml.read(doc);
-        if (!this.maxFeatures || features.length <= this.maxFeatures) {
-            this.addFeatures(features);
-            this.events.triggerEvent("loadend");
-            featureList = []; 
-        } else {
-            featureList = features;
-            displayFeatureWarning();
-        }
+  function startDrag() {
+    $("browse_select_box").innerHTML='Drag a box on the map to select an area';
+
+    browseBoxControl.activate();
+
+    return false;
+  };
+
+  $("browse_select_box").onclick = startDrag;
+
+  function useMap() {
+    var bounds = map.getExtent();
+    var projected = bounds.clone().transform(map.getProjectionObject(), epsg4326);
+
+    if (!browseBounds || !browseBounds.containsBounds(projected)) {
+      var center = bounds.getCenterLonLat();
+      var tileWidth = bounds.getWidth() * 1.2;
+      var tileHeight = bounds.getHeight() * 1.2;
+      var tileBounds = new OpenLayers.Bounds(center.lon - (tileWidth / 2),
+                                             center.lat - (tileHeight / 2),
+                                             center.lon + (tileWidth / 2),
+                                             center.lat + (tileHeight / 2));
+
+      browseBounds = tileBounds;
+      getData(tileBounds);
+
+      browseMode = "auto";
+
+      $("browse_select_view").style.display = "none";
     }
 
-    function getData(bounds) {
-        
-        bounds.transform(new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326"));
-        var size = bounds.getWidth() * bounds.getHeight(); 
-        if (size > 0.25) {
-            $("status").innerHTML = "Unable to load: Bounding box size of " + size + " is too large. (Must be smaller than 0.25)<br />"; 
-            return;
-        }
+    return false;
+  }
 
-        var url = "/api/0.5/map?bbox="+bounds.toBBOX();
-        
-        loadGML(url);
+  $("browse_select_view").onclick = useMap;
+
+  function endDrag(bbox) {
+    var bounds = bbox.getBounds();
+    var projected = bounds.clone().transform(map.getProjectionObject(), epsg4326);
+
+    browseBoxControl.deactivate();
+    browseBounds = projected;
+    getData(bounds);
+
+    browseMode = "manual";  
+
+    $("browse_select_box").innerHTML = "Manually select a different area";
+    $("browse_select_view").style.display = "inline";
+  }
+
+  function displayFeatureWarning() {
+    clearStatus();
+
+    var div = document.createElement("div");
+
+    var p = document.createElement("p");
+    p.appendChild(document.createTextNode("You have loaded an area which contains " + browseFeatureList.length + " features. In general, some browsers may not cope well with displaying this quantity of data. Generally, browsers work best at displaying less than 100 features at a time: doing anything else may make your browser slow/unresponsive. If you are sure you want to display this data, you may do so by clicking the button below."));
+    div.appendChild(p);
+
+    var input = document.createElement("input");
+    input.type = "submit";
+    input.value = "Load Data";
+    input.onclick = loadFeatureList;
+    div.appendChild(input); 
+
+    $("browse_content").innerHTML = "";
+    $("browse_content").appendChild(div);
+  }
+
+  function loadFeatureList() {
+    browseDataLayer.addFeatures(browseFeatureList);
+    browseDataLayer.events.triggerEvent("loadend");
+
+    browseFeatureList = []; 
+
+    return false;
+  }    
+
+  function customDataLoader(request) {
+    if (browseActive) {
+      var doc = request.responseXML;
+
+      if (!doc || !doc.documentElement) {
+        doc = request.responseText;
+      }
+
+      var options = {};
+
+      OpenLayers.Util.extend(options, this.formatOptions);
+
+      if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+        options.externalProjection = this.projection;
+        options.internalProjection = this.map.getProjectionObject();
+      }    
+
+      var gml = this.format ? new this.format(options) : new OpenLayers.Format.GML(options);
+
+      browseFeatureList = gml.read(doc);
+
+      if (!this.maxFeatures || browseFeatureList.length <= this.maxFeatures) {
+        loadFeatureList();
+      } else {
+        displayFeatureWarning();
+      }
     }
-    function loadGML(url) {
-        $("status").innerHTML = "Loading...";
-        $("object").innerHTML = "";
-        if (!gml) {
-            var style = new OpenLayers.Style();
-            style.addRules([new OpenLayers.Rule( 
-              {'symbolizer': 
-                {"Polygon": {'fillColor': '#ff0000', 'strokeColor': '#ff0000'},
-                 "Line": {'fillColor': '#ffff00', 'strokeColor': '#000000', strokeOpacity: '0.4'},
-                 "Point": {'fillColor': '#00ff00', 'strokeColor': '#00ff00'}}
-              }
-            )]);
-            gml = new OpenLayers.Layer.GML("Data",url, 
-                    {format: OpenLayers.Format.OSM, formatOptions: {checkTags: true},
-                     maxFeatures: 100, requestSuccess: customDataLoader,
-                     styleMap: new OpenLayers.StyleMap({'default': style, 'select': {'strokeColor': '#0000ff', strokeWidth: 8}})
-                    }
-            );
-            gml.events.register("loadend", gml, dataLoaded );
-            map.addLayer(gml);
-            
-            sf = new OpenLayers.Control.SelectFeature(gml, {'onSelect': onFeatureSelect});
-            sf.handler.stopDown = false;
-            sf.handler.stopUp = false;
-            map.addControl(sf);
-            sf.activate();
-             
-        } else {
-            gml.setUrl(url);
+  }
+
+  function getData(bounds) {
+    var projected = bounds.clone().transform(new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326"));
+    var size = projected.getWidth() * projected.getHeight();
+
+    if (size > 0.25) {
+      setStatus("Unable to load: Bounding box size of " + size + " is too large (must be smaller than 0.25)");
+    } else {
+      loadGML("/api/#{API_VERSION}/map?bbox=" + projected.toBBOX());
+    }
+  }
+
+  function loadGML(url) {
+    setStatus("Loading...");
+    $("browse_content").innerHTML = "";
+
+    if (!browseDataLayer) {
+      var style = new OpenLayers.Style();
+
+      style.addRules([new OpenLayers.Rule({
+        symbolizer: {
+          Polygon: { fillColor: '#ff0000', strokeColor: '#ff0000' },
+          Line: { fillColor: '#ffff00', strokeColor: '#000000', strokeOpacity: '0.4' },
+          Point: { fillColor: '#00ff00', strokeColor: '#00ff00' }
         }
+      })]);
 
-        currentFeature = null;
+      browseDataLayer = new OpenLayers.Layer.GML("Data", url, {
+        format: OpenLayers.Format.OSM,
+        formatOptions: { checkTags: true },
+        maxFeatures: 100,
+        requestSuccess: customDataLoader,
+        displayInLayerSwitcher: false,
+        styleMap: new OpenLayers.StyleMap({
+          'default': style,
+          'select': { strokeColor: '#0000ff', strokeWidth: 8 }
+        })
+      });
+      browseDataLayer.events.register("loadend", browseDataLayer, dataLoaded );
+      map.addLayer(browseDataLayer);
+            
+      browseSelectControl = new OpenLayers.Control.SelectFeature(browseDataLayer, { onSelect: onFeatureSelect });
+      browseSelectControl.handlers.feature.stopDown = false;
+      browseSelectControl.handlers.feature.stopUp = false;
+      map.addControl(browseSelectControl);
+      browseSelectControl.activate();
+    } else {
+      browseDataLayer.setUrl(url);
     }
-    function dataLoaded() { 
-        $("status").innerHTML = "Loaded " + this.features.length + " features. (<a href='"+ this.url+"'>API</a>)";
+
+    browseActiveFeature = null;
+  }
+
+  function dataLoaded() {
+    if (browseActive) {
+      clearStatus();
         
-        objList = document.createElement("ul");
-        for (var i = 0; i < this.features.length; i++) {
-            var feature = this.features[i]; 
+      browseObjectList = document.createElement("div")
+
+      var heading = document.createElement("p");
+      heading.className = "browse_heading";
+      heading.appendChild(document.createTextNode("Object list"));
+      browseObjectList.appendChild(heading);
+
+      var list = document.createElement("ul");
+
+      for (var i = 0; i < this.features.length; i++) {
+        var feature = this.features[i]; 
             
-            // Type, for linking
-            var type ="way";
-            if (feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
-                type = "node";
-            }   
-            var nice_name = type.substr(0,1).toUpperCase() + type.substr(1,type.length); 
-            var li = document.createElement("li");
-            li.appendChild(document.createTextNode(nice_name + " "));
+        // Type, for linking
+        var type = featureType(feature);
+        var typeName = ucFirst(type);
+        var li = document.createElement("li");
+        li.appendChild(document.createTextNode(typeName + " "));
             
-            // Link, for viewing in the tab
-            var link = document.createElement("a");
-            link.href =  "/browse/" + type + "/" + feature.osm_id; 
-            var name = feature.attributes.name || feature.osm_id;
-            link.appendChild(document.createTextNode(name));
-            link.feature = feature;
-            link.onclick = OpenLayers.Function.bind(viewFeatureLink, link);   
-            li.appendChild(link);
-
-            objList.appendChild(li);
-        }
-        $("object").innerHTML = "";
-        $("object").appendChild(objList); 
+        // Link, for viewing in the tab
+        var link = document.createElement("a");
+        link.href =  "/browse/" + type + "/" + feature.osm_id; 
+        var name = feature.attributes.name || feature.osm_id;
+        link.appendChild(document.createTextNode(name));
+        link.feature = feature;
+        link.onclick = OpenLayers.Function.bind(viewFeatureLink, link);   
+        li.appendChild(link);
+
+        list.appendChild(li);
+      }
+
+      browseObjectList.appendChild(list)
+
+      var link = document.createElement("a");
+      link.href = this.url;
+      link.appendChild(document.createTextNode("API"));
+      browseObjectList.appendChild(link);
+
+      $("browse_content").innerHTML = "";
+      $("browse_content").appendChild(browseObjectList); 
     }
+  }
     
-    function viewFeatureLink() {
-        var layer = this.feature.layer;
-        for (var i = 0; i < layer.selectedFeatures.length; i++) {
-            var f = layer.selectedFeatures[i]; 
-            layer.drawFeature(f, layer.styleMap.createSymbolizer(f, "default"));
-        }
-        onFeatureSelect(this.feature);
-        if (mode != "auto") {
-            map.setCenter(this.feature.geometry.getBounds().getCenterLonLat()); 
-        }
-        return false;
+  function viewFeatureLink() {
+    var layer = this.feature.layer;
+
+    for (var i = 0; i < layer.selectedFeatures.length; i++) {
+      var f = layer.selectedFeatures[i]; 
+      layer.drawFeature(f, layer.styleMap.createSymbolizer(f, "default"));
     }
-    
-    function loadObjList() {
-        $("object").innerHTML="";
-        $("object").appendChild(objList);
-        return false;
+
+    onFeatureSelect(this.feature);
+
+    if (browseMode != "auto") {
+      map.setCenter(this.feature.geometry.getBounds().getCenterLonLat()); 
     }
+
+    return false;
+  }
+    
+  function loadObjectList() {
+    $("browse_content").innerHTML="";
+    $("browse_content").appendChild(browseObjectList);
+
+    return false;
+  }
       
-    function onFeatureSelect(feature) {
-        // Unselect previously selected feature
-        if (currentFeature) {
-          currentFeature.layer.drawFeature(
-            currentFeature, currentFeature.layer.styleMap.createSymbolizer(currentFeature, "default")
-          );
-        }
+  function onFeatureSelect(feature) {
+    // Unselect previously selected feature
+    if (browseActiveFeature) {
+      browseActiveFeature.layer.drawFeature(
+        browseActiveFeature, 
+        browseActiveFeature.layer.styleMap.createSymbolizer(browseActiveFeature, "default")
+      );
+    }
 
-        // Redraw in selected style
-        feature.layer.drawFeature(
-          feature, feature.layer.styleMap.createSymbolizer(feature, "select")
-        );
-
-        // If the current object is the list, don't innerHTML="", since that could clar it.   
-        if ($("object").firstChild == objList) { 
-            $("object").removeChild(objList);
-        } else { 
-            $("object").innerHTML = "";
-        }   
-        
-        // Create a link back to the object list
-        var div = document.createElement("div");
-        var link = document.createElement("a");
-        link.href="#";
-        link.onclick = loadObjList;
-        link.appendChild(document.createTextNode("Back to Object List"));
-        div.appendChild(link)
-        $("object").appendChild(div);    
+    // Redraw in selected style
+    feature.layer.drawFeature(
+      feature, feature.layer.styleMap.createSymbolizer(feature, "select")
+    );
+
+    // If the current object is the list, don't innerHTML="", since that could clear it.
+    if ($("browse_content").firstChild == browseObjectList) { 
+      $("browse_content").removeChild(browseObjectList);
+    } else { 
+      $("browse_content").innerHTML = "";
+    }   
         
-        // Now the list of attributes
-        var ul = document.createElement("ul");
-        var type = "way";
-        if (feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
-            type = "node";
-        }    
-        var li = document.createElement("li");
-        var link = document.createElement("a");   
-        link.href =  "/browse/"+type+"/"+feature.osm_id;
-        link.appendChild(document.createTextNode(feature.osm_id));
-        li.appendChild(link);
-        ul.appendChild(li);
-        for (var key in feature.attributes) {
-            var li = document.createElement("li");
-            var b = document.createElement("b");
-            b.appendChild(document.createTextNode(key));
-            li.appendChild(b);
-            li.appendChild(document.createTextNode(": " + feature.attributes[key]));
-            ul.appendChild(li);
-        }
+    // Create a link back to the object list
+    var div = document.createElement("div");
+    div.style.textAlign = "center";
+    div.style.marginBottom = "20px";
+    $("browse_content").appendChild(div);
+    var link = document.createElement("a");
+    link.href = "#";
+    link.onclick = loadObjectList;
+    link.appendChild(document.createTextNode("Display object list"));
+    div.appendChild(link);
+
+    var table = document.createElement("table");
+    table.width = "100%";
+    table.className = "browse_heading";
+    $("browse_content").appendChild(table);
+
+    var tr = document.createElement("tr");
+    table.appendChild(tr);
+
+    var heading = document.createElement("td");
+    heading.appendChild(document.createTextNode(featureName(feature)));
+    tr.appendChild(heading);
+
+    var td = document.createElement("td");
+    td.align = "right";
+    tr.appendChild(td);
+
+    var type = featureType(feature);
+    var link = document.createElement("a");   
+    link.href = "/browse/" + type + "/" + feature.osm_id;
+    link.appendChild(document.createTextNode("Details"));
+    td.appendChild(link);
+
+    var div = document.createElement("div");
+    div.className = "browse_details";
+
+    $("browse_content").appendChild(div);
+
+    // Now the list of attributes
+    var ul = document.createElement("ul");
+    for (var key in feature.attributes) {
+      var li = document.createElement("li");
+      var b = document.createElement("b");
+      b.appendChild(document.createTextNode(key));
+      li.appendChild(b);
+      li.appendChild(document.createTextNode(": " + feature.attributes[key]));
+      ul.appendChild(li);
+    }
         
-        var li = document.createElement("li");
-        var link = document.createElement("a");   
-        link.href =  "/browse/"+type+"/"+feature.osm_id+"/history";
-        link.appendChild(document.createTextNode("History"));
-        li.appendChild(link);
-        ul.appendChild(li);
-        link.onclick = OpenLayers.Function.bind(loadHistory, {type: type, feature: feature, link: link});
-        $("object").appendChild(ul);
+    div.appendChild(ul);
         
+    var link = document.createElement("a");   
+    link.href =  "/browse/" + type + "/" + feature.osm_id + "/history";
+    link.appendChild(document.createTextNode("Show history"));
+    link.onclick = OpenLayers.Function.bind(loadHistory, {
+      type: type, feature: feature, link: link
+    });
         
-        // Stash the currently drawn feature
-        currentFeature = feature; 
-    }   
-    function loadHistory() {
-        this.link.href = "";
-        this.link.innerHTML = "Wait...";
-        new Ajax.Request("/api/0.5/"+this.type+"/"+this.feature.osm_id+"/history", {onComplete: OpenLayers.Function.bind(displayHistory, this)});
+    div.appendChild(link);
+
+    // Stash the currently drawn feature
+    browseActiveFeature = feature; 
+  }   
+
+  function loadHistory() {
+    this.link.href = "";
+    this.link.innerHTML = "Wait...";
+
+    new Ajax.Request("/api/#{API_VERSION}/" + this.type + "/" + this.feature.osm_id + "/history", {
+      onComplete: OpenLayers.Function.bind(displayHistory, this)
+    });
+
+    return false;
+  }
+
+  function displayHistory(request) {
+    if (browseActiveFeature.osm_id != this.feature.osm_id || $("browse_content").firstChild == browseObjectList)  { 
         return false;
+    } 
+
+    this.link.parentNode.removeChild(this.link);
+
+    var doc = request.responseXML;
+
+    var table = document.createElement("table");
+    table.width = "100%";
+    table.className = "browse_heading";
+    $("browse_content").appendChild(table);
+
+    var tr = document.createElement("tr");
+    table.appendChild(tr);
+
+    var heading = document.createElement("td");
+    heading.appendChild(document.createTextNode("History for " + featureName(this.feature)));
+    tr.appendChild(heading);
+
+    var td = document.createElement("td");
+    td.align = "right";
+    tr.appendChild(td);
+
+    var link = document.createElement("a");   
+    link.href = "/browse/" + this.type + "/" + this.feature.osm_id + "/history";
+    link.appendChild(document.createTextNode("Details"));
+    td.appendChild(link);
+
+    var div = document.createElement("div");
+    div.className = "browse_details";
+
+    var nodes = doc.getElementsByTagName(this.type);
+    var history = document.createElement("ul");  
+    for (var i = nodes.length - 1; i >= 0; i--) {
+      var user = nodes[i].getAttribute("user") || "private user";
+      var timestamp = nodes[i].getAttribute("timestamp");
+      var item = document.createElement("li");
+      item.appendChild(document.createTextNode("Edited by " + user + " at " + timestamp));
+      history.appendChild(item);
     }
-    function displayHistory(request) {
-        if (currentFeature.osm_id != this.feature.osm_id || $("object").firstChild == objList)  { 
-            return false;
-        } 
-        this.link.parentNode.parentNode.removeChild(this.link.parentNode);
-        var doc = request.responseXML;
-        var div = document.createElement("div");
-        var h3 = document.createElement("h3"); 
-        h3.appendChild(document.createTextNode("History"));
-        div.appendChild(h3);
-        var nodes = doc.getElementsByTagName(this.type);
-        var history = document.createElement("ul");  
-        for (var i = 0; i < nodes.length; i++) {
-            var user = nodes[i].getAttribute("user") || "private user";
-            var timestamp = nodes[i].getAttribute("timestamp");
-            var item = document.createElement("li");
-            item.appendChild(document.createTextNode("Edited by " + user + " at " + timestamp));
-            history.appendChild(item);
-        }
-        div.appendChild(history);
-        var link = document.createElement("a");
-        link.appendChild(document.createTextNode("History"));
-        link.href = "/browse/"+this.type+"/"+this.feature.osm_id+"/history";
-        div.appendChild(link);
-        $("object").appendChild(div); 
+    div.appendChild(history);
+
+    $("browse_content").appendChild(div); 
+  }
+
+  function featureType(feature) {
+    if (feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+      return "node";
+    } else {
+      return "way";
     }
-    
-    start();
+  }
+  
+  function featureName(feature) {
+    if (feature.attributes.name) {
+      return feature.attributes.name;
+    } else {
+      return ucFirst(featureType(feature)) + " " + feature.osm_id;
+    }
+  }
+
+  function setStatus(status) {
+    $("browse_status").innerHTML = status;
+    $("browse_status").style.display = "block";
+  }
+  
+  function clearStatus() {
+    $("browse_status").innerHTML = "";
+    $("browse_status").style.display = "none";
+  }
+
+  function ucFirst(str) {
+    return str.substr(0,1).toUpperCase() + str.substr(1,str.length);
+  }
+
+  startBrowse();
 EOJ