Upload
konstantin-kaefer
View
2.706
Download
1
Embed Size (px)
Citation preview
InstantDynamic Forms
with#states
Konstantin Käfer2
2006
#states 3
4
5
Anatomy of a state
$form['payment_information'] = array( '#type' => 'fieldset', '#title' => t('Payment information'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#states' => array( 'expanded' => array( '[name="payment"]' => array('checked' => TRUE) ), ),);
5
Anatomy of a state
$form['payment_information'] = array( '#type' => 'fieldset', '#title' => t('Payment information'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#states' => array( 'expanded' => array( '[name="payment"]' => array('checked' => TRUE) ), ),);
5
Anatomy of a state
$form['payment_information'] = array( '#type' => 'fieldset', '#title' => t('Payment information'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#states' => array( 'expanded' => array( '[name="payment"]' => array('checked' => TRUE) ), ),);
5
Anatomy of a state
$form['payment_information'] = array( '#type' => 'fieldset', '#title' => t('Payment information'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#states' => array( 'expanded' => array( '[name="payment"]' => array('checked' => TRUE) ), ),);
5
Anatomy of a state
$form['payment_information'] = array( '#type' => 'fieldset', '#title' => t('Payment information'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#states' => array( 'expanded' => array( '[name="payment"]' => array('checked' => TRUE) ), ),);
5
Anatomy of a state
$form['payment_information'] = array( '#type' => 'fieldset', '#title' => t('Payment information'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#states' => array( 'expanded' => array( '[name="payment"]' => array('checked' => TRUE) ), ),);
5
Anatomy of a state
$form['payment_information'] = array( '#type' => 'fieldset', '#title' => t('Payment information'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#states' => array( 'expanded' => array( '[name="payment"]' => array('checked' => TRUE) ), ),);
5
Anatomy of a state
$form['payment_information'] = array( '#type' => 'fieldset', '#title' => t('Payment information'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#states' => array( 'expanded' => array( '[name="payment"]' => array('checked' => TRUE) ), ),);
Declarative definition6
➜Expanded when the element “payment”is checked
'expanded' => array( '[name="payment"]' => array('checked' => TRUE)),
7
Dependencies$form['payment_information'] = array(... '#states' => array( 'expanded' => array( '[name="payment"]' => array('checked' => TRUE) ), ),); <fieldset>
<input type="checkbox">
Depends on Influences
Targeting elements◆ Uses plain CSS selectors with jQuery◆ [name="payment"]#edit-payment.payment :checkbox, #edit-payment
◆ Don’t use #selector for auto-assigned IDs
8
States◆ Arbitrary names are possible◆ visible irrelevant confirmed
checked valid important
◆ Prefixing with ! negates◆ visible = !invisible
invisible = !visible
9
State aliases◆ Associate custom aliases◆ Drupal.states.State.aliases ['unimportant'] = '!important';
◆ enabled = !disabled invisible = !visible
invalid = !valid untouched = !touched
optional = !required filled = !empty
unchecked = !checked irrelevant = !relevant
expanded = !collapsed readwrite = !readonly
10
Primary name
Drawbacks◆ Doesn’t support OR and XOR
11
Drawbacks◆ Doesn’t support OR and XOR
11
Patch!drupal.org/node/735528
AND operator12
'disabled' => array( '[name="ccv"]' => array( 'invalid' => TRUE ), '[name="card_number"]' => array( 'invalid' => TRUE ), ),
OR operator13
'disabled' => array( array( '[name="ccv"]' => array( 'invalid' => TRUE ), ), array( '[name="card_number"]' => array( 'invalid' => TRUE ), ),),
OR operator13
'disabled' => array( array( '[name="ccv"]' => array( 'invalid' => TRUE ), ), array( '[name="card_number"]' => array( 'invalid' => TRUE ), ),),
Numeric keys
XOR operator14
'disabled' => array('xor', array( '[name="ccv"]' => array( 'invalid' => TRUE ), ), array( '[name="card_number"]' => array( 'invalid' => TRUE ), ),),
XOR operator14
'disabled' => array('xor', array( '[name="ccv"]' => array( 'invalid' => TRUE ), ), array( '[name="card_number"]' => array( 'invalid' => TRUE ), ),),
Operator
Drawbacks◆ Doesn’t support OR and XOR
15
Patch!drupal.org/node/735528
Drawbacks◆ Doesn’t support OR and XOR
◆ Doesn’t support radio buttons
15
Patch!drupal.org/node/735528
Drawbacks◆ Doesn’t support OR and XOR
◆ Doesn’t support radio buttons
15
Patch!drupal.org/node/735528
Extend!
Triggers
Drupal.states.Trigger.states = { ... checked: { 'change': function () { return this.attr('checked'); } }, ...};
Default Triggers17
Drupal.states.Trigger.states = { ... checked: { 'change': function () { return this.attr('checked'); } }, ...};
Default Triggers17
Native DOM event
Drupal.states.Trigger.states = { ... checked: { 'change': function () { return this.attr('checked'); } }, ...};
Default Triggers17
Drupal.states.Trigger.states = { ... checked: { 'change': function () { return this.attr('checked'); } }, ...};
Default Triggers17
Value function
Default Triggers18
Initialization ExecutionDrupal.states.Trigger.states = { ... checked: { 'change': function () { return this.attr('checked'); } }, ...};
$('#element').bind('change', function() { ... });
Drupal.states.Trigger.states = { ... checked: { 'change': function () { return this.attr('checked'); } }, ...};
$('#element').bind('change', function() { ... });
Default Triggers18
Initialization ExecutionDrupal.states.Trigger.states = { ... checked: { 'change': function () { return this.attr('checked'); } }, ...};
$('#element').bind('change', function() { ... });
Drupal.states.Trigger.states = { ... checked: { 'change': function () { return this.attr('checked'); } }, ...};
$('#element').bind('change', function() { ... });
Default Triggers18
Initialization ExecutionDrupal.states.Trigger.states = { ... checked: { 'change': function () { return this.attr('checked'); } }, ...};
$('#element').bind('change', function() { ... });
Drupal.states.Trigger.states = { ... checked: { 'change': function () { return this.attr('checked'); } }, ...};
$('#element').bind('change', function() { ... });
Multiple Triggers19
Drupal.states.Trigger.states = { ... value: { 'keyup': function () { return this.val(); }, 'change': function () { return this.val(); } }, ...};
Multiple Triggers20
Drupal.states.Trigger.states = { ... value: { 'keyup change': function () { return this.val(); } }, ...};
Custom Triggers21
'#states' => array( 'disabled' => array( '[name="delayed"]' => array('value' => 'foo') ),),
Custom Triggers22
Drupal.states.Trigger.states.delayedValue = function(element) { var value = element.val(), oldValue, timeout; var trigger = function() { if (oldValue !== value) { element.trigger({ type: 'state:delayedValue', value: value, oldValue: oldValue }); oldValue = value; } };
... };
Custom Triggers22
Drupal.states.Trigger.states.delayedValue = function(element) { var value = element.val(), oldValue, timeout; var trigger = function() { if (oldValue !== value) { element.trigger({ type: 'state:delayedValue', value: value, oldValue: oldValue }); oldValue = value; } };
... };
Custom Triggers22
Drupal.states.Trigger.states.delayedValue = function(element) { var value = element.val(), oldValue, timeout; var trigger = function() { if (oldValue !== value) { element.trigger({ type: 'state:delayedValue', value: value, oldValue: oldValue }); oldValue = value; } };
... };
Custom Triggers22
Drupal.states.Trigger.states.delayedValue = function(element) { var value = element.val(), oldValue, timeout; var trigger = function() { if (oldValue !== value) { element.trigger({ type: 'state:delayedValue', value: value, oldValue: oldValue }); oldValue = value; } };
... };
Custom Triggers22
Drupal.states.Trigger.states.delayedValue = function(element) { var value = element.val(), oldValue, timeout; var trigger = function() { if (oldValue !== value) { element.trigger({ type: 'state:delayedValue', value: value, oldValue: oldValue }); oldValue = value; } };
... };
Custom Triggers23
Drupal.states.Trigger.states.delayedValue = function(element) { ...
element.bind('keyup change', function (e) { if (timeout) clearTimeout(timeout); timeout = setTimeout(function() { value = element.val(); trigger(); }, 1000); }); Drupal.states.postponed.push(trigger); };
Custom Triggers23
Drupal.states.Trigger.states.delayedValue = function(element) { ...
element.bind('keyup change', function (e) { if (timeout) clearTimeout(timeout); timeout = setTimeout(function() { value = element.val(); trigger(); }, 1000); }); Drupal.states.postponed.push(trigger); };
Custom Triggers23
Drupal.states.Trigger.states.delayedValue = function(element) { ...
element.bind('keyup change', function (e) { if (timeout) clearTimeout(timeout); timeout = setTimeout(function() { value = element.val(); trigger(); }, 1000); }); Drupal.states.postponed.push(trigger); };
Custom Triggers23
Drupal.states.Trigger.states.delayedValue = function(element) { ...
element.bind('keyup change', function (e) { if (timeout) clearTimeout(timeout); timeout = setTimeout(function() { value = element.val(); trigger(); }, 1000); }); Drupal.states.postponed.push(trigger); };
Custom Triggers23
Drupal.states.Trigger.states.delayedValue = function(element) { ...
element.bind('keyup change', function (e) { if (timeout) clearTimeout(timeout); timeout = setTimeout(function() { value = element.val(); trigger(); }, 1000); }); Drupal.states.postponed.push(trigger); };
Custom Triggers24
Drupal.states.Trigger.states.toggle = function(element) { var value = true, oldValue = undefined; var trigger = function() { value = !value; element.trigger({ type: 'state:toggle', value: value, oldValue: oldValue }); oldValue = value; };
setInterval(trigger, 1000); Drupal.states.postponed.push(trigger); };
Comparisons
$form['payment_information'] = array( ... '#states' => array( 'expanded' => array( '[name="payment"]' => array('checked' => TRUE) ), ),);
Comparisons26
$form['payment_information'] = array( ... '#states' => array( 'expanded' => array( '[name="payment"]' => array('checked' => TRUE) ), ),);
Comparisons26
===
Advanced Comparisons27
states.Dependant.comparisons = { 'RegExp': function (reference, value) { return reference.test(value); }, 'Function': function (reference, value) { return reference(value); }};
Advanced Comparisons27
states.Dependant.comparisons = { 'RegExp': function (reference, value) { return reference.test(value); }, 'Function': function (reference, value) { return reference(value); }}; Prototype name
JSON only allowsstrings and numbers 28
:( 29
Advanced Comparisons30
'invalid' => array( '[name="card_number"]' => array( '!value' => array('regex' => '^(\d{4}[ -]*){4}$'), ),),
'invalid' => array( '[name="card_number"]' => array( '!value' => '0000 0000 0000 0000', ),),
Advanced Comparisons30
'invalid' => array( '[name="card_number"]' => array( '!value' => array('regex' => '^(\d{4}[ -]*){4}$'), ),),
'invalid' => array( '[name="card_number"]' => array( '!value' => '0000 0000 0000 0000', ),),
Advanced Comparisons31
Drupal.states.Dependant.comparisons.Object = function(reference, value) { if ('regex' in reference) { return RegExp(reference.regex, ↵ reference.flags).test(value); } else { return reference.indexOf(value) !== false; } };
Advanced Comparisons32
'invalid' => array( '[name="card_number"]' => array( '!value' => array('regex' => '^(\d{4}[ -]*){4}$'), ),),
Transitions
State changes◆ Transition an element from one state
to another
34
Direct Indirect◆ Triggered by user◆ Notify listeners
◆ Triggered by other element
◆ Transition element
State changes35
$(document).bind('state:checked', function(e) { if (e.trigger) { $(e.target).attr('checked', e.value); }});
State changes35
$(document).bind('state:checked', function(e) { if (e.trigger) { $(e.target).attr('checked', e.value); }});
State changes35
$(document).bind('state:checked', function(e) { if (e.trigger) { $(e.target).attr('checked', e.value); }});
State changes35
$(document).bind('state:checked', function(e) { if (e.trigger) { $(e.target).attr('checked', e.value); }});
State changes35
$(document).bind('state:checked', function(e) { if (e.trigger) { $(e.target).attr('checked', e.value); }});
State changes
◆ Event bubbling allows overwriting handlers for specific regions
◆ CSS selectors allow overwriting handlers for specific elements
36
document
<html>
<body>
<div id="body">
<div class="element">
<input type="text">
Future Work
Domain-specific language38
state_of('[name="baz"]') ->is('checked') ->when('[name="bar"]')->checked() ->and('[name="foo"]')->value('foo') ->orWhen('[name="bar"]')->unchecked()
->is('disabled') ->when('[name="bar"]')->unchecked()
->is('invisible') ->when('[name="foo"]')->empty();
Ideas?
Copy & Paste support 39
Multi-value support40
// The value of at least one element is true.{'any': true}
// At least two elements are true.{'n > 2': true}
// The third element is false.{'2': false}
Extended values41
// The value is greater than 8 or smaller than 5.[ {'>': 8}, {'<': 5} ]
// At least two elements are between 5 and 8.{'n > 2': {'>': 5, '<': 8}}
// The sum of the values of all elements is// greater than 10.{'sum': {'>=': 10}}