Prototype-powered Popups

Okay, I admit this is a little silly but after seeing Peter Cooper make a Prototype-inspired popup object, I decided to make a Prototype-powered popup object. This requires Prototype's Object.extend() feature to map parameters over default values.

var Popup = {
  open: function(options)
  {
    this.options = {
      url: '#',
      width: 300,
      height: 300
    }
    Object.extend(this.options, options || {});
    window.open(this.options.url, '', 'width='+this.options.width+',height='+this.options.height);
  }
}

Popup.open({url:'http://www.example.com/'});

...or how about this variation that uses the Class object...

var Popup = Class.create();
Popup.prototype = 
{
  initialize: function(options)
  {
    this.options = {
      url: '#',
      width: 300,
      height: 300
    }
    Object.extend(this.options, options || {});
    window.open(this.options.url, '', 'width='+this.options.width+',height='+this.options.height);
  }
}

new Popup({url:'http://www.yahoo.com/'});

Now to get this Popup code into the Prototype base. ;)

Published February 18, 2006 · Updated September 14, 2006
Categorized as JavaScript
Short URL: https://snook.ca/s/530

Conversation

15 Comments · RSS feed
Greg Militello said on February 20, 2006

Jonathan,
I had a similar idea, but to replace javascripts alert() and confirm() functions. Mainly because you can not style them, or tweak them. It still has IE transparency issues, and Safari needs to click twice (working on both of those). Here is a link:

http://thinkof.net/projects/js/notify/notify.html

I also started on a prototype base row highlighter/table ruler here:

http://thinkof.net/projects/js/highlight/highlight.html

Greg Militello said on February 20, 2006

Now that I read your post a little more I realized what you were working on... :) My bad. I for some reason had something else in mind when I read your post. Anyway, feel free to check those projects out anyway... Especially if they include constructive critisism.

Peter Cooper said on February 22, 2006

Thanks for the reference, Jonathan. I must confess I didn't actually test my code as it was just a demonstration of the concept of using objects, but good luck with this, as I'd certainly find it useful!

Jason said on February 23, 2006

Excellent post. I always find it useful to see the extent that AJAX can be driven to. Greg, I really like the added links as well.

Matthew Pennell said on February 28, 2006

Just to clarify for any confused future readers - Jason's comment makes no sense at all, as there is no AJAX involved in these examples...

Jason *prime* said on April 09, 2006

Okay, so I've been using JSON to clean up my Javascript namespace, and I've been using Prototype for a lot of my recent Javascript needs. And I've seen tutorials everwhere, some using JSON and others using Prototype's class creation. What are the advantages/disadvantages to each of these approaches? I see the two different methods are similar, as you've demonstrated in this post, but are there instances where one is better than the other?

Jonathan Snook said on April 09, 2006

Jason: JSON was designed as a data format and not specifically as an object creation method.

Prototype's Class approach is handy if you want to make use of the initialize feature of Prototype. In Prototype, whenever you use Class.create(), the initialize method becomes your constructor method. Handy for setting default values and the like. In fact, you should only use Class.create() if you want to make use of the initialize feature; that is its sole purpose.

Jason *prime* said on April 09, 2006

I was referring to the second object method above. ie:
var Ex = {
var tmp : 'x',
var func : function(y){return z;}
}

I recognize that JSON is used for data transfer and I guess I mis-spoke. I believe my example above is simply called object notation, and it can clearly contain functions. So, my question then is why and where would one use the general object notation used above vs. class.create? I could see that class.create will give you a constructor, and an ability to create multiple object instances. Whereas the general object notation only reduces namespace clutter and creates a single instance. Are there any other 'features' of one over the other?

Jonathan Snook said on April 10, 2006

The only difference between object notation as in your example and Class.create is the constructor. Even with object notation, you can still establish new objects from it.

eg: var exo = new Ex();

Jonathan Snook said on September 01, 2006

coming back to this post, I just thought I'd correct myself in that you cannot instantiate a new object from an object literal.

