Create a many to many relationship with a multi-select picklist

This scontrol allows you to build one to many relationships between objects in Force.com with a multi-select picklist. This s-control is designed to be invoked from a button or custom link of the account page. This scontrol requires three objects: Account, Book and Book Placement. A book placement is the many-to-many joiner object between Account and Book. When invoked a window will open which has a list of available books. The user can then select as many books as they would like. When the save button is pushed the records for the book placements are inserted into Force.com.

The Account object has a related list for book placements which looks like this:

1000 px

When the scontrol is invoked it looks like this:
Image:Place_Books_Scontrol.JPG

The first section of the code sets up the look and feel ...
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title></title>
<link href="/css/ie_global.css" rel="stylesheet" type="text/css">
<link href="/css/ie_navigation.css" rel="stylesheet" type="text/css">
<style type="text/css">
<!--
.row1 {
background-color: #EEEEEE;
padding: 0px 3px 0px 3px;
vertical-align: top;
}
.row2 {
background-color: #DDDDDD;
padding: 0px 3px 0px 3px;
vertical-align: top;
}
A, A:visited, A:active {
color: #000000;
text-decoration: underline;
}
A:hover {
color: #FFCC00;
text-decoration: underline;
}
-->
</style>

This Section sets up the Salesforce.com AJAX Toolkit

<script language="JavaScript1.2" src="/js/functions.js"></script>
<script src="/soap/ajax/9.0/connection.js" type="text/javascript"></script>
<script id="clientEventHandlersJS" language="javascript">

The toIsoDateTime function helps us accept a date and insert the data into salesforce.com

function toIsoDateTime(theDate) {

var today = new Date(theDate);
var year = today.getYear();
if (year < 2000) { 
	year = year + 1900; 
}
var month = today.getMonth() + 1;
var day = today.getDate();
var hour = today.getHours();
var hourUTC = today.getUTCHours();
var diff = hour - hourUTC;
var hourdifference = Math.abs(diff);
var minute = today.getMinutes();
var minuteUTC = today.getUTCMinutes();
var minutedifference;
var second = today.getSeconds();
var timezone;

if (minute != minuteUTC && minuteUTC < 30 && diff < 0) { hourdifference--; }
if (minute != minuteUTC && minuteUTC > 30 && diff > 0) { hourdifference--; }
if (minute != minuteUTC) { 
	minutedifference = ":30";
} else {
	minutedifference = ":00";
}
if (hourdifference < 10) { 
	timezone = "0" + hourdifference + minutedifference;
} else {
	timezone = "" + hourdifference + minutedifference;
}
if (diff < 0) {
	timezone = "-" + timezone;
} else {
	timezone = "+" + timezone;
}
if (month <= 9) month = "0" + month;
if (day <= 9) day = "0" + day;
if (hour <= 9) hour = "0" + hour;
if (minute <= 9) minute = "0" + minute;
if (second <= 9) second = "0" + second;
return year + "-" + month + "-" + day + "T" + hour + ":" + minute + ":" + second + timezone;
}

The InitPage function is where the excitement begins. InitPage queries salesforce.com to get a list of books to populate in the multi-select box.

// InitPage Called by OnLoad.
function initPage() {
var existingSel = document.getElementById('select_0');
try
	{
  var qr = sforce.connection.query("Select Name, Id From book__c ORDER BY Name") ;	
	}
catch (error)
	{
    alert(error.faultstring);
	}

if (qr.records.length > 0)
	{
	for (var i=0;i<qr.records.length;i++) 
		{
	     existingSel.options[i] = new Option(qr.records[i].get("Name"), qr.records[i].get("Id"));
		 }
	}
	else
	{
		alert("Query to populate picklist failed. No Rows.");
	}
// End Init Page with the bracket below.
}

The create placement function is called when the save button is pressed. This function validates the data and then performs the insert into salesforce.com. It also refreshes the parent page.

