Saturday, June 27, 2009

Maintaining Javascript Pop-Up Window Communication Across Window Opener Page Loads

I came across a blog post that talks how to maintain a reference to javascript popup window while the parent window has been navigated away. This scenario doesn't want to reload the child popup window. I just quoted out from 1 Pixel Out. There is a really nick trick.

In the main window:

var popupWin = null;

function openPopup() {
   var url = "popup.htm";
   popupWin = open( "", "popupWin", "width=500,height=400" );
   if( !popupWin || popupWin.closed || !popupWin.doSomething ) {
      popupWin = window.open( url, "popupWin", "width=500,height=400" );
   } else {
      popupWin.focus();
   }
}

function doSomething() {
   openPopup();
   popupWin.doSomething();
}


In the popup:

self.focus();

function doSomething() {
   alert("I'm doing something");
}


http://www.bennadel.com/blog/89-Maintaining-Javascript-Pop-Up-Window-Communication-Across-Window-Opener-Page-Loads.htm
http://www.1pixelout.net/2005/04/19/cross-window-javascript-communication/
http://www.1pixelout.net/2006/12/15/cross-window-javascript-communication-20/
http://www.1pixelout.net/wp-content/downloads/popups20.zip

Cross-window Javascript communication

It reminds me about uploading via iframe that I did a year ago when my colleague asked me to help with login openid in a popup window. It's a similar story with this problem. Actually, login with openid could not place in a iframe because you could the code that prevents this.

<script type="text/javascript">if(top == self) { document.write(""); } else { top.location.href = "http://www.yahoo.com"; }</script>

Now, let's see a quick summary on this basic communication.

Communication from parent to child window, you need to a reference of the child window so that can call any function in the child window.

// Create a new popup window
var popupWin = window.open(url, "popupWin");

// To call functions defined in the popup:
popupWin.doSomething();

Communication from child to parent window, you need to use this way:

window.opener.doSomethingOnParent();

Here is the problem, the parent window needs to know when the uploading (in iframe) or logging in (in popup window) is done. The only way that the parent window can notified by the child window after finish processing. Usually, for uploading and logging in with openid, the action in your controller would render a view back. The trick is here on the onload of the body, you could notify the parent window.

def login
@status = "something"
end

#view
<html><head></head>
<body onload="window.opener.handleOpenIDResponse('" + @status + "');window.close();">
</body>
</html>

That would solve the problem, and you could send any information back through your view.

Thursday, June 18, 2009

has_and_belongs_to_many or has_many :through?

Well, I had been quite confusing about these two topics before I read some rails books. These are just different ways to do many to many relationships in ActiveRecord.

has_and_belongs_to_many (habtm)
habtm is the very old way since rails 1.2. It creates a link between associated models through an intermediate join table.

class CreateProjectsProgrammers < ActiveRecord::Migration
   def self.up
      create_table :projects_programmers, :id => false do |t|
         t.column :project_id, :integer, :null => false
         t.column :programmer_id, :integer, :null => false
      end
   end
   def self.down
      drop_table :projects_programmers
   end
end
class Programmer < ActiveRecord::Base
   has_and_belongs_to_many :projects # foreign keys in the join table
end
class Project < ActiveRecord::Base
   has_and_belongs_to_many :programmers # foreign keys in the join table
end

Note that an id primary key is not needed in the join table and there is no join model, only join table. Here we will face a problem when we want to add extra columns on the join table. If it is in rails 1.2, we would have used push_with_attributes to do this. However, push_with_attributes has been deprecated in favor of a far more powerful technique, where regular Active Record models are used as join tables (remember that with habtm, the join table is not an Active Record object).

To conclude, habtm is a simple way to do a many-to-many relationship using a join table when the join table doesn't have extra columns. You will need to upgrade the relationship to use has_many :through once you need to add additional columns.

has_many :through
Records in the join table of habtm implementation has no independent existence. Later, we will find it very soon that the join table has a life of its own and should have a model when we add extra columns on that join table. Let's talk about relationship between article, user, and the join model is reading.

When a user reads an article, we can record the fact.

class Article < ActiveRecord::Base
   has_many :readings
end
class User < ActiveRecord::Base
   has_many :readings
end
class Reading < ActiveRecord::Base
   belongs_to :article
   belongs_to :user
end

reading = Reading.new
reading.rating = params[:rating]
reading.read_at = Time.now
reading.article = current_article
reading.user = session[:user]
reading.save


Here we lost what habtm solved. We could not ask a user which articles that they has read and vice versa. To solve this, use :through options inside has_many.

class Article < ActiveRecord::Base
   has_many :readings
   has_many :users, :through => :readings
end
class Reading < ActiveRecord::Base
   belongs_to :article
   belongs_to :user
end
class User < ActiveRecord::Base
   has_many :readings
   has_many :articles, :through => :readings
end

Now, you could do query both direction:

readers = an_article.users
articles = a_reader.articles


Unlike a normal has_many, ActiveRecord won’t let us add an object to the the has_many :through association if both ends of the relationship are unsaved records. The create method saves the record before adding it, so it does work as expected, provided the parent object isn’t unsaved itself. To add extra attributes:


user.readings.create(:read_at => Time.now,
         :rating => params[:rating],
         :article => Article.new)


Choosing which way to build a many-to-many relationship is not always simple. If you need to work with the relationship model as its own entity, use has_many :through. Use has_and_belongs_to_many when working with legacy schemas or when you never work directly with the relationship itself.

http://blog.hasmanythrough.com/2006/4/20/many-to-many-dance-off
http://blog.hasmanythrough.com/2006/4/17/join-models-not-proxy-collections

Wednesday, June 3, 2009

Proper use of session

Consider the following scenario, you want to store information about current user. After successful login, you might do this.

user = User.authenticate(params[:user_name], params[:password])
if user
   session[:current_user] = user.attributes
else
   flash[:notice] = "Email and password do not match."
   redirect_to :controller => "login"
end


Everything would work as you expected except when you try to change the structure of your session. This would make sessions of online users invalid while they are using your web application. For example, they will feel annoying while they are adding items to their wish lists. Another problem is that you want to make that session invalid after you delete that user account, for example. That won't work because you stored the entire record in his session. The only way to do is to add before_filter in application controller to check the existence of the current user. That would make another job to do it.

The best practice is store only simple data in the session: strings, numbers, and so on. Keep your application objects in the database, and then reference them using their primary keys from the session data.

class ApplicationController < ActionController::Base
   before_filter :get_current_user

   private
   def get_current_user
      @current_user = User.find_by_id(session[:user_id])
   end
end

Here you can access @current_user everywhere in your application and solve many issues during development.

Subscribe in a Reader