SharePoint Team Services: Deleting Multiple Items from a List

 

July 2002

Applies To:
     SharePoint™ Team Services from Microsoft

Summary:   Use Collaborative Application Markup Language (CAML) to create a tool for deleting multiple items in a list on a Web site based on SharePoint Team Services from Microsoft. (28 pages)

Contents

Overview
Delete Items Based on User Input
Constructing the UI
Creating Arrays for Field Names and Types
Prepopulating the Value Picker
Deleting the Items
Complete Code Sample

Overview

Using a combination of Collaborative Application Markup Language (CAML), remote procedure call (RPC) protocol, script, and XMLHTTP, you can create a tool for deleting multiple items in a list on a Web site based on SharePoint™ Team Services from Microsoft®. CAML can be used in queries against the database to return fields or values, RPC protocol provides a vehicle for communicating with FrontPage® Server Extensions from Microsoft in order to work with lists, and script can be used to construct the CAML and RPC strings that are posted to the server. XMLHTTP provides a convenient way to make posts.

Note   You must have Advanced Author permissions on the server to upload the samples and perform the required customizations presented in this article.

Delete Items Based on User Input

To delete items based on user input, you first need to get the IDs for the items to delete, which requires posting a CAML view to the server. After the IDs are returned, the following command must be posted for each item ID:

http://[Server_Name]/[Subweb_Name]/_vti_bin/owssvr.dll?Cmd=Delete&List=[ListName]&ID=[ID]

As part of constructing and posting strings to delete items, this sample shows how to create a form for the user interface (UI) that enables the user to specify two sets of criteria and implement "and" or "or" logic between them. In part for security purposes, the field and value pickers are pre-populated so that the end user doesn't need to type values in a text box. The following figure provides an illustration of the resulting UI:

Figure 1. User interface for deleting multiple items from a list

Note   The example in this article assumes that you have installed the Microsoft XML Core Services (MSXML) 4.0, formerly called the Microsoft XML Parser, on the client. Download the Core Services.

The code provided later in this article can be put in an .htm file that includes the required instructions for processing in SharePoint Team Services: a script block that writes the CAML HTMLBase element for relative links and a RedirectToFrame block that specifies the path to the page for processing by the interpreter.

In addition, this example assumes that you've added to the list's AllItems.htm page a link that passes the current list name as a parameter.

