/** * @class ST.locator.Strategy * This class is the default locator strategy. This can also be used as a base class for * other locator strategies. To register custom locator strategies, see * {@link ST#addLocatorStrategy} or {@link ST#setLocatorStrategies}. */ST.locator.Strategy = ST.define({ // TODO refactor target hunting methods to return targets found, not pass around arrays ignoreElIdRe: /^ext-(?:gen|element)(?:\d+)$/, validIdRe: /^[a-z_][a-z0-9\-_]*$/i, autoGenIdRe: /^ext-/, includeFullLocator: ST.apply(ST.apply({}, ST.event.Event.keyEvents), ST.event.Event.focusEvents), constructor: function (config) { var me = this; ST.apply(me, config); me.locate = me._initter; // hook the first call to locate() }, /** * Initializes this instance. This method is called immediately prior to the first * call to `locate`. This is done in a deferred manner to ensure that any necessary * code has been loaded (such as Ext JS). * * @since 1.0.1 * @protected */ init: function () { var me = this, x = ST.Ext, cssIgnore = me.cssIgnore, cssIgnorePat; // Copy off the prototype me.cssIgnore = cssIgnore = ST.apply({}, cssIgnore); x = x && x.baseCSSPrefix; cssIgnorePat = me.getCssIgnorePatterns(x); if (cssIgnorePat.length) { me.cssIgnoreRe = new RegExp(cssIgnorePat.join('|')); } if (x) { ST.apply(cssIgnore, ST.Array.toMap([ x + 'body', x + 'box-item', x + 'box-target', x + 'btn-inner', x + 'btn-wrap', x + 'component', x + 'fa', x + 'fit-item', x + 'form-field', x + 'grid-cell-inner', x + 'noicon' ])); } }, _initter: function () { var me = this; delete me.locate; me.init(); return me.locate.apply(me, arguments); }, /** * This method should append target locators to the `targets` array based on the * provided `el`. Each identified target should be appended to the `targets` array * (e.g., using `push()`). * * Because a locator can describe the passed `el` or a parent node, results added to * the `targets` array should be an array consisting of the element and its locator. * For example: * * if (el.id) { * targets.push([ el, '@' + el.id ]); * } * * @param {HTMLElement} el The element for which to generate target locator(s). * @param {Array[]} targets The array to which to append targets and their locator(s) * as an array of `[ el, locator ]`. * @param {Object} ev * @param {Boolean} noComponentLocators if `true` then don't return locators which locate Ext Components * @return {Boolean} Returns `true` if any `targets` were generated. * @method locate */ locate: function (el, targets, ev, noComponentLocators) { var me = this, ExtJS = ST.Ext, good = false, c, cmp, fly; if (ExtJS && ExtJS.ComponentQuery && !noComponentLocators) { fly = ST.fly(el); cmp = fly && fly.getComponent(); for (c = cmp; c; c = c.getRefOwner && c.getRefOwner()) { if (!me.ignoreCmp(c)) { if (me.getCQ(c, el, targets, ev)) { me.processCustomBuilder(c, el, targets, ev); good = true; } break; } } } if (me.getAtPath(el, targets)) { good = true; } if (me.getXPath(el, targets)) { good = true; } return !!good; }, getAtPath: function (el, targets) { if (!el) { el = document.body; } var me = this, good = false, path = [], stopper = (el.ownerDocument || el).body, count, sibling, t, tag; for (t = el; t; t = t.parentNode) { if (t == stopper) { path.unshift('@'); good = true; break; } if (t.id && t.id.indexOf('/') < 0 && !me.ignoreElIdRe.test(t.id)) { path.unshift('@' + t.id); good = true; break; } for (count = 1, sibling = t; (sibling = sibling.previousSibling); ) { if (sibling.tagName == t.tagName) { ++count; } } tag = t.tagName && t.tagName.toLowerCase(); if (tag) { if (count < 2) { path.unshift(tag); } else { path.unshift(tag + '[' + count + ']'); } } else if (t.window == t) { // must use == for IE8 (from Ext.dom.Element) break; } } if (targets && good) { var tmpel = ST.Locator.find(path[0]); if (tmpel && tmpel == el) { targets.push([t, path[0]]); } if (path.length > 1) { targets.push([el, path.join('/')]); } } return good; }, getXPath: function (el, targets) { if (!el) { el = document.body; } if (el.$className) { el = el.dom; } var fly = ST.fly(el), xpath = fly.getXPath(), good = !!xpath; if (targets && good) { targets.push([el, xpath]); } return good; }, /** * Generates a set of ComponentQuery candidates for the given Component. The generated * CQ selectors are "shallow" in that they do not describe the containment hierarchy * of the component. * * @param {Ext.Component} cmp * @param {HTMLElement} el The actual element to target. If this parameter is not * `null` this method may include an additional DOM query on the generated selectors * separated by "=>" (a "Composite Query"). * @param {Array[]} targets The array to which to append targets and their locator(s) * as an array of `[ el, locator ]`. * @param {Object} ev * @return {Boolean} Returns `true` if any `targets` were generated. * @since 1.0.1 */ getCQ: function (cmp, el, targets, ev) { var me = this, configList = me.configList, len = configList.length, good = false, n = targets && targets.length, i, item, k, sel, xtype; for (i = 0; i < len; ++i) { if (me.getCQForProperty(cmp, configList[i], targets)) { good = true; } } if (!good) { xtype = me.getXType(cmp); if (xtype) { if (targets) { targets.push([cmp.el, xtype]); } good = true; } } if (targets && el && n < (k = targets.length)) { sel = me.getItemSelector(cmp, el, ev); if (sel) { item = sel[0]; sel = ' => ' + sel[1]; for (; n < k; ++n) { targets[n][0] = item; targets[n][1] += sel; } } } return good; }, /** * Generates a ComponentQuery selector for the given Component using the specified * config property. The selector is "shallow" in that they do not describe the * containment hierarchy of the component. * * The supported properties are listed in the `configList` array. * * @param {Ext.Component} cmp * @param {String} prop The property to use in the generated selector. * @param {Array[]} targets The array to which to append targets and their locator(s) * as an array of `[ el, locator ]`. * @return {Boolean} Returns `true` if any `targets` were generated. * @since 1.0.1 */ getCQForProperty: function (cmp, prop, targets) { var extractor = this.extractors[prop], good = false; if (extractor) { good = extractor.call(this, cmp, targets); } return good; }, getItemSelector: function (cmp, el, ev) { var view = ST.fly(el).getComponent(), item = view.findItemByChild && view.findItemByChild(el), cell, col, colId, recId, ret, idx; if (item) { recId = item.getAttribute('data-recordindex'); if (recId) { ret = [item, '[data-recordindex=' + JSON.stringify(recId) + ']']; for (cell = el; cell && cell !== item; cell = cell.parentNode) { colId = cell.getAttribute('data-columnid'); col = colId && ST.Ext.getCmp(colId); if (col) { if (!col.autoGenId) { ret[0] = cell; ret[1] += ' [data-columnid=' + JSON.stringify(colId) + ']'; } break; } } } else { // try to find item by index...not perfect, but something idx = el.tagName.toLowerCase() === 'li' && view.indexOf && view.indexOf(el); if (typeof idx !== 'undefined' && idx >= 0) { ret = [item, 'li:nth-child(' + (idx + 1) + ')']; } } } else if (ev && this.includeFullLocator[ev.type]) { var matches = cmp.el.dom.querySelectorAll(el.tagName); if (matches && matches.length === 1) { ret = [el, el.tagName.toLowerCase()]; } } return ret; }, extractors: { iconCls: function (cmp, targets) { var iconCls = cmp.iconCls; if (iconCls) { if (targets) { targets.push([ cmp.el.dom, this.getXType(cmp) + '[iconCls="' + iconCls + '"]' ]); } return 1; } return 0; }, id: function (cmp, targets) { var me = this, id = me.getCmpId(cmp), parent; if (cmp.autoGenId || !me.validIdRe.test(id) || me.autoGenIdRe.test(id)) { return 0; } // We can still have an autoGenId on a parent that is then used to produce // this id (panel-1010_header). So start with the parent and see if its id // is a prefix of cmp's id and if it is an autoGenId. for (parent = cmp; (parent = parent.getRefOwner && parent.getRefOwner()); ) { if (!ST.String.startsWith(id, me.getCmpId(parent))) { break; } if (parent.autoGenId) { return 0; } } if (targets) { targets.push([ cmp.el.dom, '#' + id ]); } return 1; } }, /** * @property {Object} classIgnore * Property names are the Ext JS classes to ignore. */ classIgnore: { // }, /** * @property {Object} cmpIgnore * Property names are the component xtypes to ignore. Values are either the xtype * of the parent if the component should only be ignored when inside this type of * parent, or `true` to always ignore. */ cmpIgnore: { gridview: 'grid', tableview: 'grid', treeview: 'tree' }, /** * @property {String[]} configList * The list of config properties used to identify components in order of priority. * @since 1.0.1 */ configList: [ 'id', 'stateId', 'reference', 'itemId', 'fieldLabel', //Moving fieldLabel to the top of generic identifier list to give preference. Fix for ORION-2300 'boxLabel', 'name', 'iconCls', 'text', 'label' ], /** * @property {Object} cssIgnore * Property names are the CSS class names to ignore. */ cssIgnore: { fa: 1, // FontAwesome // Some old framework bugs rendered null/undefined into class attribute 'null': 1, 'undefined': 1 }, /** * Returns the id of the given Component. * @param {Ext.Component} cmp * @return {String} */ getCmpId: function (cmp) { return cmp.getId ? cmp.getId() : cmp.id; }, /** * Returns an array of `RegExp` patterns that describe CSS classes to be ignored. * @param {String} baseCSSPrefix The CSS prefix for Ext JS (typically "x-"). * @return {String[]} * @since 1.0.1 * @protected */ getCssIgnorePatterns: function (baseCSSPrefix) { var x = baseCSSPrefix; if (!x) { return []; } x = '^' + x; return [ x + 'noborder' ]; }, /** * Returns the `xtype` of the given Component. If the component has multiple xtypes, * the primary is returned. * * @param {Ext.Component} cmp * @return {String} * @since 1.0.1 */ getXType: function (cmp) { var xtype = cmp.getXType && cmp.getXType(); if (!xtype) { xtype = cmp.xtype || (cmp.xtypes && cmp.xtypes[0]); } xtype = cmp.$reactorComponentName || xtype; return xtype; }, /** * Returns `true` if the given CSS class should be ignored. * @param {String} cls * @return {Boolean} * @protected */ ignoreCls: function (cls) { var cssIgnoreRe = this.cssIgnoreRe; return this.cssIgnore[cls] || (cssIgnoreRe && cssIgnoreRe.test(cls)); }, ignoreCmp: function (cmp) { var me = this, xtype = me.getXType(cmp), ignore = me.cmpIgnore[xtype], parent, parentXType; if (ignore) { if (typeof ignore !== 'string') { return true; } parent = cmp.getRefOwner && cmp.getRefOwner(); if (parent) { if (typeof parent.isXType === 'function') { if (parent.isXType(ignore)) { return true; } } else { parentXType = me.getXType(parent); if (parentXType === ignore) { return true; } } } } if (me.classIgnore[cmp.$className]) { return true; } return false; }, /** * Returns the array of CSS classes given the `className` (space-separated classes). * The ignored classes have been removed from the array. * * @param {String} cls * @return {String[]} * @since 1.0.1 * @protected */ splitCls: function (cls) { var array = ST.String.split(ST.String.trim(cls)), len = array.length, c, i, ret; for (i = 0; i < len; ++i) { c = array[i]; if (c && !this.ignoreCls(c)) { (ret || (ret = [])).push(c); } } return ret; }, customBuilders: { datePicker: function (cmp, el, targets, ev) { var me = this, node = el, classes = el.classList, len = targets.length, isItem = false, isField = !!cmp.pickerField, // could be standalone datepicker lt423 = Ext.versions.extjs.isLessThan('4.2.3'), is5 = Ext.versions.extjs.gtEq('5'), id, postLen, i; if (classes.contains('x-datepicker-cell')) { isItem = true; } else if (classes.contains('x-datepicker-date')) { node = node.parentNode; isItem = true; } else { el = cmp.el; } // if this is a picker attached to a field, make the field the root query object if (isField) { cmp = cmp.pickerField; } me.getCQ(cmp, el, targets, ev); postLen = targets.length; if (isItem) { id = node.id.replace(/^datefield|datepicker-[0-9]*-/, ''); for (i=len; i<postLen; i++) { if (isField) { if (lt423) { targets[i][1] = 'datepicker[hidden=false]'; } else { targets[i][1] += ' datepicker'; } } targets[i][0] = el; if (lt423) { targets[i][1] += ' => ' + '[title="' + node.title + '"] a'; } else if (is5) { targets[i][1] += ' => ' + '[aria-label="' + node.getAttribute('aria-label') + '"] div'; } } } else if (isField) { for (i=len; i<postLen; i++) { targets[i][0] = el; if (lt423) { targets[i][1] = 'datepicker[hidden=false]'; } else { targets[i][1] += ' datepicker'; } } } me.promoteLocators(targets, len, postLen-len); }, timePicker: function (cmp, el, targets, ev) { var me = this, len = targets.length, postLen, i; me.getCQ(cmp.pickerField, el, targets, ev); postLen = targets.length; for (i=len; i<postLen; i++) { targets[i][0] = el; if (Ext.versions.extjs.isLessThan('4.2.3')) { targets[i][1] = targets[i][1].replace(/.*=>/, 'timepicker[hidden=false] =>'); } else { targets[i][1] = targets[i][1].replace('=>', 'timepicker =>'); } // if we're scrolling, the scroll is happening on the boundlist, so target its element if (ev && ev.type === 'scroll' && targets[i][1].search(/=>/) === -1) { if (Ext.versions.extjs.isLessThan('4.2.3')) { targets[i][1] = ' timepicker[hidden=false]'; } else { targets[i][1] += ' timepicker'; } } } me.promoteLocators(targets, len, postLen-len); }, boundList: function (cmp, el, targets, ev) { var me = this, len = targets.length, postLen, i, boundlists, index; var owner = cmp.up('itemselectorfield') || cmp.up('multiselect'); if (owner) { me.getCQ(owner, el, targets, ev); boundlists = owner.query('boundlist'); if (boundlists.length > 0) { index = boundlists.indexOf(cmp); } } else if (cmp.pickerField) { me.getCQ(cmp.pickerField, el, targets, ev); } else { return false; } postLen = targets.length; for (i=len; i<postLen; i++) { targets[i][0] = el; if (typeof boundlists !=='undefined' && boundlists.length > 1) { if (index == 0) { targets[i][1] = targets[i][1].replace('=>', 'boundlist:first =>'); } else { targets[i][1] = targets[i][1].replace('=>', 'boundlist:last =>'); } } else if (!Ext.versions.extjs.isLessThan('4.2.3') || owner) { targets[i][1] = targets[i][1].replace('=>', 'boundlist =>'); } else { targets[i][1] = targets[i][1].replace(/.*=>/, 'boundlist[hidden=false][pickerField] =>'); } // if we're scrolling, the scroll is happening on the boundlist, so target its element if (ev && ev.type === 'scroll' && targets[i][1].search(/=>/) === -1) { if (typeof boundlists !=='undefined' && boundlists.length > 1) { if (index == 0) { targets[i][1] += ' boundlist:first'; } else { targets[i][1] += ' boundlist:last'; } } else if (!Ext.versions.extjs.isLessThan('4.2.3') || owner) { targets[i][1] += ' boundlist'; } else { targets[i][1] = ' boundlist[hidden=false][pickerField]'; } } } me.promoteLocators(targets, len, postLen-len); }, tablePanel: function (cmp, el, targets, ev) { var me = this, len = targets.length, postLen, i; // if a scroll is happening on a grid, target the dataview of the grid if (ev && ev.type === 'scroll') { me.getCQ(cmp, el, targets, ev); postLen = targets.length; for (i = len; i < postLen; i++) { targets[i][1] += ' dataview'; } me.promoteLocators(targets, len, postLen - len); } }, component: function (cmp, el, targets, ev) { var me = this, len = targets.length, postLen, listOwner, dvOwner; if (ev && ev.type === 'scroll' && Ext.versions.modern) { listOwner = cmp.up('list'); dvOwner = cmp.up('dataview'); // since modern lists/grids may have components as items (or as items of items!), // the scroll target might get lost; so we'll sniff for it and standardize to the view if found if (listOwner || dvOwner) { cmp = listOwner || dvOwner; me.getCQ(cmp, el, targets, ev); postLen = targets.length; me.promoteLocators(targets, len, postLen - len); } } }, gridColumn: function (cmp, el, targets, ev) { var me = this, len = targets.length, classicTriggerCls = 'x-column-header-trigger', modernTriggerCls = 'x-trigger-el', triggerCls = [classicTriggerCls, modernTriggerCls], classicCheckboxCls = 'x-column-header-checkbox', modernCheckboxCls = 'x-checkbox-el', checkboxCls = [classicCheckboxCls, modernCheckboxCls], isTrigger = false, isCheckbox = false, isCheckerHd = cmp.isCheckerHd, // classic selection checkbox isModernSelectionCheckbox = cmp.isXType('selectioncolumn'), // modern selection checkbox isSelectionCheckbox = isCheckerHd || isModernSelectionCheckbox, isModern = Ext.versions.modern, owner = cmp.up('tablepanel') || cmp.up('grid'), // support classic and modern postLen, i, selector, type; Ext.Array.forEach(checkboxCls, function (cls) { if (el.classList.contains(cls)) { isCheckbox = true; } }); Ext.Array.forEach(triggerCls, function (cls) { if (el.classList.contains(cls)) { isTrigger = true; } }); if (owner) { me.getCQ(owner, el, targets, ev); postLen = targets.length; for (i = len; i < postLen; i++) { targets[i][0] = el; selector = cmp.dataIndex || cmp.getDataIndex && cmp.getDataIndex(); type = 'dataIndex'; if (!selector || isSelectionCheckbox) { // if a checkbox header, we want to use it instead of the text if (isCheckerHd) { selector = true; type = 'isCheckerHd'; } else if (isModernSelectionCheckbox) { selector = true; type = 'rendered'; } else { selector = cmp.text || cmp.getText && cmp.getText(); type = 'text'; } } if (selector) { targets[i][1] += ' ' + me.getXType(cmp) + '[' + type + '="' + selector + '"]'; // if it's a trigger or a checkbox header, we want to use the actual element since the header // could have extra content in it (text, etc) if (isTrigger) { targets[i][1] += ' => .' + (isModern ? modernTriggerCls : classicTriggerCls); } else if (isModernSelectionCheckbox) { targets[i][1] += ' => .x-checkbox-el'; // modern } else if (isCheckbox) { targets[i][1] += ' => .' + (isModern ? modernCheckboxCls : classicCheckboxCls); // modern, classic 6.2.x + } else if (isCheckerHd) { targets[i][1] += ' => .x-column-header-text'; // < classic 6.2.x } } } me.promoteLocators(targets, len, postLen - len); } }, menuItem: function (cmp, el, targets, ev) { //filter for textfield does not perform pointer or mouse event //so it doesn't give back child element so here manually we move to child component and //put repeat check so that it won't wind up in infinte loop. if (ev.type === 'blur' && ev.relatedTarget) { if (ev.relatedTarget.localName === 'input' && !ev.repeat && cmp.down) { cmp = cmp.down().down(); ev.repeat = true; } } var me = this, len = targets.length, buttonOwner = cmp.up('button'), headerOwner = cmp.up('headercontainer'), prereqs = [], postLen, i, fn, menuFn, itemFn; // build out a component query for the menu; could be nested, so recursively call this as needed // so that we get at the right things menuFn = function (item, locator) { var partial, owner, text; if (item.isXType('menuitem')) { text = item.getText ? item.getText() : item.text; partial = me.getXType(item) + '[text="' + text + '"]'; if (locator) { partial += ' > '; } else { locator = ''; } locator = menuFn(item.up('menu'), partial + locator); } else if (item.isXType('menu')) { locator = 'menu > ' + locator; owner = item.up('menuitem'); if (owner) { locator = menuFn(owner, locator); } } return locator; } if (buttonOwner) { me.getCQ(buttonOwner, el, targets, ev); postLen = targets.length; for (i=len; i<postLen; i++) { targets[i][0] = el; targets[i][1] += ' ' + menuFn(cmp); } } else if (headerOwner) { me.getCQ(cmp.up('grid'), el, targets, ev); postLen = targets.length; for (i=len; i<postLen; i++) { targets[i][0] = el; if (Ext.versions.modern) { targets[i][1] = menuFn(cmp).replace(/^menu/, 'menu[floated=true][hidden=false]'); } else if (Ext.versions.extjs.isLessThan('6.2.0')) { targets[i][1] = menuFn(cmp).replace(/^menu/, 'menu[floating=true][hidden=false]'); } else { targets[i][1] += ' ' + menuFn(cmp); } } } if (ev && (ev.type === 'mousedown' || ev.type === 'pointerdown' || (ev.type === 'blur' && ev.repeat === true))) { itemFn = function (_cmp) { var owner = _cmp.up('menuitem'), parent, match; if (owner) { match = owner; } else { // in earlier versions of Ext JS, menus didn't have menu items as ancestors // so we need to be a bit more prescriptive in searching for the correct ancestor parent = _cmp.up('menu'); owner = parent.up('menu'); if (owner) { // go through items collection and see if menu matches owner.items.each(function (item) { if (item.menu && item.menu === parent) { match = item; } }); } } return match; } fn = function (_cmp) { var itemOwner = itemFn(_cmp), ownerTargets = [], prereq, x; if (itemOwner) { me.locate(itemOwner.el, ownerTargets, ev); prereq = { type: 'menuitemover', target: ownerTargets[0][1], targets: [] }; for (x=0; x<ownerTargets.length; x++) { prereq.targets.push({ target: ownerTargets[x][1] }); } prereqs.push(prereq); // call recursively until we reach the top fn(itemOwner); } }; fn(cmp); if (prereqs.length) { ev.prereq = prereqs.reverse(); } } me.promoteLocators(targets, len, postLen-len); }, spinnerField: function (cmp, el, targets, ev) { var me = this, node = el, len = targets.length, isTrigger = false, triggerCls, postLen, i, cls; triggerCls = el.className.match(/x-form-spinner-up|x-form-spinner-down/); if (triggerCls && triggerCls.length) { isTrigger = true; cls = triggerCls[0]; } if (isTrigger) { me.getCQ(cmp, el, targets, ev); postLen = targets.length; for (i = len; i < postLen; i++) { targets[i][0] = node; targets[i][1] += ' => ' + '[class*="' + cls + '"]'; } } if (postLen !== 'undefined') { me.promoteLocators(targets, len, postLen - len); } }, colorPicker: function (cmp, el, targets, ev) { var me = this, node = el, len = targets.length, isItem = false, isButton = false, lt423 = Ext.versions.extjs.isLessThan('4.2.3'), cls, postLen, i; if (el.className.match(/x-color-picker-item-inner/)) { isItem = true; node = el.parentNode; } else if (el.className.match(/x-color-picker-item/)) { isItem = true; } if (cmp.up('button')) { cmp = cmp.up('button'); el = cmp.el; isButton = true; } me.getCQ(cmp, el, targets, ev); postLen = targets.length; if (isItem) { cls = node.className.match(/color-[0-9A-Z]{6}/); if (cls) { for (i = len; i < postLen; i++) { targets[i][0] = node; if (isButton) { targets[i][1] += ' colorpicker'; } else { targets[i][1] = 'colorpicker[rendered=true][hidden=false]'; } if (lt423) { targets[i][1] += ' => ' + '[class*="' + cls[0] + '"]'; } else { targets[i][1] += ' => ' + '[class*="' + cls[0] + '"]'; } } } } me.promoteLocators(targets, len, postLen - len); }, monthPicker: function (cmp, el, targets, ev) { var len = targets.length, i; for (i = 0; i < len; i++) { targets[i][1] = targets[i][1].replace('monthpicker', 'monthpicker[hidden=false]'); } }, button: function (cmp, el, targets, ev) { var me = this, len = targets.length, lt423 = Ext.versions.extjs.isLessThan('4.2.3'), lt5 = Ext.versions.extjs.isLessThan('5'), postLen, i, parent, isField, fld, pos, isTodayBtn, isMonthBtn, toolbarCls; // only proceed if this is a button within a datepicker if (cmp.up('monthpicker')) { parent = cmp.up('monthpicker'); if (lt5) { var stel = ST.get(parent.container.dom) var found = stel && stel.getComponent(); if (found) { isField = !!found.pickerField; fld = found.pickerField; } } else { isField = !!cmp.up('datepicker').pickerField; fld = cmp.up('datepicker').pickerField; } pos = cmp === parent.okBtn ? 1 : parent.cancelBtn ? 2 : 1; me.getCQ(cmp, el, targets, ev); postLen = targets.length; for (i = len; i < postLen; i++) { if (isField) { targets[i][1] = 'monthpicker[hidden=false] => .x-monthpicker-buttons .x-btn:nth-child(' + pos + ')'; } else { targets[i][1] += ' => .x-monthpicker-buttons .x-btn:nth-child(' + pos + ')'; } } } else if (cmp.up('datepicker')) { parent = cmp.up('datepicker'); isField = !!cmp.up('datepicker').pickerField; fld = cmp.up('datepicker').pickerField; // could be standalone datepicker if (isField && !lt423) { me.getCQ(fld, fld.el, targets, ev); } else { me.getCQ(cmp, el, targets, ev); } postLen = targets.length; for (i = len; i < postLen; i++) { if (lt423) { isTodayBtn = fld.picker.todayBtn === cmp; isMonthBtn = fld.picker.monthBtn === cmp; toolbarCls = isTodayBtn ? '.x-datepicker-footer' : '.x-datepicker-header'; targets[i][1] = 'datepicker[hidden=false] => ' + toolbarCls + ' .x-btn'; } else { if (isField) { // switch el to actual button since we're rooting to field targets[i][0] = el; targets[i][1] += ' datepicker button[text="' + cmp.text + '"]'; } else { targets[i][1] = 'datepicker ' + targets[i][1]; } } } } if (typeof postLen !== 'undefined') { me.promoteLocators(targets, len, postLen - len); } }, radio: function (cmp, el, targets, ev) { var me = this, len = targets.length, owner = cmp.up('radiogroup'), postLen, i; // If the radio button belongs to a radio group, get a component query for that group if (owner) { me.getCQ(owner, el, targets, ev); postLen = targets.length; if (postLen > len) { for (i = len; i < postLen; i++) { targets[i][0] = el; // Generate a combined radio group and radio button locator, using first radio locator targets[i][1] += ' ' + targets[0][1]; } me.promoteLocators(targets, len, postLen - len); } } }, checkboxField: function (cmp, el, targets, ev) { var me = this, len = targets.length, owner = cmp.up('checkboxgroup'), postLen, i; // If the checkbox belongs to a checkbox group, get a component query for that group if (owner) { me.getCQ(owner, el, targets, ev); postLen = targets.length; if (postLen > len) { for (i = len; i < postLen; i++) { targets[i][0] = el; // Generate a combined checkbox group and checkbox locator, using first checkbox locator targets[i][1] += ' ' + targets[0][1]; } me.promoteLocators(targets, len, postLen - len); } } } }, promoteLocators: function (targets, pos, count) { var map = {}, spliced = targets.splice(pos, count).reverse(), i, key, x; for (i = 0; i < spliced.length; i++) { targets.unshift(spliced[i]); } for (x = targets.length - 1; x >= 0; --x) { key = targets[x][1]; if (map[key]) { // get rid of array item, it's a dupe targets.splice(x, 1); } else { map[key] = true; } } }, findCustomBuilder: function (cmp) { // Default to use "getXTypes", or failing that use "xtypesChain" - copy it so we don't mutate the original var chain = cmp.getXTypes ? cmp.getXTypes().split('/') : cmp.xtypesChain.slice(), xtypeList = chain.reverse(), // reverse the xytype list so we get most specific first matchers = ['monthpicker', 'datepicker', 'timepicker', 'colorpicker', 'tablepanel', 'component', 'boundlist', 'spinnerfield', 'gridcolumn', 'menuitem', 'button', 'radio', 'checkboxfield'], len = matchers.length, xtype, i; for (i = 0; i < len; i++) { if (Ext.Array.contains(matchers, xtypeList[i])) { xtype = xtypeList[i]; break; } } return xtype; }, processCustomBuilder: function (cmp) { var me = this, xtype = me.findCustomBuilder(cmp); if (xtype) { switch (xtype) { case 'button': me.customBuilders.button.apply(me, arguments); break; case 'spinnerfield': me.customBuilders.spinnerField.apply(me, arguments); break; case 'monthpicker': me.customBuilders.monthPicker.apply(me, arguments); break; case 'datepicker': me.customBuilders.datePicker.apply(me, arguments); break; case 'timepicker': me.customBuilders.timePicker.apply(me, arguments); break; case 'colorpicker': me.customBuilders.colorPicker.apply(me, arguments); break; case 'boundlist': me.customBuilders.boundList.apply(me, arguments); break; case 'gridcolumn': me.customBuilders.gridColumn.apply(me, arguments); break; case 'menuitem': me.customBuilders.menuItem.apply(me, arguments); break; case 'tablepanel': me.customBuilders.tablePanel.apply(me, arguments); break; case 'radio': me.customBuilders.radio.apply(me, arguments); break; case 'checkboxfield': me.customBuilders.checkboxField.apply(me, arguments); break; case 'component': me.customBuilders.component.apply(me, arguments); break; } } }}, function (Strategy) { function make (prop, includeXType, skipGetter) { var attr = '[' + prop + '=', getter = 'get' + prop.charAt(0).toUpperCase() + prop.substring(1); Strategy.prototype.extractors[prop] = function (cmp, targets) { var value; if (skipGetter || prop in cmp) { value = cmp[prop]; } else if (cmp[getter]) { // If stateId is equal id locator is not able to find a match if (cmp[getter]() !== cmp['id']) { value = cmp[getter](); } } if (value || value === 0) { if (typeof(value) === 'string') { value = value.replace(/,/g, '\\,'); } if (targets) { // some configs (like text) could have quotes targets.push([ cmp.el.dom, (includeXType ? this.getXType(cmp) : '') + attr + JSON.stringify(value) + ']' ]); } return true; } }; } make('reference', true); make('itemId', true, true); make('name', true, true); make('stateId'); make('text', true); make('boxLabel', true, false); make('fieldLabel', true); make('label', true);});