I am working on the basic concept for multiple group selections where some input options are disabled because they are generally unavailable at the moment and others being disabled because they were already picked elsewhere.
This can be two or more forms or multiple elements within one form as in the example. I need to link the numerical output of selections and disable/enable options between separated elements.
We use PHP to generate the option list alphabetically through a server-side PHP include
. I left the PHP part out for having only static HTML to test.
The example shows two multiple select form inputs. Team members are identified by number in the background and with human names in the UX. The normally hidden numerical output is now made visible for testing below each team.
I wanted to simply pre-check already selected values from the hidden numerical output onBlur
and onFocus
when selecting the next team. Yet since the form elements are taken over by the multi.js
, there are no Focus and Blur events triggered anymore.
CSS based on multi.min.css
by Fabian Lindfors:
/* basic styling */
body { font-family: Avenir,sans-serif; }
.container { box-sizing: border-box; margin: 0 auto; max-width: 500px; padding: 0 20px; width: 100%; }
.developer { color:#999; font-size: small; margin-bottom:20px }
/* form and selection styling */
label{ margin-left:20px; color: #666; font-weight:bolder }
.multi-wrapper{ border: 1px solid #999; border-radius: 8px; width: 450px; margin:10px 0 10px 0 }
.multi-wrapper .non-selected-wrapper,.multi-wrapper .selected-wrapper{ box-sizing: border-box; display: inline-block; height: 150px; overflow-y: scroll; padding: 10px; vertical-align: top; width: 50% }
.multi-wrapper .non-selected-wrapper{ background: #fafafa; border-radius: 0 0 0 8px; border-right: 1px solid #ccc }
.multi-wrapper .selected-wrapper{ background: #FFF; border-radius: 0 0 8px 0 }
.multi-wrapper .header{ color: #4f4f4f; cursor: default; font-weight: 700; margin-bottom: 5px; padding: 5px 10px }
.multi-wrapper .item{ cursor: pointer; display: block; padding: 5px 10px }
.multi-wrapper .item: hover{ background: #ececec; border-radius: 2px }
.multi-wrapper .item-group{ padding: 5px 10px }
.multi-wrapper .item-group .group-label{ display: block; font-size: .875rem; opacity: .5; padding: 5px 0 }
.multi-wrapper .search-input{ border: 0; border-bottom: 1px solid #ccc; border-radius: 8px 8px 0 0; display: block; font-size: 1em; margin: 0; outline: 0; padding: 10px 20px; width: 100%; box-sizing: border-box }
.multi-wrapper .non-selected-wrapper .item.selected{ display: none; opacity: .5 }
.multi-wrapper .non-selected-wrapper .item.disabled,.multi-wrapper .selected-wrapper .item.disabled{ opacity: .5; text-decoration: line-through }
.multi-wrapper .non-selected-wrapper .item.disabled: hover,.multi-wrapper .selected-wrapper .item.disabled: hover{ background: inherit; cursor: inherit}
The HTML form:
<div class="container">
<h1>team selection demo</h1>
<form>
<label for="team_1">Select members for day shift</label>
<select onChange="reportUpdatedValues(this,this.name);"
multiple="multiple"
name="team_1"
id="team_1_select">
<option value="13" disabled="disabled">Alex</option>
<option value="1">Bob</option>
<option value="8">Diana</option>
<option value="5">Frank</option>
<option value="9">Fred</option>
<option value="11">Helen</option>
<option value="10">Jeanne</option>
<option value="4">Linda</option>
<option value="3">Mary</option>
<option value="2" disabled="disabled">Max</option>
<option value="7">Mo</option>
<option value="6">Paul</option>
<option value="12">Sara</option>
</select>
<span class="developer" style="display:inherit; padding:10px 0 10px 20px">
normally hidden digital output:
<input id="output_team_1" type="text" style="float:right">
</span>
<label for="team_2">Select members for night shift</label>
<select onChange="reportUpdatedValues(this,this.name);"
multiple="multiple"
name="team_2"
id="team_2_select">
<option value="13" disabled="disabled">Alex</option>
<option value="1">Bob</option>
<option value="8">Diana</option>
<option value="5">Frank</option>
<option value="9">Fred</option>
<option value="11">Helen</option>
<option value="10">Jeanne</option>
<option value="4">Linda</option>
<option value="3">Mary</option>
<option value="2" disabled="disabled">Max</option>
<option value="7">Mo</option>
<option value="6">Paul</option>
<option value="12">Sara</option>
</select>
</form>
<span class="developer" style="display:inherit; padding:10px 0 10px 20px">
normally hidden digital output:
<input id="output_team_2" type="text" style="float:right">
</span>
</div>
The javascript
// initialise multi, set headers for group 1
var select = document.getElementById("team_1_select");
multi(select, {
non_selected_header: "Candidates",
selected_header: "Team 1"
});
// initialise multi, set headers for group 2
var select = document.getElementById("team_2_select");
multi(select, {
non_selected_header: "Candidates",
selected_header: "Team 2"
});
function reportUpdatedValues(element,team){
// Return an array of the selected options in element
var result = [];
var options = element && element.options;
var opt;
for (var i=0, iLen=options.length; i<iLen; i++) {
opt = options[i];
if (opt.selected) {
result.push(opt.value || opt.text);
}
}
// for development purpose only we display the result in the team output
document.getElementById('output_'+team).value = result;
return result;
}
/*! multi.min.js version 03-12-2018 by Fabian Lindfors */
var multi=function(){var e=function(e,t,n){var a=e.options[t.target.getAttribute("multi-index")];if(!a.disabled){a.selected=!a.selected;var i,d,r,l=n.limit;if(l>-1){for(var s=0,o=0;o<e.options.length;o++)e.options[o].selected&&s++;if(s===l){this.disabled_limit=!0,"function"==typeof n.limit_reached&&n.limit_reached();for(o=0;o<e.options.length;o++){(c=e.options[o]).selected||c.setAttribute("disabled",!0)}}else if(this.disabled_limit){for(o=0;o<e.options.length;o++){var c;"false"===(c=e.options[o]).getAttribute("data-origin-disabled")&&c.removeAttribute("disabled")}this.disabled_limit=!1}}i="change",d=e,(r=document.createEvent("HTMLEvents")).initEvent(i,!1,!0),d.dispatchEvent(r)}},t=function(e,t){if(e.wrapper.selected.innerHTML="",e.wrapper.non_selected.innerHTML="",t.non_selected_header&&t.selected_header){var n=document.createElement("div"),a=document.createElement("div");n.className="header",a.className="header",n.innerText=t.non_selected_header,a.innerText=t.selected_header,e.wrapper.non_selected.appendChild(n),e.wrapper.selected.appendChild(a)}if(e.wrapper.search)var i=e.wrapper.search.value;for(var d=null,r=null,l=0;l<e.options.length;l++){var s=e.options[l],o=s.value,c=s.textContent||s.innerText,p=document.createElement("a");if(p.tabIndex=0,p.className="item",p.innerHTML=c,p.setAttribute("role","button"),p.setAttribute("data-value",o),p.setAttribute("multi-index",l),s.disabled&&(p.className+=" disabled"),s.selected){p.className+=" selected";var u=p.cloneNode(!0);e.wrapper.selected.appendChild(u)}if("OPTGROUP"==s.parentNode.nodeName&&s.parentNode!=r){if(r=s.parentNode,(d=document.createElement("div")).className="item-group",s.parentNode.label){var m=document.createElement("span");m.innerHTML=s.parentNode.label,m.className="group-label",d.appendChild(m)}e.wrapper.non_selected.appendChild(d)}s.parentNode==e&&(d=null,r=null),(!i||i&&c.toLowerCase().indexOf(i.toLowerCase())>-1)&&(null!=d?d.appendChild(p):e.wrapper.non_selected.appendChild(p))}};return function(n,a){if((a=void 0!==a?a:{}).enable_search=void 0===a.enable_search||a.enable_search,a.search_placeholder=void 0!==a.search_placeholder?a.search_placeholder:"Search...",a.non_selected_header=void 0!==a.non_selected_header?a.non_selected_header:null,a.selected_header=void 0!==a.selected_header?a.selected_header:null,a.limit=void 0!==a.limit?parseInt(a.limit):-1,isNaN(a.limit)&&(a.limit=-1),null==n.dataset.multijs&&"SELECT"==n.nodeName&&n.multiple){n.style.display="none",n.setAttribute("data-multijs",!0);var i=document.createElement("div");if(i.className="multi-wrapper",a.enable_search){var d=document.createElement("input");d.className="search-input",d.type="text",d.setAttribute("placeholder",a.search_placeholder),d.addEventListener("input",function(){t(n,a)}),i.appendChild(d),i.search=d}var r=document.createElement("div");r.className="non-selected-wrapper";var l=document.createElement("div");l.className="selected-wrapper",i.addEventListener("click",function(t){t.target.getAttribute("multi-index")&&e(n,t,a)}),i.addEventListener("keypress",function(t){var i=32===t.keyCode||13===t.keyCode;t.target.getAttribute("multi-index")&&i&&(t.preventDefault(),e(n,t,a))}),i.appendChild(r),i.appendChild(l),i.non_selected=r,i.selected=l,n.wrapper=i,n.parentNode.insertBefore(i,n.nextSibling);for(var s=0;s<n.options.length;s++){var o=n.options[s];o.setAttribute("data-origin-disabled",o.disabled)}t(n,a),n.addEventListener("change",function(){t(n,a)})}}}();"undefined"!=typeof jQuery&&function(e){e.fn.multi=function(t){return t=void 0!==t?t:{},this.each(function(){var n=e(this);multi(n.get(0),t)})}}(jQuery);
In the case example the names Alex and Max are on vacation and therefore PHP already made them generally unavailable for all teams. Now, if Diana (numerical member 8) gets selected for Team 1, the Diana option should become disabled or hidden for Team 2, Team 3, Team 4 and so on. The total number of teams can vary per case.