Chris Cagle said on November 19, 2006

Thanks! just what i was looking for... but how would you enable scrollbars in this popup? ive tried but i keep breaking it to where it freezes the browser.

Thanks for any help...

yoav said on January 18, 2007

i've made a prototype popup class with a few more options to it....

you can send also options for the popup itself, through a window_options variable..

the class also extend enumerable so you can easily iterate & perform actions on the opened windows...

a window can be closed using the Popup.close() function.. just pass it the url used to originally open the window.
for example:
Popup.open('http://www.google.com/')

can be easily closed with:

Popup.close('http://www.google.com'/)

it can also be focused:
Popup.focus('http://www.google.com'/)

here is the code:


/*
* A Popup class to open new windows, using the Prototype.js lib.
*
* Basic Usage:
*   Popup.open( url_for_popup [, options [, window_options ] ] )
*
* Example:
*   Popup.open('http://www.google.com/');
* With options:
*   Popup.open('http://www.google.com/', { focus: true }, { statusbar: 1});
*
* To close the window:
*		Popup.close('http://www.google.com');
*
* To focus the window:
*		Popup.focus('http://www.google.com');
*/

var Popup = Object.extend(Object.extend(Enumerable), {
	// open a popup
	// only mandatory is the url for the popup
	open: function(url) {
		// get the options
		var options = Object.extend({
			name: 'popup',
			focus: false
		}, arguments[1] || {});

		// get the window options
		var window_options = $H(Object.extend({
			width: 640,
			height: 480,
			toolbar: 0,
			scrollbars: 1,
			location: 0,
			statusbar: 0,
			menubar: 0,
			resizable: 1
		}, arguments[2] || {}));

		Popup[url] = window.open(url, options.name, window_options.map(function(v, i) {
			return v[0] + '=' + v[1];
		}).join(', '));

		if (options.focus) {
			this.focus(url);
		}
		return Popup[url];
	},

	// close a popup window
	// pass it the original url used to open the popup
	close: function(url) {
		if (url in this) {
			this[url].close();
			delete this[url];
		}
	},

	// focus am open popup window
	// pass it original url used to open the popup
	focus: function(url) {
		if (url in this) {
			this[url].focus();
		}
	}
});

since it was offered by the original author of this post, i thought to put also a classed version...

open: var win = new Popup.Base('http://www.gooel.com');
and to close: win.close();
focus: win.focus

you can still use all the enumerable functions on Popup itself to iterate over the opened windows..
so you can still actually do calls like:
Popup['http://www.google.com'].close();

so :)


/*
* A Popup class to open new windows, using the Prototype.js lib.
*
* Basic Usage:
*		var win = new Popup.Base( url_for_popup [, options [, window_options ] ] )
*
* Example:
*		var win = new Popup.Base('http://www.google.com/');
* With options:
*   var win = new Popup.Base('http://www.google.com/', { focus: true }, { statusbar: 1});
*
* To close the window:
*		win.close();
*
* To focus the window:
*		win.focus();
*/

var Popup = Object.extend(Object.extend(Enumerable), {
});

Popup.Base = Class.create();
Popup.Base.prototype = {
	// open a popup
	// only mandatory is the url for the popup
	initialize: function() {
		this.url = arguments[0];
		// get the options
		this.options = Object.extend({
			name: 'popup',
			focus: false
		}, arguments[1] || {});

		// get the window options
		this.window_options = $H(Object.extend({
			width: 640,
			height: 480,
			toolbar: 0,
			scrollbars: 1,
			location: 0,
			statusbar: 0,
			menubar: 0,
			resizable: 1
		}, arguments[2] || {}));

		this.window = window.open(this.url, this.options.name, this.window_options.map(function(v, i) {
			return v[0] + '=' + v[1];
		}).join(', '));
		Popup[this.url] = this; // place in the popup object, to be able to iterate over it

		if (this.options.focus) {
			this.focus();
		}
	},

	// close a popup window
	close: function() {
		this.window.close();
		if (this.url in Popup) { // remove from the popup object
			delete Popup[this.url];
		}
	},

	// focus an open popup window
	focus: function() {
		this.window.focus();
	}
};
Joe Aston said on March 03, 2007

