JSFiddle - React, Tailwind, and code Playground
HTML
<table class="table table-stripped" id="compositions_table">
<thead>
<tr>
<th style="width:40%">Название</th>
<th style="width:40%">Значение</th>
<th style="width:20%"></th>
</tr>
</thead>
<tbody>
<tr class="item">
<td class="name">
<input type="text" value="" class="wfull" placeholder="Название"/>
</td>
<td class="value">
<input type="text" value="" class="wfull" placeholder="Значение"/>
</td>
<td class="control">
<button type="button" data-role="remove-line" class="btn btn-danger btn-block">
<i class="fa fa-times"></i> Удалить
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3" style="text-align:right">
<button type="button" class="btn btn-success" data-role="save-compositions" style="margin-left:15px;float:right">
<i class="fa fa-save"></i> Сохранить
</button>
<div id="message-box" style="line-height:30px"></div>
</td>
</tr>
</tfoot>
</table>
JavaScript
var __compositions_index=-1,
____compositions_timer;
$(function(){
var $table=$('#compositions_table');
$table.find('.item').each(set_listener).end()
.find('[data-role="save-compositions"]').click(function(){
var $t=$(this);
if($t.attr('disabled')!==undefined)
return;
$t.attr('disabled',true);
var props={};
$('#compositions_table [data-index].item').each(function(){
var $inputs=$(this).find('input');
props[$inputs.eq(0).val()]=$inputs.eq(1).val();
});
$.post(location.href.replace('mode=edit','mode=save_compositions'),{
props:props
},function(res){
try{
if(!!____compositions_timer)
clearTimeout(____compositions_timer);
if(typeof res=='string')
res=JSON.parse(res);
$('#message-box').text(
!!res.message?res.message:(
res.result!=='ok'?'Произошла ошибка':'Сохранено'
)
);
____compositions_timer=setTimeout(function(){
$('#message-box').text('');
},5000);
} catch(e){
console.error(e);
}
$t.removeAttr('disabled');
});
});
});
function set_listener(index,row){
var $row=$(row);
if($row.attr('data-index')!==undefined)
return;
$row.attr('data-index',++__compositions_index).find('input').each(function(i){
this.setAttribute('data-name',(i?'value':'name')+'['+__compositions_index+']');
}).on('input blur',function(){
var $row=$(this).parents('[data-index]'),
$clone;
if(!$row.is(':last-child'))
return;
$row.find('input').filter(function(){
return this.value.trim().length<=0;
}).length==0 && (
$clone=$row.clone().removeAttr('data-index').find('input').val('').removeAttr('data-name').end()
) &&
$row.parents('tbody').append($clone).find('tr:last').each(set_listener);
}).end().find('[data-role="remove-line"]').click(function(){
var $tr=$(this).parents('tr');
if($tr.parent().find('tr').length>1)
$tr.remove();
else
$tr.find('input').val('');
});
}