Scripty2 and AJAX autocompleter

written by seb on September 3rd, 2010 @ 03:19 PM

As of today, September 3rd 2010, scripty2 is still in alpha release and the autocompleter as experimental implementation. Anyway, I feel it stable enough for my current needs and just needed few tweaks to get it working as expected with AJAX support.

NOTICE: Development made for BookingSync

What new features I needed

  • My choices are coming from an AJAX request, returning a JSON formatted array of objects.
  • The list's items need to be more verbose, showing more that one attribute of each object and with custom HTML schema.
  • Once a choice selected, I need access to the corresponding object ID to get more details on this object.

New options introduced

  • choiceAttribute: If defined, the search will occur against this object attribute, and once selected will set only this attribute back as input value. (eg. The complete object is returned as meno's value of the fired event ui:autocompleter:selected)
  • listTemplate: If defined, the list items will use this template to update them content. It will be evaluated with the following arguments:
    • text: the value defined by object[choiceAttribute], potentially highlighted
    • object: the complete object

Usage example


<span id="autocompleter2">
  <input type="text" name="firstname" />
</span>

<script type="text/javascript">
  // That's where you want to get the choices with an AJAX request
  var choices = [
    { client: { login: "madrobby", name: "Thomas Fuchs"} },
    { client: { login: "savetheclocktower", name: "Andrew Dupont"} },
    { client: { login: "ZenCocoon", name: "Sébastien Grosjean"} },
    ...
  ];

  new S2.UI.Autocompleter('autocompleter2', {
    choices: choices.collect(function(o) { return o.client }),
    choiceAttribute: 'name',
    listTemplate: new Template('#{text} <em>(#{object.login})</em>')
  });
</script>

Live demo visible at Scripty2 functional test at ZenCocoon.com

Production usage coming to BookingSync's booking creation to prevent dual client creation

Patch

The patch can be found at http://github.com/ZenCocoon/scripty2

Installing rdoc 2.4.3 ruby gem from source

written by seb on May 10th, 2010 @ 09:55 AM

Have you ever messed up with your gems? Well, I did!

Having a conflict with rdoc versions I felt the need to remove rdoc 2.4.3
As personal advise, if you read this article before you do so, DO NOT DO IT ;-)

I'm on snow leopard and once rdoc 2.4.3 uninstalled all rubygems got blocked. Not even possible to install local gems with


gem install -l path/to/my/local/gem.gem

Installing rdoc 2.4.3 from source

What to do next, `gem` command broken, ... I went for the source installation downloading the tarball rdoc-2.4.3.tgz

Install the gem source

First things first, we need to untar the rdoc source in the appropriate folder and give it the right permissions


mv rdoc-2.4.3.tgz /Library/Ruby/Gems/1.8/gems/
cd /Library/Ruby/Gems/1.8/gems/
tar xvzf rdoc-2.4.3.tgz
sudo chown -R root:admin rdoc-2.4.3

Install the gem file as cache

Download the gem file, and save it as cachedrdoc-2.4.3.gem


mv rdoc-2.4.3.gem /Library/Ruby/Gems/1.8/cache/
sudo chown root:admin /Library/Ruby/Gems/1.8/cache/rdoc-2.4.3.gem

Install the gemspec file

Here came a problem, where to find the gemspec?
I couldn't find so asked a friend, Sébastien Gruhier from Xilinus, thanks to him you can download rdoc-2.4.3.gemspec


mv rdoc-2.4.3.gemspec /Library/Ruby/Gems/1.8/specifications/
sudo chown root:admin /Library/Ruby/Gems/1.8/specifications/rdoc-2.4.3.gemspec

From this point, I was able to run `gem list` again, and all went back to normal, conflict included ;-)

Deep Hash Ordering With Ruby 1.8 and 1.9

written by seb on January 15th, 2010 @ 01:19 PM

More like a memo than an article, I believe this piece of code that I made for i18n-js might be helpful.

UPDATE: Now also compatible with Ruby 1.9

def convert_hash_to_ordered_hash_and_sort(object, deep = false)
  if object.is_a?(Hash)
    # Hash is ordered in Ruby 1.9! 
    res = returning(RUBY_VERSION >= '1.9' ? Hash.new : ActiveSupport::OrderedHash.new) do |map|
      object.each {|k, v| map[k] = deep ? convert_hash_to_ordered_hash_and_sort(v, deep) : v }
    end
    return res.class[res.sort {|a, b| a[0].to_s <=> b[0].to_s } ]
  elsif deep && object.is_a?(Array)
    array = Array.new
    object.each_with_index {|v, i| array[i] = convert_hash_to_ordered_hash_and_sort(v, deep) }
    return array
  else
    return object
  end
