Search Engine Optimization Class

September 29th, 2008 By: brian

Recently Moki Systems was invited to teach, at Dixie State College, the first class in a series as part of the SEED Dixie program. (http://www.seedutah.com/)

I’ve uploaded the slides (in Microsoft PowerPoint format) that were used during the class in case any class members want to refer to them. Feel free to comment with any questions you have.

Link to slides: SEO Presentation


Add Markers to a Google Map With Ruby on Rails and JSON

September 25th, 2008 By: wes

This tutorial will guide you through creating a map using the Google Maps API that will be dynamically populated with markers as the user zooms or scrolls around the map.

For this example, we’re going to create and use a generic Location model.

Geocoding Your Addresses

Geocoding will translate an address into its approximate latitude and longitude.

We’ll use GeoKit, a Ruby on Rails plugin to geocode our addresses. Install it with:

script/plugin install svn://rubyforge.org/var/svn/geokit/trunk

Follow the instructions to obtain and install your own Google API key.

Generate the model, controller and views for our map locations:

script/generate scaffold location name:string address:string city:string \
state:string zip:string

We also need to edit the migration file for this model and add fields for the location’s latitude and longitude:

t.decimal :lat, :precision => 15, :scale => 12
t.decimal :lng, :precision => 15, :scale => 12

Replace the code in app/models/location.rb with:

class Location < ActiveRecord::Base
  acts_as_mappable
 
  validates_presence_of :name, :address, :city, :state, :zip, :lat, :lng
  before_validation_on_create :geocode_address
 
  private
    def geocode_address
      geo=GeoKit::Geocoders::MultiGeocoder.geocode("#{address} #{city} #{state} #{zip}")
      errors.add(:address, "Could not Geocode address") if !geo.success
      self.lat, self.lng = geo.lat,geo.lng if geo.success
    end
end

That’s all there is to geocoding, now any time we create a Location it will automatically be assigned a latitude and longitude.

Adding the Google Map

In app/views/locations/index.html.erb add:

<div id="map" style="width: 890px; height: 600px;"></div>

And in app/views/controllers/locations_controller.rb, change the index action to:

# GET /locations
# GET /locations.xml
# GET /locations.js
def index
  respond_to do |format|
    format.html do
      @locations = Location.find(:all)
    end
    format.xml  { render :xml => @locations }
    format.js do
      ne = params[:ne].split(',').collect{|e|e.to_f}  
      sw = params[:sw].split(',').collect{|e|e.to_f}
      @locations = Location.find(:all, :limit => 100, :bounds => [sw, ne])
      render :json => @locations.to_json
    end
  end
end

The index action will now respond to javascript requests with a JSON object containing the first 100 Locations inside of the map boundaries.

In your layout file, add this code inside of the <head> tag:

<% unless @locations.blank? %>
  <script
    src="http://maps.google.com/maps?file=api&v=2.x&key=<%= GeoKit::Geocoders::google -%>"
    type="text/javascript"></script>
  <%= javascript_include_tag 'prototype', 'maps' %>
<% end %>

And finally, create a public/javascripts/maps.js file with this code:

window.onunload = GUnload;
 
var map;
var markers = new Array();
 
Event.observe(window, 'load', function() {
  if (GBrowserIsCompatible()) {
    map = new GMap2(document.getElementById("map"));
    // Center the map on the US
    map.setCenter(new GLatLng(37.731145,-97.326092),4);
    GEvent.addListener(map,"moveend",function(){updateMap();});
    map.addControl(new GLargeMapControl());
    map.addControl(new GMapTypeControl());
 
    updateMap();
  }
});
 
function updateMap() {
  var bounds = map.getBounds();
  var southWest = bounds.getSouthWest();
  var northEast = bounds.getNorthEast();
 
  // Send an AJAX request for our locations
  new Ajax.Request('/locations.js', {
    method:'get',
    parameters: {sw: southWest.toUrlValue(), ne: northEast.toUrlValue()},
    onSuccess: function(transport){
      // Remove markers outside of our maps boundaries.
      if(markers.length > 0){
        removeMarkersOutsideOfMapBounds();
      }
 
      // Add our new markers to the map (unless they are already on the map.)
      var json = transport.responseText.evalJSON();
      json.each(function(i) {
        id = i.location.id;
        if(!markers[id] || markers[id] == null){
          // Marker doesnt exist, add it.
          markers[id] = createMarker(i.location);
          map.addOverlay(markers[id]);
        }
      });      
    }
  });
}
 
function createMarkerClickHandler(marker, location) {
  return function() {
    marker.openInfoWindowHtml(
      '<div><strong>' + location.name + '</strong><br/> ' +
      location.address + '<br/>' + location.city + ', ' +
      location.state + ' ' + location.zip + '</div>'
    );
    return false;
  };
}
 
function createMarker(location) {
  var latlng = new GLatLng(location.lat, location.lng);
  var marker = new GMarker(latlng);
  var handler = createMarkerClickHandler(marker, location);
  GEvent.addListener(marker, "click", handler);
  return marker;
}
 
function removeMarkersOutsideOfMapBounds() {
  for(i in markers) {
    if(i > 0 && markers[i] && !map.getBounds().containsLatLng(markers[i].getLatLng())) {
      map.removeOverlay(markers[i]);
      markers[i] = null;
    }
  }
}

The updateMap() function is run after the page initially loads and each time the user moves or zooms the map. It sends an AJAX request to the server with the maps boundaries, and the server returns a JSON object of the locations within those boundaries. After it receives the JSON object, it will add new locations to the map (it skips locations that have already been mapped) and removes locations that are no longer within the visible map boundaries.

A sample app containing all of the code can be downloaded here: map-sample-code.zip


Ajax multi file upload with php, iframe, & javascript

July 22nd, 2008 By: andrew

I found several solutions after browsing around google trying to find a multi-file uploader built around php. Most of these solutions for multi-file uploading fit into one of these three categories.

  1. Use Flash to upload the images. (The browser only supports one image at a time) - SWFUpload
  2. Use Javascript to add multiple file type fields, while hidding previous fields - the-stickman
  3. Use Javascript and hidden iframes embedded on the same page to upload the images. - ajaxf1

I decided to take pieces of the mentioned methods and come up with a solution that worked for me.

Here is the idea behind what we are going to be doing.

  1. Create a HTML form which has a file upload field
  2. Set the target of this HTML form to an iFrame which is on the same page
  3. Have Javascript submit the form everytime the “file” field changes
  4. Javascript hides the form when submitted, and displays an “animation”
  5. PHP takes the uploaded file, thumbnails it, and stores the image data
  6. Javascript unhides the form, and re-hides the animation when upload is complete
  7. Javascript and php show the thumbnail on the same page right after it was uploaded

This makes this little script appear like an ajax application. It is NOT actually AJAX, but uses css, iframes, php sessions, and javascript to make it appear so.

I’ll try to explain what is happening at each step, but keep in mind, I’m not an expert programmer like my fellow co-workers, and I borrowed most of the work from the mentioned sources above. If you just want to download all the files and take the easy way out, skip to the bottom of the post.

Steps 1 & 2 & 3 - Make Our Form

1
2
3
4
5
6
7
8
9
<form name="pictureform" action="upload.php" method="post" enctype="multipart/form-data" target="upload_target" >
     <p id="f1_upload_process">Loading...<br/><img src="loader.gif" /><br/></p>
     <p id="f1_upload_form" align="center"><br/>
         <label>Add Pictures:  
              <input name="myfile" type="file" size="30" onchange="startUpload();document.pictureform.submit();" />
         </label>					 
     </p>
     <iframe id="upload_target" name="upload_target" src="#" style="width:0;height:0;border:0px solid #fff;"></iframe>
</form>

Read the rest of this entry »


How We’re Using VMWare Server

July 15th, 2008 By: mark

VMWare releasing a free version of their server virtualization product opened up some possibilities for us. Moki Systems had multiple servers performing a variety of tasks but there were things we didn’t have. We didn’t have a split between production and development servers and we didn’t have good utilization. Low CPU and memory utilization offers a comfort level but it adds costs, especially when you need more servers that cost money, use electricity, and generate heat.

Free VMWare server limits running virtual servers to 4. Our solution was to purchase new servers equipped with 1 SATA disk for the OS and 4 SCSI disks, 1 for each virtual machine (VM). While VMWare offers an option to write to the disk directly I opted not to do that because the virtual disk files VMWare uses make your VM’s highly portable and easy to duplicate. Disaster recovery practice becomes far easier when you can easily make a duplicate of your production server.

This architecture’s utility was brought to light when one of your servers died. We were able to pull the disks containing our production servers, copy the VM’s to another server, then get those servers running again quickly. There was no need for OS reinstall or recovery from backup and because of this portability downtime was minimized.

I have VMWare server installed on my Windows workstation with a Centos 5 VM running in the background. I use it for some MySQL and PHP stuff so it isn’t used a whole lot but I hardly know it’s there. The beauty of this that the Windows guys can have a Linux VM and the Linux guys can have a Windows VM. That can help quite a bit when testing browser compatibility on a website or getting access to a tool not available for your OS of choice.

VMWare server has helped us utilize our server resources better at a very minimal cost. Running 4 servers for the price of one saves us time and money. What else could you ask for?