Source: src/ui/moopopup.js

// load the css stylesheet
ajs.css('moopopup.css');

// look at initialize method for class description
ajs.ui.moopopup = new Class({

  Implements: [Options, Chain, Events],
  options: {
    // window identifier
    id: 'moopopup',
    // window title
    title: null,
    // window width (px)
    width: 400,
    // window height (autofill the content if null)
    height: null,
    // x coordinate of the top left corner (if not given the window will be centered)
    top_left_x: null,
    // y coordinate of the top left corner (if not given the window will be centered)
    top_left_y: null,
    // minimum width (when resizing)
    min_width: 300,
    // minimum height (when resizing)
    min_height: 100,
    // maximum height of popoup contents
    max_body_height: null,
    // make window draggable
    draggable: true,
    // make window resizable
    resizable: true,
    // show over an overlay
    overlay: true,
    // close window when clicking out of it if overlay is true
    overlay_click_close_window: true,
    // url for ajax request contents
    url: null,
    // test content
    text: null,
    // html node to get content from
    html_node: null,
    // disable all active objects in the page when sowing the popoup
    disable_objects: true,
    // function called when the window is displayed
    onComplete: function() {},
    // function called when the window is closed
    onClose: function() {},
  },
  /**
   * @summary Layer window user interface.
   * @classdesc <p>moopopup is a plugin designed to display html content in a draggable, resizable popup window 
   *            (really a layer over the document). The content shown may be passed as an option, 
   *            taken from an existing html node or through an ajax request</p>
   * @constructs ajs.ui.moopopup
   * @param {Object} options The class options object.
   * @param {String} [options.id='moopopup'] The identifier (id attribute) of the window.
   * @param {String} [options.title=null] The window title.
   * @param {Number} [options.width=400] The window width in px.
   * @param {Number} [options.height=null] The window height in px. If null the height depends on the window's content.
   * @param {Number} [options.top_left_x=null] The x coordinate of the top left corner (if not given the window will be centered).
   * @param {Number} [options.top_left_y=null] The y coordinate of the top left corner (if not given the window will be centered).
   * @param {Number} [options.min_width=300] The minimum width in px when resizing.
   * @param {Number} [options.min_height=100] The minimum height in px when resizing.
   * @param {Number} [options.max_body_height=null] The maximum height of popoup contents.
   * @param {Boolean} [options.draggable=true] Whether or not the window should be draggable.
   * @param {Boolean} [options.resizable=true] Whether or not the window should be resizable.
   * @param {Boolean} [options.overlay=true] Whether or not to show the window over an overlay which covers the whole viewport.
   * @param {Boolean} [options.overlay_click_close_window=true] Whether or not to close the window when clicking over the overlay.
   * @param {String} [options.url=null] Url used to retrieve window contents through an ajax request.
   * @param {String} [options.text=null] The window content.
   * @param {String} [options.html_node=null] Html node to get the content from.
   * @param {Boolean} [options.disable_objects=true] Whether or not to disable all active objects in the page when sowing the popoup.
   * @param {Function} [options.onComplete=function(){}] Function called when the window is displayed
   * @param {Function} [options.onClose=function(){}] Function called when the window is closed
   * @example
   *  var popup = new ajs.ui.moopopup({
   *    width: 800,
   *    max_body_height: 600,
   *    text: 'My popup content'
   *  }):
   *
   */
  initialize: function(options) {

    this.showing = false;	

    if(typeOf(options) != null) {
      this.setOptions(options);
      this.checkOptions();
    }

    this.max_z_index = ajs.shared.getMaxZindex();

  },
  /**
   * @summary Checks and validates the passed options
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @protected
   * @return void
   */
  checkOptions: function() {

    var rexp = /[0-9]+/;
    if(!rexp.test(this.options.width) || this.options.width < this.options.minWidth) {
      this.options.width = this.options.min_width;
    }
    if(typeOf(this.options.height) != 'null' && (!rexp.test(this.options.height) || this.options.height < this.options.min_height)) {
      this.options.height = this.options.min_height;
    }

  }.protect(),
  /**
   * @summary Sets an options
   * @param {String} [option] The option name
   * @param {Mixed} [value] The option value
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @protected
   * @return void
   */
  setOption: function(option, value) {

    if(typeof this.options[option] != 'undefined') {
      this.options[option] = value;
      this.checkOptions();
    }

  },
  /**
   * @summary Displays the window
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @return void
   */
  display: function() {

    if(this.options.disable_objects) {
      this.disableObjects();
    }

    // if the overlay is shown then the popup is shown after the overlay animation
    if(this.options.overlay) {
      this.renderOverlay();
    }
    else {
      this.renderPopup();
    }

    this.showing = true;

  },
  /**
   * @summary Disables all objects in the document
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @protected
   * @return void
   */
  disableObjects: function() {

    for(var i=0; i<window.frames.length; i++) {
      var myFrame = window.frames[i];
      // iframe are in the same domain? if not can't disable objects inside
      if(this.sameDomain(myFrame)) {
        var obs = myFrame.document.getElementsByTagName('object');
        for(var ii=0; ii<obs.length; ii++) {
          obs[ii].style.visibility='hidden';
        }
      }
    }

    $$('object').each(function(item) {
      item.style.visibility='hidden';
    });

  }.protect(),
  /**
   * @summary Enables all objects in the document
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @protected
   * @return void
   */
  enableObjects: function() {

    for(var i=0;i<window.frames.length;i++) {
      var myFrame = window.frames[i];
      if(this.sameDomain(myFrame)) {
        var obs = myFrame.document.getElementsByTagName('object');
        for(var ii=0; ii<obs.length; ii++) {
          obs[ii].style.visibility='visible';
        }
      }
    }

    $$('object').each(function(item) {
      item.style.visibility='visible';
    });

  }.protect(),
  /**
   * @summary Checks if a window object is in the same domain of current window
   * @param {Object} [win] the window object
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @protected
   * @return true if the given window is in the same domain, false otherwise
   */
  sameDomain: function(win) {

    var H = location.href;
      local = H.substring(0, H.indexOf(location.pathname));
    try {
      win = win.document;
      return win && win.URL && win.URL.indexOf(local) == 0;
    }
    catch(e) {
      return false;
    }
  }.protect(),
  /**
   * @summary Renders an overlay over the whole viewport
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @protected
   * @return void
   */
  renderOverlay: function() {

    // get overlay dimensions
    var doc_dimensions = document.getScrollSize();

    this.overlay = new Element('div', {'class': 'moopopup-overlay'});
    this.overlay.setStyles({
      'width': doc_dimensions.x,
      'height': doc_dimensions.y
    });

    this.overlay.setStyle('z-index', ++this.max_z_index);

    this.overlay.inject(document.body);

    // overlay animation
    this.overlay_anim = new Fx.Tween(this.overlay, {property: 'opacity'});
    this.overlay_anim.start(0, 0.7).chain(function() { this.renderPopup(); }.bind(this));

    if(this.options.overlay_click_close_window) {
      this.overlay.addEvent('click', function(e) {
        var event = new DOMEvent(e);
        if(event.target != this.container) {
          this.close();
        }
      }.bind(this));
    }

  }.protect(),
  /**
   * @summary Renders the window
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @return void
   */
  renderPopup: function() {

    this.renderContainer();

    // always show it over all other elements
    this.setFocus();

    // insert text
    if(this.options.url) {

      this.loading = new Element('div', {'class': 'moopopup-loading'});
      this.loading.setStyle('visibility', 'hidden'); // ie can't get element dimensions if not in dom
      this.loading.inject(document.body, 'top');
      var viewport = ajs.shared.getViewport();
      this.loading.setStyles({
        'position':'absolute',
        'top': (viewport.cY-this.loading.getCoordinates().height/2)+'px',		
        'left': (viewport.cX-this.loading.getCoordinates().width/2)+'px',		
        'z-index': ++this.max_z_index
      });
      this.loading.setStyle('visibility', 'visible');
      this.request();
    }
    else {
      if(this.options.html_node) {
        var html_node = typeOf(this.options.html_node) == 'element'
          ? this.options.html_node 
          : $(this.options.html_node);

        if(typeOf(html_node) == 'element') {
          this.body.set('html', html_node.clone(true, true).get('html'));
        }
      }
      else {
        this.body.set('html', this.options.text);
      }

      // now let's position the popup
      this.position();
      // ...and make it visible
      this.container.setStyle('visibility', 'visible');
      this.fireEvent('complete');
    }

    // the popup is draggable?
    if(this.options.draggable) {
      this.makeDraggable();
    }
    // the popup is resizable?
    if(this.options.resizable) {
      this.makeResizable();
    }

  },
  /**
   * @summary Performs the ajax request and inserts the content in thw window body
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @protected
   * @return void
   */
  request: function() {

    var request = new Request.HTML({
      evalScripts: false,
      url: this.options.url,
      method:	'get',
      onComplete: function(responseTree, responseElements, responseHTML, responseJavaScript) {
        $(this.options.id).getChildren('.moopopup-body')[0].set('html', responseHTML);
        // eval script when html is ready inside the target element, not before
        eval(responseJavaScript);
        // wait a moment to let the browser charge images before reposition the container
        var self = this;
        (function() {
          self.loading.dispose();
          // now let's position the popup
          self.position();
          // ... and make it visible
          self.container.setStyle('visibility', 'visible')
          self.fireEvent('complete');
        }).delay(1000);
      }.bind(this)
    }).send();

  }.protect(),
  /**
   * @summary Renders the window container
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @protected
   * @return void
   */
  renderContainer: function() {

    this.container = new Element('div', {'id': this.options.id, 'class': 'moopopup-container'});	

    // why visibility hidden?
    // because the popoup is positionated only at the end, when all its content is rendered, and its dimensions known
    this.container.setStyles({
      'visibility': 'hidden',
      'width': this.options.width+'px'
    });

    this.container.addEvent('mousedown', function() {
      this.setFocus();
    }.bind(this));

    if(typeOf(this.options.height) == 'number') {
      this.container.setStyle('height', this.options.height+'px');
    }

    this.renderHeader();
    this.renderBody();

    this.container.inject(document.body, 'top');

  }.protect(),
  /**
   * @summary Makes the window draggable
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @protected
   * @return void
   */
  makeDraggable: function() {

    var doc_dimensions = document.getCoordinates();

    var drag_instance = new Drag(this.container, {
      'handle': this.header, 
      'limit':{'x':[0, (doc_dimensions.width-this.container.getCoordinates().width)], 'y':[0, ]}
    });

    this.header.setStyle('cursor', 'move');

  }.protect(),
  /**
   * @summary Makes the window resizable
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @protected
   * @return void
   */
  makeResizable: function() {

    var ico_resize = new Element('div', {'class': 'moopopup-resize'});

    ico_resize.inject(this.container, 'bottom');

    var ylimit = $(document.body).getSize().y-20;
    var xlimit = $(document.body).getSize().x-20;

    this.container.makeResizable({
      'handle': ico_resize,
      'limit': {'x':[this.options.min_width, xlimit], 'y':[this.options.min_height, ylimit]}
    });

  }.protect(),
  /**
   * @summary Renders the window's header
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @protected
   * @return void
   */
  renderHeader: function() {

    this.header = new Element('header', {'class': 'moopopup-header'});

    // is there a title?
    if(this.options.title) {
      var h1 = new Element('h1', {'class': 'moopopup-title'});
      h1.set('html', this.options.title);
      h1.inject(this.header, 'top');
    }

    // close button, always visible
    this.close_button = new Element('div', {'class': 'moopopup-closebutton'});
    this.close_button.set('text', '×');
    this.close_button.inject(this.header, 'bottom');	
    this.close_button.addEvent('click', this.close.bind(this));

    this.header.inject(this.container, 'top');

  }.protect(),
  /**
   * @summary Renders the window's body
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @protected
   * @return void
   */
  renderBody: function() {

    var viewport = ajs.shared.getViewport();

    this.body = new Element('div', {'class': 'moopopup-body'});	

    if(typeOf(this.options.max_body_height) == 'number') {
      this.body.setStyle('max-height', this.options.max_body_height+'px');
    }
    else {
      this.body.setStyle('max-height', (viewport.height - 120) + 'px');
    }

    this.body.inject(this.container, 'bottom');

  }.protect(),
  /**
   * @summary Window repositioning
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @return void
   */
  position: function() {

    var x, y;
    var viewport = ajs.shared.getViewport();

    // set the position to the center of viewport
    x = viewport.cX - this.container.getCoordinates().width / 2;
    y = viewport.cY - this.container.getCoordinates().height / 2;

    // want it in another position?
    if(this.options.top_left_x) {
      x = this.options.top_left_x;
    }

    if(this.options.top_left_y) {
      y = this.options.top_left_y;
    }

    this.container.setStyles({
      'top': y+'px',
      'left': x+'px'
    });

  },
  /**
   * @summary Moves the window to the front
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @return void
   */
  setFocus: function() {
    if(!this.container.style.zIndex || (this.container.getStyle('z-index').toInt() <= this.max_z_index)) {
      this.container.setStyle('z-index', ++this.max_z_index);
    }
  },
  /**
   * @summary Closes the window
   * @method
   * @memberof ajs.ui.moopopup.prototype
   * @return void
   */
  close: function() {

    if(this.showing) {
      if(this.options.disable_objects) {
        this.chain(this.container.dispose(), this.enableObjects());
      }
      else {
        this.container.dispose();
      }
      // animate the overlay before disposing it
      if(this.options.overlay) {
        this.overlay_anim.start(0.7, 0).chain(function() { this.overlay.dispose(); }.bind(this));
      }
      this.showing = false;
      this.fireEvent('close');
    }

  }

});