Search code examples
sql-servercoldfusion

Dynamically update display order using coldfusion


I’m a self-taught, hobby-coder volunteering for a small, local non-profit organization and am updating its website admin section using coldfusion.

A section of it is used to display the volunteers in an assigned order on the front end. I’ve added a “Display” column in the table where I set the order number of how the volunteers will be displayed. (1 is first, 2 is second, etc.)

I’d like to be able to dynamically rearrange the display order when updating or adding a record. Typically I’d bring in a senior programmer to do this but that’s not within the budget, so I thought I’d give it a shot.

Example:

1 = 1

2 = 2

3 = 3

Currently if I change 3 to 1, I get two 1s and 3 goes away. (expected)

1 = 1 (used to be 3)

1 = 1

2 = 2

I’d like the update to move all records below it to update as well.

(If I move 3 to 1)

1 = 1 (used to be 3)

2 = 2 (used to be 1)

3 = 3 (used to be 2)

OR

(If I move 3 to 2)

1 = 1

2 = 2 (used to be 3)

3 = 3 (used to be 2)

Currently I’m just updating each record individually, but that’s clearly not ideal, especially when the volunteer list grows into the teens or more.

I’ve searched for hours looking for guidance on how to do this but am at a loss as to where to start. Any help would be greatly appreciated.

Here’s the update form code:

            <div class="form-group">
                <label for="Display">Display Order</label>
                <select name="display" class="form-control form-control-lg" required>
                    <option value="">Select Display Order</option>
                    <cfoutput query="GetBioDisplay"><option value="#GetBioDisplay.display#">#GetBioDisplay.display#</option></cfoutput>
                </select>
            </div>

Here’s the update query:

            <cfquery name = "UpdateProfile" datasource="#dsn#">
                UPDATE DBO.PROFILE
                    SET firstname  =  '#form.firstname#', 
                    lastname       =  '#form.lastname#', 
                    title          =  '#form.title#',
                    email          =  '#form.email#',
                    display        =  '#form.display#', 
                    Picture        =  '#form.picture#',
                    profile        = '#form.profile#',
                    credentials    = '#form.credentials#' 
                    WHERE ID       =   #url.id#
            </cfquery>

Here’s the query I use for the user / front end:

            <cfquery name="GetProfile" datasource="#request.dsn#">
                SELECT ID, FIRSTNAME, LASTNAME, TITLE, EMAIL, 
                    PICTURE, DISPLAY, PROFILE, CREDENTIALS
                        FROM DBO.PROFILE
                            ORDER BY DISPLAY
            </cfquery>

Solution

  • You can use a CASE/WHEN conditional statement in your WHERE clause to update to the correct value, regardless of the direction you're moving the record. You only need to pass two values: The position you're updating from (url.id) and the position you're updating to (form.position).

    SQL Fiddle MS SQL Server 2017 Schema Setup:

    CREATE TABLE test(
        display int NOT NULL,
        item varchar(2000) NULL
    ) ;
    
    INSERT INTO test (display, item)
    VALUES 
        (1,'a')
      , (2,'b')
      , (3,'c')
      , (4,'d')
      , (5,'e')
      , (6,'f')
      , (7,'g')
      , (8,'h')
      , (9,'i')
      , (10,'j')
    ;
    

    Query - Order Up:

    /* Move 8 to 4 - UP */
    
    DECLARE @updDisp int = 8
    DECLARE @updTo int = 4
    
    UPDATE test
    SET display = (CASE 
        WHEN display = @updDisp THEN @updTo 
        WHEN @updDisp < @updTo THEN display-1
        WHEN @updDisp > @updTo THEN display+1
        END
    )
    WHERE 
        ( display BETWEEN @updDisp AND @updTo )
        OR 
        ( display BETWEEN @updTo AND @updDisp )
    ;
    

    RESULTS

    SELECT * FROM test ORDER BY display 
    
    
    | display | item |
    |---------|------|
    |       1 |    a |
    |       2 |    b |
    |       3 |    c |
    |       4 |    h |
    |       5 |    d |
    |       6 |    e |
    |       7 |    f |
    |       8 |    g |
    |       9 |    i |
    |      10 |    j |
    

    Query - Order Down:

    /* Move 4 to 8 - DOWN */
    
    DECLARE @updDisp int = 4
    DECLARE @updTo int = 8
    
    UPDATE test
    SET display = (CASE 
        WHEN display = @updDisp THEN @updTo 
        WHEN @updDisp < @updTo THEN display-1
        WHEN @updDisp > @updTo THEN display+1
        END
    )
    WHERE 
        ( display BETWEEN @updDisp AND @updTo )
        OR 
        ( display BETWEEN @updTo AND @updDisp )
    

    RESULTS

    SELECT * FROM test ORDER BY display 
    
    
    | display | item |
    |---------|------|
    |       1 |    a |
    |       2 |    b |
    |       3 |    c |
    |       4 |    d |
    |       5 |    e |
    |       6 |    f |
    |       7 |    g |
    |       8 |    h |
    |       9 |    i |
    |      10 |    j |
    

    So your CF code would look something like this:

    //// process and validate form variables here ////
    local.updDisp = url.id
    local.updTo   = form.display
    //////////////////////////////////////////////////
    
    <cfquery name="UpdateProfile" datasource="#dsn#">
        UPDATE DBO.PROFILE
        SET display = (
            CASE 
                WHEN display = #val(local.updDisp)# THEN <cfqueryparam cfsqltype="cf_sql_integer" value="#local.updTo#">
                WHEN #val(local.updDisp)# < #val(local.updTo)# THEN display-1
                WHEN #val(local.updDisp)# > #val(local.updTo)# THEN display+1
            END
        )
        WHERE ( display BETWEEN <cfqueryparam cfsqltype="cf_sql_integer" value="#local.updDisp#"> AND <cfqueryparam cfsqltype="cf_sql_integer" value="#local.updTo#"> )
            OR 
            ( display BETWEEN <cfqueryparam cfsqltype="cf_sql_integer" value="#local.updTo#"> AND <cfqueryparam cfsqltype="cf_sql_integer" value="#local.updDisp#"> )
    </cfquery>
    

    I'm not sure if you can use cfqueryparam in a CASE statement inside of a SET, and I don't have a way to test. Regardless, since we know we always should be working with integers, then the val() function should offer the same protection that the cfqueryparam does in this instance.

    I'm also of the somewhat more paranoid mindset when it comes to user input. cfqueryparam is very good at what it does, but it doesn't protect against all issues. I'd still recommend doing some sort of pre-processing before you allow those values anywhere near your database.