includes/clientside/static/messagebox.js
author Dan
Wed, 15 Apr 2009 14:23:48 -0400
changeset 911 facff435cdaa
parent 906 c949e82b8f49
child 1091 bb8e04f74819
permissions -rw-r--r--
Made Special:Log tolerate literal, unescaped slashes in page names (hack of sorts, but still...)

// Message box and visual effect system

/**
 * The ultimate message box framework for Javascript
 * Syntax is (almost) identical to the MessageBox command in NSIS
 * @param int type - a bitfield consisting of the MB_* constants
 * @param string title - the blue text at the top of the window
 * @param string text - HTML for the body of the message box
 * Properties:
 *   onclick - an array of functions to be called on button click events
 *             NOTE: key names are to be strings, and they must be the value of the input, CaSe-SeNsItIvE
 *   onbeforeclick - same as onclick but called before the messagebox div is destroyed
 * Methods:
 *   destroy: kills the running message box
 * Example:
 *   var my_message = new MessageBox(MB_OK|MB_ICONSTOP, 'Error logging in', 'The username and/or password is incorrect. Please check the username and retype your password');
 *   my_message.onclick['OK'] = function() {
 *       document.getElementById('password').value = '';
 *     };
 * Deps:
 *   Modern browser that supports DOM
 *   darken() and enlighten() (above)
 *   opacity() - required for darken() and enlighten()
 *   MB_* constants are defined in enano-lib-basic.js
 */

var mb_current_obj;
var mb_previously_had_darkener = false;

