strapyourself.in and flouri.sh
I'm working at Pivotal
MySQL query_reviewer plugin
query_analyzer on steriods!
Features
QueryReviewer is an advanced SQL query analyzer. It accomplishes the following goals:
- View all EXPLAIN output for all SELECT queries to generate a page
- Rate a page's SQL usage into one of three categories: OK, WARNING, CRITICAL
- Attach meaningful warnings to individual queries
- Find out where the query was executed with a stack trace
- Display advanced interactive summary on page
It accomplishes this by injecting an absolutely positioned div into your HTML markup before it's sent out of rails. View injection can be turned off, if necessary and replaced with <%= query_review_output %> somewhere in your view.
Screenshot
View larger
Project homepage
http://code.google.com/p/query-reviewer/
Installing
svn export http://query-reviewer.googlecode.com/svn/trunk/ vendor/plugins/query_reviewer
Then optionally run "rake query_reviewer:setup" and edit config/query_reviewer.yml to change the default settings.
Mephisto Flickr AJAX Loader
I'm tired of waiting for my blog to load because of a slow flickr feed request! I designed this new plugin by copying the flickr photostream plugin, but making it actually load the pictures and the feed through an immediate AJAX request. This tremendously increased the performance of my mephisto pages!
The plugin defines a flickr controller, which accepts the AJAX request. Unfortunately, you can't change how the photos are laid out anymore (I'm not setting up a liquid context for this ajax response). Of course, if you don't like the result of the plugin, just change it to match what you want:
class FlickrController < ActionController::Base def index result = "" pics = find_pictures pics.each do |pic| result << "<a href='#{pic.link}'><img src='#{pic.send(params[:format].to_sym)}' alt='#{pic.title}'></a>" end render :inline => result end end
How to install and use
- Uninstall the flickr photostream plugin
./script/plugin install http://wush.net/svn/public/plugins/mephisto/mephisto_flickr_ajax- Use the following tag in your liquid template:
{{flickrajaxphotostream feed:<YOUR_FEED_URL> count:<NUMBER_IMAGES> format:<[square, small, etc]>}}
{{endflickrajaxphotostream}}
Originally posted on ELC's blog
Rendering views without a web request in rails
Why the heck do you want to do that?
Views are very well integrated into the rails framework, but they're only typically rendered when an http request comes in. ActionMailer is the main exception, but what if you want to render a view for use in another backend application? These days, document fragments are being used everywhere, and often times I'll need rendered HTML for a use other than just sending it back to the requesting user.
A solution: instantiate a controller and view
Controllers are just objects, and so are views. We can instantiate a controller, instantiate a view, then point the view to the controller and we're ready to go. The only think we're missing is the session and the request objects, but not every view needs those. I used this once for updating a facebook profile using a backgroundrb worker:
class FakeView < ActionView::Base include SomeHelper include SomeOtherHelper end class FakeController < ActionController::Base def render_some_view action_view = FakeView.new(File.join(RAILS_ROOT, "app", "views"), {}) action_view.instance_variable_set("@controller", self) markup = action_view.render(:partial => 'facebook/your_profile') end end
By subclasses ActionView::Base, you can mix helpers into the view class, making their
methods available.
Another solution: use the test framework!
The rails TestProcess is the only place where views are rendered. If you really want to simulate a real experience when rendering a view, use the test process. First, you'll need an actual controller with an actual action you want to render in it, like this one:
class FacebookController before_filter :login_required def your_profile end end
Then, we create and instante a test as follows:
class FacebookTest include ActionController::TestProcess attr_reader :response def initialize require_dependency 'application' unless defined?(ApplicationController) @controller = UserScheduleEntryController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new end def render_your_profile(user) @controller.instance_variable_set("@user", user) # bypass login required get :your_profile @response end end test = FacebookTest.new test.render_your_profile(user) markup = test.response.body
The require_dependency was something I threw in because backgroundrb didn't
have some of the
required classes loaded at that point, it may not be something you need in your application.
Duplicate Migrations in Rails
Why we need duplicate migrations
Have you ever been working on a large project, and had people check in migrations with the same numbers? It's happened to me probably no less than 10 times in the last year. In each case, the situation is recoverable, but sometimes requires a lot of manual rolling back of specific migrations on possibly several machines. Then you have to renumber all the migrations after the conflict, of course.
An even worse situation is when a project is branched and remerged. For example, you might want to branch out several complicated features from trunk for a few weeks, then bring them back when complete. Assuming you create 2 feature branches (for adding profiles and friends to your users), you could end up with something like this:
- 036_modify_users_to_include_first_name.rb
- 037_create_profiles.rb
- 037_create_friendships.rb
- 037_fix_a_bug.rb
- 038_add_timestamps_to_friendships.rb
- 038_modify_accounts_to_limit_length.rb
- 039_modify_users_to_include_gender.rb
In the above situation, the person merging the two branches has a very difficult situation ahead. Everyone working on the project is probably on revision 37 (profiles branch), 38 (friends branch), or 39 (trunk). The safe way to proceed with traditional rails migrations is to force all machines be migrated down to 36. No new migrations can be added while the migrations are then renumbered so they range from 036 to 042. Finally, all users can update from trunk and run rake db:migrate. Of course, people often forget to migrate down, and end up stuck in the middle of a sequence of migrations that has been renumbered (I am so tired of reversing migrations by hand).
Solution: Allowing duplicate migration version numbers
In the above example, the 3 migrations numbered 37 are not dependent in any way. Because they had to be developed independently, duplicate version numbers are very rarely dependent. For this reason, we beleive that it is usually safe to create a "partial ordering" of migrations rather than an exact ordering. In this partial ordering (which can be represented as a lattice), migrations with the same version number will be run in an arbitrary order:
Since all of the dependencies in the above lattice flow downward, we can satisfy the partial ordering by running the migrations alphabetically by filename, alphabetizing them first by version number and then by class name. This will only work if we can make the assumption that when new migrations are added, they can only be dependent on those with smaller version numbers.
How the plugin works
Traditional rails schema_info table cannot hold enough information to keep track of
which migrations have been run, so we need to adopt a new schema format, which we place in a new
schema_infos table:
In this new schema, every record represents a migration that has been run. By traversing this table, we can get an accurate picture of the state of the system, and decide which migration to run next.
If we want to migrate to version 10, for example, we create an alphabetical listing of migrations up
to and including version 10(s). Then we traverse that list in order, running "up" on
migrations which have not been previously run, and inserting a record into schema_infos.
Finally, we create a list of migrations with version numbers
larger than 10, and run "down" on those in reverse alphabetical order, removing the entries
in schema_info.
A little under the hood
Below is the main migrate function. It does exactly what is discussed in the previous section:
def migrate_with_duplicates migration_classes_before(@target_version).each do |(version, migration_class)| next if schema_information_contains?(migration_class) ActiveRecord::Base.logger.info "Migrating up #{migration_class} (#{version})" migration_class.migrate(:up) insert_schema_information(migration_class) end migration_classes_after(@target_version).each do |(version, migration_class)| next if !schema_information_contains?(migration_class) ActiveRecord::Base.logger.info "Migrating down #{migration_class} (#{version})" migration_class.migrate(:down) remove_schema_information(migration_class) end end
What would be even better...
I've always wanted to write a migration system based on partial orderings where dependencies are
explicit, and version numbers are history. Such a system would work nicely on top of the new
schema_infos table format. The tricky part would be how to state the dependencies without
forcing the migration author to work too hard.
Download
From the ELC plugin repository: http://wush.net/svn/public/plugins/duplicate_migrations
To install:./script/plugin install -x http://wush.net/svn/public/plugins/duplicate_migrations
(installing automatically creates the schema_infos table and populates it, but does NOT delete your old schema_info table... don't panic!)
Originally posted on ELC's blog
Writing view helpers with 'yield'
Ever wanted to pass in blocks of view code to a helper? Think of the power! Say we want to make a helper method which surrounds whatever block you pass in with a firefox html designer iframe, here's how I'd imagine a user would want to use it:
<% html_editor(:width => 600, :height => 400) do %> <p><b>This html should be editable by the user!</b></p> <% end %>
Here's how you could write such a helper function:
class ApplicationHelper def html_editor(options = {}, &block) concat("<IFRAME WIDTH=#{options[:width] || 400} HEIGHT=#{options[:height || 200]} ID=myEditor>") yield concat("</IFRAME>") concat("<script>frames.myEditor.document.designMode = 'On'</script>") end end
Notice that I pass the block in as the last parameter "&block", but I never use it in the function! Ruby doesn't require that you pass the name of the block in when you yield, because it only supports the passing in of 0 or 1 blocks to a function (hence it always knows which one you're talking about). I like to pass it in as a named method parameter because it makes the method signature more clear.
Now let's improve on our initial version, by passing an object back to the view. This is exactly the way form_for passes back a |form| object to the view. Our updated use case is as follows:
<% html_editor(:width => 600, :height => 400) do |editor| %> <p><b>This html should be editable by the user!</b></p> <% editor.command_button("Bold", "bold") %> <% end %>
The "command_button" method above should insert a standard html button with the text "Bold" that executes the "bold" command on the selected text range inside the html editor. Let's see how this can be implemented:
class JsHtmlEditor attr_reader :output def command_button(name, command) @output ||= "" @output << "<button onclick='javascript: frames.myEditor.document.selection.createRange().execCommand(\"#{command}\")'> #{name}</button>\n" end end module ApplicationHelper def html_editor(options = {}, &block) editor = JsHtmlEditor.new("myEditor") concat("<IFRAME WIDTH=#{options[:width] || 400} HEIGHT=#{options[:height || 200]} ID='myEditor'>") yield editor concat("</IFRAME>") concat("<script>frames.myEditor.document.designMode = 'On'</script>") concat(editor.output) unless editor.output.blank? # stick the user buttons at the bottom end end
Pretty cool huh? When we create a new editor and yield it, we make its methods accessible to the view block. In this example, the user can execute editor.command_button whereever they want inside the block, but we collect the output of those statements and force them all to the bottom of the iframe (where they probably belong).
Another common use of blocks inside view helpers is simply to control whether or not they get yielded at all. If you don't call yield, the block never executes and never gets rendered. Often times we have complex conditions determining when a certain block of code should be rendered, and this approach can clean that up immensely.
I work at ELC
I've been working at ELC Technologies/Rightcart.com since February of 2006... and I love it there! In this section of my blog, you'll find all the posts that I did on the request of ELC.
Nesting document.write
Not a good idea - IE and Firefox treat differently
I'm not an advocate of document.write by any means, but it seems unavoidable when dealing with 3rd parties. Ad networks especially seem to rely on delivering ads using a remote script tag and document.write. They typically do not provide an alternative using the more modern and well defined XML DOM manipulation functions such as createElement and appendChild.
Let's examine two simple examples of using nested document.writes, and see how they are treated in both IE and firefox:
<div id="before_main_script">Before Main Script</div> <div id="main_script"> <script> document.write("<div id='before_sub_script'>Before Sub Script</div>"); document.write("<scr"+"ipt>document.write('<div id="hello_div">hello!</div>');</scr"+"ipt>") document.write("<div id='before_sub_script'>After Sub Script</div>"); </script> </div> <div id="after_main_script">After Main Script</div>
You can see how I escape the inside <script> and </script> tag so the browser doesn't think I'm closing the outside script tag. In both IE and firefox, this renders how you'd expect:
Output in both IE7 and Firefox2:
Before Main Script
Before Sub Script
hello!
After Sub Script
After Main Script
Firefox2 DOM using Firebug:
IE7 DOM using Internet Explorer Developer Toolbar:
Now let's make the example more complicated and watch things break down. All we've done is refactored the embedded script tag into an external script with a src attribute:
<div id="before_main_script">Before Main Script</div> <div id="main_script"> <script> document.write("<div id='before_sub_script'>Before Sub Script</div>"); document.write("<scr"+"ipt src='/say_hello.js'></scr"+"ipt>") document.write("<div id='after_sub_script'>After Sub Script</div>"); </script> </div> <div id="after_main_script">After Main Script</div>
Output in FireFox2:
Before Main Script
Before Sub Script
hello!
After Sub Script
After Main Script
Output in IE7:
Before Main Script
Before Sub Script
After Sub Script
hello!
After Main Script
IE7 DOM using Internet Explorer Developer Toolbar:
Unexpectedly, IE7 did not process the nested script tag as it was being written into the document stream, instead waiting until it had finished processing the parent script. As browser implementation goes, this approach should be easier, because you wouldn't have to save the running script context temporarily to being processing a new one, and finally resume the saved context. We've also tried the same code without the nested script tags, and this weird behavior goes away. Naturally, this can get you into serious trouble if you're including a remote script that defines certain variables which you make use of immediately!


You might be skeptical that this could ever become an issue in the real world. One of our clients had to generate some javascript to skin a 3rd party site to look like theirs, and they wanted to put double-click ads in. This requirement resulted in a javascript which executed a series of document.writes to skin the 3rd party site, including writing out a 2 inline script tags and 1 with SRC to doubleclick. The doubleclick script then used document.write to print out the ad. We first noticed the difference in placements of the ads in IE7 and Firefox, and ran the above tests to determine what the problem is. Here's how we solved it:
document.write("<scr"+"ipt src='/before_3rd_party.js'></scr"+"ipt>"); document.write("<scr"+"ipt src='3RD_PARTY_URL.js'></scr"+"ipt>"); document.write("<scr"+"ipt src='/after_3rd_party.js'></scr"+"ipt>");
Preloading fixtures
Ever wonder why it takes so gosh darn long to run your tests?
Do you hate it when forgetting to load a valuable fixture breaks your test?In my experience, long test times are due to two reasons: fixture loading and fixture instantiation
If you don't use instantiated fixtures such as @users_one, and instead use users(:one), then you can safely turn off instantiated fixtures, saving you all the time of finding the data. But what about fixture load time? If you use transactional fixtures, the loading only takes place once per test file, which could be 100-150 times in a given project. Without transactional fixtures, they are loaded before every test, clearly a waste of time!
My solution was to develop a plugin which proloads and preinstantiates the fixtures only once (well, 3 times was the best I could get). It takes advantage of transactions to rollback the state of the database between each individual test (don't try this on MySQL with bad storage engines like MyIASM, kids). It usually takes a few seconds, of course, but cut a down "rake test" for a large project of ours from 20 minutes to 90 seconds. Plus, you don't have to remember which fixtures you need for every single test file anymore! Try adding the globalization plugin, for example, and you'll realize you need 2 fixtures on every single page.
In my opinion, there's no reason not to preload all your fixtures once before running tests, but that's up to you to decide.
Plugin Usage
- Install the plugin
- Add the following to test/test_helper.rb
PreloadFixtures.preload! PreloadFixtures.instantiate!(Test::Unit::TestCase)
- The "fixtures" function in a test is now overloaded to do nothing. All fixtures in the directory will be loaded once and then the tests will be run.
Download Plugin
From our svn respository
./script/plugin install https://wush.net/svn/public/preload_fixtures
Using and testing multiple databases in rails part 2
I previously published a plugin on this blog that allows you to easily use, test, and migrate multiple databases in rails.
Since then, I've found numerous problems and areas that could be improved about this process. Here they are:
- All databases are automatically cloned through rake tasks that are dependent on standard "rake test" commands.
- Transactions are properly set up across ALL open connections during testing (if transactional fixtures are enabled)
- This plugin is now in use in a large-scale production environment!
- Compatibility with my preload fixtures plugin to make fixture loading super fast and transactional (coming soon to this blog)
Examples of how to use it:
To point a model at a different database:
class OtherDbBase < ActiveRecord::Base use_db :prefix => "otherdb" # will look for otherdb_development, otherdb_test, etc. in database.yml end
To have a migration run on a different database
class AddOtherDbStuff < ActiveRecord::Migration def self.database_model OtherDbBase end end
Sample config/use_db.yml file:
db1: prefix: db1_ db2: prefix: db2_
To run tests across multiple databases (requires file config/use_db.yml):
rake test
To have fixtures load into a different database:
# Do nothing! Just name the fixture the same name as the model is represents (in our case it would be other_db_bases.yml), and the fixture loader will use that class's database connection to insert fixtures.
# I realize this might limit some people, especially those with habtm fixutres on other DBs (since there's no model). I'd love your input on where we should specify which DB connection a fixture should use to load itself.
Want to download this plugin? Download from ELC's SVN repository OR:
./script/plugin install https://wush.net/svn/public/use_db
Why associated models don't save
Acts_as_taggable conflicting with has_one
In our dashboard app, we attempted to use the following in a controller:
def create @employee = Employee.new(params[:employee]) @employee.address = Address.new(params[:address]) @employee.save end class Employee < ActiveRecord::Base has_one :address, :as => :addressable acts_as_taggable end
Result: tags in params[:employee][:tag_list] weren't being saved correctly. I then changed the order in the model:
class Employee < ActiveRecord::Base acts_as_taggable has_one :address, :as => :addressable end
The tag_list gets saved! What gives?
Failed validation weirdness
The culprit in this case was validation. There are two after_save filters involved: acts_as_taggable and has_one. Acts_as_taggable always returns true, continuing the the filter chain, regardless of the success of tagging/tag updating (I this that's a bug actually). Not the case for has_one, however, where the rails system checks that the associated model saves before continuing the filter chain. In our test, we were not passing in enough params[:address] to make the Address.new save correctly.
class Address < ActiveRecord::Base belongs_to :addressable, :polymorphic => true validates_presence_of :street, :city, :state, :postal, :country
This situation was our own fault, but nonetheless not trivial to debug. Rails did not help us understand what went wrong, just that no SQL to save the tags was ever getting run.
class Employee < ActiveRecord::Base has_one :address, :as => :addressable validates_associated :address acts_as_taggable end
Moral of the story: always use validates_associated for has_one and has_many associations because they will not stop the main model from being saved if they fail validation!
Using and Testing Rails with Multiple Databases
Using multiple databases
I recently wrote a rails plugin called "use_db", which allowed you to use a different database for some of your ActiveRecord models. I started by reading this article (which was borrowed from the rails wiki), and decided to make a plugin out of it. You can use it in the following way:class SomeBase < ActiveRecord::Base use_db :prefix => "secdb_" self.abstract_class = true end class OtherDbModel < SomeBase endNow any calls to data in OtherDbModel will go to a database called "secdb_development" (or secdb_test, secdb_production, etc). The database.yml file could have the following additions to support this:
secdb_development: adapter: mysql database: secdb_development username: root secdb_test: adapter: mysql database: secdb_test username: root
Testing multiple databases
One issue with my plugin, as stated on the original article, is that testing becomes very difficult. First, fixtures are automatically inserted into the primary database. Second, other databases will not automatically have their schemas migrated from dev to test.
Solving the fixture problem
I first examined active_record/fixtures.rb and noticed the following problem:
def delete_existing_fixtures @connection.delete "DELETE FROM #{@table_name}", 'Fixture Delete' end def insert_fixtures values.each do |fixture| @connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert' end end
These two methods are called automatically by the test helper when loading fixtures for a test. @connection was originally set to ActiveRecord::Base.connection, so the existing solution was not going to work. To solve this, I overrode those two methods in my plugin and replaced them with the following code:
alias_method :rails_delete_existing_fixtures, :delete_existing_fixtures def delete_existing_fixtures m = get_model return rails_delete_existing_fixtures unless m && m.respond_to?(:uses_db?) && m.uses_db? connection = m.connection connection.delete "DELETE FROM #{m.table_name}", 'Fixture Delete' end alias_method :rails_insert_fixtures, :insert_fixtures def insert_fixtures m = get_model return rails_insert_fixtures unless m && m.respond_to?(:uses_db?) && m.uses_db? connection = m.connection values.each do |fixture| connection.execute "INSERT INTO #{m.table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert' end end
This first code attempts to get the model associated with a fixture. If found, it asks that model if it uses a different database. FInally, it uses the connection of the model to execute the fixture INSERT and DELETE SQL commands. If any of this process fails, it falls back on the existing rails fixture methods.
Solving the schema migration problem
Rails typically does schema migrations using a rake task which runs before "rake test". It typically divides the work into 3 segments, dump_db_structure, clone_db_structure, and purge_db. The sequence is as follows:
- dump_db_structure dumps the development schema without data to an adapter-specific SQL file
- purge_db deletes all rows from the test database
- clone_db_structure imports the SQL dump into the test database
I simply duplicated the existing rake code, and modified it to use a different database connection. At the end of the day, I could execute a single command to migrate a second database. I chose to execute the command in my test helped in the following manner:
unless defined?(MIGRATED_SEC_DB_FOR_TEST) UseDbTest.prepare_test_db(:prefix => "secdb_") MIGRATED_SEC_DB_FOR_TEST = true end
The syntax is very similar to the "use_db" helper.
Source code
Download the first release 0.0.1 of use_db rails plugin here.
UPDATE: Look for the next version of this plugin in this blog
RubyGems 0.91 and the "refresh" error
All of us over at ELCTech recently upgraded to RubyGems 0.91, only to find the following error during "gem" operations:
$ sudo gem install acts_as_ferret
ERROR: While executing gem ... (NoMethodError)
undefined method `refresh' for #<Hash:0x127c854>
Apparently the format of the gem cache files have changed, the new version cannot read some older versions of the cache. To fix this problem, delete your source_cache files in the following locations:
sudo rm /usr/local/lib/ruby/gems/1.8/source_cache
rm ~/.gem/source_cache
Looks like someone forgot to think about us old folks with ancient versions of RubyGems. In the future, I'll plan on running "gem update --system" more often!
Installing RMagick properly in OSX
I've seen the craziest problems with people installing the RMagick gem before. In this article, I will show you the procedure I use to fix any and all rmagick problems. First, I start by cleaning out any of the following packages:
sudo port uninstall imagemagick
sudo port uninstall graphicsmagick
sudo port uninstall ghostscript
sudo port uninstall freetype
sudo gem uninstall rmagick
Then, I reinstall them:
sudo port install freetype
sudo port install ghostscript
sudo port install imagemagick
sudo port install graphicsmagick
sudo gem install rmagick
This software cocktail has solved the following problems:
- RMagick gem won't compile native extensions
- RMagick can't find fonts - This is a really common problem, and we have seen a case where this approach will not solve this problem.
- RMagick renders text incorrectly and/or unreadable in the validates_captcha plugin
ImageScience is an alternative to RMagick which requires fewer native libraries, but not zero unfortunately. It may or may not be easier for you to use, depending on what you need RMagick for (it won't do text rendering, for example).
Beating The Browser's Iframe Security
I recently ran into the problem of cross domain communication between iframes while working on RightCart. We wanted to be able to pop up a LightBox outside the cart iframe, but the event had to be triggered by something happening inside the iframe. To make matters worse, the parent frame could be any domain and the child frame was always RightCart. After some research, I found that DoJo and the Windows Live Team claimed to have done this using nested iFrames.
No one else who is using this technique is willing to describe EXACTLY how it is to be done, so here we go:
The situation:
- Parent frame is http://www.somedomain.com/somepath/somefile
- Child iframe is http://rightcart.com/cart
- Parent executes some javascript initially which we control
- Child is completely controlled by us, and wants to call functions in parent
How I did it:
The child knows the URL of the parent through the HTTP referer (sic) header. If it creates a "subchild" iframe within the child pointing to the same top level URL as the parent, then 1-way communication is possible. How? The child can set the location of the subchild (but not read the location) and the parent can read the location of subchild, as long as the domain is the same as the parent.
So how do you transmit messages on a URL? Use the hash!
The hash is the portion of the URL after the # sign. You can change that portion of the URL and not mess with the browser. Most importantly, the browser will not go make a new request if it already has the page, regardless of what the hash value is.
For example, I could send a message to the parent by setting the location of the subchild using document.getElementById('...').src to http://www.somedomain.com#message_is_here. The parent can get the hash of the subchild by executing some javascript like window.frames[0].frames[0].location.hash. Got it? In order to send more complex messages, you'll want to Base64 encode the contents of the hash.
As for synchronization, I gave the messages sequence numbers, so that the parent knew if they had already seen the message. I also set a limit to how often messages could be sent (every 500 ms in my case). So the child had to queue messages if they came in too quickly:
function sendJavascriptToParent(js) { rightcart_comm_queue[rightcart_comm_queue.length] = js; }
This is important, because there's only 1-way communication involved and the parent cannot tell the child that it is ready for the next message. I set both frames up at a 500 ms interval to update and check the message respectively. This is not 100% reliable, but if you're worried, try simply checking for messages twice as often as your minimum update delay.
Here's my code to check for messages in the parent:
function check_for_comm() { var rcFrame = null; var innerFrame = null; rcFrame = window.frames["rightcart"]; if (rcFrame) { innerFrame = rcFrame.frames[0]; if (innerFrame) { var loc = innerFrame.location; debug_print("InnerFrame location = "+loc.href); var frag = loc.hash.substr(1); var seqnum = frag.split("!")[0]; var data = frag.split("!")[1]; debug_print("SeqNum: "+seqnum+", Data="+data); if (data.length > 0 && seqnum != comm_last_seq) { comm_last_seq = seqnum; var decoded_data = decodeBase64(data); debug_print("Running: "+decoded_data); eval(decoded_data); } } } }
And my code to send a message from the child:
function doSend() { if (rightcart_comm_queue.length > 0) { var seq = new Date().getTime(); var ifr = document.getElementById('rightcart_comm_frame'); var all_js = ''; for(var i=0; i<rightcart_comm_queue.length; i++) { var js = rightcart_comm_queue[i]; all_js += js+'\\n'; } var data = encodeBase64(all_js); var url = rightcart_comm_baseurl+'#'+seq+'!'+data; /*alert('Sending Data: '+all_js+' using url '+url);*/ ifr.src = url; rightcart_comm_queue = []; } }
I had trouble using the EXACT same URL as the parent for the subchild. To solve this, I dissected the URL and set it to robots.txt in the same domain. This will cause a browser request for the first message.