Thomas - 26/08/2014 11:13 (laatste wijziging 12/09/2014 17:53)
Moderator
EDIT: >hier< kun je de laatste versie in actie zien.
Nota bene: dit idee is verre van origineel. Ik heb hier al een soortgelijke uitwerking van in gebruik gezien, dus het lijkt mij zeker iets wat inzetbaar is.
Het idee
Okay, waar te beginnen. Ik heb een aantal resources, denk bijvoorbeeld aan een webpagina, een artikel, een geupload bestand. Op grond van rollen/rechten (of hoe je het ook wilt noemen) die ik toeken aan een gebruiker wil ik bepalen of zo iemand (een geauthoriseerde gebruiker) toegang heeft tot deze resource. De controle hierop zal een of andere boolse expressie (een predikaat) zijn. Het antwoord op de vraag "heeft gebruiker X toegang tot resource Y" is dus altijd ja (true) of nee (false) als antwoord. Nu is de vraag dus, hoe implementeer ik dit precies (op een transparante, eenvoudige manier)?
Ik heb al even zitten zoeken. Het Yii framework gebruikt bijvoorbeeld "business rules", een soort van snippets code die geëvalueerd worden om te bepalen of iemand toegang heeft, in combinatie met het definiëren en toekennen van rollen/rechten/whatever. Zoiets wil ik ook, maar het wordt al snel één grote brei in Yii volgens mij, vooral als je niet heel strak je naamgeving regelt.
Het liefst sla ik de controle (zo dicht mogelijk) bij de resource zelf op. Maar hoe doe ik dat? Een lap code in de database (bleh) die je vervolgens evalueert (bleeehhhh). Vrij belangrijk hierbij is het formaat: hoe ziet de controle (het predikaat) er uit en is dit een "nette" oplossing (geen eval() en dat soort voodoo).
Theorie
Aan de hand van een uitwerking die ik ergens ooit heb gezien (die ik nooit echt inhoudelijk heb bekeken of begrepen) moest ik terugdenken aan mijn studententijd, waarin ik ooit een soort van geserialiseerde notatie voor (wiskundige) expressies de revue heb zien passeren. Dit betrof de (Poolse) prefix notatie.
In het WIKI-artikel staan een aantal interessante/belangrijke concepten:
- de prefix notatie is een schrijfwijze voor logica, wiskunde en algebra
- het plaatst operatoren (+, -, ...) links van haar operanden (3, -2, ...)
- als de ariteit van de operatoren (de hoeveelheid operanden waarop een operator werkt) vastligt, dan resulteert dit in een syntax die geen haken of andere groepeersymbolen nodig heeft waarbij expressies nog steeds ondubbelzinnig geïnterpreteerd (geparsed) kunnen worden
Dit laatste is een interessant gegeven, de waarheidstabel van het predikaat A && (B || C) ziet er namelijk heel anders uit dan (A && B) || C terwijl het, afgezien van de haken, dezelfde expressie is.
Dus wat heb ik nu eigenlijk? De operatoren voor de te bouwen expressie voor het controleren van rechten zijn de logische AND, de logische OR en de negatie (meestal geschreven als ! of ¬). Deze negatie wil ik zowel kunnen laten werken op een operand (!A) of een verzameling hiervan (!(A && B)). (En ja deze laatste variant kun je weer omschrijven naar !A || !B (DeMorgan)).
Hierbij pin ik de ariteit van de operatoren vast op (maximaal) 2 (de negatie werkt maar op één operand (een verzameling van operanden is (vereenvoudigd) ook weer een operand)).
De WIKI geeft tevens een (abstracte) implementatie van de Poolse notatie met behulp van een stack:
Citaat:
Scan the given prefix expression from right to left
for each symbol
{
if operand then
push onto stack
if operator then
{
operand1=pop stack
operand2=pop stack
compute operand1 operator operand2
push result onto stack
}
}
return top of stack as result
De uitwerking (theoretisch)
Wat wil ik vervolgens kunnen doen? Ik wil mijn predikaat geserialiseerd opslaan bij mijn resource. Ik zal dus moeten kiezen voor een notatiewijze. In eerste instantie moet ik onderscheid kunnen maken tussen de verschillende onderdelen (tokens) in de expressie. Hiertoe kies ik een scheidingskarakter, bijvoorbeeld de komma (,). Daarnaast heb ik symbolen nodig voor mijn operatoren. Bijvoorbeeld ! voor de negatie, | voor de logische OR en & voor de logische AND. En tot slot zijn daar de operanden: de rechten waar ik op wil kunnen controleren, deze zullen in eerste instantie enkel vertegenwoordigd worden door getallen. Dit zijn indexen van de rechten id's. Maar dit kan veel diverser als je wilt, je zou bijvoorbeeld onderscheid kunnen maken tussen groepsrechten (geef een id een G prefix) en gebruikersrechten (geef een id een U prefix) - de mogelijkheden zijn onbeperkt, je kunt je eigen definities verzinnen.
Een voorbeeld: het predikaat !(A || B) && (C || (D && E)) zou je dan als volgt schrijven in mijn variant van de Poolse notatie:
&,!|,A,B,|,C,&,D,E
Het vereist wat oefening om deze omzetting te doen. De truc is om de expressie van rechts naar links om te zetten en hierbij kan het handig zijn om alle gegroepeerde (deel)expressies eerst naar rechts de verplaatsen. Dit mag, immers:
(A || B) && C
is equivalent aan
C && (A || B)
(oftewel && (en ook ||) is commutatief)
Dan zijn er eigenlijk twee bewerkingen die ik wil kunnen doen: 1. een controle die kijkt of de Poolse notatie syntactisch klopt, je wilt hier namelijk van uit kunnen gaan als je deze gaat valideren. Een goede plaats om deze controle uit te voeren is als je de controle wegschrijft bij de resource. Dit kun je verifiëren door:
- te kijken of de expressie uit enkel toegestane karakters bestaat
- vanwege de ariteit is de volgende regel "invariant": het aantal operatoren (met uitzondering van de negatie) moet gelijk zijn aan het aantal operanden + 1
Hieruit volgt een klein dilemma in combinatie met de stack-implementatie hierboven: wat nu als je maar één recht hebt waar je op controleert? De "stack" zou tijdens verwerking te allen tijde minimaal twee waarden moeten bevatten, maar je hebt dan maar één operand.
Praktische oplossing: stel je hebt het predikaat A. Dit is hetzelfde als A && True (en ook A && True && True et cetera). Oftewel: plak aan je expressie "&& True" vast. Hiermee verander je de werking niet, maar zorg je wel dat dit altijd werkt, ook met slechts één operand (recht) waar je op controleert.
en 2, waar het eigenlijk allemaal om begonnen was: een (enkele, eenvoudige) controle die de expressie toepast op de gebruikersrechten die iemand heeft, en op grond hiervan besluit of iemand toegang heeft tot de resource, of niet.
Een uitwerking (praktijk)
Hieronder de twee eerder genoemde bewerkingen (mogelijk kunnen deze nog gerefactord worden) en een hulpfunctie:
<?php
// helper
function validateNumber($in) {
return preg_match('#^[1-9][0-9]*$#', $in);
}
function validateSerializedString($in) {
// empty string is always correct
if ($in == '') {
return true;
}
$operators = 0; // the added & operator will be counted automatically below
$operands = 1; // the added "true" on the (imaginary) stack
// we add "&& true" to ensure at least two operands ($in could consist of exactly one right)
$in = explode(',', '&,'.$in); // mind the prefixed "&,"
foreach ($in as $token) {
$current = $token;
if ($token{0} == '!') {
$current = substr($token, 1);
}
if ($current == '|' || $current == '&') {
$operators++;
} elseif (validateNumber($current)) {
$operands++;
} else {
// illegal character (sequence)
return false;
}
}
return ($operators + 1 == $operands);
}
function isAllowed($in, $rights=array()) {
if ($in == '') {
return true;
}
// we add "&& true" to ensure at least two operands ($in could consist of exactly one right)
$stack = array(true);
$in = array_reverse(explode(',', '&,'.$in)); // read from back to front, mind the prefixed "&,"
foreach ($in as $token) {
$negate = false;
$current = $token;
if ($token{0} == '!') {
$negate = true;
$current = substr($token, 1);
}
if ($current == '|' || $current == '&') {
// $stack should have at least two items - validation should guarantee this
// note that $val1 and $val2 already are boolean values (true or false)
$val1 = array_pop($stack);
$val2 = array_pop($stack);
// calculate new value to put back on the stack
$value = ($current == '|' ? $val1 || $val2 : $val1 && $val2);
} elseif (validateNumber($current)) {
// add a boolean to the stack indicating whether the user has this right
$value = in_array($current, $rights);
}
// put (intermediate) result back on the stack
$stack[] = ($negate ? !$value : $value);
}
// after this loop $stack should have exactly 1 value - the validation should guarantee this
return $stack[0];
}
?>
// after this loop $stack should have exactly 1 value - the validation should guarantee this
return$stack[0];
}
?>
Een voorbeeld:
Gegeven de "validatieregel" (A || B) && (C || D) waarbij A, B, C en D rechten voorstellen. Je zou dan toegang moeten hebben tot de bijbehorende resource indien de gebruiker een van de volgende (minimale) combinaties van rechten heeft:
A,C (en eventueel B of D)
A,D (en eventueel B of C)
B,C (en eventueel A of D)
B,D (en eventueel A of C)
De bijbehorende Poolse notatie is:
&,|,A,B,|,C,D
Neem voor de operanden A, B, C, D respectievelijk de id's 1, 2, 3, 4.
En de test (varieer $userRights en bekijk het resultaat):
Wat vinden jullie van dit idee? Overigens, het parsen van de accessrights zou je ook met enige moeite onder kunnen brengen in MySQL zelf, bijvoorbeeld in een stored procedure, zodat je direct records zou kunnen controleren of je deze (gegeven een userid) in zou mogen zien. Je kunt dat dan dus al doen tijdens het ophalen van records, als conditie in de query.
Dit alles vormt een eenvoudig maar toch vrij krachtig mechanisme voor het toekennen/ontzeggen van toegang tot resources.
EDIT: (refactoring) regel 56 uit het bovenstaande fragment zou je om kunnen zetten in een simpele "else", het token moet op dat moment uit een getal bestaan, de voorgaande validatie zou dit moeten garanderen.
EDIT2: De "uitdaging" zal dus vooral zitten in het maken van een gebruiksvriendelijke interface voor het definiëren / bouwen van de toegangsrechten, die daarna omgezet moeten worden tot een geldige geserialiseerde notatie. Maar in eerste instantie zou je dit dus ook "rauw" kunnen doen . Met name als je complexe toegangsrechten hebt, zul je toch al redelijk goed moeten weten wat je aan het doen bent.
EDIT3: (refactoring) regel 58: en als je de rechten van gebruikers opslaat als keys kun je in_array() vervangen door isset() of array_key_exists().
EDIT4: regel 8 t/m 11 toegevoegd aan validatie: een lege rechten-string is altijd goed (true) en hoeft niet verder gecontroleerd te worden.
Ik heb 'm gelezen (een paar dagen geleden), maar zal 'm nog eens lezen en eventueel meedenken.
Maar dan weet je dat het er niet helemaal voor nop op staat .
Thomas - 01/09/2014 12:05 (laatste wijziging 01/09/2014 14:47)
Moderator
De "uitdaging" (het maken van een gebruiksvriendelijke interface voor het definiëren / bouwen van de toegangsrechten, die daarna omgezet moeten worden tot een geldige geserialiseerde notatie) is inmiddels ook aardig gelukt. Hiertoe heb ik de implementatie van de PHP-functies een klein beetje aangepast. Ik beschouw de negatie (!) nu als een apart token. Hiermee wordt de PHP implementatie aldus:
function validateSerializedString($expression) {
if ($expression == '') {
return true;
}
$operators = 0; // the added & operator will be counted automatically below
$operands = 1; // the added "true" on the (imaginary) stack
// we add "&& true" to ensure at least two operands ($expression could consist of exactly one right)
$tokens = explode(',', '&,'.$expression); // mind the prefixed "&,"
foreach ($tokens as $token) {
if ($token == '&' || $token == '|') {
$operators++;
} elseif ($token == '!') {
// do not count the ! token
} elseif (validateNumber($token)) {
$operands++;
} else {
// illegal character (sequence)
return false;
}
}
return ($operators + 1 == $operands);
}
function isAllowed($expression, $rights=array()) {
if ($expression == '') {
return true;
}
// we add "&& true" to ensure at least two operands ($expression could consist of exactly one right)
$stack = array(true);
$tokens = array_reverse(explode(',', '&,'.$expression)); // read from back to front, mind the prefixed "&,"
foreach ($tokens as $token) {
if ($token == '&' || $token == '|') {
// $stack should have at least two items - validation should guarantee this
// note that $val1 and $val2 already are boolean values (true or false)
$val1 = array_pop($stack);
$val2 = array_pop($stack);
// calculate new value to put back on the stack
$stack[] = ($token == '|' ? $val1 || $val2 : $val1 && $val2);
} elseif ($token == '!') {
// negate whatever is on the top of the stack
$stack[count($stack)-1] = !($stack[count($stack)-1]);
} else {
// this is a number, validation should guarantee this!
// add a boolean to the stack indicating whether the user has this right
$stack[] = in_array($token, $rights);
}
}
// after this loop $stack should have exactly 1 value - the validation should guarantee this
return $stack[0];
}
function validateSerializedString($expression){
if($expression==''){
returntrue;
}
$operators=0;// the added & operator will be counted automatically below
$operands=1;// the added "true" on the (imaginary) stack
// we add "&& true" to ensure at least two operands ($expression could consist of exactly one right)
$tokens=explode(',','&,'.$expression);// mind the prefixed "&,"
// after this loop $stack should have exactly 1 value - the validation should guarantee this
return$stack[0];
}
Zoals je ziet is de negatie van toepassing op het bovenste (resultaat)element op de stack.
Vervolgens het HTML/jQuery ding voor het bouwen van de logische boom (code volgt na de toelichting), hierin zijn uiteraard weer enkele problemen .
De opsteller van de boom zou in principe alle mogelijke bomen die hij/zij kan verzinnen mogen bouwen, echter, als je een boom als volgt opbouwt, wordt het uitleesproces nogal lastig:
Je moet dan namelijk operators soms gaan prefixen, soms gaan postfixen, kortom je moet een heleboel beslissingen nemen. Uiteindelijk wil je dat het uitleesresultaat er zo uitziet:
Het uitleesproces was dan SUPER simpel geweest, je kunt dan de boom gewoon van onder naar boven uitlezen! Merk hierbij op dat je A en B kunt verwisselen vanwege de commutativiteit van OR (en AND), oftewel A OR B is equivalent aan B OR A.
Maar je wilt dit niet aan een gebruiker opleggen om bomen in een bepaalde voorgeschreven vorm op te stellen.
Daartoe heb ik de volgende "slimmigheid" ingebouwd: indien een tak van de boom van een "samengesteld" type (AND, OR) is, en het tweede kind-element een simpel (niet-samengesteld) element is en het eerste kind-element een "complex" (samengesteld) element is, dan draai ik deze twee om. Op deze manier kan ik dus de eerste boom omzetten naar de tweede boom en vervolgens kan ik deze dus in een ruk van achter naar voren uitlezen.
Hiermee kom ik tot de volgende implementatie in HTML/jQuery:
EDIT: het omdraaien van boomfragmenten bleek bij nader inzien niet (meer) nodig, zie notities achteraan.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>tree</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<style type="text/css">
li.red > span { color: #ff0000; }
</style>
</head>
<body>
<div id="tree_container">
<ul id="tree">
<li><span>empty</span></li>
</ul>
</div>
<p><em>Click on a list item to edit it. Special values: AND, OR, NOT (case sensitive)</em></p>
<button type="button" id="daddy">parents</button>
<button type="button" id="notation">notation</button>
<br />
<textarea id="pn" rows="10" cols="50"></textarea>
<script type="text/javascript">
//<![CDATA[
// https://github.com/janl/mustache.js/blob/master/mustache.js
var entityMap = {
"&": "&",
"<": "<",
">": ">",
'"': '"',
"'": ''',
"/": '/'
};
function escapeHtml(string) {
return String(string).replace(/[&<>"'\/]/g, function (s) {
return entityMap[s];
});
}
$().ready(function() {
// click event function for building a logical tree
var handleclick = function(e) {
// prevent the click event to bubble to the parent when clicking on input
// so parent li clicks do not get triggered again
e.stopPropagation();
var $element = $(this);
var go = false;
if ($element.has('ul').length) {
go = confirm('editing this element will cause the child elements to be deleted; proceed?');
} else {
go = true;
}
if (go) {
var value = $element.find('span').first().text();
// @todo the next line might remove an entire subtree, do anything to save or cleanup items/events?
var $html = $('<input type="text" value="" />').val(value);
$html.click(function(e) {
// so it does not trigger click event of direct parent li
e.stopPropagation();
});
$html.blur(function() {
var value = $(this).val();
if (value == '') {
value = 'empty';
}
$element.html('<span>'+escapeHtml(value)+'</span>');
if (value == 'AND' || value == 'OR') {
$ul = $('<ul></ul>');
$ul.append($('<li><span>empty</span></li>').click(handleclick));
$ul.append($('<li><span>empty</span></li>').click(handleclick));
$element.append($ul);
} else if (value == 'NOT') {
$ul = $('<ul></ul>');
$ul.append($('<li><span>empty</span></li>').click(handleclick));
$element.append($ul);
}
});
$element.html($html);
$html.focus();
} // go
} // handleclick
// onload event: add clickevents to initial present list items (serves as init)
$('#tree li').click(handleclick);
$('#daddy').click(function() {
// *ugh* how to select all first list items of an <ul> that contain an <ul>?
$('#tree_container').find('ul').each(function() {
// check if a direct descendant is a parent too
$(this).children('li').each(function() {
// reset
$(this).removeClass('red');
// first element always is a span
// note the .length element, if omitted, it returns true regardless of whether
// the element actually has a ul within
if ($(this).has('ul').length) {
$(this).addClass('red');
}
});
});
});
$('#notation').click(function() {
var out = '';
// first we check if we need to rearrange the tree
// we need the simple (non compound) statement in front
// NOT NEEDED
/*
$('#tree_container').find('ul').each(function() {
var $children = $(this).children('li');
if ($children.length == 2) {
var $child2 = $children.eq(1);
var value = $child2.find('span').html();
if (value == 'AND' || value == 'OR') {
// compound statement, do nothing
} else {
// second element is simple, check first
var $child1 = $children.eq(0);
var value = $child1.find('span').html();
// if the first element is complex, we need to swap
if (value == 'AND' || value == 'OR') {
// x.before(y) = put y before x
$child1.before($child2);
} else {
// both elements simple, swapping not needed after all
}
}
}
});
*/
// next we read the tree from back to front
// NOT NEEDED
// $($('#tree li span').get().reverse()).each(function() {
$('#tree li span').each(function() {
// NOT NEEDED
// out = $(this).text()+(out == '' ? '' : ',')+out;
out = out+(out == '' ? '' : ',')+$(this).text();
});
// output the generated string
$('#pn').val(out);
});
});
//]]>
</script>
</body>
</html>
Hiermee heb ik dus effectief het opstellen (op een redelijk gebruiksvriendelijke manier, die nog verder uitgewerkt kan worden) en het uitlezen (in PHP) bij elkaar gebracht.
EDIT: HTML/jQuery aangepast:
- escaping van karakters gaat nu... beter
- de gebruiker krijgt een bevestiging voordat een subboom wordt weggekieperd wanneer deze op een AND- of OR-tak klikt
EDIT: de volgende notatie (eerste boom) voldeed in principe ook:
Huh, misschien was die omdraaiing helemaal niet nodig .
EDIT: de omdraaiing lijkt inderdaad niet (meer) nodig, dit is voornamelijk te danken aan het apart schrijven van de NOT operator (denk ik, lol). Voorheen was het lastig om zoiets te doen:
omdat de NOT aan de buitenste OR werd gekoppeld. Je kwam dan in de problemen met C (een derde operand). Door de NOT apart te zetten heb je dit probleem niet meer. De jQuery wordt hierdoor (stukken) simpeler.
Ik moet altijd lachen van jouw zinnen. Ik begrijp je code vaak wel, maar je zinnen niet :-).
Ik ga reageren zodra ik met google.nl:definities heb uitgezocht wat worden zoals negatie, commutativiteit en dergelijke woorden betekenen.
Thomas - 02/09/2014 14:21 (laatste wijziging 28/03/2015 16:28)
Moderator
EDIT #2: In de MySQL functie staat het token achterstevoren als deze uit meer dan 1 karakter bestaat (als dit bijvoorbeeld recht-nummers betreft groter dan 9), dit omdat je expression aan het begin REVERSEd. Hiertoe moet je het token ook REVERSEn (inmiddels in onderstaande code aangepast).
EDIT: shameless bump; aan het einde staat een MySQL functie die hetzelfde doet, want soms wil je misschien op voorhand opgehaalde resultaten filteren op grond van de rechten die iemand heeft.
En dan nog het laatste stukje, om een eerder opgeslagen expressie weer uit te lezen en om te zetten in een geneste bulleted list. Om e.e.a. bij te houden en makkelijk (recursief) te doorlopen bij het afdrukken van de lijst zetten we dit in een class:
<?php
class RightsTree
{
protected $_tree;
public function __construct($expression) {
$this->_tree = $this->buildTree($expression);
}
protected function buildTree($input) {
$tree = array(); // result array
$freeSpots = array(0 => 1); // depth => number of free spots
$parents = array(); // stack with indexes of $tree which are parents
$currentDepth = 0;
// list of tokens that will create new free spots at a (one) lower level
$newSpots = array(
'&' => 2,
'|' => 2,
'!' => 1,
);
// mapping to a more readable form
$map = array(
'&' => 'AND',
'|' => 'OR',
'!' => 'NOT',
);
foreach (explode(',', $input) as $position => $token) {
// find highest level at which there are free spots
$spotFound = false;
$d = count($freeSpots) - 1;
while ($d > -1 && $spotFound === false) {
if ($freeSpots[$d] > 0) {
$currentDepth = $d;
$spotFound = true;
} else {
array_pop($freeSpots); // do not inspect this level again (also to prevent popping too many elements from $parents)
array_pop($parents); // this parent no longer has any free spots
$d--; // look one level lower
}
}
if ($spotFound === false) {
// complain, e.g. throw exception
die('malformed expression at position '.$position);
}
// add token to tree
$parent = (count($parents) ? $parents[count($parents)-1] : -1);
$tree[] = array(
'parent' => $parent,
'children' => array(),
'value' => (array_key_exists($token, $map) ? $map[$token] : $token),
);
if ($parent > -1) {
$tree[$parent]['children'][] = count($tree) - 1; // index of the element we just added
}
// subtract free spots
$freeSpots[$currentDepth] -= 1;
// is this a token that creates new spots?
if (array_key_exists($token, $newSpots)) {
// add number of free spots defined in $spots
$freeSpots[$currentDepth+1] = $newSpots[$token];
// this is a new parent
$parents[] = count($tree) - 1; // index of the element we just added
}
} // foreach
return $tree;
}
public function printTree($index=0) {
// @todo add output escaping
?><li><span><?php echo $this->_tree[$index]['value']; ?></span><?php
if (count($this->_tree[$index]['children'])) {
?><ul><?php
foreach ($this->_tree[$index]['children'] as $child) {
$this->printTree($child);
}
?></ul><?php
}
?></li><?php
}
}
<?php
$input = '&,|,&,D,E,C,!,|,A,B'; // assumption: this expression was validated successfully earlier
$tree = new RightsTree($input);
?><ul id="tree"><?php
$tree->printTree();
?></ul>
<?php
$input='&,|,&,D,E,C,!,|,A,B';// assumption: this expression was validated successfully earlier
$tree=new RightsTree($input);
?><ul id="tree"><?php
$tree->printTree();
?></ul>
En hier kun je dus weer de eerdere jQuery op toepassen (o.a. hangen van clickevents aan list-items).
Nu zijn we helemaal rond .
EDIT: extra gebruiksvriendelijkheid: waarschijnlijk zal de interactieve rechtenboom onderdeel uit gaan maken van een formulier. Als je op de ENTER-toets drukt bij het wijzigen van een recht wil je waarschijnlijk niet dat je formulier meteen wordt gesubmit. Dit kun je afvangen door op regel 65 van de jQuery code het volgende toe te voegen:
$html.keypress(function(e) {
if (e.which == 13) {
// do not submit form...
e.preventDefault();
// ... but trigger blur() event instead
$(this).blur();
}
});
$html.keypress(function(e){
if(e.which==13){
// do not submit form...
e.preventDefault();
// ... but trigger blur() event instead
$(this).blur();
}
});
Daarnaast kan het misschien handig zijn dat je meteen kunt gaan typen, door het toevoegen van .select() aan regel 85 van het oorspronkelijke fragment selecteer je meteen alle tekst, deze regel wordt:
DELIMITER ;;
DROP FUNCTION IF EXISTS has_rights;;
CREATE FUNCTION has_rights(expression VARCHAR(255), rights VARCHAR(255)) RETURNS BOOL DETERMINISTIC
BEGIN
DECLARE stack VARCHAR(255);
DECLARE token VARCHAR(5);
DECLARE val1 VARCHAR(5);
DECLARE val2 VARCHAR(5);
IF (expression = '') THEN
RETURN TRUE;
END IF;
SET stack = '1,';
SET expression = REVERSE(CONCAT(',&,', expression));
WHILE (LOCATE(',', expression) > 0) DO
SET token = REVERSE(SUBSTRING_INDEX(expression, ',', 1));
SET expression = SUBSTRING(expression, LENGTH(token) + 2);
IF (LENGTH(token) > 0) THEN
IF (token = '|' OR token = '&') THEN
SET val1 = SUBSTRING_INDEX(stack, ',', 1);
SET stack = SUBSTRING(stack, LENGTH(val1) + 2);
SET val2 = SUBSTRING_INDEX(stack, ',', 1);
SET stack = SUBSTRING(stack, LENGTH(val2) + 2);
IF (token = '|') THEN
SET stack = CONCAT((val1 OR val2), ',', stack);
ELSE
SET stack = CONCAT((val1 AND val2), ',', stack);
END IF;
ELSEIF (token = '!') THEN
SET val1 = SUBSTRING_INDEX(stack, ',', 1);
SET stack = CONCAT(NOT(val1), ',', SUBSTRING(stack, LENGTH(val1) + 2));
ELSE
SET val1 = LOCATE(CONCAT(',', token, ','), CONCAT(',', rights, ',')) > 0;
SET stack = CONCAT(val1, ',', stack);
END IF;
END IF;
END WHILE;
SET val1 = SUBSTRING_INDEX(stack, ',', 1);
RETURN (val1 = 1);
END;;
DELIMITER ;
DELIMITER ;;
DROPFUNCTIONIFEXISTS has_rights;;
CREATEFUNCTION has_rights(expression VARCHAR(255), rights VARCHAR(255)) RETURNS BOOL DETERMINISTIC
BEGIN
DECLARE stack VARCHAR(255);
DECLARE token VARCHAR(5);
DECLARE val1 VARCHAR(5);
DECLARE val2 VARCHAR(5);
IF(expression ='') THEN
RETURN TRUE;
END IF;
SET stack ='1,';
SET expression = REVERSE(CONCAT(',&,', expression));
WHILE (LOCATE(',', expression)>0) DO
SET token = REVERSE(SUBSTRING_INDEX(expression,',',1));
SET expression = SUBSTRING(expression, LENGTH(token)+2);
IF(LENGTH(token)>0) THEN
IF(token ='|'OR token ='&') THEN
SET val1 = SUBSTRING_INDEX(stack,',',1);
SET stack = SUBSTRING(stack, LENGTH(val1)+2);
SET val2 = SUBSTRING_INDEX(stack,',',1);
SET stack = SUBSTRING(stack, LENGTH(val2)+2);
IF(token ='|') THEN
SET stack = CONCAT((val1 OR val2),',', stack);
ELSE
SET stack = CONCAT((val1 AND val2),',', stack);
END IF;
ELSEIF (token ='!') THEN
SET val1 = SUBSTRING_INDEX(stack,',',1);
SET stack = CONCAT(NOT(val1),',', SUBSTRING(stack, LENGTH(val1)+2));
ELSE
SET val1 = LOCATE(CONCAT(',', token,','), CONCAT(',', rights,','))>0;