function MessageBox(type, title, message)
{
  if ( !aclDisableTransitionFX )
  {
    load_component('flyin');
  }
  
  var y = getScrollOffset();
  
  // Prevent multiple instances
  if ( document.getElementById('messageBox') )
    return;
  
  if ( document.getElementById('specialLayer_darkener') )
    if ( document.getElementById('specialLayer_darkener').style.display == 'block' )
      mb_previously_had_darkener = true;
  if ( !mb_previously_had_darkener )
    darken(true);
  if ( aclDisableTransitionFX )
  {
    document.getElementById('specialLayer_darkener').style.zIndex = '5';
  }
  var master_div = document.createElement('div');
  master_div.style.zIndex = getHighestZ() + 1;
  var mydiv = document.createElement('div');
  mydiv.style.height = '200px';
  w = getWidth();
  h = getHeight();
  if ( aclDisableTransitionFX )
  {
    master_div.style.left = ((w / 2) - 200)+'px';
    master_div.style.top = ((h / 2) + y - 120)+'px';
    master_div.style.position = 'absolute';
  }
  else
  {
    master_div.style.top = '-10000px';
    master_div.style.position = ( IE ) ? 'absolute' : 'fixed';
  }
  z = ( aclDisableTransitionFX ) ? document.getElementById('specialLayer_darkener').style.zIndex : getHighestZ();
  mydiv.style.backgroundColor = '#FFFFFF';
  mydiv.style.padding = '10px';
  mydiv.style.marginBottom = '1px';
  mydiv.id = 'messageBox';
  mydiv.style.overflow = 'auto';
  
  var buttondiv = document.createElement('div');
  
  mydiv.style.width = '400px';
  buttondiv.style.width = '400px';
  
  w = getWidth();
  h = getHeight();
  if ( aclDisableTransitionFX )
  {
    //buttondiv.style.left = ((w / 2) - 200)+'px';
    //buttondiv.style.top = ((h / 2) + y + 101)+'px';
  }
  //buttondiv.style.position = ( IE ) ? 'absolute' : 'fixed';
  z = ( aclDisableTransitionFX ) ? document.getElementById('specialLayer_darkener').style.zIndex : getHighestZ();
  buttondiv.style.backgroundColor = '#C0C0C0';
  buttondiv.style.padding = '10px';
  buttondiv.style.textAlign = 'right';
  buttondiv.style.verticalAlign = 'middle';
  buttondiv.id = 'messageBoxButtons';
  
  this.clickHandler = function() { messagebox_click(this, mb_current_obj); };
  
  if( ( type & MB_ICONINFORMATION || type & MB_ICONSTOP || type & MB_ICONQUESTION || type & MB_ICONEXCLAMATION ) && !(type & MB_ICONLOCK) )
  {
    mydiv.style.paddingLeft = '50px';
    mydiv.style.width = '360px';
    mydiv.style.backgroundRepeat = 'no-repeat';
    mydiv.style.backgroundPosition = '8px 8px';
  }
  else if ( type & MB_ICONLOCK )
  {
    mydiv.style.paddingLeft = '50px';
    mydiv.style.width = '360px';
    mydiv.style.backgroundRepeat = 'no-repeat';
  }
  
  if(type & MB_ICONINFORMATION)
  {
    mydiv.style.backgroundImage = 'url(\''+scriptPath+'/images/info.png\')';
  }
  
  if(type & MB_ICONQUESTION)
  {
    mydiv.style.backgroundImage = 'url(\''+scriptPath+'/images/question.png\')';
  }
  
  if(type & MB_ICONSTOP)
  {
    mydiv.style.backgroundImage = 'url(\''+scriptPath+'/images/error.png\')';
  }
  
  if(type & MB_ICONEXCLAMATION)
  {
    mydiv.style.backgroundImage = 'url(\''+scriptPath+'/images/warning.png\')';
  }
  
  if(type & MB_ICONLOCK)
  {
    mydiv.style.backgroundImage = 'url(\''+scriptPath+'/images/lock.png\')';
  }
  
  if(type & MB_OK)
  {
    btn = document.createElement('input');
    btn.type = 'button';
    btn.value = $lang.get('etc_ok');
    btn._GenericName = 'OK';
    btn.onclick = this.clickHandler;
    btn.style.margin = '0 3px';
    buttondiv.appendChild(btn);
  }
  
  if(type & MB_OKCANCEL)
  {
    btn = document.createElement('input');
    btn.type = 'button';
    btn.value = $lang.get('etc_ok');
    btn._GenericName = 'OK';
    btn.onclick = this.clickHandler;
    btn.style.margin = '0 3px';
    buttondiv.appendChild(btn);
    
    btn = document.createElement('input');
    btn.type = 'button';
    btn.value = $lang.get('etc_cancel');
    btn._GenericName = 'Cancel';
    btn.onclick = this.clickHandler;
    btn.style.margin = '0 3px';
    buttondiv.appendChild(btn);
  }
  
  if(type & MB_YESNO)
  {
    btn = document.createElement('input');
    btn.type = 'button';
    btn.value = $lang.get('etc_yes');
    btn._GenericName = 'Yes';
    btn.onclick = this.clickHandler;
    btn.style.margin = '0 3px';
    buttondiv.appendChild(btn);
    
    btn = document.createElement('input');
    btn.type = 'button';
    btn.value = $lang.get('etc_no');
    btn._GenericName = 'No';
    btn.onclick = this.clickHandler;
    btn.style.margin = '0 3px';
    buttondiv.appendChild(btn);
  }
  
  if(type & MB_YESNOCANCEL)
  {
    btn = document.createElement('input');
    btn.type = 'button';
    btn.value = $lang.get('etc_yes');
    btn._GenericName = 'Yes';
    btn.onclick = this.clickHandler;
    btn.style.margin = '0 3px';
    buttondiv.appendChild(btn);
    
    btn = document.createElement('input');
    btn.type = 'button';
    btn.value = $lang.get('etc_no');
    btn._GenericName = 'No';
    btn.onclick = this.clickHandler;
    btn.style.margin = '0 3px';
    buttondiv.appendChild(btn);
    
    btn = document.createElement('input');
    btn.type = 'button';
    btn.value = $lang.get('etc_cancel');
    btn._GenericName = 'Cancel';
    btn.onclick = this.clickHandler;
    btn.style.margin = '0 3px';
    buttondiv.appendChild(btn);
  }
  
  heading = document.createElement('h2');
  heading.innerHTML = title;
  heading.style.color = '#50A0D0';
  heading.style.fontFamily = 'trebuchet ms, verdana, arial, helvetica, sans-serif';
  heading.style.fontSize = '12pt';
  heading.style.fontWeight = 'lighter';
  heading.style.textTransform = 'lowercase';
  heading.style.marginTop = '0';
  mydiv.appendChild(heading);
  
  var text = document.createElement('div');
  text.innerHTML = String(message);
  this.text_area = text;
  mydiv.appendChild(text);
  
  this.updateContent = function(text)
    {
      this.text_area.innerHTML = text;
    };
    
  this.destroy = function()
    {
      var mbdiv = document.getElementById('messageBox');
      mbdiv.parentNode.removeChild(mbdiv.nextSibling);
      mbdiv.parentNode.removeChild(mbdiv);
      if ( !mb_previously_had_darkener )
        enlighten(true);
    };
  
  //domObjChangeOpac(0, mydiv);
  //domObjChangeOpac(0, master_div);
  
  body = document.getElementsByTagName('body');
  body = body[0];
  master_div.appendChild(mydiv);
  master_div.appendChild(buttondiv);
  
  body.appendChild(master_div);
  
  if ( !aclDisableTransitionFX )
    setTimeout('mb_runFlyIn();', 100);
  
  this.onclick = new Array();
  this.onbeforeclick = new Array();
  mb_current_obj = this;
}

