SlideShare a Scribd company logo
Instant
Dynamic Forms
      with
             #states
Konstantin Käfer
                   2




    2006
#states   3
4
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)
      ),
   ),
);
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)
      ),
   ),
);
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)
      ),
   ),
);
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)
      ),
   ),
);
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)
      ),
   ),
);
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)
      ),
   ),
);
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)
      ),
   ),
);
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)
      ),
   ),
);
Declarative definition
                                                    6


'expanded' => array(
   '[name="payment"]' => array('checked' => TRUE)
),



 ➜ Expanded when the element “payment”
   is checked
Dependencies
                                                         7


$form['payment_information'] = array(...
   '#states' => array(
     'expanded' => array(
        '[name="payment"]' => array('checked' => TRUE)
      ),
   ),
);
                      <fieldset>

           Depends on           Influences


           <input type="checkbox">
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
States
                                           9


◆   Arbitrary names are possible
◆   visible    irrelevant      confirmed
    checked    valid           important


◆   Prefixing with ! negates
◆   visible = !invisible
    invisible = !visible
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
Drawbacks
                                 11


◆   Doesn’t support OR and XOR
Drawbacks
                                       11


◆   Doesn’t !
         chsupport OR and XOR
      Pat
              drupal.org/node/735528
AND operator
                                        12


'disabled' => array(

     '[name="ccv"]' => array(
       'invalid' => TRUE
     ),


     '[name="card_number"]' => array(
       'invalid' => TRUE
     ),

),
OR operator
                                        13


'disabled' => array(
   array(
     '[name="ccv"]' => array(
        'invalid' => TRUE
      ),
   ),
   array(
     '[name="card_number"]' => array(
        'invalid' => TRUE
      ),
   ),
),
OR operator
                                        13


'disabled' => array(
   array(
     '[name="ccv"]' => array(
        'invalid' => TRUE
      ),
   ),     Numeric keys
   array(
     '[name="card_number"]' => array(
        'invalid' => TRUE
      ),
   ),
),
XOR operator
                                        14


'disabled' => array('xor',
   array(
     '[name="ccv"]' => array(
        'invalid' => TRUE
      ),
   ),
   array(
     '[name="card_number"]' => array(
        'invalid' => TRUE
      ),
   ),
),
XOR operator
                                             14


'disabled' => array('xor',        Operator
   array(
     '[name="ccv"]' => array(
        'invalid' => TRUE
      ),
   ),
   array(
     '[name="card_number"]' => array(
        'invalid' => TRUE
      ),
   ),
),
Drawbacks
                                       15


◆   Doesn’t !
         chsupport OR and XOR
      Pat
              drupal.org/node/735528
Drawbacks
                                       15


◆   Doesn’t !
         chsupport OR and XOR
      Pat
              drupal.org/node/735528

◆   Doesn’t support radio buttons
Drawbacks
                                       15


◆   Doesn’t !
         chsupport OR and XOR
      Pat
              drupal.org/node/735528

◆   Doesn’t support radio buttons
          end!
      E xt
Triggers
Default Triggers
                                       17


Drupal.states.Trigger.states = {
   ...
   checked: {
     'change': function () {
        return this.attr('checked');
      }
   },
   ...
};
Default Triggers
                                       17


Drupal.states.Trigger.states = {
   ...
   checked: {   Native DOM event
     'change': function () {
        return this.attr('checked');
      }
   },
   ...
};
Default Triggers
                                       17


Drupal.states.Trigger.states = {
   ...
   checked: {
     'change': function () {
        return this.attr('checked');
      }
   },
   ...
};
Default Triggers
                                       17


Drupal.states.Trigger.states = {
   ...
   checked: {
     'change': function () {
        return this.attr('checked');
      }
   },
   ...
};           Value function
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() {
     ...                                    ...
   }                                      }
);                                     );
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() {
     ...                                    ...
   }                                      }
);                                     );
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() {
     ...                                    ...
   }                                      }
);                                     );
Multiple Triggers
                                   19


Drupal.states.Trigger.states = {
   ...
   value: {
     'keyup': function () {
        return this.val();
      },
     'change': function () {
        return this.val();
      }
   },
   ...
};
Multiple Triggers
                                     20


Drupal.states.Trigger.states = {
   ...
   value: {
     'keyup change': function () {
        return this.val();
      }
   },
   ...
};
Custom Triggers
                                                     21


'#states' => array(
   'disabled' => array(
     '[name="delayed"]' => array('value' => 'foo')
   ),
),
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;
       }
    };

    ...
  };
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;
       }
    };

    ...
  };
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;
       }
    };

    ...
  };
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;
       }
    };

    ...
  };
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;
       }
    };

    ...
  };
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);
  };
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);
  };
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);
  };
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);
  };
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);
  };
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);
  };
Comparisons
Comparisons
                                                         26


$form['payment_information'] = array(
   ...
   '#states' => array(
     'expanded' => array(
        '[name="payment"]' => array('checked' => TRUE)
      ),
   ),
);
Comparisons
                                                         26


$form['payment_information'] = array(
   ...
   '#states' => array(
     'expanded' => array(
        '[name="payment"]' => array('checked' => TRUE)
      ),
   ),                                      ===
);
Advanced Comparisons
                                               27


states.Dependant.comparisons = {
   'RegExp': function (reference, value) {
     return reference.test(value);
   },
   'Function': function (reference, value) {
     return reference(value);
   }
};
Advanced Comparisons
                                               27


states.Dependant.comparisons = {
   'RegExp': function (reference, value) {
     return reference.test(value);
   },
   'Function': function (reference, value) {
     return reference(value);
   }
};
       Prototype name
JSON only allows
strings and numbers   28
:(   29
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}$'),
   ),
),
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}$'),
   ),
),
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;
     }
  };
Advanced Comparisons
                                                      32




'invalid' => array(
   '[name="card_number"]' => array(
     '!value' => array('regex' => '^(d{4}[ -]*){4}$'),
   ),
),
Transitions
State changes
                                               34


◆   Transition an element from one state
    to another

 Direct                  Indirect
◆ Triggered by user     ◆ Triggered by other

◆ Notify listeners        element
                        ◆ Transition element
State changes
                                                  35




$(document).bind('state:checked', function(e) {
  if (e.trigger) {
    $(e.target).attr('checked', e.value);
  }
});
State changes
                                                  35




$(document).bind('state:checked', function(e) {
  if (e.trigger) {
    $(e.target).attr('checked', e.value);
  }
});
State changes
                                                  35




$(document).bind('state:checked', function(e) {
  if (e.trigger) {
    $(e.target).attr('checked', e.value);
  }
});
State changes
                                                  35




$(document).bind('state:checked', function(e) {
  if (e.trigger) {
    $(e.target).attr('checked', e.value);
  }
});
State changes
                                                  35




$(document).bind('state:checked', function(e) {
  if (e.trigger) {
    $(e.target).attr('checked', e.value);
  }
});
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
Future Work
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?
Copy & Paste support   39
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}
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}}
Questions?
                              42




kkaefer.com/2010/states.pdf
    mail@kkaefer.com

More Related Content

Instant Dynamic Forms with #states

  • 1. Instant Dynamic Forms with #states
  • 4. 4
  • 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
  • 55. JSON only allows strings and numbers 28
  • 56. :( 29
  • 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?
  • 71. Copy & Paste support 39
  • 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