JSFiddle - React, Tailwind, and code Playground
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.epoxy/1.3.1/backbone.epoxy.js"></script>
<form action="javascript:void(0);">
<label><input type="checkbox" name="debug"> Debug</label>
<div class="done">
<h1>Done</h1>
<button type="button" class="js-repeat">Repeat?</button>
</div>
<div class="my-form-wrapper">
<div class="col">
<div class="block">
<label for="inputName">Your Name:</label>
<br>
<input type="text" id="inputName" name="name">
</div>
<div class="name-required zip-group">
<div class="block">
<label for="inputZip">Your ZIP:</label>
<br>
<input type="text" id="inputZip" name="zip">
<button type="button" class="js-set-zip">Set</button>
<img class="preloader" src="http://preloaders.net/preloaders/712/Floating%20rays.gif">
</div>
<div class="block">
<label for="inputRegion">Your Region:</label>
<br>
<input type="text" id="inputRegion" name="region" readonly>
</div>
<div class="block">
<label for="inputCity">Your City:</label>
<br>
<input type="text" id="inputCity" name="city" readonly>
</div>
</div>
</div>
<div class="col name-required car-group">
<div class="block">
<label for="inputCarNumber">Your Car Number:</label>
<br>
<input type="text" id="inputCarNumber" name="carNumber">
<button type="button" class="js-set-car-number">Set</button>
<img class="preloader" src="http://preloaders.net/preloaders/712/Floating%20rays.gif">
</div>
...
SCSS
body {
background-color: #fff;
}
.my-form-wrapper {
display: flex;
position: relative;
.col {
display: inline-block;
padding-right: 10px;
margin-bottom: 10px;
flex-basis: 0;
flex-grow: 1;
}
.block {
&:last-of-type {
margin-bottom: 10px;
}
&.--disabled {
opacity: .5;
}
}
input {
border: 1px solid;
&[readonly] {
background-color: rgba(200,200,200,.1);
}
}
&.--loading {
> .preloader {
display: inline-block;
position: absolute;
left: 50%;
top: 50%;
margin-left: -10px;
margin-top: -10px;
}
&:after {
content: '';
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: rgba(255, 255, 255, .8);
}
}
.done {
display: none;
}
}
.preloader {
width: 20px;
height: 20px;
display: none;
}
JavaScript
var FormView = Backbone.Epoxy.View.extend({
events: {
'submit': 'submit',
'click .js-set-zip': 'setZip',
'click .js-set-car-number': 'setCardNumber',
'click .js-set-social-id': 'setSocialID',
'click .js-repeat': 'repeat'
},
bindings: {
'[name="debug"]':
'checked:state_debug',
'[name="name"]':
'value:name,' +
'events:["keyup"]',
'[name="zip"]':
'value:zip,' +
'events:["keyup"]',
'[name="region"]':
'value:region,' +
'events:["keyup"]',
'[name="city"]':
'value:city,' +
'events:["keyup"]',
'[name="carNumber"]':
'value:carNumber,' +
'events:["keyup"]',
'[name="carModel"]':
'value:carModel,' +
'events:["keyup"]',
'[name="carAge"]':
'value:carAge,' +
'events:["keyup"]',
'[name="socialID"]':
'value:socialID,' +
'events:["keyup"]',
'[name="gender"]':
'value:gender,' +
'events:["keyup"]',
'[name="age"]':
'value:age,' +
'events:["keyup"]',
'[name="sexualOrientation"]':
'value:sexualOrientation,' +
'events:["keyup"]',
'[type=submit]':
'attr:{"disabled":not(isSendEnabled)}',
'.js-set-zip':
'attr:{"disabled":not(zip)},' +
'toggle:not(state_zipLoading)',
'.js-set-car-number':
'attr:{"disabled":not(carNumber)},' +
'toggle:not(state_carLoading)',
'.js-set-social-id':
'attr:{"disabled":not(socialID)},' +
'toggle:not(state_socialLoading)',
'.name-required .block':
'classes:{"--disabled":not(name)},',
'.name-required input':
'attr:{"disabled":not(name)}',
'.zip-group...