Skip to content

Instantly share code, notes, and snippets.

@SanthoshBabuMR
Last active October 24, 2017 18:15
Show Gist options
  • Save SanthoshBabuMR/6fc6ed4f77030ceca5b1e341db6ddc67 to your computer and use it in GitHub Desktop.
Save SanthoshBabuMR/6fc6ed4f77030ceca5b1e341db6ddc67 to your computer and use it in GitHub Desktop.
Table/list row selection using 'click' / 'ctrl-click' / 'shift-click'
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Table row click</title>
<style media="screen">
.ui-selectable-row-disable-text-selection {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none;
}
table {
table-layout: fixed;
border-collapse: collapse;
width: 100%;
border: 0;
}
tr {
background: #87cefa;
}
.ui-selectable-row-selected,
tr.row-selected {
background: indianred;
}
tr.highlight-row {
background: crimson;
}
td {
padding: 4px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<div class="list">
<li>one</li>
<li>two</li>
<li>three</li>
<li>four</li>
<li>five</li>
</div>
<div class="target">
<table >
<tr>
<td><span class="ui-selectable-row-toggle-target"><input type="checkbox"></span></td>
<td>One</td>
<td>
<a href="https://google.com" onclick="return false;">Google.com</a>
</td>
</tr>
<tr>
<td> <span class="ui-selectable-row-toggle-target"> <input type="radio" disabled> </span> </td>
<td>Two</td>
<td>
<select name="week" id="week">
<option value="">Select</option>
<option value="1">sat</option>
<option value="2">sun</option>
</select>
</td>
</tr>
<tr>
<td><span class="ui-selectable-row-toggle-target"><input type="checkbox"></span></td>
<td>Three</td>
<td>
<a href="https://google.com" onclick="return false;">Google.com</a>
</td>
</tr>
<tr>
<td> <span class="ui-selectable-row-toggle-target"> <input type="radio"> </span> </td>
<td>Four</td>
<td>
<textarea name="name" rows="1" cols="24">comments goes here!</textarea>
</td>
</tr>
<tr>
<td><span class="ui-selectable-row-toggle-target"><input type="checkbox"></span></td>
<td>Five</td>
<td class="ui-selectable-row-toggle-target">
<a href="https://google.com" onclick="return false;">Google.com</a>
</td>
</tr>
<tr>
<td> <span class="ui-selectable-row-toggle-target"> <input type="radio"> </span> </td>
<td>Six</td>
<td>
<select name="week" id="week">
<option value="">Select</option>
<option value="1">sat</option>
<option value="2">sun</option>
</select>
</td>
</tr>
<tr>
<td> <span class="ui-selectable-row-toggle-target"> <input type="radio"> </span> </td>
<td>Seven</td>
<td>
<input type="text" />
</td>
</tr>
<tr>
<td> <span class="ui-selectable-row-toggle-target"> <input type="radio"> </span> </td>
<td>Eight</td>
<td>
<button>hello</button>
</td>
</tr>
<tr>
<td> <span class="ui-selectable-row-toggle-target"> <input type="radio"> </span> </td>
<td>Nine</td>
<td>
<select name="week" id="week" class="ui-selectable-row-toggle-target">
<option value="">Select</option>
<option value="1">sat</option>
<option value="2">sun</option>
</select>
</td>
</tr>
</table>
</div>
<hr />
<div class="target2" data-ui-selectable-row-selected-class="pick-this-row">
<table >
<tr>
<td><span class="ui-selectable-row-toggle-target"><input type="checkbox"></span></td>
<td>One</td>
<td>
<a href="https://google.com" onclick="return false;">Google.com</a>
</td>
</tr>
<tr>
<td> <span class="ui-selectable-row-toggle-target"> <input type="radio" disabled> </span> </td>
<td>Two</td>
<td>
<select name="week" id="week">
<option value="">Select</option>
<option value="1">sat</option>
<option value="2">sun</option>
</select>
</td>
</tr>
<tr>
<td><span class="ui-selectable-row-toggle-target"><input type="checkbox"></span></td>
<td>Three</td>
<td>
<a href="https://google.com" onclick="return false;">Google.com</a>
</td>
</tr>
<tr>
<td> <span class="ui-selectable-row-toggle-target"> <input type="radio"> </span> </td>
<td>Four</td>
<td>
<textarea name="name" rows="1" cols="24">comments goes here!</textarea>
</td>
</tr>
<tr>
<td><span class="ui-selectable-row-toggle-target"><input type="checkbox"></span></td>
<td>Five</td>
<td class="ui-selectable-row-toggle-target">
<a href="https://google.com" onclick="return false;">Google.com</a>
</td>
</tr>
<tr>
<td> <span class="ui-selectable-row-toggle-target"> <input type="radio"> </span> </td>
<td>Six</td>
<td>
<select name="week" id="week">
<option value="">Select</option>
<option value="1">sat</option>
<option value="2">sun</option>
</select>
</td>
</tr>
<tr>
<td> <span class="ui-selectable-row-toggle-target"> <input type="radio"> </span> </td>
<td>Seven</td>
<td>
<input type="text" />
</td>
</tr>
<tr>
<td> <span class="ui-selectable-row-toggle-target"> <input type="radio"> </span> </td>
<td>Eight</td>
<td>
<button>hello</button>
</td>
</tr>
<tr>
<td> <span class="ui-selectable-row-toggle-target"> <input type="radio"> </span> </td>
<td>Nine</td>
<td>
<select name="week" id="week" class="ui-selectable-row-toggle-target">
<option value="">Select</option>
<option value="1">sat</option>
<option value="2">sun</option>
</select>
</td>
</tr>
</table>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script type="text/javascript">
var gCount = 0;
(function ($) {
if(!$) {
return console.log('jQuery library not available!');
}
var defaults = {
rowIdentifier: 'tbody tr',
selectRowIfTargetIs: [],
selectRowIfTargetIsNot: [],
eventNs: 'uiSelectableRow',
eventType: {
toggleSelection: 'toggle-selection',
enable: 'enable',
disable: 'disable',
destroy: 'destroy',
destroyed: 'destroyed',
shiftSelectable: 'shift-selectable',
ctrlSelectable: 'ctrl-selectable'
},
dataAttr: {
rowIdentifier: 'data-row-identifier',
selectRowIfTargetIs: 'data-select-row-if-target-is',
selectRowIfTargetIsNot: 'data-select-row-if-target-is-not',
disabled: 'data-disabled',
shiftSelectable: 'data-shift-selectable',
ctrlSelectable: 'data-ctrl-selectable',
selectedRowClass: 'data-selected-class'
},
isDisabled: false,
isShiftSelectable: true,
isCtrlSelectable: true,
selectedRowClass: 'ui-selectable-row-selected',
containerHoverClass: 'ui-selectable-row-hover',
disableTextSelectionClass : 'ui-selectable-row-disable-text-selection',
modifierKey: {
shift: 'shift',
ctrl: 'ctrl'
},
directions: {
top: 'TOP',
bottom: 'BOTTOM'
}
};
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}
function dataAttributeBasedConfig (config, containerEl) {
var attr = config.dataAttr;
return {
rowIdentifier: containerEl.attr(attr.rowIdentifier),
selectRowIfTargetIs: containerEl.attr(attr.selectRowIfTargetIs),
selectRowIfTargetIsNot: containerEl.attr(attr.selectRowIfTargetIsNot),
isDisabled: containerEl.attr(attr.disabled),
isShiftSelectable: containerEl.attr(attr.shiftSelectable),
isCtrlSelectable: containerEl.attr(attr.ctrlSelectable),
selectedRowClass: containerEl.attr(attr.selectedRowClass)
};
}
function formatConfig (config) {
var configuration = {};
configuration.eventNs = config.eventNs + '-' + guid();
if (!(config.selectRowIfTargetIs instanceof Array)) {
configuration.selectRowIfTargetIs = [];
console.log('\'selectRowIfTargetIs\' should be an array')
}
if (!(config.selectRowIfTargetIsNot instanceof Array)) {
configuration.selectRowIfTargetIsNot = [];
console.log('\'selectRowIfTargetIsNot\' should be an array')
}
return configuration;
}
function configure (option, containerEl) {
var mashedConfig = $.extend( {}, defaults, option);
var mashedConfig = $.extend( mashedConfig, dataAttributeBasedConfig(mashedConfig, containerEl))
var mashedConfig = $.extend( mashedConfig, formatConfig(mashedConfig));
return mashedConfig;
}
function updateState (argsJson, newState) {
$.extend(argsJson.state, newState);
}
function updateUi (argsJson, newUi) {
var config = argsJson.config;
if (newUi === undefined) {
return;
}
if (newUi.rowsToSelect && newUi.rowsToSelect.length) {
newUi.rowsToSelect.addClass(config.selectedRowClass);
}
if (newUi.rowsToDeselect && newUi.rowsToDeselect.length) {
newUi.rowsToDeselect.removeClass(config.selectedRowClass);
}
}
function update (argsJson, newState, newUi) {
var containerEl = argsJson.containerEl;
var config = argsJson.config;
updateState(argsJson, newState);
updateUi(argsJson, newUi);
containerEl.trigger(config.eventType.toggleSelection,
[ $(newUi.rowsToSelect), $(newUi.rowsToDeselect), getSelectedRows(argsJson) ]);
}
function computeState (argsJson, currentRow, modifierKey) {
var config = argsJson.config;
var state = argsJson.state;
var newState = {};
var rIndex = $(state.recentRowElected).index();
var cIndex = currentRow.index();
newState.recentRowElected = currentRow.get(0);
newState.pastRowElected = state.pastRowElected ? state.recentRowElected : currentRow.get(0);
if (state.recentRowElected) {
newState.recentDirection = rIndex > cIndex ? config.directions.top : config.directions.bottom;
newState.pastDirection = state.recentDirection ? state.recentDirection : newState.recentDirection;
}
newState.recentModifierKey = modifierKey;
return newState;
}
function filterSelection (argsJson, rows) {
var config = argsJson.config;
if (typeof config.filterSelection === 'function') {
if (rows && rows.length) {
return rows.filter(function (index, row) {
return config.filterSelection(index, row);
});
}
}
return rows;
}
function getSelectedRows (argsJson) {
var config = argsJson.config;
var containerEl = argsJson.containerEl;
return containerEl.find('.' + config.selectedRowClass);
}
function isRowSelected (argsJson, row) {
var config = argsJson.config;
if (row && row.length === 1) {
return row.is('.'+ config.selectedRowClass);
}
}
function createRowGroup (argsJson, r1, r2) {
var containerEl = argsJson.containerEl;
if (r1.index() > r2.index()) {
return r2.add(containerEl.find(r2).nextUntil(r1)).add(r1);
} else {
return r1.add(containerEl.find(r1).nextUntil(r2)).add(r2);
}
}
function splitRowGroupByPastRowElected (argsJson, currentRow, rowGroup) {
var state = argsJson.state;
var rIndex = $(state.recentRowElected).index();
var cIndex = currentRow.index();
var firstRow = rowGroup[0];
var lastRow = rowGroup[rowGroup.length-1];
var r1;
var r2;
if (rIndex > cIndex) {
r1 = $().add(firstRow).add($(firstRow).nextUntil(state.pastRowElected))
r2 = $().add(lastRow).add($(lastRow).prevUntil(state.pastRowElected))
} else {
r1 = $().add(lastRow).add($(lastRow).prevUntil(state.pastRowElected))
r2 = $().add(firstRow).add($(firstRow).nextUntil(state.pastRowElected))
}
return {
r1: r1,
r2: r2
}
}
function getCurrentSelectionDirection (argsJson, currentRow) {
var state = argsJson.state;
var config = argsJson.config;
var rIndex = $(state.recentRowElected).index();
var cIndex = currentRow.index();
if (state.recentRowElected) {
return rIndex > cIndex ? config.directions.top : config.directions.bottom;
}
}
function findIfSelectionDirectionInverted (argsJson, currentRow) {
var state = argsJson.state;
var currentSelectionDirection = getCurrentSelectionDirection(argsJson, currentRow);
if (typeof currentSelectionDirection === 'string' && typeof state.recentDirection === 'string') {
return currentSelectionDirection !== state.recentDirection
}
return false;
}
function isCurrentRowBetweenRecentAndPastElectedRow (argsJson, currentRow) {
var state = argsJson.state;
var rIndex = $(state.recentRowElected).index();
var pIndex = $(state.pastRowElected).index();
var cIndex = currentRow.index();
return Math.min(rIndex, pIndex) > cIndex > Math.max(rIndex, pIndex);
}
function doesCurrentRowsHaveAGroupedSelection (argsJson) {
var config = argsJson.config;
var state = argsJson.state;
var r = $(state.recentRowElected).index();
var p = $(state.pastRowElected).index()
var selectedRowsLength;
var isInSequence = true;
if ( r - p < 2) {
isInSequence = true;
}
if ( r > p ) {
selectedRowsLength = $().add(
$(state.pastRowElected).nextUntil(state.recentRowElected).filter('.' + config.selectedRowClass)
).length;
isInSequence = ( selectedRowsLength + 1 ) === (r - p)
} else {
selectedRowsLength = $().add(
$(state.recentRowElected).nextUntil(state.pastRowElected).filter('.' + config.selectedRowClass)
).length;
isInSequence = ( selectedRowsLength + 1 ) === (p - r)
}
return isInSequence;
}
function isTargetClickable (e, argsJson) {
var config = argsJson.config;
var clickedAllowed = true;
if (config.selectRowIfTargetIs.length > 0) {
clickedAllowed = $(e.target).is(config.selectRowIfTargetIs.join(" ,"));
} else if (config.selectRowIfTargetIsNot.length > 0) {
clickedAllowed = !$(e.target).is(config.selectRowIfTargetIsNot.join(" ,"));
}
return clickedAllowed;
}
function manageClick (e, argsJson, currentRow ) {
var config = argsJson.config;
var state = argsJson.state;
var containerEl = argsJson.containerEl;
var textSelected = typeof window.getSelection === 'function' ? !!(window.getSelection().toString()) : false;
if (config.isDisabled || textSelected || !isTargetClickable(e, argsJson)) {
return;
}
var isMultiSelectable = config.isShiftSelectable || config.isCtrlSelectable;
var modifierKey = e.shiftKey ? config.modifierKey.shift : (e.metaKey || e.ctrlKey ? config.modifierKey.ctrl : null);
currentRow = filterSelection(argsJson, currentRow);
if (!(currentRow && currentRow.length)) {
e.preventDefault();
return;
}
if (isMultiSelectable) {
if (modifierKey === config.modifierKey.shift) {
return shiftClick(argsJson, currentRow);
} else if (modifierKey === config.modifierKey.ctrl) {
return ctrlClick(argsJson, currentRow);
} else {
click(argsJson, currentRow);
}
} else {
click(argsJson, currentRow);
}
}
function click (argsJson, currentRow) {
var config = argsJson.config;
var state = argsJson.state;
var containerEl = argsJson.containerEl;
var newState;
var newUi = {};
var isMultipleRowsSelected = getSelectedRows(argsJson).length > 1;
if (!isMultipleRowsSelected && isRowSelected(currentRow)) {
newUi.rowsToDeselect = currentRow;
} else {
newUi.rowsToSelect = currentRow;
newUi.rowsToDeselect = getSelectedRows(argsJson).not(currentRow);
}
newState = computeState(argsJson, currentRow);
update(argsJson, newState, newUi);
}
function ctrlClick (argsJson, currentRow) {
var config = argsJson.config;
var state = argsJson.state;
var containerEl = argsJson.containerEl;
var newState;
var newUi = {};
if (currentRow.hasClass(config.selectedRowClass)) {
newUi.rowsToDeselect = currentRow;
} else {
newUi.rowsToSelect = currentRow;
}
newState = computeState(argsJson, currentRow, config.modifierKey.ctrl);
update(argsJson, newState, newUi);
}
function shiftClick (argsJson, currentRow) {
var config = argsJson.config;
var state = argsJson.state;
var containerEl = argsJson.containerEl;
var newState;
var newUi = {
rowsToSelect: $(),
rowsToDeselect: $()
};
var isSelectionDirectionInverted = findIfSelectionDirectionInverted(argsJson, currentRow);
var rowGroup;
var splitRowSet = {};
var isAllRowsInRowGroupSelected;
if (currentRow.get(0) === state.recentRowElected) {
newState = computeState(argsJson, currentRow, config.modifierKey.shift);
if (isRowSelected(argsJson, currentRow)) {
newUi.rowsToDeselect = newUi.rowsToDeselect.add(currentRow)
} else {
newUi.rowsToSelect = newUi.rowsToSelect.add(currentRow)
}
update(argsJson, newState, newUi);
return;
}
rowGroup = filterSelection(argsJson, createRowGroup (argsJson, $(state.recentRowElected), currentRow));
if (!(rowGroup && rowGroup.length)) {
return;
}
isAllRowsInRowGroupSelected = rowGroup.not('.' + config.selectedRowClass).length === 0;
if (state.recentModifierKey === config.modifierKey.shift && isSelectionDirectionInverted && !isAllRowsInRowGroupSelected) {
if (!doesCurrentRowsHaveAGroupedSelection(argsJson, rowGroup)) {
newUi.rowsToSelect = rowGroup.filter(':not(".' + config.selectedRowClass + '")');
} else {
splitRowSet = splitRowGroupByPastRowElected(argsJson, currentRow, rowGroup);
newUi.rowsToSelect = splitRowSet.r1.filter(':not(".' + config.selectedRowClass + '")');
newUi.rowsToDeselect = splitRowSet.r2.filter('.' + config.selectedRowClass)
}
} else if (isAllRowsInRowGroupSelected && !isCurrentRowBetweenRecentAndPastElectedRow(argsJson, currentRow)) {
newUi.rowsToDeselect = rowGroup.not(currentRow);
} else {
newUi.rowsToSelect = rowGroup.filter(':not(".' + config.selectedRowClass + '")');
}
newState = computeState(argsJson, currentRow, config.modifierKey.shift);
update(argsJson, newState, newUi);
}
function handleEvents (argsJson) {
var config = argsJson.config;
var state = argsJson.state;
var containerEl = argsJson.containerEl;
$(document).on('keydown.' + config.eventNs, function(e) {
if (containerEl.hasClass(config.containerHoverClass) && e.shiftKey || e.ctrlKey || e.metaKey) {
containerEl.addClass(config.disableTextSelectionClass);
}
});
$(document).on('keyup.' + config.eventNs, function(e) {
if (containerEl.hasClass(config.containerHoverClass)) {
containerEl.removeClass(config.disableTextSelectionClass);
}
});
containerEl.on('mouseenter.' + config.eventNs, function (e) {
containerEl.addClass(config.containerHoverClass);
}).on('mouseleave.' + config.eventNs, function (e) {
containerEl.removeClass(config.containerHoverClass);
});
containerEl.on('selectstart.' + config.eventNs, function (e) {
if (containerEl.hasClass(config.disableTextSelectionClass)) {
e.preventDefault();
}
});
containerEl.on('click.' + config.eventNs, config.rowIdentifier, function (e) {
manageClick(e, argsJson, $(this));
});
containerEl.on(config.eventType.shiftSelectable + '.' + config.eventNs, function (e, shiftSelectable) {
if (typeof shiftSelectable === 'boolean') {
state.isShiftSelectable = shiftSelectable
}
});
containerEl.on(config.eventType.disable + '.' + config.eventNs, function (e, disabled) {
config.isDisabled = typeof disabled === 'boolean' ? disabled : true;
});
containerEl.on(config.eventType.enable + '.' + config.eventNs, function (e, enabled) {
config.isDisabled = typeof enabled === 'boolean' ? !enabled : false;
});
containerEl.on(config.eventType.destroy + '.' + config.eventNs, function (e) {
containerEl.find(config.rowIdentifier).removeClass(config.selectedRowClass);
containerEl.off('.'+config.eventNs);
$(document).off('.'+config.eventNs);
containerEl.attr('data-ui-selectable-row', false);
containerEl.trigger(config.eventType.destroyed);
});
}
function init (config, state, containerEl) {
if (containerEl.attr('data-ui-selectable-row') !== 'true') {
handleEvents({
config: config,
state: state,
containerEl: containerEl
});
containerEl.attr('data-ui-selectable-row', true);
}
}
$.fn.uiSelectableRow = function (option) {
'use strict';
var containerEl = $(this);
var config;
var state = {};
if (containerEl.length) {
config = configure(option, containerEl);
state = {
pastRowElected: undefined,
recentRowElected: undefined,
pastDirection: undefined,
recentDirection: undefined,
modifierKey: undefined
};
init (config, state, containerEl);
return containerEl;
}
}
})(jQuery);
$('.target-not-exist').uiSelectableRow({
rowIdentifier: 'td',
disabled: false,
selectedRowClass: 'highlight-row',
lastSelectedRow: 'last-one',
filterSelection: function (index, row) {
if ($(row).find('[type="radio"]').length) {
return row;
}
}
})
$('.target-not-exist').on('event-ui-selectable-row-selection', function(e, selectedRows, DeselectedRows, allSelectedRows) {
console.log('selectedRows', selectedRows && selectedRows.length);
console.log('DeselectedRows', DeselectedRows && DeselectedRows.length);
console.log('allSelectedRows', allSelectedRows && allSelectedRows.length);
console.log('');
DeselectedRows && DeselectedRows.find('td:first-child input:enabled')
.prop('checked', false);
selectedRows && selectedRows.find('td:first-child input:enabled')
.prop('checked', true);
});
$('.target').trigger('event-ui-selectable-row-disable', false);
// $('.target').trigger('event-ui-selectable-row-destroy');
$('.list').uiSelectableRow({
rowIdentifier: 'li'
})
$('.target2').uiSelectableRow({
rowIdentifier: 'tr',
selectRowIfTargetIsNot: "",
selectRowIfTargetIs: ['td'],
disabled: true,
// rowIdentifier: 'tbody td',
// selectRowIfTargetIsNot: ['select'],
selectedRowClass: 'the-choosen-one',
filterSelection: function (index, row) {
// if ($(row).index()%2 !== 0)
if(localStorage.select === "true")
return row;
// return false;
}
}).on('toggle-selection', function(e, selectedRows, DeselectedRows, allSelectedRows) {
// console.log('selectedRows', selectedRows && selectedRows.length);
// console.log('DeselectedRows', DeselectedRows && DeselectedRows.length);
// console.log('allSelectedRows', allSelectedRows && allSelectedRows.length);
// console.log('');
DeselectedRows && DeselectedRows.removeAttr('style');
selectedRows && selectedRows.attr('style', 'background: tomato');
}).on('destroyed', function(e) {
$(this).find('tbody tr').removeAttr('style');
});
$('.target2').trigger('event-ui-selectable-row-disable', false);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment