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