Here is a summary of the problem: I set up a column sortChange() listener, which responds to sort changes by firing off a query to fetch newly sorted data. I save the grid state before the fetch, and restore the grid state after the fetch. The problem is that the restore gridState mechanism triggers the original sort listener, causing the whole process to start over again, and again, and again.
scope.sitesGrid.onRegisterApi = function(gridApi) {
scope.gridApi = gridApi;
scope.gridApi.core.on.sortChanged(scope, function () {
// load new sites on a sort change
scope.initialize();
});
};
scope.initialize = function() {
// save current grid state
scope.gridApi && (scope.gridState = scope.gridApi.saveState.save());
fetchSites().then(function (sites) {
scope.sitesGrid.data = sites
// restore current grid state, but inadvertently retrigger the 'sortChanged' listener
scope.gridApi.saveState.restore(scope,scope.gridState);
})
};
I was thinking that I could set up a click listener on each column header, instead of using a sortChange listener, however this solution seems ugly and requires going into every header cell template and making changes.
How about some kind of scope variable to track the loading of data?
scope.gridApi.core.on.sortChanged(scope, function () {
if (!scope.isLoading) {
scope.initialize();
}
});
and
fetchSites().then(function (sites) {
scope.isLoading = true;
scope.sitesGrid.data = sites;
scope.gridApi.saveState.restore(scope,scope.gridState);
scope.isLoading = false;
})
You might need to add some timeout() calls in places if there are timing issues with this. Creating a Plunker to demonstrate this would help in that case.
I think i find solution. I created restore function in my directive (u can use it where you want). I just block executing next iteration until action is finished.
function restoreState() {
if ($scope.gridState.columns !== undefined && !isRestoring) { //check is any state exists and is restored
isRestoring = true; //set flag
$scope.gridApi.saveState.restore($scope, $scope.gridState)
.then(function () {
isRestoring = false; //after execute release flag
});
}
}
function saveState() {
if (!isRestoring) {
$scope.gridState = $scope.gridApi.saveState.save();
}
}
I'm trying to figure out a way to have an Excel-like behavior with the Grids on Ext JS.
Here is the sample grid I am working with. So far we can already naviguate through the cells with the arrows but only in edit mode.
However what I am trying to reach is the naviguation with the arrows, TAB and Enter keys outside of the edit mode, just like excel.
I tried to integrate this piece of code which overrides the Editor class, hoping that it would change the behavior of the cells but it doesn't change a thing.
I believe this is the most important part that overrides the Editor class and tries to include the keys input :
Ext.override(Ext.Editor, {
startEdit: function (el, value) {
var me = this,
field = me.field;
me.completeEdit();
me.boundEl = Ext.get(el);
value = Ext.isDefined(value) ? value : me.boundEl.dom.innerHTML;
if (!me.rendered) {
me.render(me.parentEl || document.body);
}
if (me.fireEvent('beforestartedit', me, me.boundEl, value) !== false) {
me.startValue = value;
me.show();
field.reset();
if (deleteGridCellValue) {
field.setValue('');
me.editing = true;
me.completeEdit();
deleteGridCellValue = false; // reset global variable
}
else {
if (newGridCellValue == '') {
// default behaviour of Ext.Editor (see source if needed)
field.setValue(value);
}
else {
// custom behaviour to handle an alphanumeric key press from non-edit mode
field.setRawValue(newGridCellValue);
newGridCellValue = ''; // reset global variable
if (field instanceof Ext.form.field.ComboBox) {
// force the combo box's filtered dropdown list to be displayed (some browsers need this)
field.doQueryTask.delay(field.queryDelay);
}
}
me.realign(true);
field.focus(false, 10);
if (field.autoSize) {
field.autoSize();
}
me.editing = true;
}
}
}
});
This is the first time that I am working on a project that is outside of Comp-Sci classes so any help would be very much appreciated. Thanks !
In my website, in order to load diferent pages (to be multipage website) I have a main panel that has the id 'content-panel'.
When I want to load a diferent page I have a javascript function that is called 'loadPage' that loads the page (panel) that I want to the 'content-panel'.
But the page that I want to load has to have this code:
Ext.require(['*']);
Ext.onReady(function() {
...
var panel = Ext.Cmp('content-panel');
panel.add(loginPanel);
panel.layout.setActiveItem(loginPanel);
panel.doLayout();
panel.setLoading(false);
});
In this case it is loading the page/panel that is loginPanel, that is defined inside Ext.onReady
For me this is fine, I don't know of any other way of my website being multi-page.
But everytime that I want to go to a page it loads that page to the 'content-panel', even if it already been loaded before. I want a way to only add the page to 'content-panel' if it is not inside 'content-panel' items.
UPDATE:
Here is the loadPage
function swap(parent, replacement, url) {
var alpha = document.querySelector(parent);
var target = alpha.childNodes[0];
var omega = document.createElement(replacement);
omega.src = url;
omega.type = 'text/javascript';
alpha.replaceChild(omega, target);
}
function loadPage(panel, toPanel) {
toPanel.setLoading(true);
swap('head', 'script', panel);
}
it is used like this: loadPage('Ctl_base/view_admin/mainPage', Ext.getCmp('panel'));
I'm using CodeIgniter with ExtJS.
What I have already tried:
I want to do panel.add(loginPanel) only if the loginPanel doesn't exist.
I have tried:
if(panel.getComponent(loginScreen) == undefined) { panel.add(loginPanel); }
and it adds the component even when panel already has that component.
I have also tried:
function hasComponent(parent, child) {
parent.items.items.forEach(function(item) {
if(item == child){
return true;
}
});
return false;
}
if(!hasComponent(panel, loginPanel)) { panel.add(loginPanel); }
and it also doesn't work.
I have manage to tackle this question by putting and itemId on the panel that I want to load, and on panel.layout.setActiveItem(loginPanel); I have put panel.layout.setActiveItem('itemIdOfPanel');
How do I reset my ExtJS filters in my grids. More specifically, how do I get the header to honour the changes to the filtering.
ie. This works fine :
grid.store.clearFilter();
But the header rendering is all wrong. I need to get into all the menu objects and unselect the checkboxes.
I am getting pretty lost. I am pretty sure this gives me the filterItems :
var filterItems = grid.filters.filters.items;
And from each of these filter items, i can get to menu items like so :
var menuItems = filter.menu.items;
But that's as far as I can get. I am expecting some kind of checkbox object inside menu items, and then I can uncheck that checkbox, and hopefully the header rendering will then change.
UPDATE :
I now have this code. The grid store has its filter cleared. Next I get the filterItems from grid.filters.filters.items and iterate over them. Then I call a function on each of the menu items.
grid.store.clearFilter();
var filterItems = grid.filters.filters.items;
for (var i = 0; i<filterItems.length; i++){
var filter = filterItems[i];
filter.menu.items.each(function(checkbox) {
if (checkbox.setChecked)
checkbox.setChecked(false, true);
});
}
The checkboxes do get called, but still nothing is happening :(
Try this code:
grid.filters.clearFilters();
This should take care of both the grid and its underlying store.
When you do
grid.store.clearFilter();
it can only clear the filters on the store but the grid's view doesn't get updated with that call. Hence to handle it automatically for both the grid's view as well as the grid's store, just use
grid.filters.clearFilters();
Hope it helps!
Cheers!
Your update help me but you forget the case where you have input text instead of checkbox.
So this is my addition of your solution:
grid.filters.clearFilters();
var filterItems = grid.filters.filters.items;
for (var i = 0; i<filterItems.length; i++){
var filter = filterItems[i];
filter.menu.items.each(function(element) {
if (element.setChecked) {
element.setChecked(false, true);
}
if(typeof element.getValue !== "undefined" && element.getValue() !== "") {
element.setValue("");
}
});
}
When you use grid wiht gridfilters plugin
and inovoke
grid.filters.clearFilters();
it reset applyed filters, but it don't clean value in textfield inside menu.
For clean textfield text you can try this:
grid.filters.clearFilters();
const plugin = grid.getPlugin('gridfilters');
let activeFilter;
if('activeFilterMenuItem' in plugin) {
activeFilter = plugin.activeFilterMenuItem.activeFilter
}
if (activeFilter && activeFilter.type === "string") {
activeFilter.setValue("");
}
Say I have a button that triggers a push of a new view.
I noticed that if I click it more than once, fast enough, it will push the same view twice.
You can mimic this behavior using their official docs on this page, where they have a live sample:
http://docs.sencha.com/touch/2-0/#!/guide/navigation_view
the clear question is, simply how to prevent it?
Another method is to check what the active view is, and only push if it is not the same as the view you are about to push. I've tested this and it works.
E.g.
if (this.getNavigationView().getActiveItem().xtype != "someView") {
this.getNavigationView().push({ xtype: "someView" });
}
Extending jayteejee's answer, I've overridden the push method in a custom navigation view, like this:
Ext.define('BT.navigation.View', {
extend: 'Ext.navigation.View',
xtype: 'btnavigationview',
push: function (view) {
if(this.getActiveItem().xtype != view.xtype)
this.callParent(arguments);
else
console.warn("Prevented pushing a potentially duplicate view of xtype: " + view.xtype);
}
});
I'm not totally sure if the xtype assumption is safe enough, but I can't think of any situation in my current app that would require one view pushing another view of the same type onto the navigation stack. So, the solution works for me, and it's pretty neat. The warning is there to save me headache later on and possibly pulling my hair out trying to work out why push wouldn't work!
Masking successfully prevents double tapping problem.
In my code I'm using two functions for mask/unmask navigation container:
/**
* Mask container with rolling wheel. Usually need if Ajax-request is sent to the server and app waiting for response
* Best practice is masking the current navigator container, to prevent blocking whole app. Method warns if no container
* is defined. In some cases warning could be suppress with parameter
*
* #param container
* #param {boolean} [suppressWarning]
*/
startLoading: function(container, suppressWarning) {
var loadingComponent = container;
if (!loadingComponent) {
// <debug>
if (!suppressWarning) {
console.warn('Please define navigator container for non-blocking operation, or define suppressWarning parameter');
}
// </debug>
loadingComponent = Ext.Viewport;
}
// var lastMaskedContainer = container;
this.lastMaskedContainer = container;
loadingComponent.setMasked({
xtype: 'loadmask',
message: 'Loading...'
});
/*
Ext.defer(function() {
lastMaskedContainer.setMasked(false);
}, Pipedrive.app.maskingTimeout * 1000)
*/
},
/**
*
* #param {Ext.Container} container
* #param {boolean} [suppressWarning]
*/
stopLoading: function(container, suppressWarning) {
var loadingComponent = container;
if (!loadingComponent) {
// <debug>
if (!suppressWarning) {
console.warn('Please define either navigator container for non-blocking operation, or define suppressWarning parameter');
}
// </debug>
loadingComponent = Ext.Viewport;
}
var alreadyMasked = loadingComponent.getMasked();
var lastMaskedContainer = this.lastMaskedContainer;
if (!alreadyMasked && !suppressWarning) {
// <debug>
if (lastMaskedContainer != container) {
console.warn('Found Start/Stop Loading inconsistency. Please revise code'
+ (container ? '. Container: ' + container.getId() : 'Ext.Viewport')
+ (lastMaskedContainer ? ', last masked container: ' + lastMaskedContainer.getId() : '')
);
}
// </debug>
loadingComponent = Ext.Viewport;
}
loadingComponent.setMasked(false);
}
than in the tap handler:
onDealDetailsTap: function(ct) {
console.log('onDealDetailsTap', ct);
var form = ct.getReferenceForm(),
navigatorContainer = this.getNavigatorContainer(form),
model = form.getRecord();
UiHelper.startLoading(navigatorContainer);
Ext.Viewport.fireEvent('detailfields', {
title: model.get('title'),
id: model.get('id'),
store: 'DealFields',
navigatorContainer: navigatorContainer
})
},
to cleanup the loading mask:
control : {
activitiesContainer: {
push: 'onPushActivitiesContainer'
},
onPushActivitiesContainer: function(ct) {
//console.log('onPushActivitiesContainer', ct);
UiHelper.stopLoading(ct);
},
especially it is cool for waiting for long-timed ajax requests....
Cheers, Oleg
Just suspend the events on the button when it's tapped and resume them when the view is pushed
button.suspendEvents();
...
button.resumeEvents();
I don't think there is another way. As a developer or a user, when you tap a button twice, you expect the event handler to be called twice.
Hope this helps
simply mask the entire container and then unmask it; create a ref for the container or panel in which the button exists in your controller and on tap set:
ref.setMasked(true)
After the new view is pushed simply unmask by
ref.setMasked(false)
Another way is to flip a parameter once the list item has been tapped once, like this:
{
onListItemTap: function () {
if (!this.tapped) {
this.tapped = true;
...
}
}
}
Of course, that only works if you are destroying the list view as soon as the user goes to a different screen.
I created a method for checking this:
ENSURE_NO_DOUBLE_TAP : function(classNameToPush) {
if (Ext.getClassName(Ext.getCmp('MyViewport').getActiveItem()) == classNameToPush) {
return false;
}
return true;
}
Then from your app before anything that could be double tapped is processed:
if (!ENSURE_NO_DOUBLE_TAP('MyApp.view.View')) {
return;
}
If you are listening to the tap event of a button using listeners,then here is
my solution:
listeners : {
release : function(){
if(this.getDisabled())return false;
this.setDisabled(true);
this.fireEvent('tap');
},
tap : function() {
//do what you want
}
}
Extending on jayteejee's and Merott's answers, I've added some code to intercept on multiple fast pushes to not only prevent duplicates but to prevent pushing of different views as well before the page transition completes. Think of a user tapping different list items.
Also notice the view.destroy(); method in the else block to prevent view instances from heaping up in memory.
Ext.define('Overrides.navigation.View', {
extend: 'Ext.navigation.View',
xtype: 'ovrnavigationview',
interceptPush: false,
push: function (view) {
var activeItem = this.getActiveItem();
// Prevent multiple pushes & duplicates
if (!this.interceptPush && activeItem.xtype !== view.xtype) {
// Set interceptPush
this.interceptPush = true;
// Reset interceptPush after 500 ms
Ext.defer(function() {
this.interceptPush = false;
}, 500, this);
// Handle push
this.callParent(arguments);
} else {
// Warn developer
console.warn("Prevented pushing view of xtype: " + view.xtype);
// Destroy view
view.destroy();
return false;
}
}
});
You can just use the "itemsingletap" event.
If you want to support double taps as well, make a second listener for "itemdoubletap" and invoke the same function, both listeners work fine together.