/** * A plugin which is a {@link Ext.tip.ToolTip} which shows itself upon mouseover of a DataView item. * * The associated {@link Ext.data.Model record} is passed into the {@link #setData} method * just before the tip is shown. The record is stored in the `record` property. */Ext.define('Ext.dataview.plugin.ItemTip', { extend: 'Ext.tip.ToolTip', alias: 'plugin.dataviewtip', anchor: true, showOnTap: true, defaultBindProperty: 'data', config: { // So that we can get early access to the owning DataView // in applyBind so we can ensure we have a ViewModel. cmp: null, /** * By default, the tip is constrained to within the client dataview and * only show if it will fit within the dataview. To allow tips to show outside * of the dataview, configure this as `false`. */ constrainToView: true }, listeners: { beforeshow: 'onBeforeShow', show: 'onShow', scope: 'this' }, init: Ext.emptyFn, destroy: function() { // We need to null out the parent very early, otherwise // it will try and call remove() when this isn't really // a child item. this.parent = null; this.callParent(); }, applyData: function(data) { if (data.isEntity) { data = data.getData(true); } return data; }, updateCmp: function(dataview) { var me = this, scroller = dataview.getScrollable(); me.dataview = me.parent = dataview; dataview.on('initialize', 'onDataViewInitialized', me); if (scroller) { scroller.on('scroll', 'onDataViewScroll', me); } }, onDataViewInitialized: function(dataview) { var me = this; me.setTarget(dataview.bodyElement); me.itemSelector = dataview.itemSelector; if (!me.getDelegate()) { me.setDelegate(me.itemSelector); } }, onBeforeShow: function() { var me = this, viewModel = me.getViewModel(), location = me.getCmp().createLocation(me.currentTarget); // In case location has become invalidated during the showDelay. // For example the hovered item being removed. if (location.record) { if (me.getBind()) { viewModel.set('record', location.record); viewModel.set('recordIndex', me.location.recordIndex); // Flush the data now so that the alignment is correct viewModel.notify(); } else { me.setData(location.record.data); } } }, onShow: function() { // If we are outside the scrolling viewport, then we cannot be anchored // to a visible target, so we must not show. this.checkScrollVisibility(); }, onDataViewScroll: function() { // If we are outside the scrolling viewport, then we cannot be anchored // to a visible target, so we must hide. this.checkScrollVisibility(); }, privates: { // Plugins get assigned an id of their alias.type by default. // This plugin is a component so must have a unique id. initId: function(config) { if (config.id === 'dataviewtip') { config.id = this.id = this.getId(); } else { this.callParent([config]); } }, checkScrollVisibility: function() { var me = this, isInView, testEl; if (me.isVisible() && me.getConstrainToView()) { // Ensure alignment is correct due to this possibly being called in a // scroll handler. me.realignToTarget(); testEl = me.getAnchor() || me.el; isInView = me.dataview.getScrollable().isInView(testEl); // If we are not in view, then hide if (!(isInView.x && isInView.y)) { me.hide(); } } }, applyBind: function(binds, currentBindings) { var me = this, dataview = me.getCmp(), viewModel = me.getViewModel(), parentViewModel = dataview.lookupViewModel(); // Ensure we have a connected ViewModel before binding is processed. if (viewModel) { viewModel.setParent(parentViewModel); } else { me.setViewModel(Ext.Factory.viewModel({ parent: parentViewModel, data: {} })); } me.callParent([binds, currentBindings]); } }});