var messagebox = MessageBox;

function mb_runFlyIn()
{
  var mydiv = document.getElementById('messageBox');
  var maindiv = mydiv.parentNode;
  fly_in_top(maindiv, true, false);
}

function messagebox_click(obj, mb)
{
  val = ( typeof ( obj._GenericName ) == 'string' ) ? obj._GenericName : obj.value;
  if(typeof mb.onbeforeclick[val] == 'function')
  {
    var o = mb.onbeforeclick[val];
    var resp = o();
    if ( resp )
      return false;
    o = false;
  }
  
  var mydiv = document.getElementById('messageBox');
  var maindiv = mydiv.parentNode;
  
  if ( aclDisableTransitionFX )
  {
    var mbdiv = document.getElementById('messageBox');
    mbdiv.parentNode.removeChild(mbdiv.nextSibling);
    mbdiv.parentNode.removeChild(mbdiv);
    if ( !mb_previously_had_darkener )
      enlighten(true);
  }
  else
  {
    var to = fly_out_top(maindiv, true, false);
    setTimeout("var mbdiv = document.getElementById('messageBox'); mbdiv.parentNode.removeChild(mbdiv.nextSibling); mbdiv.parentNode.removeChild(mbdiv); if ( !mb_previously_had_darkener ) enlighten(true);", to);
  }
  if(typeof mb.onclick[val] == 'function')
  {
    (mb.onclick[val])();
  }
}

function testMessageBox()
{
  mb = new MessageBox(MB_OKCANCEL|MB_ICONINFORMATION, 'Javascripted dynamic message boxes', 'This is soooooo coool, now if only document.createElement() worked in IE!<br />this is some more text<br /><br /><br /><br /><br />this is some more text<br /><br /><br /><br /><br />this is some more text<br /><br /><br /><br /><br />this is some more text<br /><br /><br /><br /><br />this is some more text<br /><br /><br /><br /><br />this is some more text<br /><br /><br /><br /><br />this is some more text<br /><br /><br /><br /><br />this is some more text');
  mb.onclick['OK'] = function()
    {
      alert('You clicked OK!');
    }
  mb.onbeforeclick['Cancel'] = function()
    {
      alert('You clicked Cancel!');
    }
}

/**
 * The miniPrompt function, for creating small prompts and dialogs. The window will be flown in and the window darkened with opac=0.4.
 * @param function Will be passed an HTMLElement that is the body of the prompt window; the function can do with this as it pleases
 */

