views:

336

answers:

3

I have the following array.

<cfset ItemHasUsers = arrayNew(1)>
<cfloop query="qReadData">
 <cfset ItemHasUsers[qReadData.currentrow]["ID"] = qReadData.ID >
 <cfset ItemHasUsers[qReadData.currentrow]["Asset"] = qReadData.COUNTOFITEMS >
</cfloop>

I get some records from my database which i put into a table and which manipulate through a form.

<form action="same-site.cfm method="post">
<table>
 <tr>
  <th>ID</th>
  <th>Asset</th>
  <th>Delete</th>
 <tr>
<cfset ItemHasUsers = Item.getItemHasUsers() >
<cfoutput>
<cfloop index="i" from="1" to="#arrayLen(ItemHasUsers)#">
  <td>#ItemHasUsers[i]["ID"]#</td>
  <td><input type="text" name="upd_#ItemHasUsers[i]["ID"]#" maxlength="6" size="6" value="#ItemHasUsers[i]["Asset"]#"></td>
  <td><input type="checkbox" name="del_#ItemHasUsers[i]["ID"]#"></td>
 </tr>
</cfloop>
</cfouput>
</table>
<input type="submit" value="OK">
</form>

Dependend on my input i want to update my database. Currently i loop through the form struct to clear the User i want to delete. Looks ugly, but i do not know a better way -> watch the beginner tag ;)

<cfset ItemHasUsers = Item.getItemHasUsers() >
<cfloop collection="#form#" item="key">
 <cfif left(key,len("DEL_")) eq ("DEL_")>
  <cfset Id = listLast(key,"_") >
  <cfloop index="i" from="1" to="#arrayLen(ItemHasUsers)#">
   <cfif ItemHasUsers[i]["ID"] eq Id>
    <cfset structClear(ItemHasUsers[i]) >
   </cfif>
  </cfloop>
 </cfif>
</cfloop>

<cfset ItemHasUsers = Item.getItemHasUsers() >
<cfloop index="i" from="1" to="#arrayLen(ItemHasUsers)#">
 <cfif ItemHasUsers[i]["ID"] eq Id>
  <cfset arrayDeleteAt(ItemHasUsers,i) >
 </cfif>
</cfloop>

This works only if i check the last element in my input form for deletion. If i check any other i get the following error

The element at position 3 of dimension 1, of array variable "ITEMHASUSERS," cannot be found.

Ok, arrayDeleteAt resizes the array and deletes the gaps automatically. How do i update the loop length for the next iteration?

+9  A: 

The trick to doing this is to step through your array backwards. Start at the last element and loop down to the first, that way you won't try to reference an element past the array length after an item has been deleted from the array.

<cfset ItemHasUsers = Item.getItemHasUsers() >
<cfloop index="i" from="#arrayLen(ItemHasUsers)#" to="1" step="-1">
 <cfif ItemHasUsers[i]["ID"] eq Id>
  <cfset arrayDeleteAt(ItemHasUsers,i) >
 </cfif>
</cfloop>
Jayson
You would end up checking a few items twice, but otherwise that's +1
Tomalak
The variable Id in the second loop (which you're showing here?) is always going to have the same value - whatever it finished as from the first loop.
Peter Boughton
+1  A: 

Jayson's answer is probably how I would do it.

Another way is to use a conditional loop and keep your own counter. The condition being that your counter is less than or equal to the length of your array.

<cfset counter=1 />
<cfloop condition="#counter LTE arrayLen(myArray)#">
    <cfif iNeedToDelete>
        <cfset arrayDeleteAt(myArray,counter) />
    <cfelse>
        <cfset counter=counter+1 />
    </cfif>
</cfloop>

But, frankly, Jayson's answer is simpler and cleaner.

Al Everett
+4  A: 

Don't do this:

<input type="checkbox" name="del_#ItemHasUsers[i]["ID"]#">

Do this:

<input type="checkbox" name="del" value="#ItemHasUsers[i]["ID"]#">


Then, instead of all that nonsense looping, you can just do:

<cfloop index="i" from="#ArrayLen(ItemHasUsers)#" to="1" step="-1">
 <cfif ListFind(Form.Del,ItemHasUsers[i].Id)>
  <cfset ArrayDeleteAt(ItemHasUsers,i)/>
 </cfif>
</cfloop>


Or better still, stop converting the query to an array of structs, and you can do it even easier:

<cfquery name="ItemHasUsers" dbtype="Query">
    SELECT id , assets
    FROM ItemHasUsers
    WHERE id NOT IN (<cfqueryparam list value="#Form.Del#" cfsqltype="cf_sql_integer"/>)
</cfquery>

No loops required!

Peter Boughton
This option seems the simplest. (Just a small typo on the "list" attribute syntax).
Leigh
For boolean attributes it is valid to specify the attribute names only, as a shorthand for `list="true"`.
Peter Boughton
I never knew that one! Good tip Peter.
Leigh
That cfqueryparam needs to be inside parentheses.Note that Oracle has a hard 1000 item limit to IN clauses.
Al Everett
Argh! I always forget the parentheses. Should be auto inserted by the cfqueryparam!Since this is a QoQ, Oracle's limit wont apply here - does anyone know if the CF query engine has a similar limit?
Peter Boughton