// The url must refer to an HTTP service that responds to a number of
// standard requests in the form of JSON text.
//
// action=save_structure
// =====================
// Save the structure parameter against the user for subsequent return
// in response to get_structure request.
//
// action=get_structure
// =====================
// Return the last saved structure string for the current user.
//
// action=get_content
// ==================
//
// Also has app_name=XXX and the current values of any editable
// parameters to the app.
//
// Return a JSON object with the following members:
//   title_html       to be placed in the title of its box
//   edit_html        to be included in the edit form
//   body_html        to be placed in the content area of its box
//   removable        set to true or false
//   minimisable      set to true or false
//   edit_on_add      set to true if editing is open when first added
//   refresh_interval in milliseconds
// All are optional.  The values captured through the edit_html
// will be implicitly stored in the structure and always included as query
// parameters to the get_content calls originating from the same box.
//
// The edit_html can contain <input>, single valued <select> and <textarea>
// form input elements.

// Notes on implementation
// =======================
//
// Each box in the layout has an app of a particular name associated with it
// at all times.  The app_name attribute is always set to this.

function MultiApp(id,url) {
  this.id = id;
  this.url = url;
}

MultiApp.new_http_request = function() {
  var http = false;

  if ( !http ) {
    try { http = new XMLHttpRequest(); } catch (e) {}
  }

  if ( !http ) {
    try { http = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) {}
  }

  if ( !http ) {
    try { http = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {}
  }

  return http;
}

MultiApp.queue = [];
MultiApp.processing_queue = false;
MultiApp.outstanding_requests = [];

MultiApp.queue_request = function(url,params,object,handler,parameters) {
  MultiApp.queue.push({"type":         "GET",
                       "url":          url,
                       "http_params":  params,
                       "object":       object,
		       "handler":      handler,
		       "parameters":   parameters});
}

MultiApp.queue_post = function(url,params) {
  MultiApp.queue.push({"type":         "POST",
                       "url":          url,
                       "http_params":  params});
}

MultiApp.process_queue = function() {
  if ( MultiApp.processing_queue ) return;
  MultiApp.processing_queue = true;
  while ( MultiApp.queue.length > 0 ) {
    var req = MultiApp.queue.shift();
    var http = MultiApp.new_http_request();

    var url = req.url;

    if ( req.type == 'GET' ) {

      for(var i=0; i<req.http_params.length; i++) {
        var param = req.http_params[i][0] + '=' + encodeURIComponent(req.http_params[i][1]);
        url += (url.indexOf('?') > -1 ? '&' : '?') + param;
      }
      url += (url.indexOf('?') > -1 ? '&' : '?') + 'random=' + Math.random();

      http.open('GET', url, true);
      http.onreadystatechange = MultiApp.handle_response;

      var request_info = new Object;
      request_info.url = url;
      request_info.http_request = http;
      request_info.http_type = req.type;
      request_info.object = req.object;
      request_info.object_response_handler = req.handler;
      request_info.object_response_handler_parameters = req.parameters;

      MultiApp.outstanding_requests.push(request_info);

      http.send(null);
      
    } else {

      var params = '';
      for(var i=0; i<req.http_params.length; i++) {
        var param = req.http_params[i][0] + '=' + encodeURIComponent(req.http_params[i][1]);
	if ( params != '' ) {
          params += '&';
	}
        params += param;
      }

      http.open('POST', url, true);
      http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
      http.setRequestHeader("Content-length", params.length);
      http.setRequestHeader("Connection", "close");
      http.onreadystatechange = MultiApp.handle_response;

      var request_info = new Object;
      request_info.url = url;
      request_info.http_request = http;
      request_info.http_type = req.type;
      request_info.object = req.object;
      request_info.object_response_handler = req.handler;
      request_info.object_response_handler_parameters = req.parameters;

      MultiApp.outstanding_requests.push(request_info);

      http.send(params);
    }
  }
  MultiApp.processing_queue = false;
}

MultiApp.handle_response = function() {

  for (var i=0;i<MultiApp.outstanding_requests.length;i++) {
    var request_info = MultiApp.outstanding_requests[i];

    if ( request_info.http_request.readyState == 4 ) {
      MultiApp.outstanding_requests.splice(i,1);
      if ( request_info.http_type == 'GET' ) {
        var call_params = [request_info.http_request.responseText];
        call_params = call_params.concat(request_info.object_response_handler_parameters);
        request_info.object_response_handler.apply(request_info.object,call_params);
      }
      MultiApp.process_queue();
    }
  }
}

// Box auto refresh
MultiApp.next_refresh_id = 0;
MultiApp.refresh_table = [];

MultiApp.refresh_box = function(id) {
  for (var i=0;i<MultiApp.refresh_table.length;i++) {
    var entry = MultiApp.refresh_table[i];
    if ( entry.id == id ) {
      MultiApp.refresh_table.splice(i,1);
      if ( entry.box ) {
	var multiapp_div = MultiApp.multiapp_from_box(entry.box);
	var multiapp = MultiApp.multiapp_object_of_div(multiapp_div);
	multiapp.reload(entry.box);
        MultiApp.process_queue();
      }
      return;
    }
  }
}

MultiApp.set_refresh = function(box,ms) {
  var entry = new Object;
  entry.id = MultiApp.next_refresh_id++;
  entry.box = box;
  entry.ms_interval = ms;
  entry.suspended = false;
  entry.timeout_id = setTimeout('MultiApp.refresh_box('+entry.id+');',entry.ms_interval);

  MultiApp.refresh_table.push(entry);
}

MultiApp.suspend_refresh = function(box) {
  for (var i=0;i<MultiApp.refresh_table.length;i++) {
    var entry = MultiApp.refresh_table[i];
    if ( entry.box == box ) {
      clearTimeout(entry.timeout_id);
      entry.suspended = true;
      return;
    }
  }
}

MultiApp.unsuspend_refresh = function(box) {
  for (var i=0;i<MultiApp.refresh_table.length;i++) {
    var entry = MultiApp.refresh_table[i];
    if ( entry.box == box ) {
      entry.timeout_id = setTimeout('MultiApp.refresh_box('+entry.id+');',entry.ms_interval);
      entry.suspended = false;
      return;
    }
  }
}

MultiApp.stop_refresh = function(box) {
  for (var i=0;i<MultiApp.refresh_table.length;i++) {
    var entry = MultiApp.refresh_table[i];
    if ( entry.box == box ) {
      clearTimeout(entry.timeout_id);
      MultiApp.refresh_table.splice(i,1);
      return;
    }
  }
}

MultiApp.associate_object_with_div = function(object,div) {
  div.multiapp_object = object;
}

MultiApp.multiapp_object_of_div = function(div) {
  return div.multiapp_object;
}


MultiApp.picked_up_box = null;


MultiApp.absolute_left = function(element) {
  var left = 0;
  do {
    left += element.offsetLeft || 0;
    if (element.offsetParent == document.body)
      if (element.style.position == 'absolute') break;
      element = element.offsetParent;
  } while (element);

  return left;
}

MultiApp.absolute_top = function(element) {
  var top = 0;
  do {
    top += element.offsetTop || 0;
    if (element.offsetParent == document.body)
      if (element.style.position == 'absolute') break;
      element = element.offsetParent;
  } while (element);

  return top;
}

// Navigation

MultiApp.box_from_grabber = function(grabber) {
  return grabber.parentNode;
}

MultiApp.box_from_edit_div = function(edit_div) {
  return edit_div.parentNode;
}

MultiApp.box_from_content = function(content) {
  return content.parentNode;
}

MultiApp.box_from_box_border = function(box_border) {
  return box_border.firstChild;
}

MultiApp.box_from_edit = function(edit_ancor) {
  return MultiApp.box_from_grabber(edit_ancor.parentNode.parentNode.parentNode.parentNode);
}

MultiApp.box_from_minimise = function(minimise_ancor) {
  return MultiApp.box_from_grabber(minimise_ancor.parentNode.parentNode.parentNode.parentNode);
}

MultiApp.box_from_remove = function(remove_ancor) {
  return MultiApp.box_from_grabber(remove_ancor.parentNode.parentNode.parentNode.parentNode);
}

MultiApp.box_border_from_box = function(box) {
  return box.parentNode;
}

MultiApp.grabber_from_box = function(box) {
  return box.firstChild;
}

MultiApp.edit_div_from_box = function(box) {
  return box.childNodes[1];
}

MultiApp.content_from_box = function(box) {
  return box.lastChild;
}

MultiApp.edit_from_grabber = function(grabber) {
  return grabber.firstChild.firstChild.childNodes[0].firstChild;
}

MultiApp.edit_from_box = function(box) {
  return MultiApp.edit_from_grabber(MultiApp.grabber_from_box(box));
}

MultiApp.minimise_from_grabber = function(grabber) {
  return grabber.firstChild.firstChild.childNodes[1].firstChild;
}

MultiApp.minimise_from_box = function(box) {
  return MultiApp.minimise_from_grabber(MultiApp.grabber_from_box(box));
}

MultiApp.remove_from_grabber = function(grabber) {
  return grabber.firstChild.firstChild.childNodes[2].firstChild;
}

MultiApp.remove_from_box = function(box) {
  return MultiApp.remove_from_grabber(MultiApp.grabber_from_box(box));
}

MultiApp.edit_form_from_box = function(box) {
  return MultiApp.edit_div_from_box(box).firstChild;
}

MultiApp.edit_div_from_form = function(form) {
  return form.parentNode;
}

MultiApp.title_from_box = function(box) {
  return MultiApp.grabber_from_box(box).lastChild;
}

MultiApp.multiapp_from_box = function(box) {
  var multiapp_div;
  var box_border = box.parentNode;
  if ( MultiApp.is_within_preview(box_border) ) {
    var preview = box_border.parentNode;
    var multiapp = MultiApp.multiapp_object_of_div(preview);
    multiapp_div = document.getElementById(multiapp.id);
  } else {
    var column = box_border.parentNode;
    multiapp_div = column.parentNode;
  }
  return multiapp_div;
}

MultiApp.column_from_multiapp = function(multiapp,col) {
  return multiapp.childNodes[col];
}

MultiApp.box_from_column = function(column,row) {
  var box = column.childNodes[row].firstChild; // divs, border div, box div
  return box;
}

MultiApp.box_from_multiapp = function(multiapp,col,row) {
  var column = MultiApp.column_from_multiapp(multiapp,col);
  var box = MultiApp.box_from_column(column,row);
  return box;
}

MultiApp.column_number_of_box = function(box) {
  var multiapp = MultiApp.multiapp_from_box(box);
  var column_divs = multiapp.childNodes;

  for (var col=0; col<column_divs.length; col++) {
    for (var row=0; row<column_divs[col].childNodes.length; row++) { 
      if ( column_divs[col].childNodes[row] == box.parentNode ) {
        return col;
      }
    }
  }

  return null;
}

MultiApp.row_number_of_box = function(box) {
  var multiapp = MultiApp.multiapp_from_box(box);
  var column_divs = multiapp.childNodes;

  for (var col=0; col<column_divs.length; col++) {
    for (var row=0; row<column_divs[col].childNodes.length; row++) {
      if ( column_divs[col].childNodes[row] == box.parentNode ) {
        return row;
      }
    }
  }

  return null;
}

MultiApp.number_of_columns = function(multiapp) {
  return multiapp.childNodes.length;
}

MultiApp.number_of_rows_in_column = function(multiapp,col) {
  var column = MultiApp.column_from_multiapp(multiapp,col);
  return column.childNodes.length;
}

MultiApp.is_within_preview = function(el) {
  while ( el ) {
    if ( el.className == 'preview' ) {
      return true;
    }
    el = el.parentNode;
  }
  return false;
}

MultiApp.column_width = function(multiapp,col) {
  
  var width;

  if ( MultiApp.number_of_rows_in_column(multiapp,col) > 0 ) {
    width = MultiApp.column_from_multiapp(multiapp,col).offsetWidth;
  } else {
    var col_div = MultiApp.column_from_multiapp(multiapp,col);
    col_div.appendChild(document.createTextNode("Dummy"));
    width = col_div.offsetWidth;
    col_div.removeChild(col_div.firstChild);
  }

  return width;
}

MultiApp.box_height = function(multiapp,col,row) {
  return MultiApp.box_from_multiapp(multiapp,col,row).offsetHeight;
}


// Mouse handling

MultiApp.onmousedown = function(evt,grabber_div) {
  evt = evt ? evt : event;
  var box = MultiApp.box_from_grabber(grabber_div);
  MultiApp.pickup(box,evt);
  document.onmousemove=MultiApp.onmousemove;
  document.onmouseup=MultiApp.onmouseup;
  document.onblur=MultiApp.onmouseup;
  return false;
}

MultiApp.onmouseup = function(evt) {
  evt = evt ? evt : event;
  if ( MultiApp.picked_up_box != null ) {
    var box = MultiApp.picked_up_box;
    MultiApp.drop();
    document.onmousemove=null;
    document.onmouseup=null;
    document.onblur=null;
    MultiApp.div_left_start = null;
    MultiApp.div_top_start = null;

    var multiapp_div = MultiApp.multiapp_from_box(box);

    var multiapp = MultiApp.multiapp_object_of_div(multiapp_div);

    multiapp.save();
    MultiApp.process_queue();
	
    return false;
  }
}

MultiApp.onmousemove = function(evt) {
  evt = evt ? evt : event;
  if ( MultiApp.picked_up_box != null ) {

    var mouseX_move = evt.clientX - MultiApp.mouseX_start;
    var mouseY_move = evt.clientY - MultiApp.mouseY_start;

    MultiApp.picked_up_box.style.left = MultiApp.div_left_start - MultiApp.div_left_shift + mouseX_move + 'px';
    MultiApp.picked_up_box.style.top = MultiApp.div_top_start - MultiApp.div_top_shift + mouseY_move + 'px';

    var multiapp_div = MultiApp.multiapp_from_box(MultiApp.picked_up_box);
    var multiapp = MultiApp.multiapp_object_of_div(multiapp_div);

    var original_col = MultiApp.column_number_of_box(MultiApp.picked_up_box);
    var original_row = MultiApp.row_number_of_box(MultiApp.picked_up_box);

    do {

      var column = MultiApp.column_number_of_box(MultiApp.picked_up_box);
      var row = MultiApp.row_number_of_box(MultiApp.picked_up_box);

      var new_col = column;
      var new_row = row;

      // Check for move right
      if ( MultiApp.number_of_columns(multiapp_div) - 1 > column &&
           mouseX_move - MultiApp.div_left_shift > 
           MultiApp.column_width(multiapp_div,column) / 2 + 10 &&
	   column >= original_col ) {	// to ensure doesn't just go straight back if column widths are changing

        new_col = column+1;
        new_row = 0;
        // Rely on up/down checks to find the right row

      // Check for move left
      } else if ( column > 0 &&
                  -(mouseX_move - MultiApp.div_left_shift) >
                  MultiApp.column_width(multiapp_div,column-1) / 2 + 10 &&
	          column <= original_col ) {

        new_col = column-1;
        new_row = 0;

      // Check for move down
      } else if ( MultiApp.number_of_rows_in_column(multiapp_div,column) - 1 > row &&
           mouseY_move - MultiApp.div_top_shift > 
           MultiApp.box_height(multiapp_div,column,row+1) / 2 + 10 ) {

        new_col = column;
        new_row = row+1;

      // Check for move up
      } else if ( row > 0 &&
                  -(mouseY_move - MultiApp.div_top_shift) >
                  MultiApp.box_height(multiapp_div,column,row-1) / 2 + 10 ) {

        new_col = column;
        new_row = row-1;
      }

      var moving = false;

      if ( (new_col != column ||
            new_row != row) &&
	   !(new_col == original_col && new_row == original_row) ) {

        var old_left = MultiApp.absolute_left(MultiApp.picked_up_box);
        var old_top = MultiApp.absolute_top(MultiApp.picked_up_box);
        
	MultiApp.move(new_col,new_row);

	var new_left = MultiApp.absolute_left(MultiApp.picked_up_box);
	var new_top = MultiApp.absolute_top(MultiApp.picked_up_box);

	MultiApp.div_left_shift += (new_left - old_left);
	MultiApp.div_top_shift += (new_top - old_top);
	MultiApp.picked_up_box.style.left = (parseInt(MultiApp.picked_up_box.style.left) - (new_left - old_left)) + 'px';
	MultiApp.picked_up_box.style.top = (parseInt(MultiApp.picked_up_box.style.top) - (new_top - old_top)) + 'px';

	moving = true;
      }
    } while ( moving );
  }
  
  return false;
}

MultiApp.edit = function(ancor) {
  var box = MultiApp.box_from_edit(ancor);
  var edit_div = MultiApp.edit_div_from_box(box);
  var form = MultiApp.edit_form_from_box(box);
  if ( edit_div.style.display == "none" ) {
    edit_div.style.display = "block";

    // Put focus in edit form if easy
    var first_input_element = form.getElementsByTagName('INPUT')[0];
    if ( first_input_element ) {
      first_input_element.focus();
      first_input_element.select();
    }
    
  } else {
    MultiApp.cancel_edit(form);
  }
  
  return false;
}

MultiApp.minimise = function(ancor) {
  var box = MultiApp.box_from_minimise(ancor);
  var content = MultiApp.content_from_box(box);
  if ( content.style.display == "none" ) {
    content.style.display = "block";
  } else {
    content.style.display = "none";
    MultiApp.cancel_edit(MultiApp.edit_form_from_box(box));
  }
  var object = MultiApp.multiapp_object_of_div(MultiApp.multiapp_from_box(box));
  object.save();
  MultiApp.process_queue();
  
  return false;
}

MultiApp.remove = function(ancor) {
  var box = MultiApp.box_from_remove(ancor);
  
  var div_to_remove = MultiApp.box_border_from_box(box);

  if ( MultiApp.is_within_preview(box) ) {

    var preview = div_to_remove.parentNode;
    preview.removeChild(div_to_remove);
    var shield = preview.previousSibling;
    preview.parentNode.removeChild(preview);
    shield.parentNode.removeChild(shield);

  } else if ( confirm('Are you sure you wish to remove this?') ) {

    MultiApp.stop_refresh(box);

    var object = MultiApp.multiapp_object_of_div(MultiApp.multiapp_from_box(box));

    div_to_remove.parentNode.removeChild(div_to_remove);

    object.save();
    MultiApp.process_queue();
  }
	
  return false;
}

MultiApp.save_edit = function(form) {
  var edit_div = MultiApp.edit_div_from_form(form);
  edit_div.style.display = "none";

  var box = MultiApp.box_from_edit_div(edit_div);
  var multiapp_div = MultiApp.multiapp_from_box(box);
  var multiapp = MultiApp.multiapp_object_of_div(multiapp_div);
  multiapp.save();
  multiapp.reload(box);
  MultiApp.process_queue();

  return false;
}

MultiApp.cancel_edit = function(form) {
  var edit_div = MultiApp.edit_div_from_form(form);
  edit_div.style.display = "none";

  if ( !MultiApp.is_within_preview(edit_div) ) {
    var box = MultiApp.box_from_edit_div(edit_div);
    var multiapp_div = MultiApp.multiapp_from_box(box);
    var multiapp = MultiApp.multiapp_object_of_div(multiapp_div);
    var column = MultiApp.column_number_of_box(box);
    var row = MultiApp.row_number_of_box(box);

    var app_info = multiapp.structure.apps[column][row];

    for (var i=0;i<app_info.parameters.length;i++) {
      if ( form[app_info.parameters[i].name] ) {
        form[app_info.parameters[i].name].value = app_info.parameters[i].value;
      }
    }
  }

  return false;
}

MultiApp.add_preview = function(evt,ancor) {
  evt = evt ? evt : event;

  var preview = ancor.parentNode.parentNode.parentNode; // li,ul,div
  var box_border = preview.firstChild;
  var box = MultiApp.box_from_box_border(box_border);

  MultiApp.pickup(box,evt);
  var mouseup_return = MultiApp.onmouseup(evt);
  
  return mouseup_return;
}

MultiApp.cancel_preview = function(ancor) {
  var preview = ancor.parentNode.parentNode.parentNode; // li,ul,div
  var box_border = preview.firstChild;
  var box = MultiApp.box_from_box_border(box_border);
  var remove_ancor = MultiApp.remove_from_box(box);
  return MultiApp.remove(remove_ancor);
}

MultiApp.pickup = function(box,evt) {

  var src = (evt.target ? evt.target : evt.srcElement);

  MultiApp.suspend_refresh(box);

  MultiApp.mouseX_start = evt.clientX;
  MultiApp.mouseY_start = evt.clientY;

  var initial_left = 0;
  var initial_top = 0;

  var src_within_grabber = false;

  var el = src;
  while ( el ) {
    if ( el.className == 'box-grabber' ) {
      src_within_grabber = true;
    }
    el = el.parentNode;
  }

  if ( !src_within_grabber ) {
    initial_left = MultiApp.absolute_left(src) - MultiApp.absolute_left(MultiApp.grabber_from_box(box));
    initial_top = MultiApp.absolute_top(src) - MultiApp.absolute_top(MultiApp.grabber_from_box(box));
  }

  MultiApp.picked_up_box = box;
  box.style.position = 'relative';
  box.style.left = initial_left + 'px';
  box.style.top = initial_top + 'px';
  box.className = 'box-moving module';
  box.parentNode.className = 'module-container-moving';

  MultiApp.div_left_start = initial_left;
  MultiApp.div_top_start = initial_top;
  MultiApp.div_left_shift = 0;
  MultiApp.div_top_shift = 0;

  // If the preview is being picked up then need to move it to a position.
  if ( MultiApp.is_within_preview(box) ) {
    MultiApp.move(0,0);
    MultiApp.onmousemove(evt);

    // Now ensure that edit box actions added in
    var edit = MultiApp.edit_from_box(box);

    if ( edit.style.display == 'block' ) {
      var form = MultiApp.edit_form_from_box(box);

      var dummy = document.createElement("DIV");
      dummy.innerHTML = '<ul class="edit-actions">' +
                          '<li><input type="submit" class="button" value="Save &amp; update" /></li>' + 
                          '<li><input type="button" class="button" value="Cancel this edit" onclick="MultiApp.cancel_edit(this.form);" /></li>' +
          	      '</ul>';

      while ( dummy.firstChild ) {
        var element_to_add = dummy.firstChild;
        dummy.removeChild(element_to_add);
        form.appendChild(element_to_add);
      }

      MultiApp.save_edit(form);
    }
  }
}

MultiApp.drop = function() {
  MultiApp.picked_up_box.style.position = 'static';
  MultiApp.picked_up_box.parentNode.className = 'box-container';
  MultiApp.picked_up_box.className = 'box module';
  MultiApp.unsuspend_refresh(MultiApp.picked_up_box);
  MultiApp.picked_up_box = null;
}

MultiApp.move = function(new_column,new_row) {
// means move the picked up div so that it is in the given position after the move.
// NB the picked up div may be in the preview div at the moment.
// The app info structure is rearranged to keep it in sync with the new arrangement.

  var old_left = MultiApp.absolute_left(MultiApp.picked_up_box);
  var old_top = MultiApp.absolute_top(MultiApp.picked_up_box);

  var div_to_be_moved = MultiApp.picked_up_box.parentNode; // border div

  var multiapp_div = MultiApp.multiapp_from_box(MultiApp.picked_up_box);
  var multiapp = MultiApp.multiapp_object_of_div(multiapp_div);
  var apps = multiapp.structure.apps;

  var app_info_to_be_inserted;

  var columns;

  if ( MultiApp.is_within_preview(div_to_be_moved) ) {
    preview = div_to_be_moved.parentNode;
    preview.removeChild(div_to_be_moved);
    var multiapp = MultiApp.multiapp_object_of_div(preview);
    var multiapp_div = document.getElementById(multiapp.id);
    var shield = preview.previousSibling;
    preview.parentNode.removeChild(preview);
    shield.parentNode.removeChild(shield);
    columns = multiapp_div.childNodes;

    // create app_info from preview
    app_info_to_be_inserted = new Object;
  } else {

    var old_col = MultiApp.column_number_of_box(MultiApp.picked_up_box);
    var old_row = MultiApp.row_number_of_box(MultiApp.picked_up_box);
    app_info_to_be_inserted = apps[old_col][old_row];
    apps[old_col].splice(old_row,1);

    var column_div = div_to_be_moved.parentNode;
    column_div.removeChild(div_to_be_moved);

    columns = column_div.parentNode.childNodes;
  }

  var column = 0;
  while ( column < new_column && columns[column+1] ) {
    column += 1;
  } 
  var new_column_div = columns[column];

  var divs_in_column = new_column_div.childNodes;

  if ( new_row < divs_in_column.length ) {
    var insert_before_div = divs_in_column[new_row];
    new_column_div.insertBefore(div_to_be_moved,insert_before_div);
  } else {
    new_column_div.appendChild(div_to_be_moved);
  }

  var new_left = MultiApp.absolute_left(MultiApp.picked_up_box);
  var new_top = MultiApp.absolute_top(MultiApp.picked_up_box);

  MultiApp.div_left_shift += (new_left - old_left);
  MultiApp.div_top_shift += (new_top - old_top);
  MultiApp.picked_up_box.style.left = (parseInt(MultiApp.picked_up_box.style.left) - (new_left - old_left)) + 'px';
  MultiApp.picked_up_box.style.top = (parseInt(MultiApp.picked_up_box.style.top) - (new_top - old_top)) + 'px';

  apps[new_column].splice(new_row,0,app_info_to_be_inserted);
}

MultiApp.prototype.save = function() {
// Must only post new structure if all boxes are loaded.

  var multiapp_div = document.getElementById(this.id);
  var column_cells = multiapp_div.childNodes;

  var apps = [];

  for (var i=0; i<column_cells.length; i++ ) {
    var app_col = [];
    var rows = column_cells[i].childNodes;
    for (var j=0; j<rows.length; j++ ) {
      var box = rows[j].firstChild;  // border div,box div
      var edit_div = box.childNodes[1];
      var content = box.lastChild;
      var title = MultiApp.title_from_box(box);

      var app_info = new Object;

      if ( title.firstChild.firstChild.nodeValue == 'Loading...' ) { // Must match app_html
        // The box is currently being loaded so use app_info within the structure.
	var old_app_info = this.structure.apps[i][j];
	app_info.name = old_app_info.name;
	app_info.state = old_app_info.state;

	var parameters = [];

	for (var x=0;x<old_app_info.parameters.length;x++) {
	  var param = new Object;
	  param.name = old_app_info.parameters[x].name;
	  param.value = old_app_info.parameters[x].value;
	  parameters.push(param);
	}

	app_info.parameters = parameters;

      } else {

	app_info.name = box.app_name;
	app_info.state = (content.style.display == 'none' ? 'minimised' : '');
	
	var parameters = [];
	var input_elements = edit_div.getElementsByTagName('INPUT');
	for (var k=0;k<input_elements.length-2;k++) {
	  var param = new Object;
	  param.name = input_elements[k].name;
	  param.value = input_elements[k].value;
	  parameters.push(param);
	}

	var select_elements = edit_div.getElementsByTagName('SELECT');
	for (var k=0;k<select_elements.length;k++) {
	  var param = new Object;
	  param.name = select_elements[k].name;
	  param.value = select_elements[k].value;
	  parameters.push(param);
	}

	var textarea_elements = edit_div.getElementsByTagName('TEXTAREA');
	for (var k=0;k<textarea_elements.length;k++) {
	  var param = new Object;
	  param.name = textarea_elements[k].name;
	  param.value = textarea_elements[k].value;
	  parameters.push(param);
	}

	app_info.parameters = parameters;
      }

      app_col.push(app_info);
    }
    apps.push(app_col);
  }

  var new_structure = new Object;
  new_structure.apps = apps;

  this.structure = new_structure;

  var json = new_structure.toJSONString();

  MultiApp.queue_post(this.url,
                      [['action','save_structure'],
      	               ['structure',json]]);
}

MultiApp.prototype.content_at_position = function(column,row) {
  var multiapp_div = document.getElementById(this.id);
  var content = MultiApp.content_from_box(MultiApp.box_from_multiapp(multiapp_div,column,row));
  return content;
}


MultiApp.prototype.app_html = function(minimised) {

  var edit_ancor = '<a class="edit" style="display:none;" href="#" onclick="return MultiApp.edit(this);" onmousedown="event.cancelBubble=true;return false;">Edit</a>';
  var minimise_ancor = '<a class="buttons minimise" style="display:none;" href="#" onclick="return MultiApp.minimise(this);" onmousedown="event.cancelBubble=true;return false;">Minimise</a>';
  var remove_ancor = '<a class="buttons remove" style="display:none;" href="#" onclick="return MultiApp.remove(this);" onmousedown="event.cancelBubble=true;return false;">Remove</a>';

  var grabber_div = 
            '<div class="box-grabber"' +
      	    ' onmousedown="return MultiApp.onmousedown(event,this);"' +
            '>' + '<div class="box-actions"><ul><li>' + edit_ancor + '</li><li>' + minimise_ancor + '</li><li>' + remove_ancor + '</li></ul></div>' + '<div class="box-title"><h2>Loading...</h2></div>' + '</div>';

  var content_style = '';
  if ( minimised ) {
    content_style = ' style="display:none;"';
  }

  var edit_div = '<div class="box-edit" style="display:none;">' +
                 '<form onsubmit="return MultiApp.save_edit(this);"></form>' +
		 '</div>';

  var content_div = '<div class="box-content"' + content_style + '>' + '</div>';

  var html = '<div class="box-container">' +
             '<div class="module trans">' + 
             grabber_div + 
             edit_div + 
             content_div + 
             '</div>' +
             '</div>';

  return html;
}

MultiApp.prototype.load_app = function(box,content,app_info) {

  var app_name = box.app_name;

  var content_div = MultiApp.content_from_box(box);
  if ( content.body_html ) {
    content_div.innerHTML = '<div class="top-shadow"></div>' + content.body_html + '<div class="bottom-shadow"></div>';
  }


  var title = MultiApp.title_from_box(box);
  if ( content.title_html ) {
    title.innerHTML = content.title_html;
  } else {
    title.innerHTML = '<h2>' + app_name + '</h2>';
  }

  var edit = MultiApp.edit_from_box(box);
  if ( content.edit_html ) {
    edit.style.display = "block";

    var dummy = document.createElement("DIV");
    dummy.innerHTML = content.edit_html;

    var form = MultiApp.edit_form_from_box(box);

    if ( MultiApp.is_within_preview(box) ) {
      form.innerHTML = '';
    } else {
      form.innerHTML = '<ul class="edit-actions">' +
                         '<li><input type="submit" class="button" value="Save &amp; update" /></li>' + 
		         '<li><input type="button" class="button" value="Cancel this edit" onclick="MultiApp.cancel_edit(this.form);" /></li>' +
		       '</ul>';
    }

    while ( dummy.firstChild ) {
      var element_to_add = dummy.lastChild;
      dummy.removeChild(element_to_add);
      form.insertBefore(element_to_add,form.firstChild);
    }

    if ( app_info ) {
      for (var i=0;i<app_info.parameters.length;i++) {
        if ( form[app_info.parameters[i].name] ) {
          form[app_info.parameters[i].name].value = app_info.parameters[i].value;
        }
      }
    }

    if ( MultiApp.is_within_preview(box) && content.edit_on_add ) {
      MultiApp.edit_div_from_box(box).style.display = "block";

      // Put focus in edit form if easy
      var first_input_element = form.getElementsByTagName('INPUT')[0];
      if ( first_input_element ) {
        first_input_element.focus();
        first_input_element.select();
      }
    }

  } else {
    edit.style.display = "none";
  }

  var minimise = MultiApp.minimise_from_box(box);
  if ( content.minimisable && 
       !MultiApp.is_within_preview(box) ) {
    minimise.style.display = "block";
  } else {
    minimise.style.display = "none";
  }

  var remove = MultiApp.remove_from_box(box);
  if ( content.removable ) {
    remove.style.display = "block";
  } else {
    remove.style.display = "none";
  }

  if ( content.refresh_interval > 0 ) {
    MultiApp.set_refresh(box,content.refresh_interval);
  }

  if ( MultiApp.is_within_preview(box) ) {
    MultiApp.suspend_refresh(box);
    var box_border = MultiApp.box_border_from_box(box);
    var preview = box_border.parentNode;
    var left = (document.documentElement.clientWidth - preview.clientWidth ) / 2;
    var top = (document.documentElement.clientHeight - preview.clientHeight ) / 2;
    preview.style.left = left + 'px';
    preview.style.top = top + 'px';
  }
}


MultiApp.prototype.initialise = function(json) {
  this.structure = eval('(' + json + ')');

  var multiapp_div = document.getElementById(this.id);

  var html = '';

  var col_no = 0;
  for(var col=0;col<this.structure.apps.length;col++) {

    col_no += 1;
    html += '<div class="column no-' + col_no + '">';

    for(var row=0;row<this.structure.apps[col].length;row++) {
    
      var app_info = this.structure.apps[col][row];

      html += this.app_html(app_info.state=='minimised');
    }

    html += '</div>';
  }

  multiapp_div.innerHTML = html;


  for(var col=0;col<this.structure.apps.length;col++) {
    for(var row=0;row<this.structure.apps[col].length;row++) {
      var app_info = this.structure.apps[col][row];
      var box = MultiApp.box_from_multiapp(multiapp_div,col,row);
      box.app_name = app_info.name;

      var query_params = [['action','get_content'],
         	          ['app_name',app_info.name]];

      for(var i=0;i<app_info.parameters.length;i++) {
        query_params.push([app_info.parameters[i].name,app_info.parameters[i].value]);
      }

      MultiApp.queue_request(this.url,
                             query_params,
                             this,
			     MultiApp.prototype.load_app_into_box,
			     [box]);
    }
  }

  MultiApp.process_queue();
}

MultiApp.prototype.load_app_into_box = function(json,box) {
  var content = eval('(' + json + ')');
  var multiapp_div = document.getElementById(this.id);
  var column = MultiApp.column_number_of_box(box);
  var row = MultiApp.row_number_of_box(box);
  this.load_app(box,content,this.structure.apps[column][row]);
}

MultiApp.prototype.reload = function(box) {
  var column = MultiApp.column_number_of_box(box);
  var row = MultiApp.row_number_of_box(box);

  var app_info = this.structure.apps[column][row];

  var query_params = [['action','get_content'],
    	              ['app_name',app_info.name]];

  for(var i=0;i<app_info.parameters.length;i++) {
    query_params.push([app_info.parameters[i].name,app_info.parameters[i].value]);
  }

  MultiApp.queue_request(this.url,
                         query_params,
                         this,
    		         MultiApp.prototype.load_app_into_box,
    		         [box]);
}

MultiApp.prototype.load_preview = function(json) {
  //TODO: There is no error handling here.
  var content = eval('(' + json + ')');
  var preview = document.body.lastChild;
  var shield = preview.previousSibling;
  var box_border = preview.firstChild;
  var box = MultiApp.box_from_box_border(box_border);
  this.load_app(box,content);
}

MultiApp.prototype.write = function() {
  var multiapp_div = document.getElementById(this.id);
  MultiApp.associate_object_with_div(this,multiapp_div);

  this.load();
}

MultiApp.prototype.load = function() {
  MultiApp.queue_request(this.url,[['action','get_structure']],this,MultiApp.prototype.initialise,[]);
  MultiApp.process_queue();
}

MultiApp.prototype.add = function(app_name) {

  var col = 0;
  var row = 0;

  var dummy = document.createElement("DIV");
  dummy.innerHTML = this.app_html(false);

  var div_to_add = dummy.firstChild;
  dummy.removeChild(div_to_add);
  
  div_to_add.firstChild.app_name = app_name;

  var multiapp_div = document.getElementById(this.id);

  var column_div = MultiApp.column_from_multiapp(multiapp_div,col);

  var divs_in_column = column_div.childNodes;

  if ( row < divs_in_column.length ) {
    var insert_before_div = divs_in_column[row];
    column_div.insertBefore(div_to_add,insert_before_div);
  } else {
    column_div.appendChild(div_to_add);
  }

  var box = MultiApp.box_from_multiapp(multiapp_div,col,row);
  box.app_name = app_name;

  MultiApp.queue_request(this.url,
                         [['action','get_content'],
    		          ['app_name',app_name]],
                         this,
    		         MultiApp.prototype.load_app_into_box,
    		         [box]);

  this.save();
  MultiApp.process_queue();
  return false;
}

MultiApp.prototype.add_with_preview = function(app_name) {
  var shield = document.createElement("DIV");
  document.body.appendChild(shield);

  shield.className = 'shield';
  shield.style.position = 'absolute';
  shield.style.left = '0px';
  shield.style.top = '0px';
  shield.style.width = document.documentElement.scrollWidth + 'px';

  var shieldHeight;
  if(document.documentElement.scrollHeight < document.documentElement.clientHeight){
    shieldHeight = document.documentElement.clientHeight;
  }else{
    shieldHeight = document.documentElement.scrollHeight;
  }
  shield.style.height = shieldHeight + 'px';

  var preview = document.createElement("DIV");
  document.body.appendChild(preview);

  preview.innerHTML = this.app_html(false) +
                      '<ul class="preview-actions">' +
                      '<li><a href="#" onclick="return MultiApp.add_preview(event,this);">Add this to my page</a></li>' +
                      '<li><a href="#" onclick="return MultiApp.cancel_preview(this);">Cancel</a></li>' +
		      '</ul>';
 
  var box_border = preview.firstChild;
  var box = MultiApp.box_from_box_border(box_border);
  box.app_name = app_name;

  preview.className = 'preview';
  preview.style.position = 'absolute';
  preview.style.left = '-1000px';

  MultiApp.associate_object_with_div(this,preview);


  MultiApp.queue_request(this.url,
                         [['action','get_content'],
    		          ['app_name',app_name]],
                         this,
    		         MultiApp.prototype.load_preview,
    		         []);

  MultiApp.process_queue();
  return false;
}