// Create Placement is called when the submit button is pushed.
function CreatePlacement() {
var bp = [];
var NewSel = document.getElementById('select_1');
var nd = toIsoDateTime(document.getElementById('inputDate').value);
var qt = document.getElementById('quantity');
if ((NewSel.options[0].value == '--None--') || (NewSel.options[0].value == '')) {
	alert("You Must select a book to be added!");
}
else 
{
for (var i=0;i<NewSel.options.length;i++) 
	{
	var placement = new sforce.SObject("Book_Placement__c");
	placement.set("Book__c", NewSel.options[i].value);
	placement.set("Account__c", "{!Account.Id}");
	placement.set("Delivery_Date__c",nd);
	placement.set("Quantity__c",qt.value);
	bp.push(placement);
	}
var cr = sforce.connection.create(bp);
for (var i=0; i<cr.length ;i++ )
    {
    if (cr[i].getBoolean("success")) 
		{
	    } else {
		alert("Failed to create Book Placement: " + cr[i]);
		}
    }
}
parent.frames.location.replace("/{!Account.Id}");
}

There is a bunch of stuff below to make the validation work and the screen look nice!

//-->
</script>
<script type="text/JavaScript">
<!--
function MM_findObj(n, d) { //v4.01
  var p,i,x;  if(!d) d=document; if((p=n.indexOf("?"))>0&&parent.frames.length) {
    d=parent.frames[n.substring(p+1)].document; n=n.substring(0,p);}
  if(!(x=d[n])&&d.all) x=d.all[n]; for (i=0;!x&&i<d.forms.length;i++) x=d.forms[i][n];
  for(i=0;!x&&d.layers&&i<d.layers.length;i++) x=MM_findObj(n,d.layers[i].document);
  if(!x && d.getElementById) x=d.getElementById(n); return x;
}

function MM_validateForm() { //v4.0
  var i,p,q,nm,test,num,min,max,errors='',args=MM_validateForm.arguments;
  for (i=0; i<(args.length-2); i+=3) { test=args[i+2]; val=MM_findObj(args[i]);
    if (val) { nm=val.name; if ((val=val.value)!="") {
      if (test.indexOf('isEmail')!=-1) { p=val.indexOf('@');
        if (p<1 || p==(val.length-1)) errors+='- '+nm+' must contain an e-mail address.\n';
      } else if (test!='R') { num = parseFloat(val);
        if (isNaN(val)) errors+='- '+nm+' must contain a number.\n';
        if (test.indexOf('inRange') != -1) { p=test.indexOf(':');
          min=test.substring(8,p); max=test.substring(p+1);
          if (num<min || max<num) errors+='- '+nm+' must contain a number between '+min+' and '+max+'.\n';
    } } } else if (test.charAt(0) == 'R') errors += '- '+nm+' is required.\n'; }
  } if (errors) alert('The following error(s) occurred:\n'+errors);
  document.MM_returnValue = (errors == '');
}
//-->
</script>
</head>
<body onload="javascript:initPage();">
<form name="placebook" id="placebook">
<table width="100%" cellpadding=0 cellspacing=0 border=0>
<tr>
<td> <table width="100%" cellpadding=0 cellspacing=0 border=0>
<tr>
<td align=LEFT class="pageTitle" nowrap><img src="/img/campaign_target_large.gif" border="0" alt="Accounts"

width=30 height=20 align="texttop">Mass Place Books for: &nbsp<b>{!Account.Name}</b></td>
<td width="100%" align=RIGHT bgcolor="#FFFFFF" nowrap><a href="#"><img src="/img/help_icon.gif" border="0"

width=25 height=18 align="texttop">Help</a></td>
</tr>
</table></td>
</tr>
<tr>
<td class="moduleLine"><img src="/s.gif" height="2"></td>
</tr>
<tr>
<td><img src="/s.gif"></td>
</tr>
<tr>
<td align=LEFT bgcolor="#CCCCCC" height=15 class="bodySmallBold"><a href="javascript:history.back()">« Back</a></td>
</tr>
</table>
<table border=0 cellspacing=1 cellpadding=0 id="ep" width="100%">
<tr id="btn">
<td colspan=5 align=center><input type="button" name="save" value=" Save " class="button"

onclick="javascript:CreatePlacement();">
   
<input type="button" name="cancel" value="Cancel" class="button" onClick="javascript:history.back();"><div

class=errorMsg id="error_General"></div></td>
</tr>
<tr>
<td colspan=5> </td>
</tr>
<tr id="head_1_ep">
<td class="bodyBold" colspan=4 nowrap>Book Placement Information:</td>
<td nowrap colspan=1 align=left><img src="/img/required_icon.gif" border="0" alt="Required Information" title="Required

Information" width=18 height=18 align="texttop"><span class="bodySmallBold"> = Required Information</span></td>
</tr>
<TR >
<TD CLASS="blackLine" COLSPAN=5><img src="/s.gif"></TD>
</TR>
<tr >
<td class="requiredInput">Book Name:</td>
<td colspan="4"><table border=0 cellspacing=1 cellpadding=0 id="selbook" width="100%">
<TR>
<TD COLSPAN=5 ><table cellpadding=1 cellspacing=1 border=0>
<tr>
<div class=errorMsg id="error_select_0"></div></td>
</tr>
<tr>
<td height=5 colspan=100%></td>
</tr>
<tr>
<td align=center valign=bottom class=bodyBold>Available Books </td>
<td width=3 rowSpan=2><IMG src="/s.gif" width=3 border=0></td>
<td> </td>
<td width=3 rowSpan=2><IMG src="/s.gif" width=3 border=0></td>
<td align=center valign=bottom class=bodyBold>Selected Books </td>
</tr>
<tr>
<td align=center valign=top><select name="select_0" id="select_0" MULTIPLE width="200" size="10">
<option value="">--None--</option>
</select></td>
<td align=center valign=center class=bodySmall><table cellpadding=0 cellspacing=0 border=0>
<tr>
<td valign=bottom align=center class=bodySmall>Add</td>
</tr>
<tr>
<td align=center><a href="javascript:moveOption(document.placebook.select_0,

document.placebook.select_1,'--None--', [], null,'--None--');"><img src="/img/arrow_rt.gif" border="0" alt="Add" title="Add"

width=23 height=23></a></td>
</tr>
<tr>
<td><IMG src="/s.gif" height=3>
<tr>
<td align=center><a href="javascript:moveOption(document.placebook.select_1,

document.placebook.select_0,'--None--', [], null,'--None--');"><img src="/img/arrow_lt.gif" border="0" alt="Remove"

title="Remove" width=23 height=23></a></td>
</tr>
<tr>
<td align=center valign=top class=bodySmall>Remove</td>
</tr>
</table></td>
<td align=center valign=top><select name="select_1" id="select_1" MULTIPLE width="200" size="10">
<option value="">--None-- </option>
</select></td>
</tr>
<tr><td colspan="5"><div class=errorMsg id="error_select_1"></div></td></tr>
</table></TD>
</TR>
</table></td>
</tr>
<tr >
<td nowrap class="dataLabel">Date Delivered:</td>
<td style="border-left: 5px solid #990000;"><nobr>
<input name="inputDate" id="inputDate" type="text" size=12 tabindex="3">
<a href="javascript:openPopupFocus('/home/calendar.jsp?form=placebook&field=inputDate&mo=0', '_blank', 193, 148,

'width=193,height=148,resizable=yes,toolbar=no,status=no,scrollbars=no,menubar=no,directories=no,location=no,dependant=yes',

true, true);" tabindex="3" onclick="setLastMousePosition(event)"><img src="/img/date_picker.gif" border="0" alt="Pick A Date"

title="Pick A Date" width=24 height=16></a><span class="bodySmall">[ <a href="#" onclick="if(document.getElementById

('inputDate') && document.getElementById('inputDate').disabled == false) { document.getElementById('inputDate').value = '{!Today}';}return false;">{!Today}</a> ]</span></nobr><div class=errorMsg id="error_inputDate"></div></td>
</tr>
<tr>
<td nowrap class="dataLabel"><label>Quantity</label></td>
<td style="border-left: 5px solid #990000;">
<input name="quantity" type="text" value="1" id="quantity" onblur="MM_validateForm('quantity','','RisNum');return document.MM_returnValue" />
</td>
</tr>
</table>
</form>
<hr><br>
</body>
</html>