includes/clientside/static/rank-manager.js
author Dan
Sun, 25 Jan 2009 20:35:06 -0500
changeset 823 4596c40aaa94
parent 794 720516162012
child 1227 bdac73ed481e
permissions -rw-r--r--
AJAX core library: possible breaking change, readystatechange functions are now called with the XHR instance as the first parameter, to allow requests to run in parallel. This means much better stability but may break some applets (compatibility hack is included)

/**
 * Creates a control that can be used to edit a rank.
 */

var RankEditorControl = function(rankdata)
{
  this.rankdata = ( typeof(rankdata) == 'object' ) ? rankdata : {};
  if ( !this.rankdata.rank_style )
  {
    this.rankdata.rank_style = '';
  }
  
  // have the browser parse CSS for us and use an anchor to be as close
  // as possible in calculating CSS
  
  // this is kind of a hack as it relies on setAttribute/getAttribute in
  // order to obtain stringified versions of CSS data
  var cssobj = document.createElement('a');
  cssobj.setAttribute('style', this.rankdata.rank_style);
  
  this.style_sim_obj = cssobj;
  
  // figure out if we're editing or creating
  this.editing = ( typeof(this.rankdata.rank_id) == 'number' );
  
  this.render = function()
  {
    var editor = document.createElement('div');
    editor.className = 'tblholder';
    // stash this editor instance in the parent div for later function calls
    editor.editor = this;
    this.wrapperdiv = editor;
    editor.style.width = '100%';
    
    // tables suck.
    var table = document.createElement('table');
    table.setAttribute('cellspacing', '1');
    table.setAttribute('cellpadding', '4');
    table.setAttribute('width', '100%');
    
    // heading: "Edit rank: foo" or "Create a new rank"
    var tr_head = document.createElement('tr');
    var th_head = document.createElement('th');
    th_head.setAttribute('colspan', '2');
    if ( this.editing )
    {
      var th_head_string = 'acpur_th_edit_rank';
      var th_head_data = { rank_title: $lang.get(this.rankdata.rank_title) };
    }
    else
    {
      var th_head_string = 'acpur_th_create_rank';
      var th_head_data = { };
    }
    th_head.appendChild(document.createTextNode($lang.get(th_head_string, th_head_data)));
    tr_head.appendChild(th_head);
    this.th_head = th_head;
    table.appendChild(tr_head);
    
    // row: rank title
    var tr_title = document.createElement('tr');
    var td_title_l = document.createElement('td');
    var td_title_f = document.createElement('td');
    
    td_title_l.className = td_title_f.className = 'row1';
    
    td_title_l.appendChild(document.createTextNode($lang.get('acpur_field_rank_title')));
    
    // field: rank title
    var f_rank_title = document.createElement('input');
    f_rank_title.type = 'text';
    f_rank_title.size = '30';
    f_rank_title.value = ( this.editing ) ? this.rankdata.rank_title : '';
    f_rank_title.editor = this;
    f_rank_title.onkeyup = function()
    {
      this.editor.renderPreview();
    }
    this.f_rank_title = f_rank_title;
    td_title_f.appendChild(f_rank_title);
    
    tr_title.appendChild(td_title_l);
    tr_title.appendChild(td_title_f);
    table.appendChild(tr_title);
    
    // row: basic style options
    var tr_basic = document.createElement('tr');
    var td_basic_l = document.createElement('td');
    var td_basic_f = document.createElement('td');
    
    td_basic_l.className = td_basic_f.className = 'row2';
    
    td_basic_l.appendChild(document.createTextNode($lang.get('acpur_field_style_basic')));
    
    // fieldset: basic style options
    // field: bold
    var l_basic_bold = document.createElement('label');
    var f_basic_bold = document.createElement('input');
    f_basic_bold.type = 'checkbox';
    f_basic_bold.checked = ( this.style_sim_obj.style.fontWeight == 'bold' ) ? true : false;
    f_basic_bold.editor = this;
    f_basic_bold.onclick = function()
    {
      this.editor.style_sim_obj.style.fontWeight = ( this.checked ) ? 'bold' : null;
      this.editor.renderPreview();
    }
    l_basic_bold.style.fontWeight = 'bold';
    l_basic_bold.appendChild(f_basic_bold);
    l_basic_bold.appendChild(document.createTextNode(' '));
    l_basic_bold.appendChild(document.createTextNode($lang.get('acpur_field_style_basic_bold')));
    
    // field: italic
    var l_basic_italic = document.createElement('label');
    var f_basic_italic = document.createElement('input');
    f_basic_italic.type = 'checkbox';
    f_basic_italic.checked = ( this.style_sim_obj.style.fontStyle == 'italic' ) ? true : false;
    f_basic_italic.editor = this;
    f_basic_italic.onclick = function()
    {
      this.editor.style_sim_obj.style.fontStyle = ( this.checked ) ? 'italic' : null;
      this.editor.renderPreview();
    }
    l_basic_italic.style.fontStyle = 'italic';
    l_basic_italic.appendChild(f_basic_italic);
    l_basic_italic.appendChild(document.createTextNode(' '));
    l_basic_italic.appendChild(document.createTextNode($lang.get('acpur_field_style_basic_italic')));
    
    // field: underline
    var l_basic_underline = document.createElement('label');
    var f_basic_underline = document.createElement('input');
    f_basic_underline.type = 'checkbox';
    f_basic_underline.checked = ( this.style_sim_obj.style.textDecoration == 'underline' ) ? true : false;
    f_basic_underline.editor = this;
    f_basic_underline.onclick = function()
    {
      this.editor.style_sim_obj.style.textDecoration = ( this.checked ) ? 'underline' : null;
      this.editor.renderPreview();
    }
    l_basic_underline.style.textDecoration = 'underline';
    l_basic_underline.appendChild(f_basic_underline);
    l_basic_underline.appendChild(document.createTextNode(' '));
    l_basic_underline.appendChild(document.createTextNode($lang.get('acpur_field_style_basic_underline')));
    
    // finish up formatting row#1
    td_basic_f.appendChild(l_basic_bold);
    td_basic_f.appendChild(document.createTextNode(' '));
    td_basic_f.appendChild(l_basic_italic);
    td_basic_f.appendChild(document.createTextNode(' '));
    td_basic_f.appendChild(l_basic_underline);
    
    tr_basic.appendChild(td_basic_l);
    tr_basic.appendChild(td_basic_f);
    table.appendChild(tr_basic);
    
    // row: rank color
    var tr_color = document.createElement('tr');
    var td_color_l = document.createElement('td');
    var td_color_f = document.createElement('td');
    
    td_color_l.className = td_color_f.className = 'row1';
    
    td_color_l.appendChild(document.createTextNode($lang.get('acpur_field_style_color')));
    
    // field: rank color
    var f_rank_color = document.createElement('input');
    f_rank_color.type = 'text';
    f_rank_color.size = '7';
    f_rank_color.value = ( this.editing ) ? this.rgb2hex(this.style_sim_obj.style.color) : '';
    f_rank_color.style.backgroundColor = this.style_sim_obj.style.color;
    f_rank_color.editor = this;
    this.f_rank_color = f_rank_color;
    f_rank_color.onkeyup = function(e)
    {
      if ( !e.keyCode )
        e = window.event;
      if ( !e )
        return false;
      var chr = (String.fromCharCode(e.keyCode)).toLowerCase();
      this.value = this.value.replace(/[^a-fA-F0-9]/g, '');
      if ( this.value.length > 6 )
      {
        this.value = this.value.substr(0, 6);
      }
      if ( this.value.length == 6 || this.value.length == 3 )
      {
        this.style.backgroundColor = '#' + this.value;
        this.editor.style_sim_obj.style.color = '#' + this.value;
        this.style.color = '#' + this.editor.determineLightness(this.value);
        this.editor.renderPreview();
      }
      else if ( this.value.length == 0 )
      {
        this.style.backgroundColor = null;
        this.editor.style_sim_obj.style.color = null;
        this.editor.renderPreview();
      }
    }
    td_color_f.appendChild(f_rank_color);
    
    tr_color.appendChild(td_color_l);
    tr_color.appendChild(td_color_f);
    table.appendChild(tr_color);
    
    // field: additional CSS
    var tr_css = document.createElement('tr');
    
    var td_css_l = document.createElement('td');
    td_css_l.className = 'row2';
    td_css_l.appendChild(document.createTextNode($lang.get('acpur_field_style_css')));
    tr_css.appendChild(td_css_l);
    
    var td_css_f = document.createElement('td');
    td_css_f.className = 'row2';
    var f_css = document.createElement('input');
    f_css.type = 'text';
    f_css.value = this.stripBasicCSSAttributes(this.rankdata.rank_style);
    f_css.style.width = '98%';
    f_css.editor = this;
    f_css.onkeyup = function()
    {
      if ( !(trim(this.value)).match(/^((([a-z-]+):(.+?);)+)?$/) )
        return;
      var newcss = this.editor.stripExtendedCSSAttributes(String(this.editor.style_sim_obj.getAttribute('style'))) + ' ' + this.value;
      this.editor.preview_div.setAttribute('style', 'font-size: x-large; ' + newcss);
      this.editor.style_sim_obj.setAttribute('style', newcss);
    }
    this.f_css = f_css;
    td_css_f.appendChild(f_css);
    tr_css.appendChild(td_css_f);
    table.appendChild(tr_css);
    
    // "field": preview
    var tr_preview = document.createElement('tr');
    var td_preview_l = document.createElement('td');
    td_preview_l.className = 'row1';
    td_preview_l.appendChild(document.createTextNode($lang.get('acpur_field_preview')));
    tr_preview.appendChild(td_preview_l);
    
    var td_preview_f = document.createElement('td');
    td_preview_f.className = 'row1';
    var div_preview = document.createElement('a');
    this.preview_div = div_preview;
    div_preview.style.fontSize = 'x-large';
    div_preview.appendChild(document.createTextNode(''));
    div_preview.firstChild.nodeValue = ( this.editing ) ? this.rankdata.rank_title : '';
    td_preview_f.appendChild(div_preview);
    tr_preview.appendChild(td_preview_f);
    
    table.appendChild(tr_preview);
    
    // submit button
    var tr_submit = document.createElement('tr');
    var th_submit = document.createElement('th');
    th_submit.className = 'subhead';
    th_submit.setAttribute('colspan', '2');
    var btn_submit = document.createElement('input');
    btn_submit.type = 'submit';
    btn_submit.value = ( this.editing ) ? $lang.get('acpur_btn_save') : $lang.get('acpur_btn_create_submit');
    btn_submit.editor = this;
    btn_submit.style.fontWeight = 'bold';
    btn_submit.onclick = function(e)
    {
      this.editor.submitEvent(e);
    }
    this.btn_submit = btn_submit;
    th_submit.appendChild(btn_submit);
    
    // delete button
    if ( this.editing )
    {
      var btn_delete = document.createElement('input');
      btn_delete.type = 'button';
      btn_delete.value = $lang.get('acpur_btn_delete');
      btn_delete.editor = this;
      btn_delete.onclick = function(e)
      {
        this.editor.deleteEvent(e);
      }
      th_submit.appendChild(document.createTextNode(' '));
      th_submit.appendChild(btn_delete);
    }
    
    tr_submit.appendChild(th_submit);
    
    table.appendChild(tr_submit);
    
    // render preview
    this.renderPreview();
    
    // finalize the editor table
    editor.appendChild(table);
    
    // stash rendered editor
    this.editordiv = editor;
    
    // send output
    return editor;
  }
  
  /**
   * Takes the existing editor div and transforms the necessary elements so that it goes from "create" mode to "edit" mode
   * @param object Edit data - same format as the rankdata parameter to the constructor, but we should only need rank_id
   */
  
  this.transformToEditor = function(rankdata)
  {
    // we need a rank ID
    if ( typeof(rankdata.rank_id) != 'number' )
      return false;
    
    if ( this.editing )
      return false;
    
    this.editing = true;
    
    this.rankdata = rankdata;
    this.rankdata.rank_title = this.f_rank_title.value;
    this.rankdata.rank_style = this.getCSS();
    
    // transform various controls
    this.th_head.firstChild.nodeValue = $lang.get('acpur_th_edit_rank', {
        rank_title: $lang.get(this.rankdata.rank_title)
      });
    this.btn_submit.value = $lang.get('acpur_btn_save');
    
    // add the delete button
    var th_submit = this.btn_submit.parentNode;
    
    var btn_delete = document.createElement('input');
    btn_delete.type = 'button';
    btn_delete.value = $lang.get('acpur_btn_delete');
    btn_delete.editor = this;
    btn_delete.onclick = function(e)
    {
      this.editor.deleteEvent(e);
    }
    th_submit.appendChild(document.createTextNode(' '));
    th_submit.appendChild(btn_delete);
    
    return true;
  }
  
  /**
   * Takes a hex color, averages the three channels, and returns either 'ffffff' or '000000' depending on the luminosity of the color.
   * @param string
   * @return string
   */
  
  this.determineLightness = function(hexval)
  {
    var rgb = this.hex2rgb(hexval);
    var lumin = ( rgb[0] + rgb[1] + rgb[2] ) / 3;
    return ( lumin > 60 ) ? '000000' : 'ffffff';
  }
  
  /**
   * Strips out basic CSS attributes (color, font-weight, font-style, text-decoration) from a snippet of CSS.
   * @param string
   * @return string
   */
  
  this.stripBasicCSSAttributes = function(css)
  {
    return trim(css.replace(/(color|font-weight|font-style|text-decoration): ?([A-z0-9# ,\(\)]+);/g, ''));
  }
  
  /**
   * Strips out all but basic CSS attributes.
   * @param string
   * @return string
   */
  
  this.stripExtendedCSSAttributes = function(css)
  {
    var match;
    var final_css = '';
    var basics = ['color', 'font-weight', 'font-style', 'text-decoration'];
    while ( match = css.match(/([a-z-]+):(.+?);/) )
    {
      if ( in_array(match[1], basics) )
      {
        final_css += ' ' + match[0] + ' ';
      }
      css = css.replace(match[0], '');
    }
    final_css = trim(final_css);
    return final_css;
  }
  
  this.getCSS = function()
  {
    return this.style_sim_obj.getAttribute('style');
  }
  
  this.renderPreview = function()
  {
    if ( !this.preview_div )
      return false;
    var color = ( this.style_sim_obj.style.color ) ? '#' + this.rgb2hex(this.style_sim_obj.style.color) : null;
    this.preview_div.style.color = color;
    this.preview_div.style.fontWeight = this.style_sim_obj.style.fontWeight;
    this.preview_div.style.fontStyle = this.style_sim_obj.style.fontStyle;
    this.preview_div.style.textDecoration = this.style_sim_obj.style.textDecoration;
    this.preview_div.firstChild.nodeValue = $lang.get(this.f_rank_title.value);
  }
  
  this.submitEvent = function(e)
  {
    if ( this.onsubmit )
    {
      this.onsubmit(e);
    }
    else
    {
      window.console.error('RankEditorControl: no onsubmit event specified');
    }
  }
  
  this.deleteEvent = function(e)
  {
    if ( this.ondelete )
    {
      this.ondelete(e);
    }
    else
    {
      window.console.error('RankEditorControl: no ondelete event specified');
    }
  }
  
  /**
   * Converts a parenthetical color specification (rgb(x, y, z)) to hex form (xxyyzz)
   * @param string
   * @return string
   */
  
  this.rgb2hex = function(rgb)
  {
    var p = rgb.match(/^rgb\(([0-9]+), ([0-9]+), ([0-9]+)\)$/);
    if ( !p )
      return rgb.replace(/^#/, '');
    
    var r = parseInt(p[1]).toString(16), g = parseInt(p[2]).toString(16), b = parseInt(p[3]).toString(16);
    if ( r.length < 2 )
      r = '0' + r;
    if ( g.length < 2 )
      g = '0' + g;
    if ( b.length < 2 )
      b = '0' + b;
    
    return r + g + b;
  }
  
  /**
   * Get red, green, and blue values for the given hex color
   * @param string
   * @return array (numbered, e.g. not an object
   */
  
  this.hex2rgb = function(hex)
  {
    hex = hex.replace(/^#/, '');
    if ( hex.length != 3 && hex.length != 6 )
    {
      return hex;
    }
    if ( hex.length == 3 )
    {
      // is there a better way to do this?
      hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
    }
    hex = [ hex.substr(0, 2), hex.substr(2, 2), hex.substr(4, 2) ];
    var red = parseInt(hex[0], 16);
    var green = parseInt(hex[1], 16);
    var blue = parseInt(hex[2], 16);
    return [red, green, blue];
  }
}

/**
 * Perform request for editable rank data and draw editor
 */

function ajaxInitRankEdit(rank_id)
{
  load_component('messagebox');
  var json_packet = {
    mode: 'get_rank',
    rank_id: rank_id
  };
  json_packet = ajaxEscape(toJSONString(json_packet));
  ajaxPost(makeUrlNS('Admin', 'UserRanks/action.json'), 'r=' + json_packet, function(ajax)
    {
      if ( ajax.readyState == 4 && ajax.status == 200 )
      {
        var response = String(ajax.responseText + '');
        if ( !check_json_response(response) )
        {
          handle_invalid_json(ajax.responseText);
          return false;
        }
        try
        {
          var response = parseJSON(ajax.responseText);
        }
        catch(e)
        {
          handle_invalid_json(ajax.responseText);
        }
        if ( response.error )
        {
          if ( response.error == 'need_auth_to_admin' )
          {
            load_component('login');
            var rid = rank_id;
            ajaxDynamicReauth(function()
              {
                ajaxInitRankEdit(rid);
              });
          }
          else
          {
            alert(response.error);
          }
          return false;
        }
        var editor = new RankEditorControl(response);
        editor.onsubmit = ajaxRankEditHandleSaveExisting;
        editor.ondelete = ajaxRankEditHandleDelete;
        var container = document.getElementById('admin_ranks_container_right');
        container.innerHTML = '';
        container.appendChild(editor.render());
      }
    }, true);
}

function ajaxInitRankCreate()
{
  load_component('messagebox');
  var editor = new RankEditorControl();
  editor.onsubmit = ajaxRankEditHandleSaveNew;
  var container = document.getElementById('admin_ranks_container_right');
  container.innerHTML = '';
  container.appendChild(editor.render());
}

function ajaxRankEditHandleSave(editor, switch_new)
{
  var whitey = whiteOutElement(editor.wrapperdiv);
  
  // pack it up, ...
  var json_packet = {
    mode: ( switch_new ) ? 'create_rank' : 'save_rank',
    rank_title: editor.f_rank_title.value,
    rank_style: editor.getCSS()
  }
  if ( !switch_new )
  {
    json_packet.rank_id = editor.rankdata.rank_id;
  }
  /// ... pack it in
  var json_packet = ajaxEscape(toJSONString(json_packet));
  
  ajaxPost(makeUrlNS('Admin', 'UserRanks/action.json'), 'r=' + json_packet, function(ajax)
    {
      if ( ajax.readyState == 4 && ajax.status == 200 )
      {
        var response = String(ajax.responseText + '');
        if ( !check_json_response(response) )
        {
          handle_invalid_json(ajax.responseText);
          return false;
        }
        try
        {
          var response = parseJSON(ajax.responseText);
        }
        catch(e)
        {
          handle_invalid_json(ajax.responseText);
        }
        if ( response.mode == 'success' )
        {
          whiteOutReportSuccess(whitey);
          if ( switch_new )
          {
            //
            // we have a few more things to do with a newly created rank.
            //
            
            // 1. transform editor
            editor.transformToEditor(response);
            editor.onsubmit = ajaxRankEditHandleSaveExisting;
            editor.ondelete = ajaxRankEditHandleDelete;
            
            // 2. append the new rank to the list
            var create_link = document.getElementById('rankadmin_createlink');
            if ( create_link )
            {
              var parent = create_link.parentNode;
              var edit_link = document.createElement('a');
              edit_link.href = '#rank_edit:' + response.rank_id;
              edit_link.className = 'rankadmin-editlink';
              edit_link.setAttribute('style', editor.getCSS());
              edit_link.id = 'rankadmin_editlink_' + response.rank_id;
              edit_link.rank_id = response.rank_id;
              edit_link.appendChild(document.createTextNode($lang.get(editor.f_rank_title.value)));
              parent.insertBefore(edit_link, create_link);
              edit_link.onclick = function()
              {
                ajaxInitRankEdit(this.rank_id);
              }
            }
          }
          else
          {
            // update the rank title on the left
            var edit_link = document.getElementById('rankadmin_editlink_' + editor.rankdata.rank_id);
            if ( edit_link )
            {
              edit_link.firstChild.nodeValue = $lang.get(editor.f_rank_title.value);
              edit_link.setAttribute('style', editor.getCSS());
            }
          }
        }
        else
        {
          whitey.parentNode.removeChild(whitey);
          if ( response.error == 'need_auth_to_admin' )
          {
            load_component('login');
            ajaxDynamicReauth(function()
              {
                ajaxRankEditHandleSave(editor, switch_new);
              });
          }
          else
          {
            miniPromptMessage({
                title: $lang.get('acpur_err_save_failed_title'),
                message: response.error,
                buttons: [
                  {
                    text: $lang.get('etc_ok'),
                    color: 'red',
                    style: {
                      fontWeight: 'bold'
                    },
                    onclick: function()
                    {
                      miniPromptDestroy(this);
                    }
                  }
                ]
            });
          }
        }
      }
    }, true);
}

var ajaxRankEditHandleSaveExisting = function()
{
  ajaxRankEditHandleSave(this, false);
}

var ajaxRankEditHandleSaveNew = function()
{
  ajaxRankEditHandleSave(this, true);
}

var ajaxRankEditHandleDelete = function()
{
  var mp = miniPromptMessage({
      title: $lang.get('acpur_msg_rank_delete_confirm_title'),
      message: $lang.get('acpur_msg_rank_delete_confirm_body'),
      buttons: [
        {
          text: $lang.get('acpur_btn_delete'),
          color: 'red',
          style: {
            fontWeight: 'bold'
          },
          onclick: function()
          {
            var parent = miniPromptGetParent(this);
            var editor = parent.editor;
            setTimeout(function()
              {
                ajaxRankEditDeleteConfirmed(editor);
              }, 1000);
            miniPromptDestroy(parent);
          }
        },
        {
          text: $lang.get('etc_cancel'),
          onclick: function()
          {
            miniPromptDestroy(this);
          }
        }
      ]
    });
  console.debug(mp);
  mp.editor = this;
}

function ajaxRankEditDeleteConfirmed(editor)
{
  var whitey = whiteOutElement(editor.wrapperdiv);
  
  load_component(['jquery', 'jquery-ui']);
  
  var json_packet = {
    mode: 'delete_rank',
    rank_id: editor.rankdata.rank_id
  };
  var rank_id = editor.rankdata.rank_id;
  
  json_packet = ajaxEscape(toJSONString(json_packet));
  ajaxPost(makeUrlNS('Admin', 'UserRanks/action.json'), 'r=' + json_packet, function(ajax)
    {
      if ( ajax.readyState == 4 && ajax.status == 200 )
      {
        var response = String(ajax.responseText + '');
        if ( !check_json_response(response) )
        {
          handle_invalid_json(ajax.responseText);
          return false;
        }
        try
        {
          var response = parseJSON(ajax.responseText);
        }
        catch(e)
        {
          handle_invalid_json(ajax.responseText);
        }
        if ( response.mode == 'success' )
        {
          // the deletion was successful, report success and kill off the editor
          whiteOutReportSuccess(whitey);
          setTimeout(function()
            {
              // nuke the rank title on the left
              var edit_link = document.getElementById('rankadmin_editlink_' + editor.rankdata.rank_id);
              if ( edit_link )
              {
                edit_link.parentNode.removeChild(edit_link);
              }
              // collapse and destroy the editor
              $(editor.wrapperdiv).hide("blind", {}, 500, function()
                  {
                    // when the animation finishes, nuke the whole thing
                    var container = document.getElementById('admin_ranks_container_right');
                    container.innerHTML = $lang.get('acpur_msg_select_rank');
                  }
                );
            }, 1500);
        }
        else
        {
          whitey.parentNode.removeChild(whitey);
          if ( response.error == 'need_auth_to_admin' )
          {
            load_component('login');
            ajaxDynamicReauth(function()
              {
                ajaxRankEditDeleteConfirmed(editor);
              });
          }
          else
          {
            miniPromptMessage({
                title: $lang.get('acpur_err_delete_failed_title'),
                message: response.error,
                buttons: [
                  {
                    text: $lang.get('etc_ok'),
                    color: 'red',
                    style: {
                      fontWeight: 'bold'
                    },
                    onclick: function()
                    {
                      miniPromptDestroy(this);
                    }
                  }
                ]
            });
          }
        }
      }
    }, true);
}