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);