1

Working with some legacy code, and am trying to append some functionality. The page is for a disposition upload. At the bottom of the page is a table of files related to the disposition. These files(attachments) are held inside a table on the database and each file has it's own ID.

The client wants a "replace" button added to the table, on each row beside each entry (there is already a download and delete button). Once clicked, a file upload form is reveled. What should happen is the file the client uploads should replace the attachment in the table by ID. However when I click on of the "replace" buttons, it displays the form to replace the attachment at the top of the table.

How to I link the button to the forms by ID (passed through the database table)?

Here is the table... '''

<table class="table table-striped table-bordered table-hover table-hover table-full-width">
    <thead>
        <tr>
            <th class="center hidden-xs"></th>
            <th style="display:none;">ID</th>
            <th>File Name</th>
            <th>Figure Name</th>
            <th>Date Uploaded</th>
            <th>Rearrange Order</th>
        </tr>
        </thead>
    <tbody>
    <cfset loopCount = 1 />
    <cfset ids = '' />
    <cfset allowDown = #qAttachments.recordCount# />
    <cfloop query = "qAttachments">
    <cfset ID = "#qAttachments.id#">
    <cfset fileName="#qAttachments.filename#">
    <cfset fileExt=ListLast(fileName,".")>
    <cfset filePath = "/secure/edFiles/edAttachments/ED_#session.module.id#/#url.edID#/#fileName#"><!---removed.pdf--->
        <tr>
            <div id="replaceAtt" style="display: none" >
                <div class="col-md-6">
                  <div class="fileupload fileupload-new" data-provides="fileupload">
                            <div class="input-group">
                                <div class="form-control uneditable-input">
                                    <i class="fa fa-file fileupload-exists"></i>
                                    <span class="fileupload-preview"></span>
                                </div>
                                <div class="input-group-btn">
                                    <div class="btn btn-blue btn-file">
                                        <span class="fileupload-new"><i class="fa fa-folder-open-o"></i> Select file</span>
                                        <span class="fileupload-exists"><i class="fa fa-folder-open-o"></i> Change</span>
                                        <input type="file" id="replaceEDFile" name="replaceEDFile" title="Select File to Replace #ID#">
                                    </div>
                                    <a href="" class="btn btn-blue fileupload-exists" data-dismiss="fileupload">
                                        <i class="fa fa-times"></i> Remove
                                    </a>
                                </div>
                            </div>
                        </div>
                </div>

                <div class="col-md-2">
                    <div class = "btn btn-blue btn-block" value="#ID#" type = "submit" name ="replaceFile"  onClick="location.href='edFormData.cfm?replaceFile=#ID#&m=#url.m#&edID=#url.edID#&#r#&ai=#url.ai#'">
                          Upload File <i class = "fa fa-arrow-circle-right" ></i>                                
                    </div>
                </div>
            </div>
        </tr>
        <tr>

            <td class="center hidden-xs">
                <a href="#filePath#"><button type = "button" class="fa" name="download" id="download" value="#ID#" onClick="location.href='edFormData.cfm?download=#ID#&m=#url.m#&edID=#url.edID##r#&ai=#url.ai#'">           <img src="../assets/Icons/viewdoc.png"></button></a>   

                <cfif readonly NEQ "readonly">

                <button type = "button"  class="fa" name="Delete" id="Delete" value ="#ID#" onClick="location.href='edFormData.cfm?del=#ID#&m=#url.m#&edID=#url.edID##r#&ai=#url.ai#'">
                <img src="../assets/Icons/trash-o_ff0400_20.png"></button>

                <button id="replace" type = "button" class ="replace" name="replace" value="#ID#" title="Replace attachment #ID#" >
                 <img src="../assets/Icons/file_replace_000000.png">
                </button>
                </cfif>
            </td>
            <td style="display:none;">#ID#</td>
            <td id="file_#ID#">#qAttachments.filename#</td>
            <td id="figure_#ID#">#qAttachments.figureName#</td>
            <td id="uploaded_#ID#">#qAttachments.uploaded#</td>
            <td>
                <cfif loopCount NEQ 1>
                    <div class = "btn btn-green btn-block" id="moveUP_#ID#">Move Up</div><br />
                </cfif>
                <cfif loopCount NEQ allowDown>
                    <div class = "btn btn-blue btn-block" id="moveDown_#ID#">Move Down</div>
                </cfif>
            </td>
        </tr>

        <cfif loopCount NEQ allowDown>
            <cfset ids = #ids#&"#ID#," />
        <cfelse>
            <cfset ids = #ids#&"#ID#" />
        </cfif>
        <cfset loopCount=(#loopCount#+1) />
    </cfloop>
    <input type="hidden" id="possibleIDs" value="#ids#" />
</tbody>
</table> 

'''

And here is the JavaScript.... '''

<script>
    $(document).ready(function(){
        $('.replace').click(function(e){        
            e.preventDefault();
            $("#replaceAtt").slideToggle('fast');
        });
});

</script>

'''

Shawn
  • 4,758
  • 1
  • 20
  • 29
Clark
  • 63
  • 8
  • 1
    1. You have tables and divs and Bootstrap all mixed together. I seriously double this can render at all. 2. `loopCount=(#loopCount#+1)` is a whole lot of code to just do `loopcount++` – James A Mohler Apr 09 '19 at 19:48
  • This is actually code from before I started on this page. This is for a position changer that moves the attachments up and down on the table. That works fine and isn't impacting what i'm adding. – Clark Apr 09 '19 at 19:54
  • So many different ways to solve this... you could add a data-id attribute to your replace button, which is then retrieved using the jquery .data() method, or .attr() method. That gives you your id, and then you could set a hidden field in your form. Just one idea. – Redtopia Apr 09 '19 at 22:31
  • @Redtopia, thank you for the suggestion. I've not been using JavaScript long and am still learning the ins and outs. I'll give this a try tomorrow. – Clark Apr 10 '19 at 06:21
  • When you say, legacy, what versions are you working with? – Shawn Apr 10 '19 at 15:46
  • We are using ColdFusion 11. – Clark Apr 10 '19 at 17:15
  • And I would also add a big caution about using dynamic URL-supplied variables in the rest of your code without validating or sanitizing them. – Shawn Apr 10 '19 at 17:22

2 Answers2

1

This is more code review / comment than it is a full answer. There is a lot going on in this block of code. As James said, your code mixes up tables and divs and throws in some Bootstrap. There are a lot of variable floating around in there without being clear on which scope they are in (like readonly) and a lot of variables that aren't really needed (like loopcount and allowDown). There are also a couple of variables that don't need the quotes and pounds (like <cfset ID = "#qAttachments.filename#"> that can just be <cfset ID = qAttachments.ID>) and a couple of places where a URL variable is being used directly in code. And a couple of other things.

This is legacy code, so I completely understand. It is what it is, and if it's CF11 code, there have likely been a lot of things improved upon since it was first written. It's easy to be the Monday Morning Quarterback here.

That said, you can significantly reduce what you are doing in this page between your tbody tags.

To simplify things a bit, I left out most of your HTML that you are looping over.

Since you are using a query loop, you don't need to keep track the loop count, because it's already part of the query results in currentRow. And you don't need to set allowDown, because you're only referencing it once. The only thing you really need is to initialize ids so that you can ListAppend() instead of trying to figure out how to handle a trailing comma.

<cfset ids="">

<cfoutput>

<cfloop query="qAttachments">
    <!--- HTML Display Code In This Block --->
    EXAMPLE: WE ARE ON ID = #id# 
    <!--- Move Buttons --->        
    <cfif currentRow NEQ 1> MOVE UP </cfif>
    <cfif currentRow NEQ qAttachments.recordcount> MOVE DOWN </cfif>
    <br>
    <!--- --->
    <!--- Build ID list for hidden form. --->
    <cfset ids = ListAppend(ids,id)>
</cfloop>

<br>
< hiddenFormInput > possibleIDs = "#ids#" < /hiddenFormInput >

</cfoutput>

