      1 (function($, rf) {
      2
      3     rf.ui = rf.ui || {};
      4
      5     var defaultOptions = {
      6         useNative : false
      7     };
      8
      9     rf.ui.Focus = rf.BaseComponent.extendClass({
     10
     11         name : "Focus",
     12
     13         init : function(componentId, options) {
     14             $super.constructor.call(this, componentId);
     15             options = this.options = $.extend({}, defaultOptions, options);
     16             this.attachToDom(this.id);
     17
     18             var focusInput = $(document.getElementById(componentId + 'InputFocus'));
     19             var focusCandidates = this.options.focusCandidates;
     20
     21             $(document).on('focus', ':tabbable', function(e) {
     22                 var target = $(e.target);
     23                 if (!target.is(':editable')) {
     24                     return;
     25                 }
     26                 var ids = e.target.id || '';
     27                 target.parents().each(function() {
     28                     var id = $(this).attr('id');
     29                     if (id) {
     30                         ids += ' ' + id;
     31                     }
     32                 });
     33                 focusInput.val(ids);
     34                 rf.log.debug('Focus - clientId candidates for components: ' + ids);
     35             });
     36
     37             if (this.options.mode === 'VIEW') {
     38                 $(document).on('ajaxsubmit submit', 'form', function(e) {
     39                     var form = $(e.target);
     40                     var input = $("input[name='org.richfaces.focus']", form);
     41                     if (!input.length) {
     42                         input = $('<input name="org.richfaces.focus" type="hidden" />').appendTo(form);
     43                     }
     44                     input.val(focusInput.val());
     45                 });
     46             }
     47
     48             this.options.applyFocus = $.proxy(function() {
     49                 var tabbables = $();
     50
     51                 if (focusCandidates) {
     52                     var candidates = focusCandidates;
     53                     rf.log.debug('Focus - focus candidates: ' + candidates);
     54                     candidates = candidates.split(' ');
     55                     $.each(candidates, function(i, v) {
     56                         var candidate = $(document.getElementById(v));
     57                         tabbables = tabbables.add($(":tabbable", candidate));
     58
     59                         if (candidate.is(":tabbable")) {
     60                             tabbables = tabbables.add(candidate);
     61                         }
     62                     });
     63
     64                     if (tabbables.length == 0) {
     65                         tabbables = $('form').has(focusInput).find(':tabbable')
     66                     }
     67                 } else if (this.options.mode == 'VIEW') {
     68                     tabbables = $("body form:first :tabbable");
     69                 }
     70
     71                 if (tabbables.length > 0) {
     72                     tabbables = tabbables.sort(sortTabindex);
     73                     tabbables.get(0).focus();
     74                 }
     75             }, this);
     76         },
     77
     78         applyFocus : function() {
     79             $(this.options.applyFocus);
     80         },
     81
     82         // destructor definition
     83         destroy : function() {
     84             // define destructor if additional cleaning is needed but
     85             // in most cases its not nessesary.
     86             // call parent’s destructor
     87             $super.destroy.call(this);
     88         }
     89     });
     90
     91     /**
     92      * Returns the tabindex sort order of two elements based on their tabindex and position in the DOM, following real tabbing
     93      * order implemented by browsers.
     94      *
     95      * Returns negative number when element A has lesser tabindex than B or it is closer the start of the DOM; returns negative
     96      * number when element B has lesser tabindex than A or it is closer the start of the DOM; returns 0 if both A and B points
     97      * to same element.
     98      */
     99     var sortTabindex = function(a, b) {
    100         var result = sortTabindexNums($(a).attr('tabindex'), $(b).attr('tabindex'));
    101
    102         return (result != 0) ? result : sortByDOMOrder(a, b);
    103     };
    104
    105     /**
    106      * Sorts two tabindex values (positive number or undefined).
    107      *
    108      * Returns negative number when tabindex A is lesser than B; returns positive number when tabindex B is lesser than A;
    109      * returns 0 if both A and B has same values.
    110      */
    111     var sortTabindexNums = function(a, b) {
    112         if (a) {
    113             if (b) {
    114                 return a - b;
    115             } else {
    116                 return -1;
    117             }
    118         } else {
    119             if (b) {
    120                 return +1;
    121             } else {
    122                 return 0;
    123             }
    124         }
    125     };
    126
    127     /**
    128      * Detects absolute order of two elements in the DOM tree.
    129      *
    130      * Returns negative number when element A is closer the start of the DOM; returns positive number when element B is closer
    131      * the start of the DOM; returns 0 if both A and B points to same element
    132      */
    133     var sortByDOMOrder = function(a, b) {
    134         var r = searchCommonParent(a, b);
    135         if (a == b) {
    136             return 0;
    137         } else if (r.parent == a) {
    138             return -1;
    139         } else if (r.parent == b) {
    140             return +1;
    141         } else {
    142             return $(r.aPrevious).index() - $(r.bPrevious).index();
    143         }
    144     };
    145
    146     /**
    147      * Search for common parent for two given elements.
    148      *
    149      * returns object containing following parameters:
    150      *
    151      * result.parent - the commnon parent for A and B result.aPrevious - the parent's direct child which is on the branch
    152      * leading to A in DOM tree result.bPrevious - the parent's direct child which is on the branch leading to B in DOM tree
    153      */
    154     var searchCommonParent = function(a, b) {
    155         var aParents = $(a).add($(a).parents()).get().reverse();
    156         var bParents = $(b).add($(b).parents()).get().reverse();
    157         var r = {
    158             aPrevious : a,
    159             bPrevious : b
    160         };
    161         $.each(aParents, function(i, ap) {
    162             $.each(bParents, function(j, bp) {
    163                 if (ap == bp) {
    164                     r.parent = ap;
    165                     return false;
    166                 }
    167                 r.bPrevious = bp;
    168             });
    169             if (r.parent) {
    170                 return false;
    171             }
    172             r.aPrevious = ap;
    173         });
    174         if (!r.parent) {
    175             return null;
    176         }
    177         return r;
    178     };
    179
    180     /**
    181      * Exposes sortTabindex family of functions for testing
    182      */
    183     rf.ui.Focus.__fn = {
    184         'sortTabindex' : sortTabindex,
    185         'sortTabindexNums' : sortTabindexNums,
    186         'searchCommonParent' : searchCommonParent,
    187         'sortByDOMOrder' : sortByDOMOrder
    188     }
    189
    190     // define super class reference - reference to the parent prototype
    191     var $super = rf.ui.Focus.$super;
    192 })(RichFaces.jQuery, RichFaces);