Skip to content
Snippets Groups Projects
Commit 2b8457d6 authored by Jacob Schatz's avatar Jacob Schatz
Browse files

initial dropdown integration with gitlab

parent 6f4ea679
No related branches found
No related tags found
No related merge requests found
//= require sifter
/**
* multiawesome.js (v0.12.1)
* Copyright (c) 2015 Jacob Schatz & contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*
* @author Jacob Schatz <jschatz@gitlab.com>
*/
/*jshint curly:false */
/*jshint browser:true */
(function ( $ ) {
'use strict';
var defaults = {
data: [],
title: '',
tip: '',
name: 'multi-awesome',
header: [],
multiple: false,
alwaysPrefixWithSearch: false,
placeholder: 'Filter...',
onChange: function(){},
always: [],
dataObject: {
label: 'label',
data: 'data',
category: 'category',
subtitle: 'subtitle',
image: 'image'
},
minSearchLength: 2
};
$.fn.multiawesome = function( options ) {
var MultiAwesome = {
extraMenuContainerTemplate: '<li data-extra-menu-container class="dropdown-multi-menu-extras-container"></li>',
searchTemplate: '<li class="dropdown-multi-menu-search-container"><div class="input-with-icon"><i class="fa fa-search"></i><input type="text" id="multiawesome-search-input" /></div></li>',
itemContainerTemplate: '<li class="dropdown-multi-menu-selections"><ul></ul></li>',
itemTemplate: '<li><a href="#" class="item" data-item-selectable tabIndex="-1"><input type="checkbox" name="{{name}}" value="{{data}}"/>{{label}}</a></li>',
seperatorTemplate: '<li role="separator" class="divider"></li>',
headerTemplate: '<li class="dropdown-multi-menu-header"><div class="dropdown-multi-menu-header-area"><ul class="dropdown-multi-menu-header-list">{{header}}</ul></div></li>',
headerItemTemplate: '<li class="dropdown-multi-menu-header-item"><a href="#" data-header-selectable class="header-item" tabIndex="-1"><input name="{{name}}" type="checkbox" value="{{data}}" />{{headeritem}}</a></li>',
titleTemplate: '<li class="dropdown-multi-menu-title"><div class="dropdown-multi-menu-title-area"><a href="#" data-back-button class="dropdown-multi-menu-back-button"></a><h3 class="dropdown-multi-menu-title-text">{{title}}</h3></div></li>',
categoryContainerTemplate: '<li class="dropdown-multi-menu-category"><ul></ul></li>',
categoryItemTemplate: '<li><a href="#" class="category" data-category-selectable tabIndex="-1"><input type="checkbox" value="{{category}}"/>{{category}}</a></li>',
tipTemplate: '<li class="dropdown-multi-menu-tip"><div class="dropdown-multi-menu-tip-area"><p>{{tip}}</p></div></li>',
subtitleTemplate: '<p class="dropdown-multi-menu-subtitle">{{subtitle}}</p>',
imageTemplate: '<div class="dropdown-multi-menu-image" style="background-image:url(\'{{image}}\');"></div>'
};
return this.each(function() {
var self = this,
categories = [],
$self = $(self),
$form = $(self).closest('form'),
$itemContainer,
$itemContainerUL,
$extraMenuContainer,
categoriesAppended = false,
categoriesSet = false,
selectedItems = [],
selectedCategories = [],
$searchInput,
$extraMenus,
$addedMenu,
$backButton,
$currentMenu,
toHideForExtraMenus = [],
sifter,
// Merge the options and defaults into the settings.
// No need to check for undefined
settings = $.extend(true, {}, defaults, options, $self.data());
if( self.tagName !== 'UL') {
return;
}
var prepareDropdown = function() {
var $searchTemplate = $(MultiAwesome.searchTemplate);
toHideForExtraMenus.push($searchTemplate);
$self.prepend($searchTemplate);
$searchInput = $searchTemplate.find('input');
if( settings.placeholder ) {
$searchInput.prop( 'placeholder', settings.placeholder );
}
};
var attachListeners = function() {
$self.on( 'click', '[data-category-selectable]' , dropdownCategoryLinkClicked );
$self.on( 'click',
'[data-item-selectable], [data-header-selectable]', dropdownSelectionLinkClicked );
$self.on( 'click', dropdownClickedAnywhere );
$self.on( 'click', $backButton, backButtonClicked );
$searchInput.on( 'keydown keyup update', inputSearched );
};
var parseSearchResults = function(results) {
var finalData = [];
results.forEach( function( result ) {
finalData.push(settings.data[result.id]);
});
renderData( finalData );
};
var addCategories = function() {
var $categoryContainer = $(MultiAwesome.categoryContainerTemplate);
var $categoryContainerUL = $categoryContainer.find('ul');
if( categories.length ) {
var $seperatorTemplate = $(MultiAwesome.seperatorTemplate);
toHideForExtraMenus.push($seperatorTemplate);
$self.prepend($seperatorTemplate);
categories.forEach(function( category ) {
$categoryContainerUL.prepend( MultiAwesome.categoryItemTemplate
.replace( /\{\{category\}\}/g, category ) );
});
$self.prepend($categoryContainer);
}
};
var addTitle = function() {
var titleTemplate;
if( settings.title ) {
var $seperatorTemplate = $(MultiAwesome.seperatorTemplate);
var $titleTemplate = $(MultiAwesome.titleTemplate);
$self.prepend($seperatorTemplate);
$self.prepend(MultiAwesome.titleTemplate
.replace(/\{\{title\}\}/g, settings.title)
);
$backButton = $self.find('[data-back-button]');
}
};
var addExtrasContainer = function() {
$extraMenuContainer = $(MultiAwesome.extraMenuContainerTemplate);
$self.prepend($extraMenuContainer);
};
var addHeader = function() {
var $headerTemplate,
headerList = [];
// if we have some header data
if( settings.header.length ) {
settings.header.forEach( function(item) {
headerList.push(
MultiAwesome.headerItemTemplate
.replace(/\{\{headeritem\}\}/g, item[settings.dataObject.label])
.replace(/\{\{data\}\}/g, item[settings.dataObject.data])
.replace(/\{\{name\}\}/g, '_' + settings.name)
);
});
var $seperatorTemplate = $(MultiAwesome.seperatorTemplate);
toHideForExtraMenus.push($seperatorTemplate);
$self.prepend($seperatorTemplate);
$headerTemplate = $(MultiAwesome.headerTemplate.replace(/\{\{header\}\}/g, headerList.join('')));
toHideForExtraMenus.push($headerTemplate);
$self.prepend($headerTemplate);
}
};
var addData = function(callback) {
function parseDataWhenReady() {
$itemContainer = $(MultiAwesome.itemContainerTemplate);
toHideForExtraMenus.push($itemContainer);
$itemContainerUL = $itemContainer.find('ul');
sifter = new Sifter(settings.data);
renderData( settings.data,callback );
}
if ( settings.data ) {
if ( typeof settings.data === 'string') {
$.getJSON(settings.data, function(data) {
settings.data = data;
parseDataWhenReady();
});
} else if ( typeof settings.data === 'object' ) {
parseDataWhenReady();
} else {
$.error('Data must be a string or array');
}
}
};
var renderData = function( data, callback ) {
selectedItems = [];
$itemContainerUL.empty();
var emptyObj = {},
skipMatch = false,
searchInputVal = [],
o,
tempAlwaysData = [],
tempItemTemplate,
$itemTemplate;
if( !data.length ) {
emptyObj[settings.dataObject.label] = 'No matches found';
emptyObj[settings.dataObject.data] = 'dropdown-multi-menu-selectable:false';
emptyObj[settings.dataObject.category] = '';
emptyObj.selectable = false;
data.push(emptyObj);
skipMatch = true;
}
if( settings.alwaysPrefixWithSearch && $searchInput ) {
searchInputVal = $searchInput.val();
tempAlwaysData = settings.always.map(function(item){
// copy, don't alter real object.
o = $.extend({}, item);
if(searchInputVal.length){
o.label = '<strong>"' + searchInputVal + '"</strong>' + ' ' + o.label;
}
o.always = true;
return o;
});
} else {
tempAlwaysData = settings.always;
}
data = data.concat(tempAlwaysData);
data.forEach( function( item ) {
tempItemTemplate = MultiAwesome.itemTemplate;
if( !categoriesSet && !skipMatch ) {
var addCategory = item[settings.dataObject.category];
if( item.hasOwnProperty( settings.dataObject.category ) && categories.indexOf( addCategory ) === -1 ) {
categories.push( addCategory );
}
} else {
// only do this if the categories are already set... they won't search categories on the first time.
if( selectedCategories.length &&
selectedCategories.indexOf(item[settings.dataObject.category]) === -1 &&
!skipMatch) {
return;
}
}
$itemTemplate = $(MultiAwesome.itemTemplate);
if( item.hasOwnProperty('selectable') && !item.selectable ) {
tempItemTemplate = $itemTemplate
.find('a')
.addClass('disabled')
.find('input[type="checkbox"]')
.prop('disabled','disabled')
// back to the anchor tag
.end()
// back to the li
.end()
.get(0)
.outerHTML;
}
if( item.hasOwnProperty( settings.dataObject.subtitle ) &&
item[settings.dataObject.subtitle].length ) {
tempItemTemplate = $(tempItemTemplate)
.find('a')
.addClass('dropdown-multi-menu-item-with-subtitle')
.append(
MultiAwesome.subtitleTemplate
.replace(/\{\{subtitle\}\}/g, item[settings.dataObject.subtitle])
)
.end()
.get(0)
.outerHTML;
}
if( item.hasOwnProperty( settings.dataObject.image ) &&
item[settings.dataObject.image].length ) {
tempItemTemplate = $(tempItemTemplate)
.find('a')
.addClass('dropdown-multi-menu-with-image')
.prepend(
MultiAwesome.imageTemplate
.replace(/\{\{image\}\}/g, item[settings.dataObject.image])
)
.end()
.get(0)
.outerHTML;
}
$itemContainerUL.append(
tempItemTemplate
.replace(/\{\{data\}\}/g,item[settings.dataObject.data])
.replace(/\{\{label\}\}/g,item[settings.dataObject.label])
.replace(/\{\{name\}\}/g, '_' + settings.name)
);
});
if( !categoriesSet && !categoriesAppended ) {
$self.append($itemContainer);
categoriesAppended = true;
}
if( categories.length ){
categoriesSet = true;
}
if( callback ) {
callback();
}
};
var addToForm = function( val ) {
$form.prepend('<input type="hidden" name="' + settings.name + '" value="' + val + '" />');
};
var removeFromForm = function( val ) {
$form
.find('input[name="' + settings.name + '"][value="' + val + '"]')
.remove();
};
var getExtraMenus = function() {
$extraMenus = $self
// get parent button group
.closest('div.button-group')
// get the extra menu divs
.find('[data-extra-menu]');
};
/* * * * * * * * * * * * * * * */
/* listeners
/* * * * * * * * * * * * * * * */
var backButtonClicked = function() {
toHideForExtraMenus.forEach(function(menuSection){
menuSection.show();
});
$extraMenuContainer.empty();
$backButton.hide();
return false;
};
var inputSearched = function() {
//remove current hidden inputs
$('input[type="hidden"][name="' + settings.name + '"]').remove();
if( $searchInput.val().length > settings.minSearchLength ) {
var results = sifter.search($searchInput.val(), {
fields: [settings.dataObject.label],
sort: [{field: settings.dataObject.label, direction: 'asc'}]
});
parseSearchResults(results.items);
} else {
renderData( settings.data );
}
};
var moveToExtraMenu = function($menu) {
var $cloneMenu = $menu.clone();
toHideForExtraMenus.forEach(function(menuSection){
menuSection.hide();
});
$currentMenu = $menu;
$extraMenuContainer.append($cloneMenu);
$cloneMenu.show();
$backButton.show();
};
var isExtraMenuValue = function( val ) {
var isMatch = false;
$extraMenus.each( function() {
var $this = $(this);
if(val === $this.data('menu-target-value')) {
isMatch = true;
moveToExtraMenu($this);
return;
}
});
return isMatch;
};
var dropdownClickedAnywhere = function( e ) {};
var dropdownCategoryLinkClicked = function ( e ) {
var $target = $( e.currentTarget ),
$inp = $target.find( 'input' ),
val = $inp.val(),
i = selectedCategories.indexOf( val );
e.preventDefault();
// if the checkbox is disabled.
if( $inp.prop('disabled') ) {
return false;
}
if ( i > -1 ) {
var spliced = selectedCategories.splice( i, 1 );
$target.removeClass('selected');
setTimeout( function() {
$inp.prop( 'checked', false );
}, 0);
} else {
selectedCategories.push( val );
$target.addClass('selected');
setTimeout( function() {
$inp.prop( 'checked', true );
}, 0);
}
inputSearched();
$( e.target ).blur();
return false;
};
var findItemWithData = function(searchData, id) {
var item = {};
for (var i = searchData.length - 1; i >= 0; i--) {
item = searchData[i];
if( item.hasOwnProperty(settings.dataObject.data) &&
item[settings.dataObject.data] == id ) {
return item;
}
}
return undefined;
};
var dropdownSelectionLinkClicked = function ( e ) {
var $target = $( e.currentTarget ),
$inp = $target.find( 'input' ),
findItemInData,
val = $inp.val(),
i = selectedItems.indexOf( val );
e.preventDefault();
if( $inp.prop('disabled') ) {
return false;
}
if( isExtraMenuValue( val ) ) {
return false;
}
findItemInData = findItemWithData(settings.data, val);
if( typeof findItemInData === 'undefined' ) {
findItemInData = findItemWithData(settings.always, val);
}
if( typeof findItemInData === 'undefined' ) {
findItemInData = findItemWithData(settings.header, val);
}
if( typeof findItemInData === 'undefined' &&
val === 'dropdown-multi-menu-selectable:false' ) {
// don't close the dropdown.
return false;
}
if ( typeof findItemInData !== 'undefined' &&
findItemInData.hasOwnProperty('selectable') &&
findItemInData.selectable === false ) {
// don't close the dropdown
return false;
}
if( findItemInData.hasOwnProperty( 'href' ) ) {
window.location.href = findItemInData.href;
return;
}
if( findItemInData.hasOwnProperty('selectable') &&
!findItemWithData.selectable ) {
return;
}
if ( i > -1 ) {
var spliced = selectedItems.splice( i, 1 );
if( settings.multiple ) {
removeFromForm( spliced );
} else {
$form
.find('input[name="' + settings.name + '"]')
.remove();
}
$target.removeClass('selected');
setTimeout( function() {
$inp.prop( 'checked', false );
}, 0);
} else {
if( !settings.multiple ) {
selectedItems = [];
$form
.find('input[name="' + settings.name + '"]')
.remove();
$form
.find('input[name="_' + settings.name + '"]')
.parent()
.removeClass('selected');
}
selectedItems.push( val );
addToForm( val );
$target.addClass('selected');
setTimeout( function() {
$inp.prop( 'checked', true );
}, 0);
}
settings.onChange({"changed":findItemInData, "selected":selectedItems});
// close the dropdown if single selection
// otherwise don't close the dropdown
if( settings.multiple ) {
$( e.target ).blur();
return false;
} else {
var button = $self.siblings('.dropdown-toggle').first();
button.contents()
.each(
function(){
if ( this.nodeType === 3 && this.nodeValue.trim() ) {
this.textContent = $target.text();
}
});
}
};
var addTip = function() {
var $tipTemplate;
if( settings.tip ) {
var $seperatorTemplate = $(MultiAwesome.seperatorTemplate);
toHideForExtraMenus.push($seperatorTemplate);
$self.append($seperatorTemplate);
$tipTemplate = $(MultiAwesome.tipTemplate.replace(/\{\{tip\}\}/g, settings.tip));
toHideForExtraMenus.push($tipTemplate);
$self.append($tipTemplate);
}
};
/* * * * * * * * * * * * * * * */
/* setup
/* * * * * * * * * * * * * * * */
var shouldInit = function() {
if(!$self.hasClass('initialized')) {
$self.addClass('initialized');
return true;
}
return false;
};
var setup = function() {
if(!shouldInit()){
return;
}
addData(function(){
addHeader();
addCategories();
prepareDropdown();
addExtrasContainer();
addTitle();
attachListeners();
addTip();
getExtraMenus();
});
};
setup();
});
};
$(function(){
$('[data-multi-awesome]').each(function(){
$(this).multiawesome();
});
});
})( jQuery );
\ No newline at end of file
.open>.dropdown-menu {
max-width: 320px;
border-radius: 0px;
}
.dropdown-menu input[type='text'] {
margin: 0 5px;
width: 309px;
height: 35px;
padding-left: 6px;
}
.dropdown-menu input[type='checkbox'] {
margin: 0 5px;
display: none;
}
.dropdown-menu>li>a {
padding-left: 0;
}
.dropdown-menu .dropdown-multi-menu-title-area h3 {
font-size: 15px;
text-align: center;
margin-top: 5px;
}
.dropdown-menu .dropdown-multi-menu-title-area h3::after {
content: "";
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAACGUlEQVQ4jZ2VMXbiMBCGf83KKcxzntOmI1VMEyj2BLpD9gRbwQVygeUCpMsdcgKfQaYJqeIu1RYGC/stNjNbJOaBY8hu/lrz69PoH0mJCBaLhVEArqMoxhf0vFgYARBFUawWT08my7KxiMD3/fhmOLz/H7PE2ruyLL8rpRCG4b0WEQjzeVXXxq3X59ba8zAMZ/1+Pz9l9PLyEiyXy0lRFGZb18bTOhYR6GgwiOdJcu3Wa9R1bZxz5r1mespwuVxOnHO/mBla69jv9R6jwSAmALgZDu9934+JCMyMoiiMtfYuTdOgbZSmaWCtvSuKwjAziOigVbpZGIbhDACKojCnSLMsOyTz/bipPTB879nUWgvnnNkjxdnZ2TMAbDab6zbZaDQ62FCjpS5SIgIAMDOOkTVSItLZ9CRJxnmez5gZzRqlFIgIQRBMhkfiRZ1uADzPeyUiiMiOTERARPA87/VY3VHDr+pDDxtVVXXJzLtjAm9HZmZUVXV5rO5DD9M0DbIsmzSXQkQ4dSntifpA2JWzdmxO5XRnuE92KmddOd0n3Rl+NgGNPpsoAoB5koy7yLpenH6/n49Go2l79udJMgaAbz9ub02+Wv2s69poreNer/cQhuHs4uJi0zbbV1mWcxHJt9sttnVtmPmPc+63VkpBEa205z36vh8fm4C2rq6ucgDTxFqUZbkiolwplavmCwDenvB/MWtr/wv5CwCanfXE6iK0AAAAAElFTkSuQmCC');
background-repeat: no-repeat;
background-size: 11px;
width: 12px;
height: 12px;
display: inline-block;
position: absolute;
right: 13px;
top: 13px;
cursor: pointer;
}
.dropdown-menu .dropdown-multi-menu-selections {
max-height: 150px;
overflow-y: scroll;
margin-top: 15px;
}
.dropdown-menu .dropdown-multi-menu-category ul{
padding: 0 5px;
list-style: none;
max-height: 65px;
overflow-y: scroll;
}
.dropdown-menu ul.dropdown-multi-menu-header-list {
padding: 0 5px;
list-style: none;
max-height: 75px;
overflow-y: scroll;
}
.dropdown-menu .dropdown-multi-menu-selections ul{
padding: 0 5px;
list-style: none;
}
.dropdown-menu .dropdown-multi-menu-selections li,
.dropdown-menu .dropdown-multi-menu-header-list li{
padding: 7px 5px;
}
.dropdown-menu .dropdown-multi-menu-selections
li.dropdown-multi-menu-list-striped:nth-child(odd){
background: #F5F5F5;
}
.dropdown-menu .dropdown-multi-menu-selections
li a.disabled{
cursor: default;
}
.dropdown-menu .dropdown-multi-menu-selections .dropdown-multi-menu-subtitle {
font-size: 12px;
color: #9E9E9E;
}
[data-extra-menu] {
display: none;
}
.dropdown-multi-menu-back-button {
width: 12px;
height: 12px;
display: none;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACDUlEQVRIibWWMZLaMBSGn2QBGS0MMJMUTCq7CdAEF9kLqNsqJ8gFlgvQUS0XIOcgF3CXagvMVvY2UKXF9srIYCQ5FQxkIdhL8kqN/H/vvV9PMsqyDPKG73ksA4BOp+Pk/Ybk3Thz3UGSJF8QQuB7HrRzQi4C5vN5LYqivhCCKSlZiRCnSNUXAVEU9eM4ftBaAyHEoTc3k063e32LDjPXWgPGGCilzude73te8b8CXmVOqdNoNMZFxE8CFotFLQzDw8wfKaWObdujouInAWEY7jMvlUoTSqnTK9iWk4A/MzcMA64VPwIEQdBfrVYPSqlr9F4Fevb92yAI7tM0bSutb7MsA4QQIISAEOKUy2WfEPIrj9huPjDGL5Vy2e90uw7hnH/lnH/DGAPCeL8xyzJI05RtNhuWd7B2+wzDgHeVyuTZ9zl+S9lFgtRqtR9Syo//o0Wf2u1HtFucTqeDQ5MJIVCtVvv/7BQ1m80xQgiEEExKya4RPYy9B6Zpctu2R5RSB2MMSikQQrDZbHZ/DcAYDodHC+v1+klrzZVSIKW8226375fL5YdWq/XzLQB07gi6rjs4ddmZpsmLAM7epvV6fQwAe0/iON75UujSOzsHlmUdeaK1BiEEeyroycUX7bASJSUTQrx4nufnffgvAizL4gAwmrkuJEnygjHmCKHcPpw1+VR4nscQQO4/CgCA3yx5RbfFRth/AAAAAElFTkSuQmCC');
float: left;
background-size: 12px;
background-repeat: no-repeat;
margin-top: 3px;
margin-left: 10px;
margin-right: -10px;
}
.dropdown-menu .dropdown-multi-menu-selections ul a,
.dropdown-menu .dropdown-multi-menu-category ul a,
.dropdown-menu .dropdown-multi-menu-header-item a{
text-decoration: none;
color: #333;
display: inline-block;
width: 285px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding-left: 30px;
vertical-align: middle;
}
.dropdown-menu .dropdown-multi-menu-selections ul a:focus,
.dropdown-menu .dropdown-multi-menu-category ul a:focus,
.dropdown-menu .dropdown-multi-menu-header-item a:focus {
border: none;
outline: none;
}
.dropdown-menu .dropdown-multi-menu-selections ul a.selected,
.dropdown-menu .dropdown-multi-menu-category ul a.selected,
.dropdown-menu .dropdown-multi-menu-header ul a.selected {
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAYCAMAAADat72NAAABtlBMVEX////b29vc29vc3NzZ2dna2dnb2trY19fZ2NjV1dXW1tbT09PU09PV1NTR0dHS0dHS0tLPz8/Qz8/Q0NDNzc3Ozc3Ly8vMy8vMzMzJycnKycnKysrIyMjJyMjKyMjFxcXGxcXGxsbHxcXHxsbEw8PExMTFxMTBwcHBwsLCwcG/v7+9vb2+vr6/vr67u7u8vLy5ubm6ubm7ubm3t7e4t7e4uLi1tbW3tra0s7OysbGysrKzsbGvr6+wr6+urq6vrq6sq6utrKypqamrqqqsqqqop6epp6eqqKimpaWnpaWnpqako6OkpKSlo6OioaGjoqKkoqKgn5+goKChn5+hoKCenp6fnp6cm5ucnJydm5udnJyenJx+fX1/fHx/fX2Afn6Af3+Bf3+BgICCgICCgYGDgYGDgoKEgoKFgoKFg4OGhISGhYWHhYWIh4eJh4eJiIiJiYmKiIiLiYmLioqMioqMi4uNi4uNjIyOjIyPjY2Qjo6Qj4+SkJCSkZGTkZGTkpKUk5OVk5OVlJSWlZWXlZWYlpaYl5eYmJiZmJiZmZmamJibmZmbmpqcm5uenp6fn5+op6erqqouFWZ5AAAAXHRSTlMADw8PFhYWHR0kJCwsLDMzMzo6OkJCSUlJUFBQV1dXX19fX19mZmZtbW11fHx8g4OKioqSkpKZmaCoqKivr7a2vb3FxcXMzMzT09Pb29vi4uLp6enp8PD4+Pj4+E8lS9YAAAEjSURBVCjPY2DACYRtrDhwywralTZL45RldymLb5LEJctkWBOXHiSAS9q8OSOzxRiXrGJPWmqdNTcOWY3ggtQyN15cXvIpTCnxVcIhK+pYnZwZLYdDls22Nj2rywiHLKNeW2p8oxaMK6vGgiKt05edVGXPDOWJB7S6KyPJSgVnxZRb8sC4Co2JJV4icFkJl6KYXB8hOJ8/KiW5wl8TyuN0KonP81BEMs24Iye10A8S+Kwm1bGZESoobtHszkwtDtUGMS0aUjM7TdG8YhRZmJIdLc/IpNqWktpoxonuVeWw3OTC3gkT+/NTK525MINCN6QsNSuvND+93FUGW1CJeZekJiSn5wSKYw9KNc/q9IS8cH1cCYDPoT63XR13ypQwczbAEAQAnkJCZAp/V+QAAAAASUVORK5CYII=');
background-repeat: no-repeat;
background-size: 16px;
background-position: 2px 4px;
}
.dropdown-menu .dropdown-multi-menu-selections ul a.selected.dropdown-multi-menu-item-with-subtitle,
.dropdown-menu .dropdown-multi-menu-selections ul a.selected.dropdown-multi-menu-with-image {
background-position: 2px 12px;
}
.dropdown-menu .dropdown-multi-menu-image {
background-size: cover;
/*crop from the center*/
background-position: center;
width: 30px;
height: 30px;
display: inline-block;
position: relative;
border-radius: 15px;
float: left;
margin-top: 3px;
margin-right: 11px;
}
.dropdown-menu .dropdown-multi-menu-tip-area{
margin: 10px;
text-align: left;
max-height: 40px;
overflow-y: scroll;
}
.dropdown-menu .dropdown-multi-menu-header-area {
}
.dropdown-menu .dropdown-multi-menu-tip-area p {
}
.dropdown-menu .input-with-icon {
position: relative;
margin: 10px 0;
}
.dropdown-menu .input-with-icon i {
position: absolute;
right: 0;
padding: 10px 12px;
color: #817F7F;
pointer-events: none;
}
Loading
Loading
@@ -19,6 +19,10 @@ class Projects::MilestonesController < Projects::ApplicationController
 
