2

I have a dynamic form with multiple checkboxes and on submit I want to run a CFSTOREDPROC in another cfloop using only the checkbox values that changed status.

So far below is a rough concept that I am trying to test but I am sure the way I am creating my array will have issues. If anyone can provide feedback on possible solutions I would appreciate it.

HTML/CF FORM:

<form action="self.cfm" method="post" name="permissions">
 <input type="hidden" name="User_ID" value="<CFOUTPUT>#User_ID#</CFOUTPUT>">
<table>
<CFLOOP QUERY="getNthPermission">
<tr><td>#getNthPermission.Permission_Name#</td><td><input type="checkbox" value="#getNthPermission.Permission_ID#" name="#getNthPermission.Permission_Name#" <CFIF LISTVALUECOUNT(VARIABLES.UserPermission_ID_List,getNthPermission.Permission_ID) NEQ 0>CHECKED</CFIF>></td></tr>
</CFLOOP>
</table>
<input type="submit" name="submit" value="Update">
</form>

COLDFUSION ACTION:

<CFSET VARIABLES.Permission_ID_List = ValueList(getUserPermission.Permission_ID)>
    <cfset changed_permissions=ArrayNew()>
    <CFLOOP QUERY="getNthPermission">
    //If it was checked when the form was created but was unchecked by the user add it to my array.
    <CFIF LISTVALUECOUNT(VARIABLES.UserPermission_ID_List,getNthPermission.Permission_ID) NEQ 0  AND !IsDefined(FORM.#getNthPermission.Permission_Name#)>
    <cfset changed_permissions[getNthPermission.Permission_ID]>
    <CFELSEIF LISTVALUECOUNT(VARIABLES.UserPermission_ID_List,getNthPermission.Permission_ID) EQ 0  AND IsDefined(FORM.#getNthPermission.Permission_Name#)>
    //If it wasn't checked when the form was built but was checked by the user add it to my array.
    <cfset changed_permissions[getNthPermission.Permission_ID]>
    </CFIF>
    </CFLOOP>

//Now loop through the array that was just created and pass the stored procedure my values

<CFLOOP from="1" to="#arrayLen(changed_permissions)#" index="i">
    <CFSTOREDPROC DATASOURCE="#MYDB_DSN#" PROCEDURE="Update_UserPermission">
        <CFPROCPARAM DBVARNAME="@PermissionList" VALUE="#changed_permissions[i]#" TYPE="IN" CFSQLTYPE="cf_sql_longvarchar">
        <CFPROCPARAM DBVARNAME="@User_ID" VALUE="#FORM.User_ID#" CFSQLTYPE="cf_sql_integer">
    </CFSTOREDPROC>
</CFLOOP>

Update:

I am working with a database configuration that I was not part of setting up, which has these tables:

  • Permissions table (permission_name, ID) - List of permissions
  • User table (user_name, ID)
  • User_Permissions table (Permissions_ID, User_ID) - Contains an entry for each permission a user has access to.

So as the checkboxes are selected/unselected I either add a new entry or remove one. Which a stored procedure that was already in place does (or I hope it does).

2
  • 1
    Is it just a single set of permissions that can be assigned/unassigned to a single user? It is kind of hard to visualize the exact relationships without a sample of the actual values involved .... Also, how are these permissions stored in your database, in separate rows or as a csv list (hopefully not)? Commented Mar 2, 2016 at 23:49
  • Unfortunately I am working with a database configuration that I was not part of setting up. There is a table with a list of permissions (permission_name, ID) and a user table (user_name, ID) and a User_Permissions table (Permissions_ID, User_ID) which contains an entry for each permission a user has access to. So as the checkboxes are selected/unselected I either add a new entry or remove one. Which a stored procedure that was already in place does (or I hope it does) Commented Mar 3, 2016 at 0:11

1 Answer 1

3

Three tables is a good setup. I was afraid it was a single table or the values were stored as csv lists {shudder}. Still not able to visualize your form real well, but .. from what you described it sounds like it would be simpler to do this with a few queries, rather than looping.

First, give the checkboxes the same name like "Permission_IDList", so the id's will be submitted as a list. Then use that list of ID's with one of the following options:

Option 1: DELETE / INSERT ALL

An approach I often use with simple junction tables is to DELETE all existing user permissions first. Then INSERT the new permissions using an INSERT/SELECT. Two big advantages of using an INSERT/SELECT is that it eliminates the need for looping, and also provides built in validation.

This is one of the simplest options, but it is not as precise as option 2 because it deletes all records, every time. So technically it may do a bit more work than is needed in many cases. Though unless you are dealing with a huge amount of records, differences are usually negligible. Something along these lines (not tested):

    --- First remove all existing permissions for User
    DELETE FROM User_Permissions 
    WHERE  User_ID = <cfqueryparam value="#FORM.User_ID#" cfsqltype="cf_sql_integer">

    --- Now re-insert current/new permissions 
    INSERT INTO User_Permissions  ( User_ID, Permissions_ID )
    SELECT  u.ID AS User_ID
            , p.ID AS Permissions_ID
    FROM    UserTable u CROSS JOIN PermissionTable p   
    WHERE   u.ID = <cfqueryparam value="#FORM.User_ID#" cfsqltype="cf_sql_integer">
    AND     p.ID IN 
           (
                <cfqueryparam value="#FORM.Permission_IDList#" cfsqltype="cf_sql_integer" list="true">
           )

Option 2: DELETE changed / INSERT added

Another option is to use a query to identify and DELETE permissions that were removed, ie unchecked. Then use an INSERT/SELECT to insert permissions that were added. The rest remain unchanged. It is a little bit more complex than option 1, but is more precise in that it only removes or adds what actually changed. Again, not tested, but something like this:

    --- existing id's NOT in the new list were UN-checked
    DELETE FROM User_Permissions 
    WHERE  User_ID = <cfqueryparam value="#FORM.User_ID#" cfsqltype="cf_sql_integer">
    AND    Permissions_ID NOT IN 
           (
                <cfqueryparam value="#FORM.Permission_IDList#" cfsqltype="cf_sql_integer" list="true">
           )


    INSERT INTO UserPermissions ( User_ID, Permissions_ID )
    SELECT u.ID AS User_ID
            , p.ID AS Permissions_ID
    FROM   UserTable u 
                CROSS JOIN PermissionsTable p 
                LEFT JOIN UserPermissions up ON up.User_ID = u.ID AND up.Permissions_ID = p.ID
    --- this user + permission does not already exist
    WHERE  up.Permissions_ID IS NULL        
    AND    u.ID = <cfqueryparam value="#FORM.User_ID#" cfsqltype="cf_sql_integer">
    AND    p.ID IN 
        (
                    <cfqueryparam value="#FORM.Permission_IDList#" cfsqltype="cf_sql_integer" list="true">

        )

NB: Be sure to wrap both queries in a transaction so they are treated as a single unit, ie either both succeed or both fail.

Sign up to request clarification or add additional context in comments.

8 Comments

Thanks again Leigh for the detailed solution. Both make perfect sense and to be honest I am glad to get away from loops. I'm going to go with option #1 since we are only talking about a handful of permissions I am not worried about effecting performance. This is the second save you have helped me with today. Thanks again! +1 checked.
I do have a question about how the first option runs through each permission without using a loop? Is the above SQL to be used as a stored Procedure? Thanks for the follow up..
Wait after going through the code a few times I see how this will work. I will test now and let you know the results. Thanks again.
You are welcome. You probably figured out it works by passing a list of id's into an IN .... clause. A basic SELECT ID FROM tbl WHERE Column IN (1,2,3) would return three id records. Adding an INSERT in front of the select statement, allows you to insert those id's (or other columns) into another table instead of returning them as a resultset.
Looking at it with fresh eyes, I just noticed a logic error in the first DELETE statement. It shouldn't have the IN (...) clause because you want to remove all permissions for a specific user. So the where clause should only filter on user_id. Answer updated.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.