function miniPrompt(call_on_create)
{
  if ( !aclDisableTransitionFX )
  {
    load_component(['flyin', 'jquery', 'jquery-ui', 'fadefilter']);
  }
  else
  {
    load_component(['fadefilter']);
  }
  var darkener = darken(aclDisableTransitionFX, 40, 'miniprompt_darkener');
  
  var wrapper = document.createElement('div');
  wrapper.className = 'miniprompt';
  var top = document.createElement('div');
  top.className = 'mp-top';
  var body = document.createElement('div');
  body.className = 'mp-body';
  var bottom = document.createElement('div');
  bottom.className = 'mp-bottom';
  if ( typeof(call_on_create) == 'function' )
  {
    call_on_create(body);
  }
  wrapper.appendChild(top);
  wrapper.appendChild(body);
  wrapper.appendChild(bottom);
  var left = ( getWidth() / 2 ) - ( 388 / 2 );
  wrapper.style.left = left + 'px';
  var top = getScrollOffset() - 27;
  wrapper.style.top = top + 'px';
  domObjChangeOpac(0, wrapper);
  var realbody = document.getElementsByTagName('body')[0];
  realbody.appendChild(wrapper);
  
  if ( aclDisableTransitionFX )
  {
    domObjChangeOpac(100, wrapper);
  }
  else
  {
    fly_in_top(wrapper, true, true);
    
    setTimeout(function()
      {
        domObjChangeOpac(100, wrapper);
      }, 40);
  }
  
  // set the darkener's onclick to refocus/shake the miniprompt
  darkener.miniprompt = wrapper;
  darkener.onclick = function()
  {
    if ( !aclDisableTransitionFX )
    {
      $(this.miniprompt).effect("pulsate", { times: 2 }, 200);
    }
  }
  
  return wrapper;
}

/**
 * For a given element, loops through the element and all of its ancestors looking for a miniPrompt div, and returns it. Returns false on failure.
 * @param object:HTMLElement Child node to scan
 * @return object
 */

function miniPromptGetParent(obj)
{
  while ( true )
  {
    // prevent infinite loops
    if ( !obj || obj.tagName == 'BODY' )
      return false;
    
    if ( $dynano(obj).hasClass('miniprompt') )
    {
      return obj;
    }
    obj = obj.parentNode;
  }
  return false;
}

/**
 * Destroys the first miniPrompt div encountered by recursively checking all parent nodes.
 * Usage: <a href="javascript:miniPromptDestroy(this);">click</a>
 * @param object:HTMLElement a child of the div.miniprompt
 * @param bool (DEPRECATED) If true, does not call enlighten().
 */

function miniPromptDestroy(obj, nofade)
{
  obj = miniPromptGetParent(obj);
  if ( !obj )
    return false;
  
  // found it
  var parent = obj.parentNode;
  // if ( !nofade )
  //   enlighten(aclDisableTransitionFX);
  enlighten((aclDisableTransitionFX || nofade), 'miniprompt_darkener');
  if ( aclDisableTransitionFX || nofade )
  {
    parent.removeChild(obj);
  }
  else
  {
    var timeout = fly_out_top(obj, true, true);
    setTimeout(function()
      {
        parent.removeChild(obj);
      }, timeout);
  }
}

/**
 * Simple test case
 */

function miniPromptTest()
{
  miniPrompt(function(div) { div.innerHTML = 'hello world! <a href="#" onclick="miniPromptDestroy(this); return false;">destroy me</a>'; });
}

/**
 * Message box system for miniPrompts. Less customization but easier to scale than the regular messageBox framework.
 * @example
 <code>
 miniPromptMessage({
   title: 'Delete page',
   message: 'Do you really want to delete this page? This is reversible unless you clear the page logs.',
   buttons: [
     {
       text: 'Delete',
       color: 'red',
       style: {
         fontWeight: 'bold'
       },
       onclick: function() {
         ajaxDeletePage();
         miniPromptDestroy(this);
       }
     },
     {
       text: 'cancel',
       onclick: function() {
         miniPromptDestroy(this);
       }
     }
   ]
 });
 </code>
 */

