Instant Dynamic Forms with #states
- 5. Anatomy of a state
5
$form['payment_information'] = array(
'#type' => 'fieldset',
'#title' => t('Payment information'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#states' => array(
'expanded' => array(
'[name="payment"]' => array('checked' => TRUE)
),
),
);
- 6. Anatomy of a state
5
$form['payment_information'] = array(
'#type' => 'fieldset',
'#title' => t('Payment information'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#states' => array(
'expanded' => array(
'[name="payment"]' => array('checked' => TRUE)
),
),
);
- 7. Anatomy of a state
5
$form['payment_information'] = array(
'#type' => 'fieldset',
'#title' => t('Payment information'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#states' => array(
'expanded' => array(
'[name="payment"]' => array('checked' => TRUE)
),
),
);
- 8. Anatomy of a state
5
$form['payment_information'] = array(
'#type' => 'fieldset',
'#title' => t('Payment information'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#states' => array(
'expanded' => array(
'[name="payment"]' => array('checked' => TRUE)
),
),
);
- 9. Anatomy of a state
5
$form['payment_information'] = array(
'#type' => 'fieldset',
'#title' => t('Payment information'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#states' => array(
'expanded' => array(
'[name="payment"]' => array('checked' => TRUE)
),
),
);
- 10. Anatomy of a state
5
$form['payment_information'] = array(
'#type' => 'fieldset',
'#title' => t('Payment information'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#states' => array(
'expanded' => array(
'[name="payment"]' => array('checked' => TRUE)
),
),
);
- 11. Anatomy of a state
5
$form['payment_information'] = array(
'#type' => 'fieldset',
'#title' => t('Payment information'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#states' => array(
'expanded' => array(
'[name="payment"]' => array('checked' => TRUE)
),
),
);
- 12. Anatomy of a state
5
$form['payment_information'] = array(
'#type' => 'fieldset',
'#title' => t('Payment information'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#states' => array(
'expanded' => array(
'[name="payment"]' => array('checked' => TRUE)
),
),
);
- 13. Declarative definition
6
'expanded' => array(
'[name="payment"]' => array('checked' => TRUE)
),
➜ Expanded when the element “payment”
is checked
- 14. Dependencies
7
$form['payment_information'] = array(...
'#states' => array(
'expanded' => array(
'[name="payment"]' => array('checked' => TRUE)
),
),
);
<fieldset>
Depends on Influences
<input type="checkbox">
- 15. Targeting elements
8
◆ Uses plain CSS selectors with jQuery
◆ [name="payment"]
#edit-payment
.payment :checkbox, #edit-payment
◆ Don’t use #selector for auto-assigned IDs
- 16. States
9
◆ Arbitrary names are possible
◆ visible irrelevant confirmed
checked valid important
◆ Prefixing with ! negates
◆ visible = !invisible
invisible = !visible
- 17. State aliases
10
◆ Associate custom aliases Primary
name
◆ 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
- 18. Drawbacks
11
◆ Doesn’t support OR and XOR
- 19. Drawbacks
11
◆ Doesn’t !
chsupport OR and XOR
Pat
drupal.org/node/735528
- 20. AND operator
12
'disabled' => array(
'[name="ccv"]' => array(
'invalid' => TRUE
),
'[name="card_number"]' => array(
'invalid' => TRUE
),
),
- 21. OR operator
13
'disabled' => array(
array(
'[name="ccv"]' => array(
'invalid' => TRUE
),
),
array(
'[name="card_number"]' => array(
'invalid' => TRUE
),
),
),
- 22. OR operator
13
'disabled' => array(
array(
'[name="ccv"]' => array(
'invalid' => TRUE
),
), Numeric keys
array(
'[name="card_number"]' => array(
'invalid' => TRUE
),
),
),
- 23. XOR operator
14
'disabled' => array('xor',
array(
'[name="ccv"]' => array(
'invalid' => TRUE
),
),
array(
'[name="card_number"]' => array(
'invalid' => TRUE
),
),
),
- 24. XOR operator
14
'disabled' => array('xor', Operator
array(
'[name="ccv"]' => array(
'invalid' => TRUE
),
),
array(
'[name="card_number"]' => array(
'invalid' => TRUE
),
),
),
- 25. Drawbacks
15
◆ Doesn’t !
chsupport OR and XOR
Pat
drupal.org/node/735528
- 26. Drawbacks
15
◆ Doesn’t !
chsupport OR and XOR
Pat
drupal.org/node/735528
◆ Doesn’t support radio buttons
- 27. Drawbacks
15
◆ Doesn’t !
chsupport OR and XOR
Pat
drupal.org/node/735528
◆ Doesn’t support radio buttons
end!
E xt
- 29. Default Triggers
17
Drupal.states.Trigger.states = {
...
checked: {
'change': function () {
return this.attr('checked');
}
},
...
};
- 30. Default Triggers
17
Drupal.states.Trigger.states = {
...
checked: { Native DOM event
'change': function () {
return this.attr('checked');
}
},
...
};
- 31. Default Triggers
17
Drupal.states.Trigger.states = {
...
checked: {
'change': function () {
return this.attr('checked');
}
},
...
};
- 32. Default Triggers
17
Drupal.states.Trigger.states = {
...
checked: {
'change': function () {
return this.attr('checked');
}
},
...
}; Value function
- 33. Default Triggers
18
Initialization Execution
Drupal.states.Trigger.states = { Drupal.states.Trigger.states = {
... ...
checked: { checked: {
'change': function () { 'change': function () {
return this.attr('checked'); return this.attr('checked');
} }
}, },
... ...
}; };
$('#element').bind('change', $('#element').bind('change',
function() { function() {
... ...
} }
); );
- 34. Default Triggers
18
Initialization Execution
Drupal.states.Trigger.states = { Drupal.states.Trigger.states = {
... ...
checked: { checked: {
'change': function () { 'change': function () {
return this.attr('checked'); return this.attr('checked');
} }
}, },
... ...
}; };
$('#element').bind('change', $('#element').bind('change',
function() { function() {
... ...
} }
); );
- 35. Default Triggers
18
Initialization Execution
Drupal.states.Trigger.states = { Drupal.states.Trigger.states = {
... ...
checked: { checked: {
'change': function () { 'change': function () {
return this.attr('checked'); return this.attr('checked');
} }
}, },
... ...
}; };
$('#element').bind('change', $('#element').bind('change',
function() { function() {
... ...
} }
); );
- 36. Multiple Triggers
19
Drupal.states.Trigger.states = {
...
value: {
'keyup': function () {
return this.val();
},
'change': function () {
return this.val();
}
},
...
};
- 37. Multiple Triggers
20
Drupal.states.Trigger.states = {
...
value: {
'keyup change': function () {
return this.val();
}
},
...
};
- 38. Custom Triggers
21
'#states' => array(
'disabled' => array(
'[name="delayed"]' => array('value' => 'foo')
),
),
- 39. Custom Triggers
22
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;
}
};
...
};
- 40. Custom Triggers
22
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;
}
};
...
};
- 41. Custom Triggers
22
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;
}
};
...
};
- 42. Custom Triggers
22
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;
}
};
...
};
- 43. Custom Triggers
22
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;
}
};
...
};
- 44. Custom Triggers
23
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);
};
- 45. Custom Triggers
23
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);
};
- 46. Custom Triggers
23
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);
};
- 47. Custom Triggers
23
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);
};
- 48. Custom Triggers
23
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);
};
- 49. Custom Triggers
24
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);
};
- 51. Comparisons
26
$form['payment_information'] = array(
...
'#states' => array(
'expanded' => array(
'[name="payment"]' => array('checked' => TRUE)
),
),
);
- 52. Comparisons
26
$form['payment_information'] = array(
...
'#states' => array(
'expanded' => array(
'[name="payment"]' => array('checked' => TRUE)
),
), ===
);
- 53. Advanced Comparisons
27
states.Dependant.comparisons = {
'RegExp': function (reference, value) {
return reference.test(value);
},
'Function': function (reference, value) {
return reference(value);
}
};
- 54. Advanced Comparisons
27
states.Dependant.comparisons = {
'RegExp': function (reference, value) {
return reference.test(value);
},
'Function': function (reference, value) {
return reference(value);
}
};
Prototype name
- 57. Advanced Comparisons
30
'invalid' => array(
'[name="card_number"]' => array(
'!value' => '0000 0000 0000 0000',
),
),
'invalid' => array(
'[name="card_number"]' => array(
'!value' => array('regex' => '^(d{4}[ -]*){4}$'),
),
),
- 58. Advanced Comparisons
30
'invalid' => array(
'[name="card_number"]' => array(
'!value' => '0000 0000 0000 0000',
),
),
'invalid' => array(
'[name="card_number"]' => array(
'!value' => array('regex' => '^(d{4}[ -]*){4}$'),
),
),
- 59. Advanced Comparisons
31
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;
}
};
- 60. Advanced Comparisons
32
'invalid' => array(
'[name="card_number"]' => array(
'!value' => array('regex' => '^(d{4}[ -]*){4}$'),
),
),
- 62. State changes
34
◆ Transition an element from one state
to another
Direct Indirect
◆ Triggered by user ◆ Triggered by other
◆ Notify listeners element
◆ Transition element
- 63. State changes
35
$(document).bind('state:checked', function(e) {
if (e.trigger) {
$(e.target).attr('checked', e.value);
}
});
- 64. State changes
35
$(document).bind('state:checked', function(e) {
if (e.trigger) {
$(e.target).attr('checked', e.value);
}
});
- 65. State changes
35
$(document).bind('state:checked', function(e) {
if (e.trigger) {
$(e.target).attr('checked', e.value);
}
});
- 66. State changes
35
$(document).bind('state:checked', function(e) {
if (e.trigger) {
$(e.target).attr('checked', e.value);
}
});
- 67. State changes
35
$(document).bind('state:checked', function(e) {
if (e.trigger) {
$(e.target).attr('checked', e.value);
}
});
- 68. document
<html> ◆ Event bubbling allows
overwriting handlers
<body> for specific regions
<div id="body"> ◆ CSS selectors allow
overwriting handlers
<div class="element"> for specific elements
36
<input type="text"> State changes
- 70. Domain-specific language
38
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?
- 72. Multi-value support
40
// 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}
- 73. Extended values
41
// 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}}
- 74. Questions?
42
kkaefer.com/2010/states.pdf
mail@kkaefer.com