icloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsapp
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
9 years ago
|
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']
|
||
|
});
|
||
|
});
|