end

Convert Hash to Ordered Hash with Rails 2.3+ and Ruby 1.8

written by seb on January 15th, 2010 @ 10:39 AM

Problem

Ruby 1.8 doesn't support Ordered Hash.

You do not need this if using Ruby 1.9

Solution

Rails brought this with ActiveSupport::OrderedHash to make it behave much like Ruby 1.9 Hash in the ordering way.

Remaining problem

When you've got a Hash, containing Hash, Array, ... them selves containing more Hash, Array, ... you need a simple way to convert all this hash to ActiveRecord::OrderedHash (if running Ruby 1.8).

Solution to convert all Hash as ActiveSupport::OrderedHash only if Ruby is under 1.9

def convert_hash_to_ordered_hash(object, deep = false)
  # Hash is ordered in Ruby 1.9!
  if RUBY_VERSION >= '1.9'
    return object
  else
    if object.is_a?(Hash)
      returning(ActiveSupport::OrderedHash.new) do |map|
        object.each {|k,v| map[k] = deep ? convert_hash_to_ordered_hash(v, deep) : v }
      end
    elsif deep && object.is_a?(Array)
      array = Array.new
      object.each_with_index {|v,i| array[i] = convert_hash_to_ordered_hash(v, deep) }
      return array
    else
      return object
    end
  end
end

How to use :