For specific information on the formulation of these instructions and the link, see Adding Multiple Items to an Existing List. (Note that, unlike the sample for adding items, it is not necessary to pass the view's URL to the sample presented here.)

Constructing the UI

The UI contains a form (frmDelete) with two sets of three drop-down boxes — for the field, operator, and value pickers that are used to provide the two criteria sets. When the page opens, the field pickers are already populated with options. Once the user selects a field, the value picker is populated based on that selection.

The field picker below integrates CAML with HTML to return the current list's fields and construct the drop-down box. The onchange event of the field picker SELECT box calls the PrePop function, passing it the field picker value, a reference to the associated value picker, and the box's ID.

<FORM name="frmDelete" ID="frmDelete">
   <TABLE class="ms-toolbar" cellpadding=5>
      <TR><TD colspan=2 class=ms-sectionheader>Delete Items</TD></TR>
      <TR><TD>Select the field containing the value to delete and type the value. Optionally select a second set of criteria.</TD></TR>
   </TABLE>
   <TABLE class="ms-toolbar">
      <TR><TD><SELECT name="itmDelFld1" id="itmDelFld1" onchange="JavaScript:PrePop(document.frmDelete.itmDelFld1.value, lstItmDel1, id);">
         <OPTION value="None" SELECTED>None
         <ows:ForEach Select="/FIELDS/Field">
            <Switch>
               <Expr><Property Select="Type"/></Expr>
               <Case Value="Computed"></Case>
               <Case Value="Note"></Case>
               <Case Value="URL"></Case>
               <Default>
                  <Switch>
                     <Expr><Property Select="Name"/></Expr>
                     <Case Value="Modified"></Case>
                     <Case Value="Created"></Case>
                     <Case Value="PercentComplete"></Case>
                     <Default>
                        <HTML><![CDATA[<OPTION value="]]></HTML>
                        <Property Select="Name"/>
                        <HTML><![CDATA[">]]></HTML>
                        <Property Select="DisplayName" HTMLEncode="TRUE"/>
                     </Default>
                  </Switch>
               </Default>
            </Switch>
         </ows:ForEach>
      </SELECT></TD>

The CAML part enumerates the Fields collection for the list, and extends the SELECT box by adding OPTION elements that represent each field. It uses two Switch statements. The first Switch statement evaluates each field's type in order to exclude Computed and Note fields. Computed fields are virtual fields that derive from actual fields contained in the database. A LinkTitle field, for example, displays a table's Title field value as a link, surrounding the value with an appropriate <A> tag. Consequently, Computed fields are excluded from the field picker, not only because they display values from actual fields, but also because they cannot be displayed properly in the picker. Note fields are excluded because they cannot be used within a CAML Where clause and will return a "Render Failed" error message. The second Switch statement evaluates each field's name to exclude internal Modified and Created fields, and lays out the default rendering for other fields.

The operator picker is a simple drop-down box, while the value picker is initialized as a skeletal box with only None as an option. The PrePop function below prepopulates the value picker based on the field picker value.

      <TD><SELECT name="opSelect1" ID="opSelect1">
          <OPTION value="Eq">equals
          <OPTION value="Neq">does not equal
          <OPTION value="Leq">is less than or equal to
          <OPTION value="Lt">is less than
          <OPTION value="Geq">is greater than or equal to
          <OPTION value="Gt">is greater than
          </SELECT></TD>
      <TD><SELECT name="lstItmDel1" id="lstItmDel1">
          <OPTION value="None" SELECTED>None
      </SELECT></TD></TR>

The same code used here to define controls for a first set of criteria is used in the three controls of the second set. Simply change the name attributes appropriately (here, to itmDelFld2, opSelect2, and lstItmDel2). In addition, you can use an AndOr option box to specify the logical relation between criteria sets.

      <TR><TD><INPUT type=radio name="AndOr" CHECKED ID=Radio1>And
        <INPUT type=radio name="AndOr" ID=Radio2>Or</TD></TR>

The <A> tag for the Delete Items button calls the EditItems function to delete the items selected.

      <TR><TD><A href="javascript:EditItems()" target=_self ID="DelItms" NAME="DelItms" target=_self>Delete Items</A></TD></TR>
   </TABLE>
</FORM>

Creating Arrays for Field Names and Types

Within a SCRIPT block in the HEAD section, you can construct an array that associates each field in the current list with its field type. This array is used by the PrePop and EditItems functions.

var fldType = new Array();
<ows:ForEach Select="/FIELDS/Field">
   <Switch>
      <Expr><Property Select="Type"/></Expr>
      <Case Value="Computed"></Case>
      <Default>
      <HTML><![CDATA[
      fldType["]]></HTML>
      <Property Select="Name"/>
      <HTML><![CDATA["] = "]]></HTML>
      <Property Select="Type"/>
      <HTML><![CDATA[";]]></HTML>
      </Default>
   </Switch>
</ows:ForEach>

The CAML in the preceding example iterates through the list's Fields collection and, except for the case of Computed fields, constructs an array that contains each field name and type. Note that, once again, Computed fields have been excluded from the array.

Another similar array can be created that associates the internal field names with their display names. This new array will be used to create warning strings to confirm deletion.

   var fldName = new Array;
   <ows:ForEach Select="/FIELDS/Field">
      <HTML><![CDATA[fldName["]]></HTML>
      <Property Select="Name"/>
      <HTML><![CDATA["] = "]]></HTML>
      <Property Select="DisplayName" HTMLEncode="TRUE"/>
      <HTML><![CDATA[";]]></HTML> 
   </ows:ForEach>

Pre-populating the Value Picker

The PrePop function pre-populates the value picker based on user selection through the field picker. This function constructs the CAML view to post in order to return all the field's values from the database. It uses XMLHTTP to post the view, and the XML Document Object Model (DOM) to parse the server's response and construct an array that contains the values returned. For each value, it adds an OPTION element to the value picker.

Trapping Errors and Removing Previous Children

The PrePop function first traps cases where selections for the first criteria set are incomplete. In addition, the RemChildren function is called to remove OPTION elements that derive from previous attempts to make a selection in the UI.

function PrePop(selField, lstItem, itmID)
{
   var strCol;
   var lenItms = lstItem.children.length;

   if (itmID=="itmDelFld2" && (document.frmDelete.itmDelFld1.value=="None" || document.frmDelete.lstItmDel1.value=="None"))
   {
      alert("The first set of criteria is incomplete.");
      document.frmDelete.itmDelFld2.value="None";
      RemChildren(lenItms, lstItem);
      return;
   }

   RemChildren(lenItms, lstItem);

   if (selField=="None")
   {
      return;
   }

Dynamically Constructing a CAML View for Getting the Field's Values

Depending on user selection criteria, a different Where clause must be dynamically constructed and inserted as part of a CAML view to return values for the field that is selected in the field picker. Note that CAML elements have been disguised from the SharePoint Team Services interpreter in order to prevent it from attempting to process the CAML immediately. Such is the case whenever a view depends on immediate user input — say, on the value chosen in a drop-down box, or on the value typed in a text box. Otherwise, the script would not work synchronously with CAML.

   if (fldType[selField]=="Lookup")
   {
      strCol = "&lt;LookupColumn Name=\"" + selField + "\"/&gt;";
   }

   else if (fldType[selField]=="DateTime")
   {
      strCol = "&lt;Column Format=\"ISO8601\"/&gt;";
   }

   else
   {
      strCol = "&lt;Column/&gt;";
   }

The preceding code evaluates the field name and type and then formulates the CAML element used within the Where clause to return column values. If the field is Author or Editor, a LookupColumn element is used in order to return names from the UserInfo table. If the field type is DateTime, the Column element is used with the ISO8601 format specified. For all other fields, an empty Column element is used.

The following code completes construction of the CAML view string that is posted to the server.

   var strMk = "<ows:HttpVDir/>/_vti_bin/owssvr.dll?Cmd=DisplayPost" + 
      "&PostBody=&lt;ows:Batch&gt;&lt;Method ID=&quot;itmIDs1&quot;&gt;" + 
      "&lt;SetList&gt;<ows:List/>&lt;/SetList&gt;&lt;View&gt;" + 
      "&lt;ViewFields&gt;&lt;FieldRef Name=\"" + selField + "\"/&gt;" +
      "&lt;/ViewFields&gt;&lt;ViewBody&gt;&lt;Fields&gt;" + strCol + 
      "&lt;HTML&gt;&lt;![CDATA[-,-]]&gt;&lt;/HTML&gt;&lt;/Fields&gt;" +
      "&lt;/ViewBody&gt;&lt;Query&gt;&lt;OrderBy&gt;&lt;FieldRef Name=\"" + 
      selField + "\" Ascending=\"FALSE\"/&gt;&lt;/OrderBy&gt;" + 
      "&lt;/Query&gt;" + "&lt;/View&gt;&lt;/Method&gt;&lt;/ows:Batch&gt;";

   var strMk = RepCAML(strMk);

The RepCAML function (described later in this article) is called to replace angle brackets and quotation marks in the string for posting the string. In addition, any unique formulation of a separator (here, "-,-") is inserted within the view body between the values returned. The separator is used in building an array to contain the values, and it allows the use of commas in text fields.

Using XMLHTTP to Post the View and Return Values

XMLHTTP and DOMDocument objects are next created in order to post the CAML view to the server and parse the response.

   var HTTPob = new ActiveXObject("Msxml2.XMLHTTP.4.0");
   HTTPob.open("POST", strMk, false);
   HTTPob.send();
   var docObjRec = new ActiveXObject("Msxml2.DOMDocument.4.0");
   docObjRec.async = false;
   docObjRec.loadXML(HTTPob.responseXML.xml);
   docObjRec.setProperty("SelectionLanguage", "XPath");
   var valRet = docObjRec.selectSingleNode("//Results/Result");
   var strRet = valRet.text;

Note that the async parameter is set to false in order to prevent the script from continuing to execute until the server has processed the request and provided a response. The response is assigned to the DOMDocument object docObjRec, which is used to fetch the view field names. The setProperty method's parameters specify that XPath is the language used in the subsequent selectSingleNode method, which returns the values contained in all /Results/Result nodes of the server's XML response.

The extra "-,-" separator at the end of the returned string is removed, and an array is built by using the separator.

   var Len1 = strRet.length - 3;
   strRetIDs = strRet.substr(0, Len1);
   var arIDs = strRetIDs.split(/-,-/);
   var aCnt = arIDs.length;

Adding Children to the SELECT Element

Finally, the length of the array is used to iterate through all the array elements and create OPTION elements to add to the value picker. Repeated values in the array are excluded.

   for (i=0; i<aCnt; i++)
   {
      if (arIDs[i]==arIDs[i+1])
      {}
      else
      {
         var newItem = document.createElement("OPTION");
         lstItem.children(0).insertAdjacentElement("afterEnd",newItem);
         newItem.innerText = arIDs[i];
         strValue = arIDs[i].toString();
         var rep = /\$/;
         strVal = strValue.replace(rep,"");
         lstItem.style.width = "1";
         newItem.setAttribute("value", strVal);
         lstItem.style.width = "auto";
      }
   }
}

function RemChildren(lenChlds, lstItm)
{

   for (i=1; i<lenChlds; i++)
   {
      var remChld = lstItm.children[1];
      lstItm.removeChild(remChld);
   }
}

As it creates each OPTION element, the preceding loop sets the value attribute, removing the dollar sign from the contents of the currency fields, which would otherwise cause an error. In addition, the loop forces the value picker width to resize in order to accommodate the values that are added.

Deleting the Items

When the user selects a value in the UI and clicks Delete Items, the EditItems function is called. The EditItems function uses XMLHTTP at two different points — first to return item IDs based on user selection and then to post the URL string for deleting items.

Assembling the User Input

With the help of the GetSelectedValue function, the EditItems function first assembles the values specified through the UI and then assigns these values to global variables:

function GetSelectedValue(frmElem)
{
   if (frmElem)
   {
      itmVal = frmElem.options[frmElem.selectedIndex].value;
      return itmVal;
   }
   else
      return "";
}

function EditItems()
{
   var selField1;
   var selOp1;
   var lstItm1;
   var selField2;
   var selOp2;
   var lstItm2;

   selField1 = GetSelectedValue(document.frmDelete.itmDelFld1);
   selOp1 = GetSelectedValue(document.frmDelete.opSelect1);
   lstItm1 = document.frmDelete.lstItmDel1.value;
   selField2 = GetSelectedValue(document.frmDelete.itmDelFld2);
   selOp2 = GetSelectedValue(document.frmDelete.opSelect2);
   lstItm2 = document.frmDelete.lstItmDel2.value;

Trapping Problems in Logic

Once the variables are assigned values, you can set traps for various errors. Here, the code ensures that only equal or not-equal logic is used with Text or Lookup field types, and it verifies that criteria sets are complete.

   if  ((selOp1!="Neq"&&selOp1!="Eq")&&(fldType[selField1]=="Text"||
       fldType[selField1]=="Lookup"))
   {
      alert("Invalid operator.");
      return;
   }

   if ((selOp2!="Neq"&&selOp2!="Eq")&&(fldType[selField2]=="Text"||
      fldType[selField2]=="Lookup"))
   {
      alert("Invalid operator.");
      return;
   }

   if (selField1=="None" || lstItm1=="None")
   {
      alert("The first set of criteria is incomplete.");
      return;
   }

   if ((selField2!="None" && lstItm2=="None") ||
      (lstItm1=="None" &&   lstItm2!="None"))
   {
      alert("The second set of criteria is incomplete.");
      return;
   }

Constructing a CAML View to Get Item IDs

You can now construct the CAML view to post in order to get item IDs, based on the values selected by the user. As in the above PrePop function, a unique Where clause must be constructed, depending on the field(s), operator(s), and value(s) chosen.

   var Op1 = "&lt;" + selOp1 + "&gt;";
   var Op2 = "&lt;/" + selOp1 + "&gt;";
   var Op3 = "&lt;" + selOp2 + "&gt;";
   var Op4 = "&lt;/" + selOp2 + "&gt;";
   var Fld1 = "&lt;FieldRef Name=&quot;" + selField1 + "&quot;/&gt;";
   var Fld2 = "&lt;FieldRef Name=&quot;" + selField2 + "&quot;/&gt;";
   var mkWhere;
   var Where1;
   var Where2;

   Where1 = Op1 + Fld1 + "&lt;Value Type=&quot;" + fldType[selField1] +   "&quot;&gt;" + lstItm1 + "&lt;/Value&gt;" + Op2;
   Where2 = Where1 + Op3 + Fld2 + "&lt;Value Type=&quot;" + 
      fldType[selField2] + "&quot;&gt;" + lstItm2 + "&lt;/Value&gt;" + Op4;

If the second field picker equals "None," then only the Where1 part of the Where clause is needed. Otherwise, the code proceeds to trap cases where the user has selected more than one value for a given field name with And as the framing operator (in other words, eliminating the fallacy that an item field would contain more than one value).

   if (selField2 == "None")
   {
      mkWhere = Where1;
   }

   var wrnMultVal = "A field for a single item can only equal one value.";

   if (selField2!="None")
   {
      if (document.frmDelete.Radio1.checked)
      {
         if (selField1==selField2 && (selOp1=="Eq" || selOp2=="Eq"))
         {
            alert(wrnMultVal);
            return;
         }
         mkWhere = "&lt;And&gt;" + Where2 + "&lt;/And&gt;";
      }
      else
      {
         mkWhere = "&lt;Or&gt;" + Where2 + "&lt;/Or&gt;";
      }
   }

   var strParam = "<ows:HttpVDir/>/_vti_bin/owssvr.dll?Cmd=DisplayPost&" +
                  "PostBody=&lt;ows:Batch&gt;&lt;Method ID=&quot;Del1&quot;&gt;" +
                  "&lt;SetList&gt;<ows:List/>&lt;/SetList&gt;&lt;View " + 
                  "DisplayName=&quot;MyHiddenView&quot;&gt;&lt;ViewFields&gt;" +
                  "&lt;FieldRef Name=&quot;ID&quot;/&gt;&lt;/ViewFields&gt;" +
                  "&lt;Query&gt;&lt;Where&gt;" + mkWhere + "&lt;/Where&gt;" + 
                  "&lt;OrderBy&gt;&lt;FieldRef Name=&quot;ID&quot;/&gt;" + 
                  "&lt;/OrderBy&gt;&lt;/Query&gt;&lt;ViewBody&gt;&lt;Fields&gt;" +
                  "&lt;Field/&gt;&lt;HTML&gt;<![CDATA[,]]>&lt;/HTML&gt;" +
                  "&lt;/Fields&gt;&lt;/ViewBody&gt;&lt;/View&gt;" +
                  "&lt;/Method&gt;&lt;/ows:Batch&gt;";

   var strPost = RepCAML(strParam);

The completed string is passed to the RepCAML function in order to replace angle brackets and quotation marks before making the post.

Posting the View and Returning IDs

As with the PrePop function, XML is used to post the CAML view and return IDs of the items selected by the user for deletion:

   var docObjReceive;
   var HTTPobj;
   var strReturn;
   var strIDs;
   var Len;

   HTTPobj = new ActiveXObject("Msxml2.XMLHTTP.4.0");
   HTTPobj.open("POST", strPost, false);
   HTTPobj.send();
   docObjReceive = new ActiveXObject("Msxml2.DOMDocument.4.0");
   docObjReceive.async = false;
   docObjReceive.loadXML(HTTPobj.responseXML.xml);
   docObjReceive.setProperty("SelectionLanguage", "XPath");
   strReturn = docObjReceive.selectSingleNode("//Results/Result");
   strRaw = strReturn.text;

   Len = strRaw.length - 1;
   strIDs = strRaw.substr(0, Len);

   if (strIDs=="")
   {
      alert("Sorry, no items matching the specified criteria were found.");
      return;
   }

   var arrEdit = strIDs.split(/,/);
   var arrCnt = arrEdit.length;

The DOMDocument object is used to parse the XML response from the server and get the values. The returned values are loaded into an array.

Issuing a Warning and Confirming Deletion

A last warning is dynamically constructed for the user, depending on whether two sets of criteria are being implemented.

   var strOP;
   var strWarn;
   var strWarn1 = "Are you sure you want to delete all items where " +  
      fldName[selField1]  + " " +   frmDelete.opSelect1.options[frmDelete.opSelect1.selectedIndex].text + 
      " " + lstItm1;

   if (selField2=="None")
   {
      strWarn = strWarn1 + "?";
   }

   else
   {
      if (frmDelete.Radio1.checked==true)
      {
         strOP = "and";
      }
      else
      {
         strOP = "or";
      }

   var strWarn = strWarn1 + " " + strOP + " " + fldName[selField2] + " " +
      frmDelete.opSelect2.options[frmDelete.opSelect2.selectedIndex].text + " "  + lstItm2 + "?";
   }

Finally, a for loop is used to iterate through the array containing the IDs of items to delete. A complete URL string is constructed and posted for each ID. Once again, XMLHTTP is used to make the post, and the user is directed back to the AllItems.htm page.

   if (confirm(strWarn))
   {
      for (i=0; i<arrCnt; i++)
      {
         var delString = "<ows:HttpVDir/>" +   "/_vti_bin/owssvr.dll?Cmd=Delete&List=" + "<ows:List/>" + "&ID=" + arrEdit[i] + "&NextUsing=" + "<ows:ListUrlDir/>" + "/AllItems.htm";

         HTTPobj2 = new ActiveXObject("Msxml2.XMLHTTP.4.0");
         HTTPobj2.open("POST", delString, false);
         HTTPobj2.send();
      }
      window.parent.location = "<ows:ListUrlDir/>" + "/AllItems.htm";
   }

   else
   {
      return;
   }
}

Note that, in the preceding URL string, the CAML HTTPVDir and ListUrlDir elements are used to return the directory paths of the current subweb and current list, respectively.

The RepCAML function replaces entities with the appropriate angle brackets (< and >) and quotation marks (").

function RepCAML(theStr)
{
   var re;
   re = /&lt;/g;
   theStr=theStr.replace(re,"<");
   re = /&gt;/g;
   theStr=theStr.replace(re,">");
   re = /&quot;/g;
   theStr=theStr.replace(re,"\"");
   return(theStr);
}

For more information about CAML, see the CAML reference in the SharePoint Team Services SDK.

Complete Code Sample

The following script can go in the HEAD section of the Delete Items page:

<SCRIPT>
//Construct an array that associates each field name with the field's type.
var fldType = new Array();
<ows:ForEach Select="/FIELDS/Field">
   <Switch>
      <Expr><Property Select="Type"/></Expr>
      <Case Value="Computed"></Case>
      <Default>
      <HTML><![CDATA[
      fldType["]]></HTML>
      <Property Select="Name"/>
      <HTML><![CDATA["] = "]]></HTML>
      <Property Select="Type"/>
      <HTML><![CDATA[";]]></HTML>
      </Default>
   </Switch>
</ows:ForEach>

//Construct an array that associates each field's internal name with its display name.
var fldName = new Array;
<ows:ForEach Select="/FIELDS/Field">
   <HTML><![CDATA[fldName["]]></HTML>
   <Property Select="Name"/>
   <HTML><![CDATA["] = "]]></HTML>
   <Property Select="DisplayName" HTMLEncode="TRUE"/>
   <HTML><![CDATA[";]]></HTML> 
</ows:ForEach>

//Use this function to prepopulate the value picker.
function PrePop(selField, lstItem, itmID)
{
   var strCol;
   var lenItms = lstItem.children.length;

/*For frmDelete, trap cases where a change is made in the second field picker and one of first criteria is None. Set the second field picker to None, and call remChilds to remove children.*/
   if (itmID=="itmDelFld2" && (document.frmDelete.itmDelFld1.value=="None" ||   document.frmDelete.lstItmDel1.value=="None"))
   {
      alert("The first set of criteria is incomplete.");
      document.frmDelete.itmDelFld2.value="None";
      RemChildren(lenItms, lstItem);
      return;
   }

//Call remChilds to remove children and do nothing if None has been selected.
   RemChildren(lenItms, lstItem);

   if (selField=="None")
   {
      return;
   }

/*Create a different element for use in the mkWhere string, depending on name or field type.*/
   if (fldType[selField]=="Lookup")
   {
      strCol = "&lt;LookupColumn Name=\"" + selField + "\"/&gt;";
   }

   else if (fldType[selField]=="DateTime")
   {
      strCol = "&lt;Column Format=\"ISO8601\"/&gt;";
   }

   else
   {
      strCol = "&lt;Column/&gt;";
   }

/*Complete construction of the string for posting a CAML view to the server to return values for the selected field. Use -,- as separator between values. Replace entities with angle brackets and quotation marks for post.*/
   var strMk = "<ows:HttpVDir/>/_vti_bin/owssvr.dll?Cmd=DisplayPost" + 
      "&PostBody=&lt;ows:Batch&gt;&lt;Method ID=&quot;itmIDs1&quot;&gt;" + 
      "&lt;SetList&gt;<ows:List/>&lt;/SetList&gt;&lt;View&gt;" + 
      "&lt;ViewFields&gt;&lt;FieldRef Name=\"" + selField + "\"/&gt;" +
      "&lt;/ViewFields&gt;&lt;ViewBody&gt;&lt;Fields&gt;" + strCol + 
      "&lt;HTML&gt;&lt;![CDATA[-,-]]&gt;&lt;/HTML&gt;&lt;/Fields&gt;" +
      "&lt;/ViewBody&gt;&lt;Query&gt;&lt;OrderBy&gt;&lt;FieldRef Name=\"" + 
      selField + "\" Ascending=\"FALSE\"/&gt;&lt;/OrderBy&gt;" + 
      "&lt;/Query&gt;" + "&lt;/View&gt;&lt;/Method&gt;&lt;/ows:Batch&gt;";

   var strMk = RepCAML(strMk);

/*Create XMLHTTP and DOMDocument objects for posting the CAML view to the server and parsing the response.*/
   var HTTPob = new ActiveXObject("Msxml2.XMLHTTP.4.0");
   HTTPob.open("POST", strMk, false);
   HTTPob.send();
   var docObjRec = new ActiveXObject("Msxml2.DOMDocument.4.0");
   docObjRec.async = false;
   docObjRec.loadXML(HTTPob.responseXML.xml);
   docObjRec.setProperty("SelectionLanguage", "XPath");
   var valRet = docObjRec.selectSingleNode("//Results/Result");
   var strRet = valRet.text;

//Remove the extra separator at end and create an array to contain values.
   var Len1 = strRet.length - 3;
   strRetIDs = strRet.substr(0, Len1);
   var arIDs = strRetIDs.split(/-,-/);
   var aCnt = arIDs.length;

/*Using the number of elements in the array, iterate through all values and create OPTION elements to add to value picker. 
When array elements repeat, do nothing.*/
   for (i=0; i<aCnt; i++)
   {
      if (arIDs[i]==arIDs[i+1])
      {}
      else
      {
         var newItem = document.createElement("OPTION");
         lstItem.children(0).insertAdjacentElement("afterEnd",newItem);
         newItem.innerText = arIDs[i];
         strValue = arIDs[i].toString();
         var rep = /\$/;
         strVal = strValue.replace(rep,"");
         lstItem.style.width = "1";
         newItem.setAttribute("value", strVal);
         lstItem.style.width = "auto";
       }
   }
}

//Remove options from the value picker when changes are made in the field picker.
function RemChildren(lenChlds, lstItm)
{

   for (i=1; i<lenChlds; i++)
   {
      var remChld = lstItm.children[1];
      lstItm.removeChild(remChld);
   }
}

//Get the user-entered data.
function GetSelectedValue(frmElem)
{
   if (frmElem)
   {
      itmVal = frmElem.options[frmElem.selectedIndex].value;
      return itmVal;
   }
   else
      return "";
}

/*Use this function to get IDs and delete items. Start by assigning user values to variables.*/
function EditItems()
{
   var selField1;
   var selOp1;
   var lstItm1;
   var selField2;
   var selOp2;
   var lstItm2;

   selField1 = GetSelectedValue(document.frmDelete.itmDelFld1);
   selOp1 = GetSelectedValue(document.frmDelete.opSelect1);
   lstItm1 = document.frmDelete.lstItmDel1.value;
   selField2 = GetSelectedValue(document.frmDelete.itmDelFld2);
   selOp2 = GetSelectedValue(document.frmDelete.opSelect2);
   lstItm2 = document.frmDelete.lstItmDel2.value;

//Trap invalid operators.
   if  ((selOp1!="Neq"&&selOp1!="Eq")&&(fldType[selField1]=="Text"||
       fldType[selField1]=="Lookup"))
   {
      alert("Invalid operator.");
      return;
   }

   if ((selOp2!="Neq"&&selOp2!="Eq")&&(fldType[selField2]=="Text"||
      fldType[selField2]=="Lookup"))
   {
      alert("Invalid operator.");
      return;
   }

//Trap cases where the field or value picker equals None.
   if (selField1=="None" || lstItm1=="None")
   {
      alert("The first set of criteria is incomplete.");
      return;
   }

   if ((selField2!="None" && lstItm2=="None") ||
      (lstItm1=="None" &&   lstItm2!="None"))
   {
      alert("The second set of criteria is incomplete.");
      return;
   }

//Construct the operators specified by the user and create FieldRef elements.
   var Op1 = "&lt;" + selOp1 + "&gt;";
   var Op2 = "&lt;/" + selOp1 + "&gt;";
   var Op3 = "&lt;" + selOp2 + "&gt;";
   var Op4 = "&lt;/" + selOp2 + "&gt;";
   var Fld1 = "&lt;FieldRef Name=&quot;" + selField1 + "&quot;/&gt;";
   var Fld2 = "&lt;FieldRef Name=&quot;" + selField2 + "&quot;/&gt;";
   var mkWhere;
   var Where1;
   var Where2;

//Construct core parts of the Where clause for the CAML view to be posted.
   Where1 = Op1 + Fld1 + "&lt;Value Type=&quot;" + fldType[selField1] +   "&quot;&gt;" + lstItm1 + "&lt;/Value&gt;" + Op2;
   Where2 = Where1 + Op3 + Fld2 + "&lt;Value Type=&quot;" + fldType[selField2] + "&quot;&gt;" + lstItm2 + "&lt;/Value&gt;" + Op4;


//If the second criteria set is not used, create the shorter Where1 clause.
   if (selField2 == "None")
   {
      mkWhere = Where1;
   }

/*If the second criteria set is used, create the appropriate Where clause, but add
handling for cases in which the user tries to use And when the same field is
selected in both field pickers.*/
   var wrnMultVal = "A field for a single item can only equal one value.";

   if (selField2!="None")
   {
      if (document.frmDelete.Radio1.checked)
      {
         if (selField1==selField2 && (selOp1=="Eq" || selOp2=="Eq"))
         {
            alert(wrnMultVal);
            return;
         }
         mkWhere = "&lt;And&gt;" + Where2 + "&lt;/And&gt;";
      }

//Make Where clause for when Or is selected.
      else
      {
         mkWhere = "&lt;Or&gt;" + Where2 + "&lt;/Or&gt;";
      }
   }

/*Assemble the complete string to be posted for returning IDs of items to delete and remove HTML encoding.*/
   var strParam = "<ows:HttpVDir/>/_vti_bin/owssvr.dll?Cmd=DisplayPost&" +
                  "PostBody=&lt;ows:Batch&gt;&lt;Method ID=&quot;Del1&quot;&gt;" +
                  "&lt;SetList&gt;<ows:List/>&lt;/SetList&gt;&lt;View " + 
                  "DisplayName=&quot;MyHiddenView&quot;&gt;&lt;ViewFields&gt;" +
                  "&lt;FieldRef Name=&quot;ID&quot;/&gt;&lt;/ViewFields&gt;" +
                  "&lt;Query&gt;&lt;Where&gt;" + mkWhere + "&lt;/Where&gt;" + 
                  "&lt;OrderBy&gt;&lt;FieldRef Name=&quot;ID&quot;/&gt;" + 
                  "&lt;/OrderBy&gt;&lt;/Query&gt;&lt;ViewBody&gt;&lt;Fields&gt;" +
                  "&lt;Field/&gt;&lt;HTML&gt;<![CDATA[,]]>&lt;/HTML&gt;" +
                  "&lt;/Fields&gt;&lt;/ViewBody&gt;&lt;/View&gt;" +
                  "&lt;/Method&gt;&lt;/ows:Batch&gt;";

   var strPost = RepCAML(strParam);

/*Use XML HTTP to post the string to the server, and create a DOM document
object to contain and parse the server's response.*/
   var docObjReceive;
   var HTTPobj;
   var strReturn;
   var strIDs;
   var Len;

   HTTPobj = new ActiveXObject("Msxml2.XMLHTTP.4.0");
   HTTPobj.open("POST", strPost, false);
   HTTPobj.send();
   docObjReceive = new ActiveXObject("Msxml2.DOMDocument.4.0");
   docObjReceive.async = false;
   docObjReceive.loadXML(HTTPobj.responseXML.xml);
   docObjReceive.setProperty("SelectionLanguage", "XPath");
   strReturn = docObjReceive.selectSingleNode("//Results/Result");
   strRaw = strReturn.text;

/*Create an array to contain IDs of items to delete and create a warning that asks the user to confirm that they want to delete items.*/
   Len = strRaw.length - 1;
   strIDs = strRaw.substr(0, Len);

   if (strIDs=="")
   {
      alert("Sorry, no items matching the specified criteria were found.");
      return;
   }

   var arrEdit = strIDs.split(/,/);
   var arrCnt = arrEdit.length;

   var strOP;
   var strWarn;
   var strWarn1 = "Are you sure you want to delete all items where " + 
      fldName[selField1]  + " " + frmDelete.opSelect1.options[frmDelete.opSelect1.selectedIndex].text + " " + lstItm1;

   if (selField2=="None")
   {
      strWarn = strWarn1 + "?";
   }

   else
   {
      if (frmDelete.Radio1.checked==true)
      {
         strOP = "and";
      }
      else
      {
         strOP = "or";
      }

   var strWarn = strWarn1 + " " + strOP + " " + fldName[selField2] + " " +
      frmDelete.opSelect2.options[frmDelete.opSelect2.selectedIndex].text + " "  + lstItm2 + "?";
   }

/*If confirmed, build a complete delete string for deleting each item and post the string.*/
   if (confirm(strWarn))
   {
      for (i=0; i<arrCnt; i++)
      {
         var delString = "<ows:HttpVDir/>" +       "/_vti_bin/owssvr.dll?Cmd=Delete&List=" + "<ows:List/>" + "&ID=" + 
            arrEdit[i] + "&NextUsing=" + "<ows:ListUrlDir/>" + "/AllItems.htm";

         HTTPobj2 = new ActiveXObject("Msxml2.XMLHTTP.4.0");
         HTTPobj2.open("POST", delString, false);
         HTTPobj2.send();
      }
      window.parent.location = "<ows:ListUrlDir/>" + "/AllItems.htm";
   }

   else
   {
      return;
   }
}

//Replace HTML encoding.
function RepCAML(theStr)
{
   var re;
   re = /&lt;/g;
   theStr=theStr.replace(re,"<");
   re = /&gt;/g;
   theStr=theStr.replace(re,">");
   re = /&quot;/g;
   theStr=theStr.replace(re,"\"");
   return(theStr);
}

</SCRIPT>

The following form can be placed in the BODY section of the page:

<FORM name="frmDelete" ID="frmDelete">
   <TABLE class="ms-toolbar" cellpadding=5>
      <TR><TD colspan=2 class=ms-sectionheader>Delete Items</TD></TR>
      <TR><TD>Select the field containing the value to delete and type the value. Optionally select a second set of criteria.</TD></TR>
   </TABLE>
   <TABLE class="ms-toolbar">
      <TR><TD><SELECT name="itmDelFld1" id="itmDelFld1" onchange="JavaScript:PrePop(document.frmDelete.itmDelFld1.value, lstItmDel1, id);">
         <OPTION value="None" SELECTED>None
        <ows:ForEach Select="/FIELDS/Field">
          <Switch>
            <Expr><Property Select="Type"/></Expr>
            <Case Value="Computed"></Case>
            <Case Value="Note"></Case>
            <Case Value="URL"></Case>
             <Default>
               <Switch>
                 <Expr><Property Select="Name"/></Expr>
                 <Case Value="Modified"></Case>
                 <Case Value="Created"></Case>
                 <Case Value="PercentComplete"></Case>
                 <Default>
                   <HTML><![CDATA[<OPTION value="]]></HTML>
                    <Property Select="Name"/>
                    <HTML><![CDATA[">]]></HTML>
                    <Property Select="DisplayName" HTMLEncode="TRUE"/>
                 </Default>
               </Switch>
             </Default>
          </Switch>
        </ows:ForEach>
      </SELECT></TD>

      <TD><SELECT name="opSelect1" ID="opSelect1">
          <OPTION value="Eq">equals
          <OPTION value="Neq">does not equal
          <OPTION value="Leq">is less than or equal to
          <OPTION value="Lt">is less than
          <OPTION value="Geq">is greater than or equal to
          <OPTION value="Gt">is greater than
        </SELECT></TD>
        <TD><SELECT name="lstItmDel1" id="lstItmDel1">
          <OPTION value="None" SELECTED>None
      </SELECT></TD></TR>
      <TR><TD><INPUT type=radio name="AndOr" CHECKED ID=Radio1>And
        <INPUT type=radio name="AndOr" ID=Radio2>Or</TD></TR>

      <TR><TD><SELECT name="itmDelFld2" id="itmDelFld2" onchange="JavaScript:PrePop(document.frmDelete.itmDelFld2.value, lstItmDel2, id)">
         <OPTION value="None" SELECTED>None
         <ows:ForEach Select="/FIELDS/Field">
            <Switch>
               <Expr><Property Select="Type"/></Expr>
               <Case Value="Computed"></Case>
               <Case Value="Note"></Case>
               <Case Value="URL"></Case>
               <Default>
                  <Switch>
                     <Expr><Property Select="Name"/></Expr>
                     <Case Value="Modified"></Case>
                     <Case Value="Created"></Case>
                     <Case Value="PercentComplete"></Case>
                     <Default>
                        <HTML><![CDATA[<OPTION value="]]></HTML>
                        <Property Select="Name"/>
                        <HTML><![CDATA[">]]></HTML>
                        <Property Select="DisplayName" HTMLEncode="TRUE"/>
                     </Default>
                  </Switch>
               </Default>
            </Switch>
         </ows:ForEach>
      </SELECT></TD>

      <TD><SELECT name="opSelect2" ID="opSelect2">
          <OPTION value="Eq">equals
          <OPTION value="Neq">does not equal
          <OPTION value="Leq">is less than or equal to
          <OPTION value="Lt">is less than
          <OPTION value="Geq">is greater than or equal to
          <OPTION value="Gt">is greater than
          </SELECT></TD>
      <TD><SELECT name="lstItmDel2" id="lstItmDel2">
          <OPTION value="None" SELECTED>None
      </SELECT></TD></TR>
      <TR><TD><A href="javascript:EditItems()" target=_self ID="DelItms" NAME="DelItms" target=_self>Delete Items</A></TD></TR>
   </TABLE>
</FORM>