What I am trying to achieve:
My form consists of four fields of #item
, #unit
, #price
, and #total
.
A user can input multiple lines of #item
and #unit
. For this, if the user clicks on "Add new line', there will appear a new line to receive user input for item and unit (quantity). If the user clicks on "-" on an already existing line, the line will be removed.
Which item is selected is identified on real-time using javascript change()
.
The price of the selected item is dynamically retrieved using ajax and the price data is appended into the #price
element and #total
element is also filled up by calculating #unit x #price
.
To achieve this, here's what I did.
First, my HTML form as below.
<form id="form">
<input type="submit" name="create" value="Create" />
<!-- dynamically add more lines -->
<div class="form-group table-row hide" id="template">
<!-- remove button-->
<div class="table-cell" style="width: 45px;">
<a class="btn btn-default removeButton">
<span class="glyphicon glyphicon-minus">
</span>
</a>
</div>
<!-- user input: item -->
<div class="table-cell">
<select id="item" class="form-control">
<option value=""></option>
<option value="item-1">Item 1</option>
<option value="item-2">Item 2</option>
<option value="item-3">Item 3</option>
</select>
</div>
<!-- user input: quantity -->
<div class="table-cell">
<input id="unit" min="1" step="1" value="1" type="number" class="form-control" />
</div>
<!-- price is dynamically filled up using ajax -->
<div class="table-cell">
<input id="price" type="text" class="form-control" />
</div>
<!-- total is dynamically calculated -->
<div class="table-cell">
<input id="total" type="text" class="form-control" />
</div>
</div>
<button type="button" class="btn btn-default addButton">Add new line</button>
</form>
And javascript code to implement dynamic add/remove of input forms.
<script>
jQuery(document).ready(function($) {
var idx = new Number( 0 );
$('#form')
// Add button click handler
.on('click', '.addButton', function() {
var $template = $('#template'),
$clone = $template
.clone()
.removeClass('hide')
.removeAttr('id')
.insertBefore($template);
// assign name attributes with proper array name and index
$clone.find('#item').attr('name', 'lines[' + idx + '][item]').attr('required', 'required').val('');
$clone.find('#unit').attr('name', 'lines[' + idx + '][unit]').attr('required', 'required').val('');
$clone.find('#price').attr('name', 'lines[' + idx + '][price]').attr('required', 'required').val('');
$clone.find('#total').attr('name', 'lines[' + idx + '][total]').val('');
idx = idx + 1;
})
// Remove button click handler
.on('click', '.removeButton', function() {
if (confirm("<?php _e( 'Are you sure you wish to remove this line? This cannot be undone.', 'doumi' ); ?>")) {
var $row = $(this).parents('.form-group');
// Remove element containing the option
$row.remove();
idx = idx - 1;
}
});
});
</script>
Lastly, jQuery to call ajax.
<script>
/* Get Item Price */
jQuery('#item').change(function(){
var $item_id=$('#item').val();
var ajaxurl = '<?php echo admin_url('admin-ajax.php'); ?>';
// call ajax
jQuery("#price").empty();
jQuery.ajax({
url: ajaxurl,
/* ******************************************************************** */
/* ajax_get_item_price is a function that returns the price of the item */
/* ******************************************************************** */
data:' action=ajax_get_item_price&item_id='+$item_id,
type:'GET',
success:function(results) {
jQuery("#price").append(results);
}
});
});
</script>
I have no problem dynamically adding and removing lines for the input form.
The problem is that form elements in each line is given a name as an array like below;
<select id="item" name="lines[0][item]"></select>
<input id="unit" name="lines[0][unit]" />
<input id="price" name="lines[0][price]" />
<input id="total" name="lines[0][total]" />
<select id="item" name="lines[1][item]"></select>
<input id="unit" name="lines[1][unit]" />
<input id="price" name="lines[1][price]" />
<input id="total" name="lines[1][total]" />
<select id="item" name="lines[2][item]"></select>
<input id="unit" name="lines[2][unit]" />
<input id="price" name="lines[2][price]" />
<input id="total" name="lines[2][total]" />
...
...
...
<select id="item" name="lines[n][item]"></select>
<input id="unit" name="lines[n][unit]" />
<input id="price" name="lines[n][price]" />
<input id="total" name="lines[n][total]" />
I guess that since there are multiple #item
s with varying names, jQuery does not know which #item
it needs to pay attention to. However, I have no idea, either, as to how to properly call ajax with the concerned #item.
Any advice is highly appreciated.
Ok, as I said, ids should be unique for a page, so for similar items you should use classes:
....
<select class="item" name="lines[2][item]"></select>
<input class="unit" name="lines[2][unit]" />
<input class="price" name="lines[2][price]" />
<input class="total" name="lines[2][total]" />
....
So, for change
event use something like
jQuery('.item').change(function(){
// to narrow the items, use this
var this$ = $( this );
// this$ contain exactly one item, which is currently changing
// do stuff
});
No quite get what exactly wrong is happening, but I think you have too many items with price
is and thus issuing prblems. So this line does next: gets all #price
elements and appends results to each of them.
success:function(results) {
jQuery("#price").append(results);
}
You should clarify which #price
item you exactly need. First, as I already mentioned - use classes for multiple similar fields jQuery(".price")
Second, add some clarification what field exactly should be updated. There can be many ways, I show two
One way, use next
function.
success:function(results) {
this$.next('.price').eq(0).append(results);
// here we refer to this$ - we try to find all `.price` element
// which come after our `select`
// as there can be a lot of them we take only the first one
}
Another way, Use kind of context
, ie some div, in bounds of which your elements are grouped. I suggest changing your html like this
<div class="item-wrapper">
<select class="item" name="lines[2][item]"></select>
<input class="unit" name="lines[2][unit]" />
<input class="price" name="lines[2][price]" />
<input class="total" name="lines[2][total]" />
</div>
So, every block of your fields will be wrapped in a div. After that we can tell jQUery to find .price
element only in a div in which changed select
is located
success:function(results) {
var parent$ = this$.parent();
// again we refer to this$ getting its parent which is div
parent$.find('.price').append(results);
// here we try to find `.price` element located in a parent$ div.
// As it is definitely only one, we don't need to use any other code.
}
So, this is all that I can say)