JQuery closest elements, from TextBox in a GridView

2.7k views Asked by At

I've this javascript function:

function modifica(txtValoreID, txtRicalcolatoID, lblDeltaID) {
  // Ricalcolo il valore aggiornato
  var Valore = parseFloat($("#" + txtValoreID).val()).toFixed(2);
  var Ricalcolato = parseFloat($("#" + txtRicalcolatoID).val()).toFixed(2);
  $("#" + lblDeltaID).html(parseFloat(Valore - Ricalcolato).toFixed(2).replace('.', ','));
}

That refers to textbox and label in a gridview:

<asp:TemplateField> 
    <ItemTemplate>
        <asp:TextBox ID="txtValore" Width="90%" Text='<%# string.Format("{0:N}", Eval("Valore"))%>' runat="server"  />
    </ItemTemplate>
</asp:TemplateField>
<asp:TemplateField> 
    <ItemTemplate>
        <asp:TextBox ID="txtRicalcolato" Text='<%# string.Format("{0:N}", Eval("Ricalcolato")) %>' runat="server" />
    </ItemTemplate>
</asp:TemplateField>

(...)

<asp:TemplateField HeaderText="Delta"> 
    <ItemTemplate>
        <asp:Label ID="lblDelta" Text='<%# string.Format("{0:N}", Eval("Delta"))%>' runat="server" />
    </ItemTemplate>
</asp:TemplateField>

And I'm assigning it in the DataBound:

txtValore.Attributes.Add("onchange", "modifica('" + txtValore.ClientID + "', '" + txtRicalcolato.ClientID + "','" + lblDelta.ClientID + "')");
txtRicalcolato.Attributes.Add("onchange", "modifica('" + txtDaLiquidare.ClientID + "', '" + txtRicalcolato.ClientID + "','" + lblDelta.ClientID + "')");

and it's working perfectly. The problem is that when the gridview have a lot of records, databound is affecting the performance, so my first question is, am I improving this performance moving the assignment from databound server code to aspx page? And if yes, how can I do that? Because I'm trying like this:

asp:TextBox ID="txtValore" onchange="test(this.id)" Text='<%# string.Format("{0:N}", Eval("PremioLiquidare"))%>' runat="server"  />

and this:

function test(txtID) {
    alert('TEST ' + $(this));
    alert('TEST ' + $(this).val());
    alert('TEST ' + $("#" + txtID).val());
    alert('TEST ' + $("#" + txtID).find("[id*='txtRicalcolato']").val());
    alert('TEST ' + $("#" + txtID).closest("[id*='txtRicalcolato']").val());
    alert('TEST ' + $(this).find("[id*='txtRicalcolato']").val());
    alert('TEST ' + $(this).closest("[id*='txtRicalcolato']").val());    
}

But the first is printing TEST Object object, the second blank and the third the correct value, but the others are undefined.. What I'm doing wrong?

The rendered HTML is:

<table class="mGrid" id="ctl00_MainContent_grid">
    <tbody>
        (...)
        <tr>
            (...)
            <td>
                <input name="ctl00$MainContent$grid$ctl03$txtValore" value="1.693,44" id="ctl00_MainContent_grid_ctl03_txtValore" onchange="test(this.id)" type="text">
            </td>
            <td>
                <input name="ctl00$MainContent$grid$ctl03$txtRicalcolato" value="169,34" id="ctl00_MainContent_grid_ctl03_txtRicalcolato" onchange="test(this.id)" type="text">
            </td>
            (...)
            <td>
                <span id="ctl00_MainContent_grid_ctl03_lblDelta">-1.524,10</span>
            </td>
            (...)
        </tr>
        (...)
    </tbody>
</table>
1

There are 1 answers

10
iCollect.it Ltd On BEST ANSWER

You are trying to search from the element down with find, and the element upwards with closest but the target element is in another branch of the DOM.

  • find only searches from the element downwards (its descendants)
  • closest searches the ancestors only (not literally the closest match).

You need to search up with closest, then find down:

e.g. the 3rd and 4th ones should be:

    alert('TEST ' + $("#" + txtID).closest('someCommonParentElementSelector').find("[id*='txtRicalcolato']").val());

Suggestion: Use a single Delegated Event Handler

Instead of adding attribute-based change handlers, use a single delegated event handler attached to a non-changing ancestor element (e.g. a surrounding table or div). This has a much lower overhead and keeps all the code in one place.

Based on your new HTML example, a delegated event handler would go something like this:

// Shortcut DOM ready handler
$(function(){
    // Listen for change events bubbling up to the grid, 
    // then match elements with ID ending in "txtValore"
    $('.mGrid').on('change', 'input[id$="txtValore"]' function(){

          // this is the *txtValore field that changed
          var $input = $(this);

          // Find the matching input by looking up (to closest row) and then down the tree
          // for an element with ID ending in "txtRicalcolato"
          var $otherInput = $input.closest('tr').find('input[id$="txtRicalcolato"]');

          // now do "something" with the two inputs :)
    });
});

Notes:

  • You can then remove any onChange attributes from your inputs.
  • This makes the setup simpler and puts the event handler code with the event handler registration.
  • If there are no dynamically added elements, you could just use a jQuery handler, like
    • $('.mGrid input[id$="txtValore"]').on('change', function(){ or
    • $('.mGrid input[id$="txtValore"]').change(function(){ but delegated events are more flexible.
  • the [id$=""] selector is an ends-with selector as your actual IDs start with a generate prefix.
  • A delegated event handler works by listening for events to bubble-up to a non-changing ancestor element (in this case with a class of .mGrid), then applying the jQuery selector to the elements in the bubble-chain that caused the event, then applying the function. This works for dynamically added elements too as the selector is run at event time and not event-registration time.

Update: ID generation changed

In comment you mention the ID generation changed so that the suffix was no longer at the end of the id (a underscore and number was appended). jQuery also has an attribute contains selector *= which would change the example code to:

// Shortcut DOM ready handler
$(function(){
    // Listen for change events bubbling up to the grid, 
    // then match elements with ID containing "txtValore"
    $('.mGrid').on('change', 'input[id*="txtValore"]' function(){

          // this is the *txtValore field that changed
          var $input = $(this);

          // Find the matching input by looking up (to closest row) and then down the tree
          // for an element with ID containing "txtRicalcolato"
          var $otherInput = $input.closest('tr').find('input[id*="txtRicalcolato"]');

          // now do "something" with the two inputs :)
    });
});