https://cffiddle.org/app/file?filepath=870efd11-b974-4905-8d47-9afb41fa2a10/e47a5d00-1a86-4cd9-8996-f256ad72dff5/49648087-5075-48c1-9a96-23d20b6e2d82.cfm

Again, that is all just concept code to better build your loop. And on that note, since this is CF11, you'd be much better of looping with cfsccript rather than cftags. And I would recommend splitting your CFML code away from your display code, maybe with a CF function that can return a set of data to loop through your display with.

One last thing I'll comment on is using ID as a general name. Inside your loop, you have 3 different ID variables in use: 1) session.module.id 2) ID from your query 3) and an ID variable, based on your loop's query ID. In this specific case, you're getting the values that you intend, but it's usually a bad idea to have multiple variables with the same name on a page, and ID is an easy one to do. All it takes is a change in the order of evaluation, and it will cause you a headache that can be difficult to debug.

Shawn
  • 4,758
  • 1
  • 20
  • 29
  • Also, if the hidden list contains every ID value from the query, then no need no build the list manually. Just use valueList() outside the loop. – SOS Apr 11 '19 at 13:02
  • Thank you for the advice. The move up and down buttons are referenced outside of the code I posted. The person who wrote that was coding around the existing mess that is this file, I believe. The ID issue is an odd cookie too. This isn't the only place site wide that uses session.module.id, and the ID from the query and the ID variable are intended to be the same value. Going forward on new pages I will be using these notes to better build loops. Thank you. – Clark Apr 18 '19 at 18:51
  • 1
    @Clark `id` particularly got my attention, because I once worked on a system that used `id` as a key for every db table, didn't use foreign keys properly (read _AT ALL_) and referenced "`id`" all over the code. I can't remember which software version change it was, but it flipped the order that `id` was evaluated in. We were dealing with multiple tables that numbered in the millions each and started getting records that were very old. Took us forever to figure out those were because, when we thought we were looking at `t2.id` we were really looking at `t1.id`. It was a bit maddening. :-/ – Shawn Apr 18 '19 at 19:02
0

I want to thank everyone who commented and helped out on this. I know the code is janky at best, but when I start messing with whats already in the loop, something somewhere else breaks. But I did find out the issue (with the help of a much-smarter-than-I co-worker) of why I couldn't link the button to the div and show the correct upload form for that row of the table! It was the image... yes, the image. I decided to have the JS just put up an alert of the ID name when the button was hit. After some hacking I got it to work sometimes, and just put out NaN other times. Well with some "Inspect Element" magic it was discovered that it was outputting either the ID or NaN based on where I clicked on the button. This led us to realize the image itself needed an id, because that was attempting to be pulled when clicked.

So, this is what I've changed to the code posted above:

First I pulled the div that held the upload form out of the loop, so I don't have multiple forms populate and I can switch attachment replacements without reloading the page of having a bunch of moving things on screen.

Second, the actual button itself:

<button type = "button" class ="replace" name="replace" id="replace_#ID#" value="#ID#" title="Replace #qAttachments.figureName#" >
    <!--- Gave the image an ID, so if it is clicked it can still split properly. --->   
    <img id="replaceImg_#ID#" src="../assets/Icons/file_replace_000000.png">
</button>

Third, the JavaScript.

<script>

    $('[id^="replace_"], [id^="replaceImg_"]').click(function(e){

        // Split the ID to get the number (lines up with DB ID).
        var replaceID = e.target.id.split("_")[1];

        // Set this to let the user know which file is being replaced (by figure name).
        var figNameRep = $('#figure_'+replaceID).text();

        // If the upload replacement is not showing yet, make it show now.
        if ($('#replaceAtt').css("display") == 'none'){
            $('#replaceAtt').slideToggle("fast");
        }

        // Don't worry about hiding and unhiding, just switch the IDs around as needed.
        $('#figNameReplace').text("Replacing file for Figure Name: " + figNameRep);
        $('#replaceFile').val(replaceID);
    });

    </script>

Thanks again to everyone on their input. The ideas you all suggested I did try, and while I couldn't get any of them to work (that's more of a comment on my own skill) it did teach me some new tools and methods. You guys rock!

Clark
  • 63
  • 8