/* patternLock.js v 0.5.2 Author: Sudhanshu Yadav Copyright (c) 2015 Sudhanshu Yadav - ignitersworld.com , released under the MIT license. Demo on: ignitersworld.com/lab/patternLock.html */ ;(function($, window, document, undefined) { "use strict"; var nullFunc = function() {}, objectHolder = {}; //internal functions function readyDom(iObj) { var holder = iObj.holder, option = iObj.option, matrix = option.matrix, margin = option.margin, radius = option.radius, html = [''); holder.html(html.join('')).css({ 'width': (matrix[1] * (radius * 2 + margin * 2) + margin * 2) + 'px', 'height': (matrix[0] * (radius * 2 + margin * 2) + margin * 2) + 'px' }); //select pattern circle iObj.pattCircle = iObj.holder.find('.patt-circ'); } //return height and angle for lines function getLengthAngle(x1, x2, y1, y2) { var xDiff = x2 - x1, yDiff = y2 - y1; return { length: Math.ceil(Math.sqrt(xDiff * xDiff + yDiff * yDiff)), angle: Math.round((Math.atan2(yDiff, xDiff) * 180) / Math.PI) }; } var startHandler = function(e, obj) { e.preventDefault(); var iObj = objectHolder[obj.token]; if (iObj.disabled) return; //check if pattern is visible or not if (!iObj.option.patternVisible) { iObj.holder.addClass('patt-hidden'); } var touchMove = e.type == "touchstart" ? "touchmove" : "mousemove", touchEnd = e.type == "touchstart" ? "touchend" : "mouseup"; //assign events $(this).on(touchMove + '.pattern-move', function(e) { moveHandler.call(this, e, obj); }); $(document).one(touchEnd, function() { endHandler.call(this, e, obj); }); //set pattern offset var wrap = iObj.holder.find('.patt-wrap'), offset = wrap.offset(); iObj.wrapTop = offset.top; iObj.wrapLeft = offset.left; //reset pattern obj.reset(); }, moveHandler = function(e, obj) { e.preventDefault(); var x = e.pageX || e.originalEvent.touches[0].pageX, y = e.pageY || e.originalEvent.touches[0].pageY, iObj = objectHolder[obj.token], li = iObj.pattCircle, patternAry = iObj.patternAry, lineOnMove = iObj.option.lineOnMove, posObj = iObj.getIdxFromPoint(x, y), idx = posObj.idx, pattId = iObj.mapperFunc(idx) || idx; if (patternAry.length > 0) { var laMove = getLengthAngle(iObj.lineX1, posObj.x, iObj.lineY1, posObj.y); iObj.line.css({ 'width': (laMove.length + 10) + 'px', 'transform': 'rotate(' + laMove.angle + 'deg)' }); } if (idx) { if (patternAry.indexOf(pattId) == -1) { var elm = $(li[idx - 1]), direction; //direction of pattern //check and mark if any points are in middle of previous point and current point, if it does check them if (iObj.lastPosObj) { var lastPosObj = iObj.lastPosObj, ip = lastPosObj.i, jp = lastPosObj.j, iDiff = Math.abs(posObj.i - ip), jDiff = Math.abs(posObj.j - jp); while (((iDiff == 0 && jDiff > 1) || (jDiff == 0 && iDiff > 1) || (jDiff == iDiff && jDiff > 1)) && !(jp == posObj.j && ip == posObj.i)) { ip = iDiff ? Math.min(posObj.i, ip) + 1 : ip; jp = jDiff ? Math.min(posObj.j, jp) + 1 : jp; iDiff = Math.abs(posObj.i - ip); jDiff = Math.abs(posObj.j - jp); var nextIdx = (jp - 1) * iObj.option.matrix[1] + ip, nextPattId = iObj.mapperFunc(nextIdx) || nextIdx; if (patternAry.indexOf(nextPattId) == -1) { $(li[nextIdx - 1]).addClass('hovered'); //push pattern on array patternAry.push(nextPattId); } } direction = []; posObj.j - lastPosObj.j > 0 ? direction.push('s') : posObj.j - lastPosObj.j < 0 ? direction.push('n') : 0; posObj.i - lastPosObj.i > 0 ? direction.push('e') : posObj.i - lastPosObj.i < 0 ? direction.push('w') : 0; direction = direction.join('-'); } //add the current element on pattern elm.addClass('hovered'); //push pattern on array patternAry.push(pattId); //add start point for line var margin = iObj.option.margin, radius = iObj.option.radius, newX = (posObj.i - 1) * (2 * margin + 2 * radius) + 2 * margin + radius, newY = (posObj.j - 1) * (2 * margin + 2 * radius) + 2 * margin + radius; if (patternAry.length != 1) { //to fix line var lA = getLengthAngle(iObj.lineX1, newX, iObj.lineY1, newY); iObj.line.css({ 'width': (lA.length + 10) + 'px', 'transform': 'rotate(' + lA.angle + 'deg)' }); if (!lineOnMove) iObj.line.show(); } //add direction class on pattern circle and lines if (direction) { iObj.lastElm.addClass(direction + " dir"); iObj.line.addClass(direction + " dir"); } //to create new line var line = $('
'); iObj.line = line; iObj.lineX1 = newX; iObj.lineY1 = newY; //add on dom iObj.holder.append(line); if (!lineOnMove) iObj.line.hide(); iObj.lastElm = elm; } iObj.lastPosObj = posObj; } }, endHandler = function(e, obj) { e.preventDefault(); var iObj = objectHolder[obj.token], pattern = iObj.patternAry.join(iObj.option.delimiter); //remove hidden pattern class and remove event iObj.holder.off('.pattern-move').removeClass('patt-hidden'); if (!pattern) return; iObj.option.onDraw(pattern); //to remove last line iObj.line.remove(); if (iObj.rightPattern) { if (pattern == iObj.rightPattern) { iObj.onSuccess(); } else { iObj.onError(); obj.error(); } } }; function InternalMethods() {} InternalMethods.prototype = { constructor: InternalMethods, getIdxFromPoint: function(x, y) { var option = this.option, matrix = option.matrix, xi = x - this.wrapLeft, yi = y - this.wrapTop, idx = null, margin = option.margin, plotLn = option.radius * 2 + margin * 2, qsntX = Math.ceil(xi / plotLn), qsntY = Math.ceil(yi / plotLn), remX = xi % plotLn, remY = yi % plotLn; if (qsntX <= matrix[1] && qsntY <= matrix[0] && remX > margin * 2 && remY > margin * 2) { idx = (qsntY - 1) * matrix[1] + qsntX; } return { idx: idx, i: qsntX, j: qsntY, x: xi, y: yi }; } }; function PatternLock(selector, option) { var self = this, token = self.token = Math.random(), iObj = objectHolder[token] = new InternalMethods(), holder = iObj.holder = $(selector); //if holder is not present return if (holder.length == 0) return; iObj.object = self; option = iObj.option = $.extend({}, PatternLock.defaults, option); readyDom(iObj); //add class on holder holder.addClass('patt-holder'); //change offset property of holder if it does not have any property if (holder.css('position') == "static") holder.css('position', 'relative'); //assign event holder.on("touchstart mousedown", function(e) { startHandler.call(this, e, self); }); //handeling callback iObj.option.onDraw = option.onDraw || nullFunc; //adding a mapper function var mapper = option.mapper; if (typeof mapper == "object") { iObj.mapperFunc = function(idx) { return mapper[idx]; }; } else if (typeof mapper == "function") { iObj.mapperFunc = mapper; } else { iObj.mapperFunc = nullFunc; } //to delete from option object iObj.option.mapper = null; } PatternLock.prototype = { constructor: PatternLock, //method to set options after initializtion option: function(key, val) { var iObj = objectHolder[this.token], option = iObj.option; //for set methods if (val === undefined) { return option[key]; } //for setter else { option[key] = val; if (key == "margin" || key == "matrix" || key == "radius") { readyDom(iObj); } } }, //get drawn pattern as string getPattern: function() { var iObj = objectHolder[this.token]; return (iObj.patternAry || []).join(iObj.option.delimiter); }, //method to draw a pattern dynamically setPattern: function(pattern) { var iObj = objectHolder[this.token], option = iObj.option, matrix = option.matrix, margin = option.margin, radius = option.radius; //allow to set password manually only when enable set pattern option is true if (!option.enableSetPattern) return; this.reset(); iObj.wrapLeft = 0; iObj.wrapTop = 0; for (var i = 0; i < pattern.length; i++) { var idx = pattern[i] - 1, x = idx % matrix[1], y = Math.floor(idx / matrix[1]), pageX = x * (2 * margin + 2 * radius) + 2 * margin + radius, pageY = y * (2 * margin + 2 * radius) + 2 * margin + radius; moveHandler.call(null, { pageX: pageX, pageY: pageY, preventDefault: nullFunc, originalEvent: { touches: [{ pageX: pageX, pageY: pageY }] } }, this); } }, //to temprory enable disable plugin enable: function() { var iObj = objectHolder[this.token]; iObj.disabled = false; }, disable: function() { var iObj = objectHolder[this.token]; iObj.disabled = true; }, //reset pattern lock reset: function() { var iObj = objectHolder[this.token]; //to remove lines iObj.pattCircle.removeClass('hovered dir s n w e s-w s-e n-w n-e'); iObj.holder.find('.patt-lines').remove(); //add/reset a array which capture pattern iObj.patternAry = []; //remove last Obj iObj.lastPosObj = null; //remove error class if added iObj.holder.removeClass('patt-error'); }, //to display error if pattern is not drawn correct error: function() { objectHolder[this.token].holder.addClass('patt-error'); }, //to check the drawn pattern against given pattern checkForPattern: function(pattern, success, error) { var iObj = objectHolder[this.token]; iObj.rightPattern = pattern; iObj.onSuccess = success || nullFunc; iObj.onError = error || nullFunc; } }; PatternLock.defaults = { matrix: [3, 3], margin: 20, radius: 25, patternVisible: true, lineOnMove: true, delimiter: "", // a delimeter between the pattern enableSetPattern: false }; window.PatternLock = PatternLock; }(jQuery, window, document));