Thanks everyone for all your help.

This is just what I needed.

Does anyone know if there's any potential problems with yoav's code?

*Jonathan: you have the coolest surname ever! :)

Dogidoll said on April 30, 2007

Popup.Base = Class.create();
Popup.Base.prototype = {
// open a popup
// only mandatory is the url for the popup
initialize: function() {
this.url = arguments[0];
// get the options
this.options = Object.extend({
name: 'popup',
focus: false
}, arguments[1] || {});

// get the window options
this.window_options = $H(Object.extend({
width: 640,
height: 480,
toolbar: 0,
scrollbars: 1,
location: 0,
statusbar: 0,
menubar: 0,
resizable: 1
}, arguments[2] || {}));

this.window = window.open(this.url, this.options.name, this.window_options.map(function(v, i) {
return v[0] + '=' + v[1];
}).join(', '));
Popup[this.url] = this; // place in the popup object, to be able to iterate over it

if (this.options.focus) {
this.focus();

I can't manage it... doesnt work/

Lars said on September 01, 2008

Here is my version (works with Prototype 1.6.0.x):

popup = Class.create({
  popup: null,

  options: {
    url:        null,
    width:      600,
    height:     500,
    name:       '_blank',
    location:   'no',
    menubar:    'no',
    toolbar:    'no',
    status:     'yes',
    scrollbars: 'yes',
    resizable:  'yes',
    left:       null,
    top:        null,
    normal:     false,
    center:     false,
    focus:      false
  },

  initialize: function(options, element)
  {
    Object.extend(this.options, options || {});

    if (this.options.normal) {
      this.options.menubar  = 'yes';
      this.options.status   = 'yes';
      this.options.toolbar  = 'yes';
      this.options.location = 'yes';
    }

    if (Object.isElement(element) && this.options.url == null &&
        element.readAttribute('href') != null) {
      this.options.url = element.readAttribute('href');
    }

    this.options.width  = this.options.width < screen.availWidth ? this.options.width : screen.availWidth;
    this.options.height = this.options.height < screen.availHeight?  this.options.height : screen.availHeight;
  },

  open: function()
  {
    var openoptions = 'width=' + this.options.width
      + ', height=' + this.options.height
      + ', location=' + this.options.location
      + ', menubar=' + this.options.menubar
      + ', toolbar=' + this.options.toolbar
      + ', scrollbars=' + this.options.scrollbars
      + ', resizable=' + this.options.resizable
      + ', status=' + this.options.status

    if (this.options.center) {
      this.options.left = (screen.availWidth - this.options.width) / 2;
      this.options.top  = (this.options.height - this.options.height) / 2;
    }
    if (this.options.top != null) {
      openoptions+= ',top=' + this.options.top;
    }
    if (this.options.left != null) {
      openoptions+= ',left=' + this.options.left;
    }

    if (Object.isUndefined(window[this.options.name]) || window[this.options.name].closed) {
      window[this.options.name] = this.popup = window.open(this.options.url, this.options.name, openoptions);
    } else {
      window[this.options.name].location.href = this.options.url;
    }

    this.popup = window[this.options.name];

    if (this.popup) {
      if (this.options.focus) {
        this.focus();
      }

      return true;
    } else {
      return false;
    }
  },

  close: function() {
    if (thispopup != null) {
      this.popup.close();
      window[this.options.name] = null;
    }
  },

  focus: function() {
    if (this.popup != null) {
      this.popup.focus();
    }
  }
});
Sorry, comments are closed for this post. If you have any further questions or comments, feel free to send them to me directly.

Want to learn about scaling CSS for large projects?

I'm available for full and half-day workshops on scalable CSS architecture. I can provide on-site training for your team. Interested?
Get in touch.