function miniPromptMessage(parms)
{
  if ( ( !parms.title && !parms.message ) || !parms.buttons )
    return false;
  
  return miniPrompt(function(parent)
    {
      try
      {
        if ( parms.title )
        {
          var h3 = document.createElement('h3');
          h3.appendChild(document.createTextNode(parms.title));
        }
        if ( parms.message )
        {
          var body = document.createElement('p');
          var message = parms.message.split(unescape('%0A'));
          for ( var i = 0; i < message.length; i++ )
          {
            body.appendChild(document.createTextNode(message[i]));
            if ( i + 1 < message.length )
              body.appendChild(document.createElement('br'));
          }
        }
        
        parent.style.textAlign = 'center';
        
        if ( parms.title )
          parent.appendChild(h3);
        if ( parms.message )
          parent.appendChild(body);
        parent.appendChild(document.createElement('br'));
        
        // construct buttons
        for ( var i = 0; i < parms.buttons.length; i++ )
        {
          var button = parms.buttons[i];
          button.input = document.createElement('a');
          button.input.href = '#';
          button.input.clickAction = button.onclick;
          button.input.className = 'abutton';
          if ( button.color )
          {
            button.input.className += ' abutton_' + button.color;
          }
          button.input.appendChild(document.createTextNode(button.text));
          if ( button.style )
          {
            for ( var j in button.style )
            {
              button.input.style[j] = button.style[j];
            }
          }
          if ( button.sprite )
          {
            var sprite = gen_sprite(button.sprite[0], button.sprite[1], button.sprite[2], button.sprite[3], button.sprite[4]);
            sprite.style.position = 'relative';
            sprite.style.top = '3px';
            button.input.insertBefore(sprite, button.input.firstChild);
            insertAfter(button.input, document.createTextNode(' '), sprite);
          }
          else if ( button.image )
          {
            button.input.className += ' icon';
            button.input.style.backgroundImage = 'url(' + button.image + ')';
          }
          button.input.onclick = function(e)
          {
            try
            {
              this.clickAction(e);
            }
            catch(e)
            {
              console.error(e);
            }
            return false;
          }
          parent.appendChild(button.input);
        }
        // don't focus this in opera - it looks kinda ugly
        if ( parms.buttons[0] && !window.opera )
        {
          var timeout = ( aclDisableTransitionFX ) ? 10 : 1000;
          setTimeout(function()
            {
              parms.buttons[0].input.focus();
            }, timeout);
        }
      }
      catch ( e )
      {
        console.error(e);
      }
    });
}

/**
 * Identical to whiteOutElement(), but safe to call on miniPrompt divs.
 */

function whiteOutMiniPrompt(el)
{
  var top = getScrollOffset();
  var left = ( getWidth() / 2 ) - ( 320 / 2);
  var width = 320;
  var height = $dynano(el).Height() - 58;
  
  var blackout = document.createElement('div');
  // using fixed here allows modal windows to be blacked out
  blackout.style.position = ( el.style.position == 'fixed' ) ? 'fixed' : 'absolute';
  blackout.style.top = top + 'px';
  blackout.style.left = left + 'px';
  blackout.style.width = width + 'px';
  blackout.style.height = height + 'px';
  
  blackout.style.backgroundColor = '#FFFFFF';
  domObjChangeOpac(60, blackout);
  var background = ( $dynano(el).Height() < 48 ) ? 'url(' + scriptPath + '/images/loading.gif)' : 'url(' + scriptPath + '/includes/clientside/tinymce/themes/advanced/skins/default/img/progress.gif)';
  blackout.style.backgroundImage = background;
  blackout.style.backgroundPosition = 'center center';
  blackout.style.backgroundRepeat = 'no-repeat';
  blackout.style.zIndex = '1000';
  
  var body = document.getElementsByTagName('body')[0];
  body.appendChild(blackout);
  
  return blackout;
}

function testMPMessageBox()
{
  miniPromptMessage({
    title: 'The Game of LIFE question #73',
    message: 'You just got your girlfriend pregnant. Please select an option:',
    buttons: [
      {
        text: 'Abort',
        color: 'red',
        style: {
          fontWeight: 'bold'
        },
        sprite: [ cdnPath + '/images/icons/abortretryignore-sprite.png', 16, 16, 0, 0 ],
        onclick: function() {
          miniPromptDestroy(this);
        }
      },
      {
        text: 'Retry',
        color: 'blue',
        sprite: [ cdnPath + '/images/icons/abortretryignore-sprite.png', 16, 16, 0, 16 ],
        onclick: function() {
          miniPromptDestroy(this);
        }
      },
      {
        text: 'Ignore',
        color: 'green',
        sprite: [ cdnPath + '/images/icons/abortretryignore-sprite.png', 16, 16, 0, 32 ],
        onclick: function() {
          miniPromptDestroy(this);
        }
      }
    ]
  });
}