@milestones = @milestones.includes(:project)
@milestones = @milestones.page(params[:page]).per(PER_PAGE)
respond_to do |format|
format.html # show.html.erb
format.json { render :json => @milestones }
end
end
 
def new
Loading
Loading
Loading
Loading
@@ -20,7 +20,7 @@ module MilestonesHelper
end
end
 
def projects_milestones_options
def projects_milestones_data_options
milestones =
if @project
@project.milestones
Loading
Loading
@@ -28,12 +28,11 @@ module MilestonesHelper
Milestone.where(project_id: @projects)
end.active
 
epoch = DateTime.parse('1970-01-01')
grouped_milestones = GlobalMilestone.build_collection(milestones)
grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
grouped_milestones.unshift(Milestone::None)
grouped_milestones.unshift(Milestone::Any)
milestones.as_json only: [:title, :id]
end
 
options_from_collection_for_select(grouped_milestones, 'name', 'title', params[:milestone_title])
def projects_milestones_header_options
grouped_milestones = [Milestone::Any, Milestone::None].as_json
end
end
Loading
Loading
@@ -36,6 +36,41 @@ module SelectsHelper
hidden_field_tag(id, value, html)
end
 
def multi_select_tag(name, opts = {})
css_class = "dropdown-menu "
css_class << (opts[:class] || '')
header = opts[:header] || ''
ul_html = {
class: css_class,
data: {
header: header,
data: opts[:header_url],
"multi-awesome" => '',
"data-object" => {
label: "title",
data: "id"
}
}
}
button_html = {
class: ["btn", "btn-default", "dropdown-toggle"],
type: "button",
data: {
toggle: "dropdown"
}
}
button_class = "btn btn-default dropdown-toggle"
content_tag :div, :class => "button-group" do
content_tag(:button, content_tag(:span, name) +
content_tag(:span,nil ,:class => "caret"),
button_html) +
content_tag(:ul, nil, ul_html)
end
end
def groups_select_tag(id, opts = {})
opts[:class] ||= ''
opts[:class] << ' ajax-groups-select'
Loading
Loading
Loading
Loading
@@ -38,9 +38,7 @@
placeholder: 'Assignee', class: 'trigger-submit', any_user: "Any Assignee", null_user: true, first_user: true, current_user: true)
 