>> b = {"a"=>{"aa"=>"ca", "a"=>"ba", "ba"=>"aa"}, "b"=>{"bb"=>"ab", "ab"=>"cb", "b"=>"bb"}, "c"=>{"aa"=>["a", "c", "b"], "bc"=>"ac", "ac"=>"cc", "c"=>{"a"=>"bc", "b"=>"b", "c"=>"c"}}, "d"=>["a", "c", "b", {"a"=>"a", "b"=>"b", "c"=>"c"}]}
=> {"a"=>{"aa"=>"ca", "a"=>"ba", "ba"=>"aa"}, "b"=>{"bb"=>"ab", "ab"=>"cb", "b"=>"bb"}, "c"=>{"aa"=>["a", "c", "b"], "bc"=>"ac", "ac"=>"cc", "c"=>{"a"=>"bc", "b"=>"b", "c"=>"c"}}, "d"=>["a", "c", "b", {"a"=>"a", "b"=>"b", "c"=>"c"}]}
>> convert_hash_to_ordered_hash(b, false)
=> #<OrderedHash {"a"=>{"aa"=>"ca", "a"=>"ba", "ba"=>"aa"}, "b"=>{"bb"=>"ab", "ab"=>"cb", "b"=>"bb"}, "c"=>{"aa"=>["a", "c", "b"], "bc"=>"ac", "ac"=>"cc", "c"=>{"a"=>"bc", "b"=>"b", "c"=>"c"}}, "d"=>["a", "c", "b", {"a"=>"a", "b"=>"b", "c"=>"c"}]}>
>> convert_hash_to_ordered_hash([b], false)
=> [{"a"=>{"aa"=>"ca", "a"=>"ba", "ba"=>"aa"}, "b"=>{"bb"=>"ab", "ab"=>"cb", "b"=>"bb"}, "c"=>{"aa"=>["a", "c", "b"], "bc"=>"ac", "ac"=>"cc", "c"=>{"a"=>"bc", "b"=>"b", "c"=>"c"}}, "d"=>["a", "c", "b", {"a"=>"a", "b"=>"b", "c"=>"c"}]}]
>> convert_hash_to_ordered_hash(b, true)
=> #<OrderedHash {"a"=>#<OrderedHash {"aa"=>"ca", "a"=>"ba", "ba"=>"aa"}>, "b"=>#<OrderedHash {"bb"=>"ab", "ab"=>"cb", "b"=>"bb"}>, "c"=>#<OrderedHash {"aa"=>["a", "c", "b"], "bc"=>"ac", "ac"=>"cc", "c"=>#<OrderedHash {"a"=>"bc", "b"=>"b", "c"=>"c"}>}>, "d"=>["a", "c", "b", #<OrderedHash {"a"=>"a", "b"=>"b", "c"=>"c"}>]}>
>> convert_hash_to_ordered_hash([b], true)
=> [#<OrderedHash {"a"=>#<OrderedHash {"aa"=>"ca", "a"=>"ba", "ba"=>"aa"}>, "b"=>#<OrderedHash {"bb"=>"ab", "ab"=>"cb", "b"=>"bb"}>, "c"=>#<OrderedHash {"aa"=>["a", "c", "b"], "bc"=>"ac", "ac"=>"cc", "c"=>#<OrderedHash {"a"=>"bc", "b"=>"b", "c"=>"c"}>}>, "d"=>["a", "c", "b", #<OrderedHash {"a"=>"a", "b"=>"b", "c"=>"c"}>]}>]
>> b
=> {"a"=>{"aa"=>"ca", "a"=>"ba", "ba"=>"aa"}, "b"=>{"bb"=>"ab", "ab"=>"cb", "b"=>"bb"}, "c"=>{"aa"=>["a", "c", "b"], "bc"=>"ac", "ac"=>"cc", "c"=>{"a"=>"bc", "b"=>"b", "c"=>"c"}}, "d"=>["a", "c", "b", {"a"=>"a", "b"=>"b", "c"=>"c"}]}

Photoshop Brush : ZenCocoon - Greek Ornaments v1

written by seb on May 17th, 2009 @ 03:11 PM

Just a tiny freebie, a Photoshop Brush for Greek Ornaments

Maps, Geolocalization and Optimization with Maptimize

written by seb on May 1st, 2009 @ 09:30 PM

Playing with maps is great fun and here is walk through from basics to advanced use of geolocalization and how to optimize your maps with Maptimize

During this tutorial you will :

  1. start geo localizing your addresses with server side technologies and caching system
  2. add front side tools (in Javascript) to keep things lighter and nicer for the end user
  3. keep both side geo localization working nicely together and in an unobtrusive way
  4. map your localized object while keeping optimized, using Maptimize
  5. show details when clicking on markers and clusters
  6. bonus, use Maptimize makers when creating / editing entries

UPDATE: Thanks to Sébastien Gruhier from Xilinus I’ve made couple of change to improve various codes and this article in general.

UPDATE 2 – 2009-05-04: Sébastien Gruhier from Xilinus has been rocking again and helped me out improving few aspects.

UPDATE 3 – 2009-05-04: Improve maptimize configuration and task to be more flexible and portable

Feeling in hurry ?

I’ve made a basic demo running on Rails 2.3.2 to cover this tutorial : Maptimize – Demo 1 so feel free to check it out directly. Make sure to check the README.textile to get it working.

With more time, let’s get a step by step walk through

For this integration I’ll assume that you have a Rails application up-and-running (for me using Rails 2.3.2) with a model collecting address details, in my case, “business”

Geolocalize your addresses

You first gonna need to add the lat and lng float attributes in your database but also a geolocalized_address string attribute. This attribute will be used to cache the address and prevent unnecessary updates.

From the demo :

class AddGeolocalizedSupportToBusinesses < ActiveRecord::Migration
  def self.up
    change_table(:businesses) do |t|
       t.float :lat, :lng
       t.string :geolocalized_address
     end
  end

  def self.down
    change_table(:businesses) do |t|
       t.remove :lat, :lng, :geolocalized_address
     end
  end
end

Don’t forget to run the migration rake db:migrate

1. Geolocalize on server side with GeoKit

Make sure to install GeoKit gem and GeoKit rails plugin as well as configuring your KEYS in “config/initializers/geokit_config.rb

GeoKit propose you a really simple way to automatically geolocalize your address :

class Business < ActiveRecord::Base
  acts_as_mappable :auto_geocode=>true
end

In our case, we will do something a bit more advanced by caching the geolocalized address to prevent calling the Geolocalization Services when we don’t need to.

Here is the model that I would like to geolocalize :

class Business < ActiveRecord::Base
  acts_as_mappable
  before_validation :geocode_address

  private
  def geocode_address
    return if self.address.nil? || (self.geolocalized_address == self.address && !self.lat.nil? && !self.lng.nil?)
    geo=Geokit::Geocoders::MultiGeocoder.geocode(address)
    errors.add(:address, "Could not Geocode address") if !geo.success
    self.lat, self.lng, self.geolocalized_address = geo.lat, geo.lng, self.address if geo.success
  end
end

2. Geolocalize on front-end side

As Google will recommend and warn when signing up for the API key their is a geocode request limitation per day based on your ip. Using front side geocoding will prevent you from reaching this limits as the geolocalization request will be done from your customers IP address.

Client side geolocalization will also save server load and allow your customers to adjust the position when needed.

For this purpose, I’m using AddressChooser from Maptimize , and as dealing with a single address field I’m gonna add the prototype / scriptaculous autocomplete example

NOTICE : AddressChooser is Javascript framework-agnostic and Mapping system independent, so if your needs differs from mine go check AddressChooser from Maptimize to get the full details.

Start by including the required Javascripts files :
  1. Prototype JS
  2. Scriptaculous Effects and Controls
  3. AddressChooser
  4. Your own JavaScripts

I actually start to use more and more the Google hosted ones to prevent end users from loading this libraries again and again so here is how I deal with it:

<script type="text/javascript" src="http://www.google.com/jsapi?key=<%= Geokit::Geocoders::google %>"></script>
<%= javascript_include_tag 'gloader', 'effects', 'controls', 'addresschooser/proxy/googlemap', 'addresschooser/addresschooser', 'application' %>
GLoader? What’s that ?

GLoader is a tiny javascript files that take care of loading my required libraries, hosted by Google :

NOTE : ScriptAculoUs is not loaded this way as it can’t be loaded properly right now. To help getting this fixed please review, comments, rate, ... the following issue : google.load / auto-loading and scriptaculous modules .

// Load libraries
google.load("maps", "2");
google.load("prototype", "1.6.0.3");

// on page load complete, initialize the application
google.setOnLoadCallback(function() {
  $(document.body).observe('onunload', GUnload);
  new Application;
});
Add autocomplete CSS

Just add <%= stylesheet_link_tag 'autocomplete' %> in the head of your page

Update your HTML

Now we got our required javascripts files loaded, let’s update our HTML to support Maptimize.AddressChooser

You’ll need to add the following fields and divs to your creation form

  <div id='suggests' class='auto_complete' style='display:none'></div>
  <div id="map" style="position: absolute; top: 10px; left: 400px; height: 350px; width: 350px;"></div>
  <%= f.hidden_field :lat %>
  <%= f.hidden_field :lng %>

While editing, we’ll need some slightly different fields to be able to stay unobtrusive but also take full advantage of the client side geolocalization.

  <div id='suggests' class='auto_complete' style='display:none'></div>
  <div id="map" style="position: absolute; top: 10px; left: 400px; height: 350px; width: 350px;"></div>
  <%= f.hidden_field :current_lat, :value => @business.lat %>
  <%= f.hidden_field :current_lng, :value => @business.lng %>
Initialize Maptimize.AddressChooser

Now we’ve got the HTML ready, our CSS and the required libraries let’s initialize Maptimize.AddressChooser to turn on the magic.

I’ve made couple of changes to work nicely in an unobtrusive way and while creating or editing any entry

From the demo application :

Application = Class.create({
  initialize: function() {
    this.initAddressChooser('business', {street: 'address'});
  },
  initAddressChooser: function(object, options) {
    // Init options
    options = $H({
      street: 'street',
      submit: 'submit',
      lat: 'lat',
      lng: 'lng',
      current_lat: 'current_lat',
      current_lng: 'current_lng',
      suggests: 'suggests'
    }).merge(options);

    // Check if current lat/lng are defined to use them
    var current_lat, current_lng;
    if ((current_lat = $(object+'_'+options.get('current_lat'))) && (current_lng = $(object+'_'+options.get('current_lng'))))
      current_lng.insert({after: '<input type="hidden" id="'+object+'_'+options.get('lat')+'" name="'+object+'['+options.get('lat')+']" value="'+current_lat.value+'" /><input type="hidden" id="'+object+'_'+options.get('lng')+'" name="'+object+'['+options.get('lng')+']" value="'+current_lng.value+'" />'});

    var street, submit;
    if (!(street = $(object+'_'+options.get('street'))) || !(submit = $(object+'_'+options.get('submit')))) return(this);

    // BEGIN AUTOCOMPLETE SETTINGS AND HACKS :)
    // Create a local autocomplete without data. Data will be added dynamically according to map suggestions
    var autocomplete = new Autocompleter.Local(street, options.get('suggests'), [],
      {
        afterUpdateElement: function(element, selectedElement) {
          var index = selectedElement.up().immediateDescendants().indexOf(selectedElement);
          widget.showPlacemark(index);
        },
        selector: function(instance) {
          instance.changed = false;
          return "<ul><li>" + instance.options.array.join('</li><li>') + "</li></ul>";
        }
      }
    );

    // Do not observe keyboard event
    autocomplete.onObserverEvent = function() {}

    // Wrap render to update map with selected placemarks
    autocomplete.render = autocomplete.render.wrap(function(method) {
      method();
      widget.showPlacemark(this.index);
    });
    // END AUTOCOMPLETE SETTINGS AND HACKS :)

    widget = new Maptimize.AddressChooser.Widget(
      { onInitialized: function(widget) {
          // Add default controls
          widget.getMap().setUIToDefault();

          widget.initMap();

          // Observe 'suggests:started' to display spinner and disable submit button
          widget.addEventListener('suggests:started', function() {
            street.addClassName('spinner');
          });

          // Observe 'suggests:found' to hide spinner and enable submit button if a placemark has been found
          widget.addEventListener('suggests:found', function(placemarks) {
            street.removeClassName('spinner');
            street.focus();

            // Reset autocomplete suggestions to new placemarks
            autocomplete.options.array.clear();
            if (placemarks && placemarks.length > 0) {
              for (var i = 0; i < placemarks.length; i++) {
                autocomplete.options.array.push(widget.getAddress(placemarks[i]));
              }
              // For autocomplete update
              autocomplete.getUpdatedChoices();
              autocomplete.show();
            }
            else {
              autocomplete.hide();
            }
          });

          street.focus();
        },
        street: object+'_'+options.get('street'),
        lat: object+'_'+options.get('lat'),
        lng: object+'_'+options.get('lng')
      }
    );

    return(this);
  }
});

3. Take advantage of the front side geolocalization

For this purpose, we have to update our model to update the cached address when needed but also prevent from using the geolocalization on server-side if the client have already done it on front-side.

class Business < ActiveRecord::Base
  attr_accessor :current_lat, :current_lng

  acts_as_mappable
  before_validation :geocode_address

  private
  def geocode_address
    if (self.geolocalized_address != self.address)
      if (self.current_lat == self.lat && self.current_lng == self.lng)
        geo=Geokit::Geocoders::MultiGeocoder.geocode(address)
        errors.add(:address, "Could not Geocode address") if !geo.success
        self.lat, self.lng, self.geolocalized_address = geo.lat, geo.lng, self.address if geo.success
      else
        self.geolocalized_address = self.address
      end
    end
  end
end

You will notice the 2 attributes accessors that will allow to check any client changes on the coordinates.

4. Map you object and keep optimized using Maptimize

When the time comes to work with maps (google maps in my case) and lot of markers, a problem comes up as you grow, TOO MUCH MARKERS!

Too much markers makes you maps loading horribly slow!

For this reasons, the experts at Xilinus came up with a elegant solution to deal with this, Maptimize

Maptimize take care of merging your markers as clusters to reduce your amount of visible items. That keep it much lighter to load but also nicer for the viewer.

I then recommend using Maptimize from the early stage. That keep your application easier to scale but also nicer to the end users.

Signup and config Maptimize

Once signed up, go in the “Security Settings” tab to get your MAP and API KEYS.

From here you could add this keys in your environment’s configuration but I feel that using a dedicated initializer will keep things cleaner and easier to maintain.

In the file config/initializers/maptimize_config.rb with :

# You'll need to add a cronjob to automatically synchronize your data with Maptimize running the following command line
#
# IMPORTANT: This will overwrite any file called *maptimize.csv* located in your app's tmp folder
# If you like to change this behavior update lib/tasks/maptimize.rake
#
# rake maptimize:sync

MAPTIMIZE_CSV_URL = "http://localhost:3000/businesses.csv" 
MAPTIMIZE_CSV_URL.freeze
MAPTIMIZE_AUTHENTICITY_TOKEN = "AUTHENTICITY_TOKEN" 
MAPTIMIZE_AUTHENTICITY_TOKEN.freeze
MAPTIMIZE_MAP_KEY = Rails.env.development? ? "DEVELOPMENT_KEY" : "PRODUCTION_KEY" 
MAPTIMIZE_MAP_KEY.freeze

Send your data to Maptimize

Create a CSV file generator

Thanks to rails, is this trivial.

Update your controller

In your controller update your index action, from the demo “businesses_controller.rb” :

  # GET /businesses
  # GET /businesses.xml
  # GET /businesses.csv
  def index
    @businesses = Business.all

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @businesses }
      format.csv  # index.csv.erb
    end
  end
Add “app/views/businesses/index.csv.erb
id,lat,lng
<%- @businesses.each do |business| -%>
<%= business.id.to_s + ',' + business.lat.to_s + ',' + business.lng.to_s %>
<%- end -%>

Well, that’s done! If you check http://localhost:3000/businesses.csv you’ll see a simple CSV file containing the minimum data required by Maptimize

Send your CSV file to Maptimize

This is something that you may like to run in a cronjob to keep your data synchronized automatically.

From a bash shell console, just run :

IMPORTANT: This will overwrite any file called “maptimize.csv” located in your app’s tmp folder If you like to change this behavior update lib/tasks/maptimize.rake


rake maptimize:sync

If everything worked as expected, your command should ends with :

<?xml version="1.0" encoding="UTF-8"?>
<import>
  <status>succeeded</status>
</import>

Show your objects on a map

Now that Maptimize have our data, it’s time to use them and get our map

Include the CSS and Javascript files required to integrate Maptimize
In your head section :
<link href="http://www.maptimize.com/stylesheets/cluster.css" rel="stylesheet" type="text/css" />
In the bottom of your page, right after Google jsapi file
<script src="http://www.maptimize.com/api/v1/<%= MAPTIMIZE_MAP_KEY %>/embed.js" type="text/javascript"></script>

Display your map

Add a div that will contain your map

In the demo, I’ve added the div in the index file “app/views/businesses/index.html.erb

<div id="map" style="width: 900px; height: 500px;"></div>
Initialize the map

In my case I’ll update “public/javascripts/application.js” to include :

Application = Class.create({
  initialize: function() {
    this.initAddressChooser('business', {street: 'address'})
      .initMap('map');
  },
  initAddressChooser: function(object, options) {
...
    return(this);
  },
  initMap: function(map) {
    if (!GBrowserIsCompatible() || !(map = $(map)))
      return(this);

    if (typeof Maptimize.Map == "undefined") {
      map.update("Maptimize key is not correctly set, check file config/initializers/maptimize_config.rb or \
                  run 'rake bootstrap' to fill database with some data");
    } else {
      // Create a new google map
      var map = new GMap2(map);

      // Center on the world
      map.setCenter(new GLatLng(47, 1), 2);

      // Add controls
      map.addControl(new GSmallMapControl());

      // Attach maptimize service
      window.maptimizeMap = new Maptimize.Map(map);
    }

    return(this);
  }
});
Show me the real deal!

The see the goodness of Maptimize you need to have a good bunch of entry, for this purpose you can run :

rake bootstrap

It will load 500 entries in your database.

Done?

That’s it! we have a map with all our objects localized, merged when needed, ... it can’t get better!

5. Show details when clicking on markers and clusters

“it can’t get better!” I said? Well it can!

What about showing our object details when we click a marker? Let’s do it!

Update our Maptimize initialization to bind actions on click for clusters and markers

Application = Class.create({
...
  initMap: function(map) {
    if (!GBrowserIsCompatible() || !(map = $(map)))
      return(this);

    if (typeof Maptimize.Map == "undefined") {
      map.update("Maptimize key is not correctly set, check file config/initializers/maptimize_config.rb or \
                  run 'rake bootstrap' to fill database with some data");
    } else {
      // Create a new google map
      var map = new GMap2(map);

      // Center on the world
      map.setCenter(new GLatLng(47, 1), 2);

      // Add controls
      map.addControl(new GSmallMapControl());

      // Attach maptimize service
      var app = this;
      window.maptimizeMap = new Maptimize.Map(map, {
        onMarkerClicked: function(marker) {
          return app.getMarkerDetails(marker, marker.getId());
        },
        onZoomMaxClusterClicked: function(cluster, ids) {
          return app.getMarkerDetails(cluster, ids);
        }
      });
    }

    return(this);
  },
  getMarkerDetails: function(object, ids) {
    return new Ajax.Request("/businesses/"+ids, {
      method: 'GET',
      onComplete: function(response) {
        object.getGMarker().openInfoWindowHtml(response.responseText);
      }
    });
  }
});

As you can see, the id of the marker will be used to call the show action of our object, “business” in this case.

What append when you click a cluster, the id string looks like “1,2,3

For this we will update our action to support multiple ids and show the expected details

Update our controller

Looking at “app/controllers/businesses_controller.rb” we’ll change the show method as follow :

  # GET /businesses/1
  # GET /businesses/1.xml
  # GET /businesses/1,2
  def show
    @businesses = Business.find(params[:id].split(','))
    @business = @businesses.first

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @business }
      format.js   # show.js.erb
    end
  end

The split call will allow us to get more IDS at once, to keep our initial formats working properly I keep the single object as well.

Add the view “app/views/businesses/show.js.erb

<%- @businesses.each do |business| -%>
<p><%= business.name %><br /><small><%= business.address %></small></p>
<%- end -%>

6. Bonus, use Maptimize makers when creating / editing entries

To use the Maptimized map with markers when creating / editing your entries, just do the tiny change in initAddressChooser from “/public/application.js

Include this code just after widget.getMap().setUIToDefault();

// Attach maptimize plugin
var maptimizeMap = new Maptimize.Map(widget.getMap());

AJAX InPlaceRichEditor 1.3

written by seb on February 18th, 2008 @ 12:43 AM

As of today the first release candidate of the AJAX InPlaceRichEditor version 1.3 have seen the public scene.

Update: Version 1.3.2: With TinyMCE 3.4.2, Prototype 1.7 and Scriptaculous 1.9.0. Add support for Internet Explorer 9

This new release is now using the fantastic TinyMCE 3.0 from MoxieCode

It include some new features and improvements, some wished ones finally achieved ;-) so let's discover them.

Multiple TinyMCE initializations

Now you need to give an extra parameter to AJAX.InPlaceRichEditor constructor, containing the TinyMCE options you wish to use.
The best way to do so is to keep all the initialization in the tiny_mce_init file as it may preload all needed modules and make InPlaceRichEdition more responsive, let's see what it mean.

In tiny_mce_init,js

var tinymce_options = {
  mode : "textareas",
  theme : "simple"
};

var tinymce_advanced_options = {
  mode : "textareas",
  theme : "advanced"
};

tinyMCE.init(tinymce_advanced_options);
tinyMCE.init(tinymce_options);

NOTICE: It's important to take care of the initialization order as the last one will be used by default for the normal editors.

Your AJAX.InPlaceRichEditor calls

<h1 id="tobeedited">To be edited w/ simple theme</h1>
<script>
// <![CDATA[
  new Ajax.InPlaceRichEditor($('tobeedited'), 'YOU_UPDATE_URL', {}, tinymce_options);
// ]]>
</script>

<h1 id="tobeeditedadvanced">To be edited w/ advanced theme</h1>
<script>
// <![CDATA[
  new Ajax.InPlaceRichEditor($('tobeeditedadvanced'), 'YOU_UPDATE_URL', {}, tinymce_advanced_options);
// ]]>
</script>

Features fixes and changes

  • The fieldPostCreation option is now properly working on InPlaceRichEditor side and is set to focus by default, however there's still some bugs with it in some browsers.
  • As in InPlaceRichEditor 1.2, the deprecation layer from scriptaculous' InPlaceEditoris ot present anymore, I believe that it's now the time to use the actual stuff in your scripts and extensions ;-)
  • The RichEditor will be disabled while loading external text if you are using the advanced theme, the simple one doesn't seem to allow it

Highly recomanded patch

In scriptaculous' InPlaceEditor 1.8.1 the failure handling is broken which doesn't help providing clean error explanations to users.
I brought few week ago a tiny patch to fix this InPlaceEditor onFailure issue that I highly recommend to all users.

Extra feature as InPlaceEditor patch

I needed for Tiare the ability to have the empty texts in edit mode for easier comprehension, for this reason I've published a patch to editOnBlank an InPlaceEditor or InPlaceRichEditor.

Rails 2.0 users' happiness

In Rails 2.0 a new security feature introduce an authenticity_token to every form and InPlaceRichEditor can work fine with this as well.
Even if it's still not an easy-as-pie feature, it's pretty easy to build a work around for it.

Railers, grab this InPlaceRichEditor 1.3_rc0 with rails 2.0.2 demo to find more about it.

Security notice

DO NEVER FORGET to clean code sent by InPlaceRichEditor, javascript cleaning is wonderful but purely useless when we speak about security.
Railers may consider using white_list but you can also use what ever way you like to keep yourself secure.

Tested

InPlaceRichEditor has been tested on FF 2, IE 6, IE 7, Safari 3 and Opera, all on Mac (except IE) and Windows

Some bugs relative to fieldPostCreation are still present on some browser

Thanks

Thank you to all contributors, from feedbacks to patches, it's always a pleasure to listen about your experience using InPlaceRichEditor.
A big thanks as well to the prototype and scriptaculous core team and contributors.
Of course big thanks to MoxieCode too for them great tool and support

Have fun

Patch for InPlaceEditor adding editOnBlank feature

written by seb on February 3rd, 2008 @ 11:24 AM

Since a while I'm running on own patched version of InPlaceEditor for Tiare, mainly to support editOnBlank which allow an empty container to be turned as editable just after initialization.

It turned to be useful as well if you accept blank response as it will keep in edit mode in this case

Notice

Please be aware that it's patching the actual version of InPlaceEditor, actually at revision 8787 on script.aculo.us 1.8.1 and it can make more damages on previous or later versions.

How to use
...
<head>
  ...
  <script src="javascripts/prototype.js" type="text/javascript"></script>
  <script src="javascripts/control.js" type="text/javascript"></script>
  <script src="javascripts/patch_inplaceeditor_1-8-1.js" type="text/javascript"></script>
  ...
</head>
...
Advise

I advise you to use the InPlaceEditor's failure patch as well.

Download

InPlaceEditor's editOnBlank extension

Have fun ;-)

Temporary patch to InPlaceEditor failure issues

written by seb on February 3rd, 2008 @ 12:32 AM

Working one the new InPlaceRichEditor version I first made a closer look again at InPlaceEditor from scriptaculous and found 2 bugs on the failure handling.

I first made a patch a help the core team fixing it but as I needed to apply this fix right away for my own use I made a small extension fixing this issues.

Notice

Please be aware that it's patching the actual version of InPlaceEditor, actually at revision 8787 on script.aculo.us 1.8.1 and it can make more damages on previous or later versions.

How to use

You just need to include the script after the original InPlaceEditor script

...
<head>
  ...
  <script src="javascripts/prototype.js" type="text/javascript"></script>
  <script src="javascripts/control.js" type="text/javascript"></script>
  <script src="javascripts/patch_inplaceeditor_1-8-1.js" type="text/javascript"></script>
  ...
</head>
...

UPDATE: You may also like to use the feature editOnBlank

Have fun ;-)

InPlaceRichEditor V 1.2 released

written by seb on January 30th, 2008 @ 11:14 PM

Hi,
I'm pleased to announce InPlaceRichEditor v1.2

For more details please refer to the download section

It's now time to work on the v1.3_rc0 using TinyMCE 3.0 ;-)

ZenCocoon got refreshed!

written by seb on January 19th, 2008 @ 01:48 AM

I just updated ZenCocoon.com with new services and a new design.

InPlaceRichEditor V 1.2_rc0 released

written by seb on November 15th, 2007 @ 11:28 AM

UPDATE InPlaceRichEditor Version 1.2_rc1 is out since a while ;-) No big changes, juste dependency ones.

Yesterday I've released In Place Rich Editor V 1.2_rc0, it come as a full package including functional and unit test.

This release of IPRE is a full rewrite and is made to work with Prototype 1.6 , Script.Aculo.Us 1.8 and TinyMCE 2.1.2

To get more details please have a look at http://InPlaceRichEditor.Box.Re

Windsurf trip

written by seb on October 22nd, 2007 @ 04:31 PM

Actually in Egypt for an other windsurf trip you can follow what's going on at http://sebastiengrosjean.com

I've put my site on my website builder as it really simpler for me to update than mephisto, to check out why give a look at http://tiarehq.com

Rails Plugin : Multi Site : Updated

written by seb on September 2nd, 2007 @ 11:51 PM

Hi,

I've find out that the plugin multi_site I made broke when using find(:include). Well it's now fix and ready to use.

http://box.svnrepository.com/svn/plugins/multi_site/

Have fun ;-)

Public Rails Plugins

written by seb on August 16th, 2007 @ 02:46 PM

Hi,

I published few rails plugins today : http://box.svnrepository.com/svn/plugins/

There are tested on Rails 1.2.3, don't forget to test them with your version of rails before using ;-)
And don't forget, I made them but are not responsable of any problems you can have using them.

Have fun ;-)