query builder

HTML

<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
<div class="queryedit"></div>

CSS

.queryedit { position: relative; min-width: 425px; min-height: 45px; padding: 4px; border: 1px solid #CCC; font-family: arial; }
.queryedit .editor {
    width: 96%;
    margin: auto;
    height: 100%;
    min-height: 45px;
    border-radius: 6px;
    padding: 2%;
}
.queryedit ul { list-style-type: none; margin: 0; padding: 0; }
.queryedit ul li { list-style: none; float: left; padding: 4px 8px; cursor: pointer; border-radius: 4px; margin: 0 2px; }
.queryedit ul.operators,
.queryedit ul.joins { margin: 10px 0; display:inline-block; position: absolute;
bottom: -40px;
background: #D1D1D1;
padding: 4px 2px;
border-radius: 6px; display:none; }
.selectparen {
    display: inline;
    background-color: #3BA300;
    color: #FFF;
    width: 4px;
    height: 18px;
    padding: 3px 5px;
    margin: 0;
    border-radius: 2px 2px 12px 12px;
    top: 2px;
    position: relative;
    cursor: pointer;
}
.queryedit ul.operators li,
.queryedit ul.joins li { background-color:#3BA300; color: white; border-radius: 4px; }
.queryedit ul.joins li.parens { padding-left: 10px; padding-right: 10px; }
.queryedit ul.operators li:hover,
.queryedit ul.joins li:hover{ background-color: #3D3D3D; color: #D6D6D6; }
.queryedit input { border: 1px solid #B6B6B6;
background: transparent;
border-radius: 4px; width: 100%; }
.queryedit .pill,
.queryedit .typer { display: inline-block; padding: 2px 6px; margin: 0 2px; background-color: #DDD; border-radius: 6px; }
.queryedit .typer.opertyper {
    max-width: 66px;
}
.queryedit .typer.opertyper input {
    font-size: .9em;
}
.queryedit .pill:hover { background-color: #A3A3A3; }
.queryedit span.pill.oper { background-color: #A3A3A3; font-weight: bold; }
.queryedit span.pill.joiner {
background-color: #5F5F5F;
color: #fff;
}
div.expression {
    display: inline-block;
    padding: 5px 4px;
    border: 1px dotted #858585;
    margin-top: 2px;
    border-radius: 6px;
}
.queryedit .fields { 
    display: none;
position: absolute;
    bottom: -1362px;
    left:...

JavaScript

var evolvui = { firstclick:false };

evolvui.$input = $('.queryedit');
evolvui.$input.on('click',function(event) {
    $(this).unbind();
    evolvui.init( $(this) )
});

/* evolvui.$input.keyup(function(event) {
      $('#logger').prepend( "Handler for .keyup() called."+event.which+'<br>' );
});
*/
evolvui.init = function($instance) {
    $instance.append('<div style="clear:both;"></div>');
    $instance.append( $('<div/>',{class:'editor'}).css('border','1px solid green') );
    evolvui.fieldsUI.expTag($instance);
    evolvui.fieldsUI($instance);
}
evolvui.operatorUI = function($instance) {
    if( !$('.operators').length ){
        var $ul = $('<ul/>',{class:'operators'});
        $.each(evolvui.operators,function(index,value){
            $li = $('<li data-oper="'+value.symb+'"><a tabindex="' + (index+101) + '">'+value.char+'</a></li>')
                    .on('click',function(){
                        $instance.operTyper.val('');
                        $instance.operTyper.detach();
                        $('.field:last').after('<span class="pill oper">'+value.char+'</span>');
                        $ul.fadeOut('fast');
                        evolvui.fieldsUI($instance);
                    });
            $li.find('a').keyup(function(event) {
                    if(event.which == 13)
                        $(this).parent().click();
                });
            $ul.append($li);
        });
        $instance.append($ul);
        $instance.operTyper = $('<span class="typer opertyper"></span>');
        $instance.operTyper.input = $('<input tabindex="100" class="typefield" name="jointyper" />').keyup(function(event) {
            console.log('join typer: '+event.which);
            var v = $(this).val().toLowerCase();
            if(event.which == 13)
                $instance.find('.operators li[data-oper="' + v + '"]').click();
            if(v == '=')
                $instance.find('.operators li:first').click();
            else if(v == '>=')
        ...