.filter-item.inline.milestone-filter
= select_tag('milestone_title', projects_milestones_options,
class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Milestone'})
= multi_select_tag('Milestone', class: 'milestone-filter', header: projects_milestones_header_options)
 
.filter-item.inline.labels-filter
= select_tag('label_name', projects_labels_options,
Loading
Loading
/**
* sifter.js
* Copyright (c) 2013 Brian Reavis & contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*
* @author Brian Reavis <brian@thirdroute.com>
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.Sifter = factory();
}
}(this, function() {
/**
* Textually searches arrays and hashes of objects
* by property (or multiple properties). Designed
* specifically for autocomplete.
*
* @constructor
* @param {array|object} items
* @param {object} items
*/
var Sifter = function(items, settings) {
this.items = items;
this.settings = settings || {diacritics: true};
};
/**
* Splits a search string into an array of individual
* regexps to be used to match results.
*
* @param {string} query
* @returns {array}
*/
Sifter.prototype.tokenize = function(query) {
query = trim(String(query || '').toLowerCase());
if (!query || !query.length) return [];
var i, n, regex, letter;
var tokens = [];
var words = query.split(/ +/);
for (i = 0, n = words.length; i < n; i++) {
regex = escape_regex(words[i]);
if (this.settings.diacritics) {
for (letter in DIACRITICS) {
if (DIACRITICS.hasOwnProperty(letter)) {
regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]);
}
}
}
tokens.push({
string : words[i],
regex : new RegExp(regex, 'i')
});
}
return tokens;
};
/**
* Iterates over arrays and hashes.
*
* ```
* this.iterator(this.items, function(item, id) {
* // invoked for each item
* });
* ```
*
* @param {array|object} object
*/
Sifter.prototype.iterator = function(object, callback) {
var iterator;
if (is_array(object)) {
iterator = Array.prototype.forEach || function(callback) {
for (var i = 0, n = this.length; i < n; i++) {
callback(this[i], i, this);
}
};
} else {
iterator = function(callback) {
for (var key in this) {
if (this.hasOwnProperty(key)) {
callback(this[key], key, this);
}
}
};
}
iterator.apply(object, [callback]);
};
/**
* Returns a function to be used to score individual results.
*
* Good matches will have a higher score than poor matches.
* If an item is not a match, 0 will be returned by the function.
*
* @param {object|string} search
* @param {object} options (optional)
* @returns {function}
*/
Sifter.prototype.getScoreFunction = function(search, options) {
var self, fields, tokens, token_count;
self = this;
search = self.prepareSearch(search, options);
tokens = search.tokens;
fields = search.options.fields;
token_count = tokens.length;
/**
* Calculates how close of a match the
* given value is against a search token.
*
* @param {mixed} value
* @param {object} token
* @return {number}
*/
var scoreValue = function(value, token) {
var score, pos;
if (!value) return 0;
value = String(value || '');
pos = value.search(token.regex);
if (pos === -1) return 0;
score = token.string.length / value.length;
if (pos === 0) score += 0.5;
return score;
};
/**
* Calculates the score of an object
* against the search query.
*
* @param {object} token
* @param {object} data
* @return {number}
*/
var scoreObject = (function() {
var field_count = fields.length;
if (!field_count) {
return function() { return 0; };
}
if (field_count === 1) {
return function(token, data) {
return scoreValue(data[fields[0]], token);
};
}
return function(token, data) {
for (var i = 0, sum = 0; i < field_count; i++) {
sum += scoreValue(data[fields[i]], token);
}
return sum / field_count;
};
})();
if (!token_count) {
return function() { return 0; };
}
if (token_count === 1) {
return function(data) {
return scoreObject(tokens[0], data);
};
}
if (search.options.conjunction === 'and') {
return function(data) {
var score;
for (var i = 0, sum = 0; i < token_count; i++) {
score = scoreObject(tokens[i], data);
if (score <= 0) return 0;
sum += score;
}
return sum / token_count;
};
} else {
return function(data) {
for (var i = 0, sum = 0; i < token_count; i++) {
sum += scoreObject(tokens[i], data);
}
return sum / token_count;
};
}
};
/**
* Returns a function that can be used to compare two
* results, for sorting purposes. If no sorting should
* be performed, `null` will be returned.
*
* @param {string|object} search
* @param {object} options
* @return function(a,b)
*/
Sifter.prototype.getSortFunction = function(search, options) {
var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort;
self = this;
search = self.prepareSearch(search, options);
sort = (!search.query && options.sort_empty) || options.sort;
/**
* Fetches the specified sort field value
* from a search result item.
*
* @param {string} name
* @param {object} result
* @return {mixed}
*/
get_field = function(name, result) {
if (name === '$score') return result.score;
return self.items[result.id][name];
};
// parse options
fields = [];
if (sort) {
for (i = 0, n = sort.length; i < n; i++) {
if (search.query || sort[i].field !== '$score') {
fields.push(sort[i]);
}
}
}
// the "$score" field is implied to be the primary
// sort field, unless it's manually specified
if (search.query) {
implicit_score = true;
for (i = 0, n = fields.length; i < n; i++) {
if (fields[i].field === '$score') {
implicit_score = false;
break;
}
}
if (implicit_score) {
fields.unshift({field: '$score', direction: 'desc'});
}
} else {
for (i = 0, n = fields.length; i < n; i++) {
if (fields[i].field === '$score') {
fields.splice(i, 1);
break;
}
}
}
multipliers = [];
for (i = 0, n = fields.length; i < n; i++) {
multipliers.push(fields[i].direction === 'desc' ? -1 : 1);
}
// build function
fields_count = fields.length;
if (!fields_count) {
return null;
} else if (fields_count === 1) {
field = fields[0].field;
multiplier = multipliers[0];
return function(a, b) {
return multiplier * cmp(
get_field(field, a),
get_field(field, b)
);
};
} else {
return function(a, b) {
var i, result, a_value, b_value, field;
for (i = 0; i < fields_count; i++) {
field = fields[i].field;
result = multipliers[i] * cmp(
get_field(field, a),
get_field(field, b)
);
if (result) return result;
}
return 0;
};
}
};
/**
* Parses a search query and returns an object
* with tokens and fields ready to be populated
* with results.
*
* @param {string} query
* @param {object} options
* @returns {object}
*/
Sifter.prototype.prepareSearch = function(query, options) {
if (typeof query === 'object') return query;
options = extend({}, options);
var option_fields = options.fields;
var option_sort = options.sort;
var option_sort_empty = options.sort_empty;
if (option_fields && !is_array(option_fields)) options.fields = [option_fields];
if (option_sort && !is_array(option_sort)) options.sort = [option_sort];
if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty];
return {
options : options,
query : String(query || '').toLowerCase(),
tokens : this.tokenize(query),
total : 0,
items : []
};
};
/**
* Searches through all items and returns a sorted array of matches.
*
* The `options` parameter can contain:
*
* - fields {string|array}
* - sort {array}
* - score {function}
* - filter {bool}
* - limit {integer}
*
* Returns an object containing:
*
* - options {object}
* - query {string}
* - tokens {array}
* - total {int}
* - items {array}
*
* @param {string} query
* @param {object} options
* @returns {object}
*/
Sifter.prototype.search = function(query, options) {
var self = this, value, score, search, calculateScore;
var fn_sort;
var fn_score;
search = this.prepareSearch(query, options);
options = search.options;
query = search.query;
// generate result scoring function
fn_score = options.score || self.getScoreFunction(search);
// perform search and sort
if (query.length) {
self.iterator(self.items, function(item, id) {
score = fn_score(item);
if (options.filter === false || score > 0) {
search.items.push({'score': score, 'id': id});
}
});
} else {
self.iterator(self.items, function(item, id) {
search.items.push({'score': 1, 'id': id});
});
}
fn_sort = self.getSortFunction(search, options);
if (fn_sort) search.items.sort(fn_sort);
// apply limits
search.total = search.items.length;
if (typeof options.limit === 'number') {
search.items = search.items.slice(0, options.limit);
}
return search;
};
// utilities
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
var cmp = function(a, b) {
if (typeof a === 'number' && typeof b === 'number') {
return a > b ? 1 : (a < b ? -1 : 0);
}
a = asciifold(String(a || ''));
b = asciifold(String(b || ''));
if (a > b) return 1;
if (b > a) return -1;
return 0;
};
var extend = function(a, b) {
var i, n, k, object;
for (i = 1, n = arguments.length; i < n; i++) {
object = arguments[i];
if (!object) continue;
for (k in object) {
if (object.hasOwnProperty(k)) {
a[k] = object[k];
}
}
}
return a;
};
var trim = function(str) {
return (str + '').replace(/^\s+|\s+$|/g, '');
};
var escape_regex = function(str) {
return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
};
var is_array = Array.isArray || (typeof $ !== 'undefined' && $.isArray) || function(object) {
return Object.prototype.toString.call(object) === '[object Array]';
};
var DIACRITICS = {
'a': '[aÀÁÂÃÄÅàáâãäåĀāąĄ]',
'c': '[cÇçćĆčČ]',
'd': '[dđĐďĎð]',
'e': '[eÈÉÊËèéêëěĚĒēęĘ]',
'i': '[iÌÍÎÏìíîïĪī]',
'l': '[lłŁ]',
'n': '[nÑñňŇńŃ]',
'o': '[oÒÓÔÕÕÖØòóôõöøŌō]',
'r': '[rřŘ]',
's': '[sŠšśŚ]',
't': '[tťŤ]',
'u': '[uÙÚÛÜùúûüůŮŪū]',
'y': '[yŸÿýÝ]',
'z': '[zŽžżŻźŹ]'
};
var asciifold = (function() {
var i, n, k, chunk;
var foreignletters = '';
var lookup = {};
for (k in DIACRITICS) {
if (DIACRITICS.hasOwnProperty(k)) {
chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1);
foreignletters += chunk;
for (i = 0, n = chunk.length; i < n; i++) {
lookup[chunk.charAt(i)] = k;
}
}
}
var regexp = new RegExp('[' + foreignletters + ']', 'g');
return function(str) {
return str.replace(regexp, function(foreignletter) {
return lookup[foreignletter];
}).toLowerCase();
};
})();
// export
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
return Sifter;
}));
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment