How to add a custom update on a pull refresh? - sencha-touch-2

In the standard "Pull to Refresh" plugin, the list store gets updated. However, I have two lists and I need to update a different store for my detail list. How can I override the update event and reload my other store? I tried adding a simple listener but it's not firing.
[Update]
I got this snippet from the Sencha site to work:
plugins: [
{
xclass: 'Ext.plugin.PullRefresh',
pullRefreshText: 'Pull down for more new Events!',
refreshFn: function(plugin) {
console.log( "I'm pulled" );
}
}
]
Original code:
Ext.define('SenchaFiddle.view.ListView', {
extend: 'Ext.dataview.List',
xtype: 'main-list',
config: {
plugins: [
'pullrefresh',
{
pullRefreshText: 'Do it!',
type: 'listpaging',
// Don't offer "Load More" msg
autoPaging: false,
refreshFn: function() {
console.log("Boom");
},
listeners: {
'updatedata': function(plugin, list) {
console.log("Getting the data");
}
}
}
],
layout: 'fit',
width: 300,
itemTpl: '{text}'
}
});

In Sencha Touch 2.2, they have removed the refreshFn config from Ext.util.PullRefresh. I have successfully implemented a custom refreshFn with the new version of Sencha Touch by overriding the fetchLatest function inside Ext.util.PullRefresh like so...
Ext.define('MyApp.overrides.PullRefreshOverride', {
override: 'Ext.plugin.PullRefresh',
fetchLatest: function() {
var list = this.getList();
switch(list.getItemId()) {
case "list1":
this.updateStore1();
break;
case "list2":
this.updateStore2();
break;
}
this.callParent(arguments);
},
//My own custom function to add to the plugin
updateStore1: function() {
//Code to update store 1
},
//My own custom function to add to the plugin
updateStore2: function {
//Code to update store 2
}
});

Having a look at Ext.plugin.PullRefresh definition in sencha-touch-all-debug, I see this config:
/*
* #cfg {Function} refreshFn The function that will be called to refresh the list.
* If this is not defined, the store's load function will be called.
* The refresh function gets called with a reference to this plugin instance.
* #accessor
*/
refreshFn: null,
It might be a good idea that you can achieve what you need through refreshFn config.

For those who need the refreshFn back, there is a PullRefreshFn extension for PullRefresh.
I needed PullRefresh to get triggered by a Panel, rather than a List or Dataview and I also needed to manually load and set data to my Dataview upon user triggering the PullRefresh.
For this I needed the refreshFn config function that existed prior to Sencha 2.2, so here is my implementation.
PullRefreshFn (Modified)
Ext.define('Ext.plugin.PullRefreshFn', {
extend: 'Ext.plugin.PullRefresh',
alias: 'plugin.pullrefreshfn',
requires: ['Ext.DateExtras'],
config: {
/**
* #cfg {Function} refreshFn The function that will be called to refresh the list.
* If this is not defined, the store's load function will be called.
*/
refreshFn: null
},
fetchLatest: function() {
if (this.getRefreshFn()) {
this.getRefreshFn().call();
} else {
var store = this.getList().getStore(),
proxy = store.getProxy(),
operation;
operation = Ext.create('Ext.data.Operation', {
page: 1,
start: 0,
model: store.getModel(),
limit: store.getPageSize(),
action: 'read',
sorters: store.getSorters(),
filters: store.getRemoteFilter() ? store.getFilters() : []
});
proxy.read(operation, this.onLatestFetched, this);
}
}
});
My Controller
Ext.define('myApp.controller.MyController', {
extend: 'Ext.app.Controller',
requires: ['Ext.plugin.PullRefreshFn'],
...
// More code
...
// Binds the Pull Refresh to myPanel view item.
// myPanel is a panel. Not list nor dataview.
setPullRefresh: function () {
var me = this;
// We get reference to myPanel and
// we set PullRefreshFn
this.getMyPanel().setPlugins([{
xclass: 'Ext.plugin.PullRefreshFn',
docked: 'top',
// We set autoSnapBack to false,
// as we are going to trigger this manually
autoSnapBack: false,
// refreshFn will be called upon user releasing for refresh.
refreshFn: function() {
// This is a custom function that sets data to our dataview list.
// When it's done setting data, we trigger the snapBack.
me.populateMylist(function () {
me.getMyPanel().getPlugins()[0].snapBack(true);
});
}
}]);
}
});

Related

rally iteration combobox returns empty

I'm new to rally app SDK and trying to do the tutorials (from Youtube and from rally site)
when I'm trying to create an iterationComboBox the object is created but with no values ("There are no Iterations defined").
i tried to run both the video tutorial code from github (session_4_interactive_grid)
// Custom Rally App that displays Defects in a grid and filter by Iteration and/or Severity.
//
// Note: various console debugging messages intentionally kept in the code for learning purposes
Ext.define('CustomApp', {
extend: 'Rally.app.App', // The parent class manages the app 'lifecycle' and calls launch() when ready
componentCls: 'app', // CSS styles found in app.css
defectStore: undefined, // app level references to the store and grid for easy access in various methods
defectGrid: undefined,
// Entry Point to App
launch: function() {
console.log('our second app'); // see console api: https://developers.google.com/chrome-developer-tools/docs/console-api
this.pulldownContainer = Ext.create('Ext.container.Container', { // this container lets us control the layout of the pulldowns; they'll be added below
id: 'pulldown-container-id',
layout: {
type: 'hbox', // 'horizontal' layout
align: 'stretch'
}
});
this.add(this.pulldownContainer); // must add the pulldown container to the app to be part of the rendering lifecycle, even though it's empty at the moment
this._loadIterations();
},
// create iteration pulldown and load iterations
_loadIterations: function() {
this.iterComboBox = Ext.create('Rally.ui.combobox.IterationComboBox', {
fieldLabel: 'Iteration',
labelAlign: 'right',
width: 300,
listeners: {
ready: function(combobox) { // on ready: during initialization of the app, once Iterations are loaded, lets go get Defect Severities
this._loadSeverities();
},
select: function(combobox, records) { // on select: after the app has fully loaded, when the user 'select's an iteration, lets just relaod the data
this._loadData();
},
scope: this
}
});
this.pulldownContainer.add(this.iterComboBox); // add the iteration list to the pulldown container so it lays out horiz, not the app!
},
// create defect severity pulldown then load data
_loadSeverities: function() {
this.severityComboBox = Ext.create('Rally.ui.combobox.FieldValueComboBox', {
model: 'Defect',
field: 'Severity',
fieldLabel: 'Severity',
labelAlign: 'right',
listeners: {
ready: function(combobox) { // this is the last 'data' pulldown we're loading so both events go to just load the actual defect data
this._loadData();
},
select: function(combobox, records) {
this._loadData();
},
scope: this // <--- don't for get to pass the 'app' level scope into the combo box so the async event functions can call app-level func's!
}
});
this.pulldownContainer.add(this.severityComboBox); // add the severity list to the pulldown container so it lays out horiz, not the app!
},
// Get data from Rally
_loadData: function() {
var selectedIterRef = this.iterComboBox.getRecord().get('_ref'); // the _ref is unique, unlike the iteration name that can change; lets query on it instead!
var selectedSeverityValue = this.severityComboBox.getRecord().get('value'); // remember to console log the record to see the raw data and relize what you can pluck out
console.log('selected iter', selectedIterRef);
console.log('selected severity', selectedSeverityValue);
var myFilters = [ // in this format, these are AND'ed together; use Rally.data.wsapi.Filter to create programatic AND/OR constructs
{
property: 'Iteration',
operation: '=',
value: selectedIterRef
},
{
property: 'Severity',
operation: '=',
value: selectedSeverityValue
}
];
// if store exists, just load new data
if (this.defectStore) {
console.log('store exists');
this.defectStore.setFilter(myFilters);
this.defectStore.load();
// create store
} else {
console.log('creating store');
this.defectStore = Ext.create('Rally.data.wsapi.Store', { // create defectStore on the App (via this) so the code above can test for it's existence!
model: 'Defect',
autoLoad: true, // <----- Don't forget to set this to true! heh
filters: myFilters,
listeners: {
load: function(myStore, myData, success) {
console.log('got data!', myStore, myData);
if (!this.defectGrid) { // only create a grid if it does NOT already exist
this._createGrid(myStore); // if we did NOT pass scope:this below, this line would be incorrectly trying to call _createGrid() on the store which does not exist.
}
},
scope: this // This tells the wsapi data store to forward pass along the app-level context into ALL listener functions
},
fetch: ['FormattedID', 'Name', 'Severity', 'Iteration'] // Look in the WSAPI docs online to see all fields available!
});
}
},
// Create and Show a Grid of given defect
_createGrid: function(myDefectStore) {
this.defectGrid = Ext.create('Rally.ui.grid.Grid', {
store: myDefectStore,
columnCfgs: [ // Columns to display; must be the same names specified in the fetch: above in the wsapi data store
'FormattedID', 'Name', 'Severity', 'Iteration'
]
});
this.add(this.defectGrid); // add the grid Component to the app-level Container (by doing this.add, it uses the app container)
}
});
and the code from Rally site (https://help.rallydev.com/apps/2.0rc2/doc/#!/guide/first_app).
// Custom Rally App that displays Defects in a grid and filter by Iteration and/or Severity.
//
// Note: various console debugging messages intentionally kept in the code for learning purposes
Ext.define('CustomApp', {
extend: 'Rally.app.App', // The parent class manages the app 'lifecycle' and calls launch() when ready
componentCls: 'app', // CSS styles found in app.css
launch: function() {
this.iterationCombobox = this.add({
xtype: 'rallyiterationcombobox',
listeners: {
change: this._onIterationComboboxChanged,
ready: this._onIterationComboboxLoad,
scope: this
}
});
},
_onIterationComboboxLoad: function() {
var addNewConfig = {
xtype: 'rallyaddnew',
recordTypes: ['User Story', 'Defect'],
ignoredRequiredFields: ['Name', 'ScheduleState', 'Project'],
showAddWithDetails: false,
listeners: {
beforecreate: this._onBeforeCreate,
scope: this
}
};
this.addNew = this.add(addNewConfig);
var cardBoardConfig = {
xtype: 'rallycardboard',
types: ['Defect', 'User Story'],
attribute: 'ScheduleState',
storeConfig: {
filters: [this.iterationCombobox.getQueryFromSelected()]
}
};
this.cardBoard = this.add(cardBoardConfig);
},
_onBeforeCreate: function(addNewComponent, record) {
record.set('Iteration', this.iterationCombobox.getValue());
},
_onIterationComboboxChanged: function() {
var config = {
storeConfig: {
filters: [this.iterationCombobox.getQueryFromSelected()]
}
};
this.cardBoard.refresh(config);
}
});
both give me an empty iteration box.
i'm getting user stories data when running code from session 3 on the video,by creating a store of user stories. I googled it and searched here for duplicates but with no successso far, so what can be the issue?
Thanks!
I copied the code you posted, both apps, without making any changes, ran the apps and the iteration box was populated in both cases. It's not the code.
Maybe if you are getting "There are no Iterations defined" there are no iterations in your project?
The second code you posted which you copied from the example in the documentation has a bug in it and even though the iteration combobox is populated, the cards do not show on a board. DevTools console has error: "Cannot read property 'refresh' of undefined".
I have a working version of this app in this github repo.

Sencha Touch2: Passing data from Controller to floating Panel not working

I am new to Sencha Touch2 and facing problem while passing data from my Controller to Floating panel on listitem tap. Here is my controller implementation code:
Ext.define('CustomList.controller.Main', {
extend: 'Ext.app.Controller',
requires:['CustomList.view.DatePanel'],
config: {
refs: {
listView: 'listitems'
},
control: {
'main test2 list': {
activate: 'onActivate',
itemtap: 'onItemTap'
}
}
},
onActivate: function() {
console.log('Main container is active');
},
onItemTap: function(view, index, target, record, event) {
console.log('Item was tapped on the Data View');
Ext.Viewport.add({
xtype: 'DatePanel'
});
}
});
Am able to get data in the controller and DatePanel.js is my floating Panel.
DatePanel.js:
Ext.define('CustomList.view.DatePanel', {
extend: 'Ext.Panel',
alias: 'widget.DatePanel',
xtype:'datepanel',
config: {
itemid:'DatePanel',
modal:true,
centered: true,
hideOnMaskTap:true,
width:'500px',
height:'650px',
items:[
{
styleHtmlCls:'homepage',
tpl:'<h4>{name3}</h4>'
},
{
xtype:'toolbar',
docked:'bottom',
items:[{
text:'OK',
ui:'confirm',
action:'ShowTurnOverReport',
listeners : {
tap : function() {
console.log('Ok');
}
}
},
{
text:'Cancel',
ui:'confirm',
action:'Cancel',
listeners : {
tap : function() {
console.log('Cancel');
var panelToDestroy = Ext.getCmp('datepanel');
panelToDestroy.destroy();
Ext.Viewport.add(Ext.create('CustomList.view.Test2'));//Test.js is my list Panel
}
}
}]
}
]
}
});
Help me out in destroying the panel on 'Cancel' Button.
Can anyone please help me. Thanks.
Create instance of panel you want to add first.
var floatingDatePanel = Ext.create('Yourapp.view.YourDatePanel');
Next get data of selected list item on itemTap
var data = record.getData();
Assign this data to floatingDatePanel with setData() method
UPDATE,
after looking at your panel code, I guess you want to set data to first item in panel ie
{
styleHtmlCls:'homepage',
tpl:'<h4>{name3}</h4>'
}
Right ? If so then you need to change following code
floatingDatePanel.setData(data);
to
floatingDatePanel.getAt(0).setData(data);
Because, it is first item inside panel that is having a template assigned and hopefully the same where you want to set data.
then finally, you can add this panel into viewport with
Ext.Viewport.add(floatingDatePanel);

Events not caught by Controller

I am trying to respond to a view-fired event inside a controller. The event does fire, but the controller action is never called.
View:
Ext.define("MyApp.view.Dashboard", {
extend: 'Ext.Container',
xtype: 'my_dashboard',
config: {
items: [
{
xtype: 'dataview',
listeners: {
itemtap: function(sender, index, elem, record) {
// fires with param, e.g. 'inbox'
this.fireEvent(record.get('name'));
}
}
}
...
Controller:
Ext.define('MyApp.controller.Dashboard', {
extend: 'Ext.app.Controller',
config: {
control: {
'my_dashboard': {
'inbox': 'showInbox',
...
}
}
},
showInbox: function () {
/* never gets called */
},
...
What am I doing wrong?
UPDATE:
I found a solution, but it feels very hacky. I added a bubbleEvents config to the dataview and the my_dashboard view, and the events started making their way up to the controller.
Since the dashboard can have a variable number of items, I don't know which events I need to bubble up. Of course, I could bubble up all possible dashboard events, but that just seems like a massive kludge.
Should the view fire an application event instead, e.g. MyApp.app.fireEvent()?
I would put an itemId on the dataview.
itemId: 'my_dashboard_dataview'
Then in your controller...
control: {
'#my_dashboard_dataview': {
'itemtap': 'showInbox',
...
}
}
UPDATE
Here is some code that should get you started:
Controller:
Ext.define('MyApp.controller.Dashboard', {
extend: 'Ext.app.Controller',
config: {
control: {
'my_dashboard': {
painted: 'onDashboardPainted',
inbox: 'showInbox'
}
}
},
onDashboardPainted: function(dashboard, eOpts) {
dashboard.down('dataview').on('itemtap', function(sender, index, elem, record){
dashboard.fireEvent(record.get('name'));
//check the console to make sure you the value is: inbox
console.log(record.get('name'));
});
},
showInbox: function () {
console.log('showInbox init');
},
This is definately a scope issue... in your controller add a painted function for my_dashboard... the first function variable will be the my_dashboard component itself:
painted: function(dashboard,...
Now in the painted function add the listener dynamically:
dashboard.down('dataview').on( ... rest of listener code ...
Now inside of the listener do dashboard.fireEvent(....);
Sorry for the formatting im on my mobile, if u cant get it to work i will post code tomorrow

Sencha Touch 2: Call controller function from within Ext.Msg.confirm

I'm just getting started with Sencha Touch 2 and I have never worked with Sencha Touch 1.x before. I've just finished this tutorial (which is the best starter tutorial I have found so far) http://miamicoder.com/2012/how-to-create-a-sencha-touch-2-app-part-1/ and now I want to go ahead and extend this Notes App.
I have a controller and 2 views, a list view and an edit view. In the edit view I want to be able to delete the current record. The delete function is in the controller. After tapping the delete button, I want to show a confirmation dialog ("Are you sure you want to delete...?"). After the user presses yes, the delete function should be called.
Now my problem is: How do I call the controllers delete function from within Ext.Msg.confirm?
Here are the relevant snippets of my code. Please let me know if something important is missing.
Please see the "onDeleteNoteCommand" function. "this.someFunction" obviously doesn't work since "this" is a DOMWindow.
Ext.define('TestApp2.controller.Main', {
extend: 'Ext.app.Controller',
config: {
refs: {
noteEditorView: 'noteeditorview'
},
control: {
noteEditorView: {
deleteNoteCommand: 'onDeleteNoteCommand',
}
}
},
onDeleteNoteCommand: function() {
console.log('onDeleteNoteCommand');
var noteEditor = this.getNoteEditorView();
var currentNote = noteEditor.getRecord();
Ext.Msg.confirm(
"Delete note?",
"Do you reall want to delete the note <i>"+currentNote.data.title+"</i>?",
function(buttonId) {
if(buttonId === 'yes') {
//controller functions!! how to call them?
this.deleteNote(currentNote);
this.activateNotesList();
}
}
);
},
deleteNote: function(record) {
var notesStore = Ext.getStore('Notes');
notesStore.remove(record);
notesStore.sync();
},
activateNotesList: function() {
Ext.Viewport.animateActiveItem(this.getNotesListView(), this.slideRightTransition);
},
slideLeftTransition: { type: 'slide', direction: 'left' },
slideRightTransition: { type: 'slide', direction: 'right' },
launch: function() {
this.callParent();
Ext.getStore('Notes').load();
console.log('launch main controller');
},
init: function() {
this.callParent();
console.log('init main controller');
}
});
When you enter callback function of Ext.Msg the scope changes from controller scope to global scope (window), so you must set up it as parameter of confirm method:
Ext.Msg.confirm(
"Delete note?",
"Do you reall want to delete the note <i>"+currentNote.data.title+"</i>?",
function(buttonId) {
if(buttonId === 'yes') {
this.deleteNote(currentNote);
this.activateNotesList();
}
},
this // scope of the controller
);
For more info please check sencha docs: http://docs.sencha.com/touch/2-0/#!/api/Ext.MessageBox-method-confirm

Add a custom button in column header dropdown menus {EXTJS 4}

I want a button in column header dropdown menu of grid in extjs4.
so that i can add or delete columns which are linked in database.
Any help will be appreciated...
Thankyou..:)
Couple of months ago I had the same problem. I've managed to solve it by extending Ext.grid.header.Container (I've overrided getMenuItems method). However, recently, I've found another solution which requires less coding: just add menu item manualy after grid widget is created.
I'll post the second solution here:
Ext.create('Ext.grid.Panel', {
// ...
listeners: {
afterrender: function() {
var menu = this.headerCt.getMenu();
menu.add([{
text: 'Custom Item',
handler: function() {
var columnDataIndex = menu.activeHeader.dataIndex;
alert('custom item for column "'+columnDataIndex+'" was pressed');
}
}]);
}
}
});
Here is demo.​
UPDATE
Here is demo for ExtJs4.1.
From what I have been seeing, you should avoid the afterrender event.
Context:
The application I am building uses a store with a dynamic model. I want my grid to have a customizable model that is fetched from the server (So I can have customizable columns for my customizable grid).
Since the header wasn't available to be modified (since the store gets reloaded and destroys the existing menu that I modified - using the example above). An alternate solution that has the same effect can be executed as such:
Ext.create('Ext.grid.Panel', {
// ...
initComponent: function () {
// renders the header and header menu
this.callParent(arguments);
// now you have access to the header - set an event on the header itself
this.headerCt.on('menucreate', function (cmp, menu, eOpts) {
this.createHeaderMenu(menu);
}, this);
},
createHeaderMenu: function (menu) {
menu.removeAll();
menu.add([
// { custom item here }
// { custom item here }
// { custom item here }
// { custom item here }
]);
}
});
For people who would like to have not just one "standard" column menu but have an individual columnwise like me, may use the following
initComponent: function ()
{
// renders the header and header menu
this.callParent(arguments);
// now you have access to the header - set an event on the header itself
this.headerCt.on('menucreate', function (cmp, menu, eOpts) {
menu.on('beforeshow', this.showHeaderMenu);
}, this);
},
showHeaderMenu: function (menu, eOpts)
{
//define array to store added compoents in
if(this.myAddedComponents === undefined)
{
this.myAddedComponents = new Array();
}
var columnDataIndex = menu.activeHeader.dataIndex,
customMenuComponents = this.myAddedComponents.length;
//remove components if any added
if(customMenuComponents > 0)
{
for(var i = 0; i < customMenuComponents; i++)
{
menu.remove(this.myAddedComponents[i][0].getItemId());
}
this.myAddedComponents.splice(0, customMenuComponents);
}
//add components by column index
switch(columnDataIndex)
{
case 'xyz': this.myAddedComponents.push(menu.add([{
text: 'Custom Item'}]));
break;
}
}
I took #nobbler's answer an created a plugin for this:
Ext.define('Ext.grid.CustomGridColumnMenu', {
extend: 'Ext.AbstractPlugin',
init: function (component) {
var me = this;
me.customMenuItemsCache = [];
component.headerCt.on('menucreate', function (cmp, menu) {
menu.on('beforeshow', me.showHeaderMenu, me);
}, me);
},
showHeaderMenu: function (menu) {
var me = this;
me.removeCustomMenuItems(menu);
me.addCustomMenuitems(menu);
},
removeCustomMenuItems: function (menu) {
var me = this,
menuItem;
while (menuItem = me.customMenuItemsCache.pop()) {
menu.remove(menuItem.getItemId(), false);
}
},
addCustomMenuitems: function (menu) {
var me = this,
renderedItems;
var menuItems = menu.activeHeader.customMenu || [];
if (menuItems.length > 0) {
if (menu.activeHeader.renderedCustomMenuItems === undefined) {
renderedItems = menu.add(menuItems);
menu.activeHeader.renderedCustomMenuItems = renderedItems;
} else {
renderedItems = menu.activeHeader.renderedCustomMenuItems;
menu.add(renderedItems);
}
Ext.each(renderedItems, function (renderedMenuItem) {
me.customMenuItemsCache.push(renderedMenuItem);
});
}
}
});
This is the way you use it (customMenu in the column config let you define your menu):
Ext.define('MyGrid', {
extend: 'Ext.grid.Panel',
plugins: ['Ext.grid.CustomGridColumnMenu'],
columns: [
{
dataIndex: 'name',
customMenu: [
{
text: 'My menu item',
menu: [
{
text: 'My submenu item'
}
]
}
]
}
]
});
The way this plugin works also solves an issue, that the other implementations ran into. Since the custom menu items are created only once for each column (caching of the already rendered version) it will not forget if it was checked before or not.