tweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloud
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.
420 lines
16 KiB
420 lines
16 KiB
Ext.define('Ext.app.bindinspector.ComponentDetail', { |
|
extend: 'Ext.panel.Panel', |
|
alias: 'widget.bindinspector-componentdetail', |
|
|
|
requires: [ |
|
'Ext.form.field.Display', |
|
'Ext.grid.Panel', |
|
'Ext.layout.container.VBox', |
|
'Ext.form.field.Checkbox', |
|
'Ext.app.bindinspector.Util' |
|
], |
|
|
|
layout: 'border', |
|
//borderRegionSwapMin: 1000, // see onCtResize |
|
|
|
activeCls: Ext.baseCSSPrefix + 'bindinspector-stub-active', |
|
descriptorCls: Ext.baseCSSPrefix + 'bindinspector-descriptor', |
|
multipleCls: Ext.baseCSSPrefix + 'bindinspector-mult-val', |
|
directCls: Ext.baseCSSPrefix + 'bindinspector-direct-val', |
|
inheritedCls: Ext.baseCSSPrefix + 'bindinspector-inherited-val', |
|
componentKeyCls: Ext.baseCSSPrefix + 'bindinspector-comp-key', |
|
componentDescCls: Ext.baseCSSPrefix + 'bindinspector-comp-desc', |
|
componentValCls: Ext.baseCSSPrefix + 'bindinspector-comp-val', |
|
lastItemCls: Ext.baseCSSPrefix + 'bindinspector-last-item', |
|
|
|
// the default view for the view model preview tab for when a component is clicked, but has no viewModel |
|
vmPreviewDefault: { |
|
xtype: 'container', |
|
region: 'east', |
|
split: true, |
|
width: '50%', |
|
cls: Ext.baseCSSPrefix + 'bindinspector-prev-default', |
|
padding: 20, |
|
layout: { |
|
type: 'hbox', |
|
align: 'middle', |
|
pack: 'center' |
|
}, |
|
items: [{ |
|
xtype: 'component', |
|
flex: 1, |
|
html: 'Select a component with a ViewModel (or inherited ViewModel) from the ComponentList to view the ViewModel details' |
|
}] |
|
}, |
|
|
|
initComponent: function() { |
|
var me = this, |
|
comp = me.component || {}, |
|
env = me.env, |
|
publishes = comp.publishes, |
|
bindings = comp.bindings, |
|
title = 'Bindings ⇒ ', |
|
vm = env.getInheritedVM(comp), |
|
bindData, publishesTbar; |
|
|
|
// the default view for the bindings preview tab for when a component is clicked, but has no bindings |
|
me.bindingsPreviewDefault = { |
|
xtype: 'panel', |
|
border: false, |
|
region: 'center', |
|
cls: Ext.baseCSSPrefix + 'bindinspector-prev-default', |
|
bodyPadding: 20, |
|
layout: { |
|
type: 'hbox', |
|
align: 'middle', |
|
pack: 'center' |
|
}, |
|
items: [{ |
|
xtype: 'component', |
|
flex: 1, |
|
html: 'Select a component with bindings from the ComponentList to view the bindings details along with the component\'s inherited ViewModel' |
|
}], |
|
dockedItems: [{ |
|
xtype: 'toolbar', |
|
dock: 'top', |
|
defaultButtonUI: 'default', |
|
items: ['->', { |
|
text: 'Open in dedicated tab', |
|
handler: function (btn) { |
|
btn.up('bindinspector-container').onComponentDblclick(null, comp); |
|
} |
|
}] |
|
}] |
|
}; |
|
|
|
// add the component's reference to the title if it has a reference |
|
if (comp.reference) { |
|
title += '[' + comp.reference + '] • '; |
|
} |
|
|
|
// build the bindings supporting data if the component has bindings and does not already have its bindData |
|
bindData = comp.bindData || Ext.app.bindinspector.Util.buildBindData(bindings); |
|
|
|
// add any publishes info the the top of the Component Detail grid |
|
if (publishes) { |
|
publishesTbar = [{ |
|
xtype: 'component', |
|
html: 'Publishes: <span style="color: #5C5C5C;font-size: 14px;line-height: 14px;">' + Ext.Object.getKeys(publishes).join(' • ') + '</span>' |
|
}]; |
|
} |
|
|
|
// the config for the bindings grid itself (shown when a component with bindings is selected from the ComponentList tree) |
|
me.bindingsGrid = { |
|
xtype: 'gridpanel', |
|
title: title += comp.id, |
|
header: { |
|
items: [{ |
|
xtype: 'button', |
|
text: 'Open in dedicated tab', |
|
ui: 'default-toolbar', |
|
handler: function (btn) { |
|
btn.up('bindinspector-container').onComponentDblclick(null, comp); |
|
} |
|
}] |
|
}, |
|
region: 'center', |
|
cls: Ext.baseCSSPrefix + 'bindinspector-compdetail-grid', |
|
flex: 1, |
|
hideHeaders: true, |
|
store: { |
|
model: me.BindingModel, |
|
data: bindData |
|
}, |
|
columns: [{ |
|
flex: 1, |
|
scope: me, |
|
renderer: me.bindingRenderer |
|
}], |
|
tbar: publishesTbar, |
|
bbar: [{ |
|
xtype: 'checkboxfield', |
|
itemId: 'highlightToggle', |
|
boxLabel: 'Highlight VM target nodes on binding selection', |
|
checked: true, |
|
listeners: { |
|
scope: me, |
|
change: me.onHighlightChange |
|
} |
|
}], |
|
viewConfig: { |
|
stripeRows: false, |
|
trackOver: false, |
|
getRowClass: function (record, index, rowParams, store) { |
|
var cls = []; |
|
|
|
if (index === store.getCount() - 1) { |
|
cls.push(me.lastItemCls); |
|
} |
|
|
|
return cls.join(' '); |
|
} |
|
}, |
|
listeners: { |
|
scope: me, |
|
cellclick: me.onCellClick, |
|
selectionchange: me.onSelectionChange |
|
} |
|
}; |
|
|
|
// the ViewModelDetail tree (shown when a component with a View Model is selected from the ComponentList tree) |
|
me.viewModelTree = { |
|
xtype: 'bindinspector-viewmodeldetail', |
|
itemId: 'vm-' + vm.id, |
|
vm: vm, |
|
region: 'east', |
|
split: true, |
|
width: '50%', |
|
height: '50%' |
|
}; |
|
|
|
me.items = []; |
|
me.items.push(bindings ? me.bindingsGrid : me.bindingsPreviewDefault); |
|
me.items.push(vm ? me.viewModelTree : me.vmPreviewDefault); |
|
|
|
me.callParent(arguments); |
|
}, |
|
|
|
// renderer for the bindings colum to show the bindings key, descriptor, and value |
|
bindingRenderer: function (v, meta, rec) { |
|
var me = this, |
|
binding = rec.get('binding'), |
|
key = rec.get('key'), |
|
descriptor = me.descriptorRenderer(rec.get('descriptor'), meta, rec), |
|
value = Ext.app.bindinspector.Util.valueRenderer(rec.get('value')), |
|
bindingType = 'Direct'; |
|
|
|
// provide a default output for empty / null bindings values |
|
if (Ext.isEmpty(value) || value === 'null') { |
|
value = '<i>No value found</i>'; |
|
} |
|
|
|
key = '<span class="' + me.componentKeyCls + '">' + key + ': </span>'; |
|
descriptor = '<span class="' + me.componentDescCls + '">' + descriptor + '</span>'; |
|
value = '<span class="' + me.componentValCls + '">' + value + '</span>'; |
|
|
|
if (binding.isTemplateBinding) { |
|
bindingType = 'Template'; |
|
} else if (binding.isMultiBinding) { |
|
bindingType = 'Multi'; |
|
} |
|
bindingType = Ext.util.Format.format('<div data-qtip="Binding Type" data-qclass="' + Ext.baseCSSPrefix + 'componentlist-tip" class="' + Ext.baseCSSPrefix + 'bindinspector-bind-type">{0}</div>', bindingType); |
|
|
|
return key + descriptor + '<br>' + value + bindingType; |
|
}, |
|
|
|
// handler for the highlight or don't highlight checkbox in the component detail grid |
|
// selecting a binding detail node will highlight the source from the view model if the |
|
// checkbox is checked |
|
onHighlightChange: function (field) { |
|
var compDetail = this.down('gridpanel'), |
|
selModel = compDetail.getSelectionModel(); |
|
|
|
// if the checkbox is not checked then remove the highlighting from the view model detail view |
|
if (!field.checked) { |
|
this.onSelectionChange(selModel, null, true); |
|
} else { // else re-apply the highlighting |
|
this.onSelectionChange(selModel, selModel.getSelection()); |
|
} |
|
}, |
|
|
|
// when the component detail node is selected highlight (if allowed by the highlight checkbox) |
|
// the root data node from the view model detail tree |
|
onSelectionChange: function (selModel, selected, clear) { |
|
var vmDetail = this.down('bindinspector-viewmodeldetail'), |
|
store = vmDetail.getStore(), |
|
tokens, binding, highlight, root, targets, highlighted; |
|
|
|
if ((selected && selected.length === 0) || clear === true) { |
|
store.suspendEvents(); |
|
vmDetail.getStore().getRootNode().cascadeBy({ |
|
before: function (node) { |
|
node.set('highlighted', false); |
|
} |
|
}); |
|
store.resumeEvents(); |
|
vmDetail.getView().refresh(); |
|
return; |
|
} |
|
|
|
selected = selected[0]; |
|
tokens = selected.get('tokens'); |
|
binding = selected.get('binding'); |
|
highlight = this.down('#highlightToggle').checked; |
|
store = vmDetail.getStore(); |
|
root = store.getRootNode(); |
|
targets = []; |
|
|
|
// get the node or nodes from which the bindings are bound |
|
if (tokens && highlight) { |
|
if (binding.isTemplateBinding) { |
|
Ext.Array.forEach(tokens, function(token) { |
|
targets.push(root.findChild('name', token[0])); |
|
}, this); |
|
} else if (binding.isMultiBinding) { |
|
// TODO |
|
} else { |
|
targets.push(root.findChild('name', tokens[0])); |
|
} |
|
} |
|
|
|
store.suspendEvents(); |
|
root.cascadeBy({ |
|
before: function (node) { |
|
highlighted = targets.length === 0; |
|
Ext.Array.forEach(targets, function (target) { |
|
if (node === target || node.isAncestor(target)) { |
|
highlighted = true; |
|
} |
|
}); |
|
|
|
// this is set so that the view model detail panel's getRowClass |
|
// can apply the styling depending on whether the node is bound to this |
|
// particular binding (-1 highlights / fades since false means undecorated) |
|
node.set('highlighted', highlighted || -1); |
|
} |
|
}); |
|
store.resumeEvents(); |
|
vmDetail.getView().refresh(); |
|
// select / focus the first targetable view model tree node |
|
vmDetail.getSelectionModel().select(targets[0]); |
|
}, |
|
|
|
// if the cell click is on a descriptor token focus on its data node in the |
|
// view model tree |
|
onCellClick: function(view, cell, colIdx, record, row, rowIdx, e) { |
|
var target = e.getTarget('.' + this.activeCls), |
|
path; |
|
|
|
if (target) { |
|
path = target.getAttribute('data-path'); |
|
this.showPath(path); |
|
} |
|
}, |
|
|
|
showPath: function(path) { |
|
this.selectPath(this.down('bindinspector-viewmodeldetail'), path); |
|
}, |
|
|
|
// select the view model tree's node using the passed path |
|
// which is furnished by the onCellClick -> showPath method |
|
selectPath: function(tab, path) { |
|
var node = tab.getRootNode(), |
|
parts = path.split('.'), |
|
len = parts.length, |
|
i; |
|
|
|
for (i = 0; node && i < len; ++i) { |
|
node = this.getChildByKey(node, parts[i]); |
|
} |
|
|
|
if (node) { |
|
tab.getSelectionModel().select(node); |
|
} |
|
}, |
|
|
|
getChildByKey: function(node, key) { |
|
var childNodes = node.childNodes; |
|
if (childNodes) { |
|
return Ext.Array.findBy(childNodes, function(child) { |
|
return child.get('name') === key; |
|
}); |
|
} |
|
return null; |
|
}, |
|
|
|
// extracts the descriptor markup for the component detail grid |
|
descriptorRenderer: function(v, meta, rec) { |
|
var binding = rec.get('binding'), |
|
descriptor = rec.get('descriptor'), |
|
tokens = rec.get('tokens'); |
|
|
|
v = v || ''; |
|
|
|
if (binding.isTemplateBinding) { |
|
Ext.Array.forEach(tokens, function(token) { |
|
var tokenRe = new RegExp('{' + token.join('\\.') + '}', 'g'); |
|
v = v.replace(tokenRe, this.parseTokens(token)); |
|
}, this); |
|
} else if (binding.isMultiBinding) { |
|
// TODO |
|
} else { |
|
return v.replace(descriptor, this.parseTokens(tokens)); |
|
} |
|
return Ext.String.htmlEncode(v); |
|
}, |
|
|
|
// decorates the descriptor markup using the descriptor tokens from the binding |
|
parseTokens: function(tokens) { |
|
var me = this, |
|
out = [], |
|
vm = me.env.getInheritedVM(me.component), |
|
currPath = '', |
|
currParent = vm.rootStub, |
|
direct = false, |
|
inherited = false, |
|
addlCls = '', |
|
tip = '', |
|
baseToken = '', |
|
ownerVMs, len, vmPlural; |
|
|
|
tokens = tokens || []; |
|
|
|
Ext.Array.forEach(tokens, function(token) { |
|
var stub = Ext.app.bindinspector.Util.getChildStub(token, currParent), |
|
cls = '', |
|
value; |
|
|
|
if (stub) { |
|
value = stub.value; |
|
if (value !== undefined) { |
|
cls = me.activeCls; |
|
} |
|
} else { |
|
// TODO Never here... |
|
} |
|
out.push('<span data-path="' + currPath + token + '" class="stub ' + cls + '">' + token + '</span>'); |
|
currPath += token + '.'; |
|
currParent = stub; |
|
}, me); |
|
|
|
// determine whether a binding's source can be found on the nearest view model, is |
|
// inherited from some ancestor view model, or is on both this view model and an ancestor |
|
if (tokens[0]) { |
|
baseToken = tokens[0]; |
|
ownerVMs = vm.dataMap[tokens[0]].ownerVMs; |
|
len = ownerVMs.length; |
|
Ext.Array.forEach(ownerVMs, function (v) { |
|
if (v.id === vm.id) { |
|
direct = true; |
|
} |
|
if (v.id !== vm.id) { |
|
inherited = true; |
|
} |
|
}); |
|
} |
|
|
|
// set the class for the descriptor markup depending on whether the date source |
|
// is direct, inherited, or direct, but with ancestor view models also posessing the data |
|
if (direct && inherited) { |
|
addlCls += ' ' + me.multipleCls; |
|
vmPlural = len > 1 ? 'VMs' : 'VM'; |
|
tip = 'data-qclass="' + Ext.baseCSSPrefix + 'componentlist-tip" data-qtip="<b>' + baseToken + '</b> provided by this VM and ' + (len - 1) + ' ancestor ' + vmPlural + '"'; |
|
} else if (direct) { |
|
addlCls += ' ' + me.directCls; |
|
tip = 'data-qclass="' + Ext.baseCSSPrefix + 'componentlist-tip" data-qtip="<b>' + baseToken + '</b> is provided by this VM"'; |
|
} else if (inherited) { |
|
addlCls += ' ' + me.inheritedCls; |
|
vmPlural = len > 1 ? 'VMs' : 'VM'; |
|
tip = 'data-qclass="' + Ext.baseCSSPrefix + 'componentlist-tip" data-qtip="<b>' + baseToken + '</b> is provided by ' + len + ' ancestor ' + vmPlural + '"'; |
|
} |
|
|
|
return '<span ' + tip + 'class="' + me.descriptorCls + addlCls + '">{' + out.join('.') + '}</span>'; |
|
} |
|
}, function() { |
|
this.prototype.BindingModel = Ext.define(null, { |
|
extend: 'Ext.data.Model', |
|
|
|
fields: ['key', 'descriptor', 'tokens', 'value', 'binding'] |
|
}); |
|
}); |