282 lines
8.5 KiB
JavaScript
282 lines
8.5 KiB
JavaScript
/*! jQuery UI Virtual Keyboard Scramble Extension v1.8.0 *//*
|
|
* for Keyboard v1.18+ (updated 2019-05-02)
|
|
*
|
|
* By Rob Garrison (aka Mottie)
|
|
* Licensed under the MIT License
|
|
*
|
|
* Use this extension with the Virtual Keyboard to scramble the
|
|
* specified keyboard keys
|
|
*
|
|
* Requires:
|
|
* jQuery v1.4.4+
|
|
* Keyboard v1.17.14+ - https://github.com/Mottie/Keyboard
|
|
*
|
|
* Setup:
|
|
* $('.ui-keyboard-input')
|
|
* .keyboard(options)
|
|
* .addScramble();
|
|
*
|
|
* // or if targeting a specific keyboard
|
|
* $('#keyboard1')
|
|
* .keyboard(options) // keyboard plugin
|
|
* .addScramble(); // this keyboard extension
|
|
*
|
|
*/
|
|
/* jshint browser:true, jquery:true, unused:false */
|
|
/* global require:false, define:false, module:false */
|
|
;(function(factory) {
|
|
if (typeof define === 'function' && define.amd) {
|
|
define(['jquery'], factory);
|
|
} else if (typeof module === 'object' && typeof module.exports === 'object') {
|
|
module.exports = factory(require('jquery'));
|
|
} else {
|
|
factory(jQuery);
|
|
}
|
|
}(function($) {
|
|
'use strict';
|
|
$.keyboard = $.keyboard || {};
|
|
|
|
$.fn.addScramble = function(options) {
|
|
//Set the default values, use comma to separate the settings, example:
|
|
var defaults = {
|
|
// keys to randomize
|
|
targetKeys : /[a-z\d]/i,
|
|
// randomize by row, otherwise randomize all keys
|
|
byRow : true,
|
|
// if true, randomize one keyset & duplicate
|
|
byKeySet : false,
|
|
// if true, randomize only once on keyboard visible
|
|
randomizeOnce : true,
|
|
// if true, randomize after user input;
|
|
// only `targetKeys` cause a new randomization
|
|
randomizeInput : false,
|
|
// initialization callback function
|
|
init : null, // function(keyboard){}
|
|
// use the same scrambled keyboard for all targetted keyboards
|
|
// not fully implemented!
|
|
sameForAll : false
|
|
};
|
|
return this.each(function() {
|
|
// make sure a keyboard is attached
|
|
var o,
|
|
base = $(this).data('keyboard'),
|
|
namespace = base.namespace + 'Scramble',
|
|
opts = base.options;
|
|
|
|
if (!base || base.scramble_options) { return; }
|
|
o = base.scramble_options = $.extend({}, defaults, options);
|
|
base.extensionNamespace.push( namespace );
|
|
|
|
// save create callback
|
|
o.orig_create = opts.create;
|
|
|
|
base.scramble_setup = function($keyboard) {
|
|
var $sets, set, $keys, key, index, tmp,
|
|
rowIndex, keyboardmap, map, keyboard, row;
|
|
$sets = $keyboard.find('.' + $.keyboard.css.keySet);
|
|
if ($keyboard.length) {
|
|
if (o.byKeySet) {
|
|
$sets = $sets.eq(0);
|
|
}
|
|
for (set = 0; set < $sets.length; set++) {
|
|
/*jshint loopfunc:true */
|
|
$keys = $sets.eq(set);
|
|
rowIndex = 0;
|
|
row = [];
|
|
map = [];
|
|
keyboardmap = [];
|
|
keyboard = [];
|
|
$keys.children('button, span, br').each(function() {
|
|
if (this.tagName === 'BR') {
|
|
if (o.byRow) {
|
|
row.push(this);
|
|
map.push(false);
|
|
keyboard[rowIndex] = row;
|
|
keyboardmap[rowIndex] = map;
|
|
row = [];
|
|
map = [];
|
|
rowIndex++;
|
|
} else {
|
|
keyboard[rowIndex] = this;
|
|
keyboardmap[rowIndex] = false;
|
|
rowIndex++;
|
|
}
|
|
} else {
|
|
tmp = $(this).attr('data-value') || '';
|
|
tmp = tmp.length === 1 && o.targetKeys.test(tmp) ? tmp : false;
|
|
if (o.byRow) {
|
|
row.push(this);
|
|
map.push (tmp);
|
|
} else {
|
|
keyboard[rowIndex] = this;
|
|
keyboardmap[rowIndex] = tmp;
|
|
rowIndex++;
|
|
}
|
|
}
|
|
});
|
|
// remove original <br> elements
|
|
$keys.find('.' + $.keyboard.css.endRow).remove();
|
|
// re-map keys
|
|
if (!o.byRow) {
|
|
row = base.shuffle( keyboard, keyboardmap );
|
|
for (key = 0; key < row.length; key++) {
|
|
$keys.append(row[key]);
|
|
}
|
|
} else {
|
|
for (index = 0; index < keyboard.length; index++) {
|
|
row = base.shuffle( keyboard[index], keyboardmap[index] );
|
|
for (key = 0; key < row.length; key++) {
|
|
$keys.append(row[key]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (o.byKeySet) {
|
|
$keyboard = base.realign($keyboard);
|
|
}
|
|
return $keyboard;
|
|
}
|
|
};
|
|
|
|
// get a random uint from 0 ... max-1
|
|
base.getRandomUInt = function(max) {
|
|
|
|
var cryptoObj = window.crypto || window.msCrypto;
|
|
|
|
if (cryptoObj !== undefined) {
|
|
var random_array = new Uint32Array(1);
|
|
cryptoObj.getRandomValues(random_array);
|
|
return random_array[0] % max;
|
|
}
|
|
// fallback
|
|
return Math.floor(Math.random() * max);
|
|
};
|
|
|
|
// modified from Fisher-Yates shuffle ( http://bost.ocks.org/mike/shuffle/ )
|
|
// to allow not shuffling specifically mapped array elements
|
|
base.shuffle = function(array, map) {
|
|
var swap, random,
|
|
index = array.length;
|
|
// While there remain elements to shuffle...
|
|
while (index > 0) {
|
|
// Pick a remaining element...
|
|
random = base.getRandomUInt(index);
|
|
if (map[index - 1] === false) {
|
|
index--;
|
|
}
|
|
// skip elements that are mapped to false
|
|
if (map[index - 1] !== false && map[random] !== false) {
|
|
// And swap it with the current element
|
|
index--;
|
|
swap = array[index];
|
|
array[index] = array[random];
|
|
array[random] = swap;
|
|
}
|
|
}
|
|
return array;
|
|
};
|
|
|
|
// make other keysets "line-up" with scrambled keyset
|
|
base.realign = function($keyboard) {
|
|
var selector, typ, pos,
|
|
$sets = $keyboard.find('.' + $.keyboard.css.keySet),
|
|
$orig = $sets.eq(0);
|
|
$sets = $sets.filter(':gt(0)');
|
|
$orig.children().each(function(i, cell) {
|
|
typ = cell.tagName === 'BR';
|
|
pos = $(cell).attr('data-pos');
|
|
/*jshint loopfunc:true */
|
|
$sets.each(function(j, k) {
|
|
selector = typ ? 'br:first' : 'button[data-pos="' + pos + '"]';
|
|
$(k).find(selector).appendTo( k );
|
|
});
|
|
});
|
|
return $keyboard;
|
|
};
|
|
|
|
base.setScrambleLayout = function() {
|
|
// scrambled layout already initialized
|
|
if (!/^scrambled/.test(opts.layout)) {
|
|
base.orig_layout = opts.layout;
|
|
var savedLayout = savedLayout || 'scrambled' + Math.round(Math.random() * 10000);
|
|
opts.layout = o.sameForAll ? savedLayout : 'scrambled' + Math.round(Math.random() * 10000);
|
|
}
|
|
};
|
|
|
|
// create scrambled keyboard layout
|
|
opts.create = function() {
|
|
var layout = opts.layout;
|
|
$.keyboard.builtLayouts[layout] = {
|
|
mappedKeys : {},
|
|
acceptedKeys : [],
|
|
$keyboard : null
|
|
};
|
|
base.layout = opts.layout = base.orig_layout;
|
|
// build original layout, if not already built, e.g. "qwerty"
|
|
base.buildKeyboard( base.layout, true );
|
|
base.layout = opts.layout = layout;
|
|
|
|
// clone, scramble then save layout
|
|
$.keyboard.builtLayouts[layout] = $.extend(true, {}, $.keyboard.builtLayouts[base.orig_layout]);
|
|
if ( o.randomizeOnce ) {
|
|
$.keyboard.builtLayouts[layout].$keyboard =
|
|
base.scramble_setup( $.keyboard.builtLayouts[base.orig_layout].$keyboard.clone() );
|
|
}
|
|
base.$keyboard = $.keyboard.builtLayouts[layout].$keyboard;
|
|
// randomize after every input - see #522
|
|
if ( o.randomizeInput ) {
|
|
base.$el
|
|
.unbind($.keyboard.events.kbChange + namespace)
|
|
.bind($.keyboard.events.kbChange + namespace, function(e, kb) {
|
|
if ( o.targetKeys.test( kb.last.key ) ) {
|
|
// prevent hover class flash on previous key after scramble
|
|
kb.$keyboard
|
|
.find('.' + opts.css.buttonHover)
|
|
.removeClass(opts.css.buttonHover);
|
|
kb.$keyboard = kb.scramble_setup(kb.$keyboard);
|
|
// now make sure the key under the mouse is highlighted
|
|
$(document.elementFromPoint(e.clientX, e.clientY)).trigger('mouseenter');
|
|
}
|
|
});
|
|
} else if ( !o.randomizeOnce ) {
|
|
base.$el
|
|
.unbind($.keyboard.events.kbBeforeVisible + namespace)
|
|
.bind($.keyboard.events.kbBeforeVisible + namespace, function(e, kb) {
|
|
kb.$keyboard = kb.scramble_setup(kb.$keyboard);
|
|
});
|
|
}
|
|
if ( typeof o.orig_create === 'function' ) {
|
|
o.orig_create( base );
|
|
}
|
|
};
|
|
|
|
base.setScrambleLayout();
|
|
|
|
// special case when keyboard is set to always be open
|
|
if (opts.alwaysOpen && base.$keyboard.length) {
|
|
setTimeout(function() {
|
|
var built = $.keyboard.builtLayouts;
|
|
base.$keyboard = base.scramble_setup(base.$keyboard);
|
|
base.setScrambleLayout();
|
|
if (typeof built[opts.layout] === 'undefined') {
|
|
built[opts.layout] = {
|
|
mappedKeys : $.extend({}, built[base.layout].mappedKeys),
|
|
acceptedKeys : $.extend([], built[base.layout].acceptedKeys),
|
|
$keyboard : base.$keyboard.clone()
|
|
};
|
|
}
|
|
if (typeof o.init === 'function') {
|
|
o.init(base);
|
|
}
|
|
}, 0);
|
|
} else {
|
|
if (typeof o.init === 'function') {
|
|
o.init(base);
|
|
}
|
|
}
|
|
|
|
});
|
|
};
|
|
|
|
}));
|