Scripty2 and AJAX autocompleter
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 eventui: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 highlightedobject
: 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
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
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
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"}]}
Maps, Geolocalization and Optimization with Maptimize
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 :
- start geo localizing your addresses with server side technologies and caching system
- add front side tools (in Javascript) to keep things lighter and nicer for the end user
- keep both side geo localization working nicely together and in an unobtrusive way
- map your localized object while keeping optimized, using Maptimize
- show details when clicking on markers and clusters
- 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 :
- Prototype JS
- Scriptaculous Effects and Controls
- AddressChooser
- 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
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
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
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
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!
I just updated ZenCocoon.com with new services and a new design.
InPlaceRichEditor V 1.2_rc0 released
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
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
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
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 ;-)