hangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegram
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
445 lines
16 KiB
445 lines
16 KiB
Ext.define('Ext.app.bindinspector.ComponentList', { |
|
alias: 'widget.bindinspector-componentlist', |
|
extend: 'Ext.tree.Panel', |
|
|
|
requires: [ |
|
'Ext.form.field.Text' |
|
], |
|
|
|
rootVisible: false, |
|
title: 'Component Tree', |
|
hideHeaders: true, |
|
|
|
bindingsIconCls: Ext.baseCSSPrefix + 'bindings-icon', |
|
vmIconCls: Ext.baseCSSPrefix + 'vm-icon', |
|
missingDataCls: Ext.baseCSSPrefix + 'bindinspector-missing-data', |
|
filterVisibleCls: Ext.baseCSSPrefix + 'bindinspector-filter-visible', |
|
lastItemCls: Ext.baseCSSPrefix + 'bindinspector-last-item', |
|
|
|
bindingsIcon: '☍', |
|
vmIcon: '☶', |
|
|
|
initComponent: function() { |
|
var me = this, |
|
nodes = []; |
|
|
|
me.viewConfig = { |
|
toggleOnDblClick: false, |
|
getRowClass: function (record, index, rowParams, store) { |
|
var cls = []; |
|
// decoration for items found when filtering |
|
if (record.get('filtervisible')) { |
|
cls.push(me.filterVisibleCls); |
|
} |
|
// decoration for items with no associated data |
|
if (record.get('sansData')) { |
|
cls.push(me.missingDataCls); |
|
} |
|
// decoration for the last item in the tree (adds a shadow for modern browsers) |
|
if (index === store.getCount() - 1) { |
|
cls.push(me.lastItemCls); |
|
} |
|
return cls.join(' '); |
|
} |
|
}; |
|
|
|
// build the component node hierarchy |
|
Ext.Array.forEach(me.components, function(comp) { |
|
nodes.push(me.buildNode(comp)); |
|
}, me); |
|
|
|
me.store = { |
|
model: me.Model, |
|
root: { |
|
expanded: true, |
|
children: nodes |
|
} |
|
}; |
|
|
|
me.columns = [{ |
|
// used by the Container.onVMSearchClick() method when showing the source VMs for a given data point |
|
itemId: 'srcVMIndicator', |
|
width: 40, |
|
hidden: true, |
|
renderer: me.srcVMIndicator, |
|
scope: me |
|
}, { |
|
xtype: 'treecolumn', |
|
dataIndex: 'text', |
|
flex: 1 |
|
}]; |
|
|
|
me.dockedItems = [{ |
|
// the toolbar for searching within the component list |
|
xtype: 'toolbar', |
|
itemId: 'queryFieldTb', |
|
dock: 'top', |
|
items: [{ |
|
xtype: 'textfield', |
|
reference: 'queryField', |
|
itemId: 'queryField', |
|
emptyText: 'simple search by reference / ID or use a component query...', |
|
flex: 1, |
|
triggers: { |
|
clear: { |
|
cls: Ext.baseCSSPrefix + 'form-clear-trigger', |
|
handler: function(field) { |
|
var tree = field.up('treepanel'); |
|
|
|
field.reset(); |
|
tree.clearComponentFilter(); |
|
field.focus(); |
|
} |
|
} |
|
}, |
|
listeners: { |
|
change: { |
|
fn: me.filterComponentTree, |
|
buffer: 250, |
|
scope: me |
|
}, |
|
afterrender: { |
|
fn: function (field) { |
|
var tbEl = field.up('toolbar').getEl(); |
|
|
|
// set up the toolip for the component list filter field |
|
field.mon(tbEl, 'mouseenter', function () { |
|
var tip = me.bindingsTip, |
|
showAt, x, y; |
|
|
|
tip.stopAnimation(); |
|
tip.update('<b>Simple Search</b><br>Enter the string matching the reference or ID of the target component<hr><b>Component Query</b><br>Enter a component query string to find any items matching the query'); |
|
tip.setTarget(tbEl); |
|
tip.show(); |
|
x = tip.getX(); |
|
y = tip.getY(); |
|
showAt = tip.getAlignToXY(tbEl, 'l-r'); |
|
tip.animate({ |
|
from: { |
|
opacity: 0, |
|
x: showAt[0] + 20, |
|
y: showAt[1] |
|
}, |
|
to: { |
|
opacity: 1, |
|
x: showAt[0] + 10, |
|
y: showAt[1] |
|
} |
|
}); |
|
}); |
|
}, |
|
scope: me |
|
} |
|
} |
|
}] |
|
}, { |
|
// toolbar used by Container.onVMSearchClick() |
|
// is shown when the results in the Component List are filtered by the ViewModelDetail's searched data point |
|
xtype: 'toolbar', |
|
cls: Ext.baseCSSPrefix + 'vm-results-tb', |
|
itemId: 'vmQueryResultsTb', |
|
hidden: true, |
|
dock: 'top', |
|
defaultButtonUI: 'default', |
|
items: ['->', { |
|
text: 'Clear VM Filter', |
|
// restores the original filter toolbar |
|
handler: function () { |
|
//console.log(this.up('#vmQueryResultsTb')); |
|
var tb = this.up('#vmQueryResultsTb'), |
|
componentList = tb.up('bindinspector-componentlist'), |
|
queryTb = componentList.down('#queryFieldTb'), |
|
queryField = queryTb.down('#queryField'); |
|
|
|
tb.hide(); |
|
queryTb.show(); |
|
componentList.clearVMSearchIndicators(); |
|
queryField.setValue(queryField.lastValue); |
|
componentList.filterComponentTree(null, queryField.lastValue); |
|
} |
|
}] |
|
}]; |
|
|
|
me.callParent(); |
|
me.getView().on('itemdblclick', me.onItemDblclick, me); |
|
me.on('select', me.onItemSelect, me); |
|
|
|
// a quick-view tip to show the bindings for a given component |
|
me.bindingsTip = Ext.create('Ext.tip.ToolTip', { |
|
renderTo: document.body, |
|
anchor: 'left', |
|
cls: Ext.baseCSSPrefix + 'componentlist-tip', |
|
bodyPadding: 12 |
|
}); |
|
|
|
// manually show the bindings tip on itemmouseenter |
|
me.getView().on('itemmouseenter', me.showBindingsTip, me); |
|
}, |
|
|
|
// the source VM indicators column which is shown during the Container.onVMSearchClick() call is then hidden |
|
// between VM drill down searches |
|
clearVMSearchIndicators: function () { |
|
var indicatedVM = this.indicatedVM; |
|
|
|
Ext.suspendLayouts(); |
|
if (indicatedVM) { |
|
Ext.Array.forEach(indicatedVM, function (rec) { |
|
rec.set('isSrcVM', false); |
|
}); |
|
} |
|
this.down('#srcVMIndicator').hide(); |
|
Ext.resumeLayouts(true); |
|
|
|
this.indicatedVM = null; |
|
}, |
|
|
|
// renderer for the source VM indicator column |
|
srcVMIndicator: function (v, meta, rec) { |
|
var refVM = rec.get('isSrcVM'), |
|
tip = '', |
|
vmDetail, firstTierRec, firstTierName, targetRec; |
|
|
|
if (refVM) { |
|
vmDetail = this.up('bindinspector-container').down('bindinspector-viewmodeldetail'); |
|
firstTierRec = vmDetail.getFirstTierRec(refVM); |
|
firstTierName = firstTierRec.get('name'); |
|
if (firstTierRec !== refVM) { |
|
tip += 'Root data node: <span class=\'' + Ext.baseCSSPrefix + 'binding-tip-descriptor\'>' + firstTierName + '</span><hr>'; |
|
} |
|
|
|
targetRec = firstTierRec === refVM ? firstTierRec : refVM; |
|
tip += targetRec.get('name') + ':'; |
|
tip += '<br> '; |
|
tip += '<span class=\'' + Ext.baseCSSPrefix + 'binding-tip-value\'>' + Ext.app.bindinspector.Util.valueRenderer(targetRec.get('value')) + '</span>'; |
|
meta.tdCls = Ext.baseCSSPrefix + 'bindindicator-vm-src'; |
|
meta.tdAttr = 'data-qclass="' + Ext.baseCSSPrefix + 'componentlist-tip" data-qtip="' + tip + '"'; |
|
} |
|
}, |
|
|
|
// when the ComponentList is destroyed the stand-alone QuickTip needs to also be destroyed |
|
onDestroy: function () { |
|
this.bindingsTip.destroy(); |
|
this.callParent(); |
|
}, |
|
|
|
// when a nodeInterface / row is moused over show the bindings tooltip which will detail the specs > output value from the bindings on the component |
|
showBindingsTip: function (view, record, item, index, e) { |
|
var me = this, |
|
tip = me.bindingsTip, |
|
sansData = record.get('sansData'), |
|
bindings, bindingText; |
|
|
|
tip.stopAnimation(); |
|
if (record.get('hasBindings')) { |
|
bindings = me.ownerCt.env.getCmp(record.get('id')).bindings; |
|
bindingText = []; |
|
|
|
// build the bindings markup for the tip |
|
Ext.Object.each(bindings, function (key, val, o) { |
|
var kv = key + ': ' + '<span class="' + Ext.baseCSSPrefix + 'binding-tip-descriptor">' + val.descriptor + '</span><br>', |
|
bindValue = val.value, |
|
v; |
|
|
|
if (Ext.isString(bindValue)) { |
|
v = bindValue; |
|
} else if (Ext.isObject(bindValue)) { |
|
if (bindValue.isStore === true) { |
|
v = 'Store {' + bindValue.entityName + '}'; |
|
} else if (bindValue.isModel === true) { |
|
v = 'Model {' + bindValue.entityName + '}'; |
|
} |
|
} |
|
kv += '<span class="' + Ext.baseCSSPrefix + 'binding-tip-value">' + v + '</span>'; |
|
bindingText.push(kv); |
|
}); |
|
|
|
bindingText = bindingText.join('<hr>'); |
|
if (sansData) { |
|
bindingText += '<hr>'; |
|
Ext.Array.forEach(sansData, function (missing) { |
|
bindingText += '<div class="' + Ext.baseCSSPrefix + 'binding-missing-data">Missing data: ' + missing + '</div>'; |
|
}); |
|
} |
|
tip.update(bindingText); |
|
tip.setTarget(item); |
|
tip.show(); |
|
tip.alignTo(item, 'l-r', [20, 0]); |
|
tip.animate({ |
|
from: { |
|
opacity: 0 |
|
}, |
|
to: { |
|
opacity: 1, |
|
x: tip.getX() - 10 |
|
} |
|
}); |
|
} |
|
}, |
|
|
|
// filter for the component list (tree) |
|
filterComponentTree: function (field, val) { |
|
var tree = this, |
|
field = tree.down('#queryField'), |
|
newVal = val || field.getValue(), |
|
store = tree.store, |
|
queryRe = /[\s>\[\]=()^'"~$@*:+#,]/g, |
|
valIsArray = Ext.isArray(newVal), |
|
ids = valIsArray ? newVal : [], |
|
components = [], |
|
isQuery, len, i; |
|
|
|
if (Ext.isString(newVal)) { |
|
isQuery = queryRe.test(Ext.String.trim(newVal)); |
|
} |
|
|
|
if (newVal.length > 0) { |
|
tree.filteredComponents = []; |
|
|
|
// if newVal matches the queryRe regex attempt the lookup using Ext.ComponentQuery |
|
if (isQuery) { |
|
try |
|
{ |
|
components = Ext.ComponentQuery.query(newVal); |
|
} catch (e) {} |
|
|
|
len = components.length; |
|
|
|
for (i = 0; i < len; i++) { |
|
ids.push(components[i].id); |
|
} |
|
} |
|
|
|
store.suspendEvents(); |
|
store.filter({ |
|
filterFn: function (node) { |
|
var children = node.childNodes, |
|
length = children && children.length, |
|
visible = false, |
|
j; |
|
|
|
if (isQuery || valIsArray) { |
|
visible = Ext.Array.contains(ids, node.get('id')); |
|
} else { |
|
visible = node.get('text').indexOf(newVal) > -1; |
|
} |
|
node.set('filtervisible', visible); |
|
|
|
if (visible) { |
|
tree.filteredComponents.push(node); |
|
} |
|
|
|
// check the child nodes to see if they are 'visible' and if so then show the parent node, too |
|
for (j = 0; j < length; j++) { |
|
if (children[j].get('visible')) { |
|
visible = true; |
|
} |
|
} |
|
return visible; |
|
}, |
|
id: 'queryFilter' |
|
}); |
|
store.resumeEvents(); |
|
tree.getView().refresh(); |
|
} else { |
|
tree.clearComponentFilter(); |
|
} |
|
}, |
|
|
|
// method to clear the filter from the component list (tree) |
|
clearComponentFilter: function () { |
|
var tree = this, |
|
store = tree.store, |
|
filtered = tree.filteredComponents || [], |
|
len = filtered.length, |
|
i = 0; |
|
|
|
for (; i < len; i++) { |
|
filtered[i].set('filtervisible', false); |
|
} |
|
store.clearFilter(); |
|
}, |
|
|
|
// constructs the tree node for the given component |
|
buildNode: function(comp) { |
|
var me = this, |
|
ownerCt = me.getRefOwner(), |
|
childItems = comp.items, |
|
viewModel = comp.viewModel, |
|
bindings = comp.bindings, |
|
hasBindings = !!comp.bindings, |
|
suffix = [], |
|
sansData = [], |
|
missing = {}, |
|
binding, len, i, o, child, ref, key, bindData; |
|
|
|
if (viewModel) { |
|
suffix.push('<span class="' + me.vmIconCls + '">' + me.vmIcon + '</span>'); |
|
ownerCt.buildVMDataMap(viewModel); |
|
} |
|
if (hasBindings) { |
|
suffix.push('<span class="' + me.bindingsIconCls + '">' + me.bindingsIcon + '</span>'); |
|
|
|
for (key in bindings) { |
|
binding = bindings[key]; |
|
if (binding.descriptor && Ext.isEmpty(binding.value)) { |
|
sansData.push(missing[key] = binding.descriptor); |
|
} |
|
} |
|
|
|
bindData = comp.bindData = Ext.app.bindinspector.Util.buildBindData(bindings); |
|
} |
|
|
|
if (sansData.length === 0) { |
|
sansData = undefined; |
|
} |
|
|
|
ref = comp.reference ? '<b>[' + comp.reference + ']</b> • ' : ''; |
|
|
|
o = { |
|
id: comp.id, |
|
text: ref + comp.id + (suffix.length ? (' ' + suffix.join(' ')) : ''), |
|
hasViewModel: !!viewModel, |
|
hasBindings: hasBindings, |
|
hasDeepBindings: hasBindings, |
|
reference: comp.reference, |
|
sansData: sansData, |
|
bindData: bindData, |
|
children: [] |
|
}; |
|
|
|
if (childItems) { |
|
for (i = 0, len = childItems.length; i < len; ++i) { |
|
child = me.buildNode(childItems[i]); |
|
o.hasDeepBindings = o.hasDeepBindings || child.hasDeepBindings; |
|
if (child.hasDeepBindings) { |
|
o.children.push(child); |
|
} |
|
} |
|
} |
|
|
|
if (o.children.length) { |
|
o.expanded = true; |
|
o.leaf = false; |
|
} else { |
|
o.leaf = true; |
|
} |
|
|
|
return o; |
|
}, |
|
|
|
// on item dblclick fire the 'componentdblclick' event for the bindinspector-container to listen for |
|
onItemDblclick: function(view, rec) { |
|
this.fireEvent('componentdblclick', this, rec); |
|
}, |
|
|
|
// on item select fire the 'componentselect' event for the bindinspector-container to listen for |
|
onItemSelect: function (selModel, rec) { |
|
var node = this.getView().getNode(rec); |
|
this.fireEvent('componentselect', this, rec, node); |
|
} |
|
}, function() { |
|
this.prototype.Model = Ext.define(null, { |
|
extend: 'Ext.data.TreeModel', |
|
fields: ['hasViewModel', 'hasBindings', 'reference', 'hasDeepBindings', 'reference', 'sansData', 'bindData', 'isSrcVM'] |
|
}); |
|
}); |