1

In my form in one set of repeating rows (created dynamically) there is a button on each row that allows the user to upload a file which will then be linked/tied to the data in that row.

head   head    upload
----   ----    --------
x      y       <button>
a      b       <button>
m      n       <button>

When any of those buttons are clicked I can obviously easily determine what row I am in. Let's say I am in the row with id="row_2". I have that in the function called from the button click.

Now I call $("#fileUploader").click() in order to initiate the file choice dialog. When the user chooses a file and clicks OK the OnFileUploaderChange() function is automatically called. Within that function I obtain the file and now I need to know that the initial button was from row_2 so I can link/associate it with the correct row.

Summary: OnButtonClick() triggers OnFileUploaderClick() which causes OnFileUploaderChange() to be automatically triggered. I have a value in OnButtonClick() which I need to have available in OnFileUploaderChange().

How can I do that?! How can I take a value that I know in the OnButtonClick() function and make it available within (pass it to) the OnFileUploaderChange() function when the OnFileUploaderClick() function is being called in between.

I have figured out how to pass parameters to the OnFileUploaderClick() function by changing $("#fileUploader").click() to $("#fileUploader").trigger('click', [ 2 ]). But that parameter is then available only within OnFileUploaderClick() and I need it in OnFileUploaderChange().

It is possible that the user would initiate uploading a large file and (while that is still uploading) would initiate uploading another smaller file so that multiple threads were uploading at the same time. So using globals in any way doesn't work - it's gotta be a parameter/argument to avoid race conditions and timing issues.

Here's a fiddle to help explain: https://jsfiddle.net/ho819tu0/1/


EDIT WITH SOLUTION:

Based on @vincent-d's comments and answer below (now marked as correct) I have fixed my code and here's another fiddle showing how it works: https://jsfiddle.net/sm2L9kt8/

Peter Bowers
  • 3,063
  • 1
  • 10
  • 18
  • Sorry, I deleted my answer. The more I thought through it, the more I realized I would need to see more code to see how you have it setup. When you call jQuery's trigger() you can pass extra params as you know, but how to get that over to your onChange event isn't clear to me. – Jeremy Harris Oct 23 '19 at 15:08
  • I'll try to come up with a minimal example and put it as a fiddle... – Peter Bowers Oct 23 '19 at 15:12
  • I know you said using globals won't work, but is it correct that they can only be uploading one file per row at a time? If so, couldn't you us an object that stores row number by filename? Something like `var uploads = {"myfile.txt": "row1", "another.txt": "row2"};` Basically put the row number by filename in the map in `OnFileUploaderClick` and retrieve it by filename in `OnFileUploaderChange`? – clav Oct 23 '19 at 15:21
  • Why you don't use a live method? – Richard Oct 23 '19 at 15:22
  • I don't know if I was clear. You can't pass live created/updated objects nor variables throught params or current bubble. Except if you are in a "recall" (the live event of jquery library) – Richard Oct 23 '19 at 15:29
  • @vincent-d, I've never used live methods before. I ran across them researching this and a couple places said they were deprecated. Looks like I'd better look into them in more detail if it's a possible solution. – Peter Bowers Oct 23 '19 at 15:44
  • @clav, using a map is a good idea, but I don't think I have access to the filename in the OnFileUploaderClick... Yes, I confirm that my OnFileUploaderClick() fires before I've even chosen which file to upload. – Peter Bowers Oct 23 '19 at 15:52
  • @vincent-d, on further research live() seems to be a rough synonym for on() in jquery. I'm not catching how that would help in this case. And as I read your second comment it seems like maybe you were having second thoughts as well? I'm happy to look further, though, if you think there's potential - can you give me any pointers? – Peter Bowers Oct 23 '19 at 15:55
  • @PeterBowers check my answer – Richard Oct 23 '19 at 15:57
  • Why doesn't every row just have a file upload input styled however you want? Then you have the ID of the input in the handler and you can pass the filepath string to an output div or something else on change if you need to show it below. – skyline3000 Oct 23 '19 at 16:05
  • @skyline3000, that would work. It feels very inelegant and repetitious, but maybe that's applying coding virtues to HTML. This is where I will go (it will involve some relatively significant refactoring) if I can't solve it otherwise... Good "change the problem" solution! – Peter Bowers Oct 23 '19 at 16:10

2 Answers2

1

The prolem is that any dom object and event created outside the initial bubble, will not be called and no parameters nor global variable will be set by those events/functions

To solve this problem, jQuery have made a very nice function that take care of creating a fresh call on these live created objects

The left-hand assignement have to be an object that contains the future "live created" objects and the right-hand is a selector that referes to these objects

var colors=['green','yellow','blue'];
$('.add').click(function(){
  let num=Math.floor(Math.random() * 3);
  var newHtml='<td>line 1 <button class="clicker">click to set to <span>'+colors[num]+'</span></button></td>'
  $('table tr').append(newHtml);
});

// click on add, it will set a new button, click on that button it will pass to this function because of the live event call

function colo(colorvar){
  $('td').css('color',colorvar);
}

//this is the call, notice the body as left-hand, and .clicker button as right-hand

$('body').on('click','.clicker',function(){
// this refer to the element in the right-hand that is clicked (it can be a child, check at event target for more infos
  colo($('span',this).text());
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class='add'>ADD</button>
<table>
<tr>
<td>line 1 <button class='clicker'>click to set to <span>red</span></button></td>
</tr>
</table>
Richard
  • 994
  • 7
  • 26
  • OK, I think I understand the concept now... Kind of like a "late-binding trigger" or something... Maybe I'm not understanding it deeply enough, but how would I use this to get the value from the OnButtonClick and have it available in OnFileUploaderChange? It seems like this would be another way to get my value in OnButtonClick, but I've already got that - obviously I'm not understanding... Which function(s) would I be using this `$('body').on('click', '***')` on? – Peter Bowers Oct 23 '19 at 16:06
  • could you provide some additional clarification? (writing again because I forgot to tag you before so you may not see it) – Peter Bowers Oct 23 '19 at 17:15
  • if `OnButtonClick` is a function, then you can do `$('body').on('click','.button-input',function(){OnButtonClick(this.value)});` for example and you can also do a `$('body').on('change','.file-input',function(){OnFileUploaderChange(this.value)})` – Richard Oct 24 '19 at 07:18
  • 1
    It was hard to wrap my mind around it, but when I figured it out it worked great. (I think of listeners as long-term, respond-to-lots-of-events kind of things; your solution makes it a listener set just-in-time for a single event and then deleted - if I've understood correctly.) This allowed me to fix my code without any refactoring so I'm marking this as the correct answer. THANKS! – Peter Bowers Oct 25 '19 at 12:52
1

I don't believe there's a way to pass arguments to the function handler of a file upload change event. As I mentioned in my comment, I think another solution is to use a unique <input type="file"> for every row so that your change handler has a reference to the ID. You can style the uploader to be hidden and use a label to trigger the click:

function OnFileUploaderChange(inputElem) {
  output.textContent = inputElem.id;
}
input[type="file"] {
  visibility: hidden;
  width: 0;
}

label {
  background: #fefefe;
  border: 1px solid black;
  padding: 0 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table>
  <tr>
    <th>Head1</th>
    <th>Head2</th>
    <th>Upload</th>
  </tr>
  <tr class="row" id="row_1">
    <th>Row_1</th>
    <th>A</th>
    <th>
      <label for="file_row_1">U</label>
      <input id="file_row_1" type="file" onchange="OnFileUploaderChange(this)">
    </th>
  </tr>
  <tr class="row" id="row_2">
    <th>Row_2</th>
    <th>B</th>
    <th>
      <label for="file_row_2">U</label>
      <input id="file_row_2" type="file" onchange="OnFileUploaderChange(this)">
    </th>
  </tr>
  <tr class="row" id="row_3">
    <th>Row_3</th>
    <th>C</th>
    <th>
      <label for="file_row_3">U</label>
      <input id="file_row_3" type="file" onchange="OnFileUploaderChange(this)">
    </th>
  </tr>
</table>
<div id="output"> </div>
skyline3000
  • 7,639
  • 2
  • 24
  • 33
  • I think this will end up being the right answer. As I mentioned in my reply to your comment it will require some re-factoring due to other things that depend on this so I will wait another day or so before marking this as the right answer, hoping that someone will come up with a solution that allows my natural laziness to shine through... :-) – Peter Bowers Oct 23 '19 at 19:58