need your help on the following problem. I have an Extjs application, and I need to get some data from database at the begining of all, I mean, before everything loads (mostly views). This is because this data I need to load will be used to create a form in a specific view.
So the question is: where should I put the function that gets the data and stores it in a global variable? I tried to put that function in launch function on app.js, but the behavior is weird (sometimes loads the data before the view renders, and sometimes does it after).
Please see above the code:
Function that gets the data (placed in App.js and invoked inside launch function):
cargarItemsEvaluacion: function()
{
AppGlobals.itemsCargarFormEvaluacion = [];
Ext.Ajax.request(
{
url : 'app/proxy.evaluacion.php',
method: 'GET',
params :{
act: 'getEvalItemsForm'
},
success : function(response)
{
var obj = Ext.decode(response.responseText);
Ext.each(obj.results, function(item)
{
//console.log(item);
var tempItem = {
xtype: 'container',
layout: 'anchor',
flex: 1,
items: [
{
//xtype: 'fieldset',
title: item.nombre,
items: [
{
id: 'item_'+item.id,
xtype: 'textfield',
fieldLabel: item.descripcion,
name: 'item_'+item.id,
allowBlank: false,
blankText: 'Este campo es obligatorio',
maskRe: /^[0-9]{1}/,
maxLenght: 2,
validateOnChange: 'true',
emptyText: 'Nota (1 al 10)',
submitEmptyText: false,
validator: function(value)
{
if(value.length>2 || value>10)
{
return 'Complete con nota del 1 al 10';
}
var stringPad=/^[1-9]{1}[0-9]{0,1}/;
if (!stringPad.test(value))
{
return 'Complete con nota del 1 al 10';
}
else
{
return true;
}
}
}]
}]
};
AppGlobals.itemsCargarFormEvaluacion.push(tempItem);
});//End Ext.each
if(AppGlobals.itemsCargarFormEvaluacion.length>0)
{
var itemObservacion = {
xtype: 'container',
layout: 'anchor',
flex: 1,
items: [
{
title: 'Observaciones',
items: [
/*{
id: 'ascenso',
xtype: 'checkbox',
boxLabel: 'Recomienda ascenso',
name: 'ascenso'
},*/
{
id: 'observaciones',
xtype: 'textareafield',
grow: true,
fieldLabel: 'Agregue observaciones adicionales si lo cree necesario',
name: 'observaciones',
labelAlign: 'top'
}
]
}]
};
AppGlobals.itemsCargarFormEvaluacion.push(itemObservacion);
}
console.log(AppGlobals.itemsCargarFormEvaluacion);
},
failure: function(response)
{
var obj = Ext.decode(response.responseText);
console.log("Entra al failure "+response.responseText);
//Ext.example.msg("Error", "No se pudo comprobar si es posible cargar al evaluador. Error: "+obj.error);
}
});//End Ext.ajax
}
And this is the initComponent function on the view where I need to generate the form using the data I got from the previous function:
initComponent: function(){
var formEvaluacion = this.items[0];
if(AppGlobals.itemsCargarFormEvaluacion.length==0)
{
console.log("Data still no loaded");
}
else
{
console.log("Data loaded!");
}
console.log(AppGlobals.itemsCargarFormEvaluacion);
Ext.apply(formEvaluacion,{items: AppGlobals.itemsCargarFormEvaluacion});
this.callParent(arguments);
}
As you can see, I check if the data loads before the view renders, or after. And sometimes does it before, sometimes after...I don't know what it depends on...
Any help will be appreciated.
Mauro
Think I found the solution. I placed cargarItemsEvaluacion function into init function on app.js. Seem to be working so far.
Thanks!
Related
Is there a solution to extend the KeyMap of the ItemSelector?
I would like to add a keymap(like pageUp and pageDown keyEvent in itemselector) that when I press the letter 'A-Z' will take me to the item that starts with the letter pressed and select it.
You can use the following override (fiddle sample) to achieve it. It will not work correctly on view sore reload. And you will have to define the record search record field. In case of complicated view templates you can remove hardcoded search function and use it as a setting.
Ext.define('overrides.view.NavigationModel', {
override: 'Ext.view.NavigationModel',
searchRecordField: false,
initKeyNav: function (view) {
var me = this;
// Drive the KeyNav off the View's itemkeydown event so that beforeitemkeydown listeners may veto.
// By default KeyNav uses defaultEventAction: 'stopEvent', and this is required for movement keys
// which by default affect scrolling.
var keyNavConfig = {
target: view,
ignoreInputFields: true,
eventName: 'itemkeydown',
defaultEventAction: 'stopEvent',
processEvent: me.processViewEvent,
up: me.onKeyUp,
down: me.onKeyDown,
right: me.onKeyRight,
left: me.onKeyLeft,
pageDown: me.onKeyPageDown,
pageUp: me.onKeyPageUp,
home: me.onKeyHome,
end: me.onKeyEnd,
space: me.onKeySpace,
enter: me.onKeyEnter,
A: {
ctrl: true,
// Need a separate function because we don't want the key
// events passed on to selectAll (causes event suppression).
handler: me.onSelectAllKeyPress
},
F: me.onAlphabetKeyPress,
scope: me
};
if(this.view.searchRecordField) {
keyNavConfig = Ext.Object.merge(keyNavConfig, this.getAdditionalKeyNav());
}
me.keyNav = new Ext.util.KeyNav(keyNavConfig);
},
getAdditionalKeyNav: function() {
var keyNav = {};
this.view.getStore().each(function(record) {
var firstLetter = record.get(this.view.searchRecordField)[0].toUpperCase();
if(!keyNav[firstLetter]) {
keyNav[firstLetter] = this.onAlphabetKeyPress
}
}, this);
return keyNav;
},
onAlphabetKeyPress: function(keyEvent) {
const key = keyEvent.event.key;
var foundRecordIndex = this.view.getStore().findBy(function(record) {
return record.get('title').toLowerCase().indexOf(key) === 0;
}, this);
if(foundRecordIndex > -1) {
this.setPosition(foundRecordIndex, keyEvent);
}
}
});
Ext.application({
name: 'Fiddle',
launch: function () {
Ext.define('ListItem', {
extend: 'Ext.data.Model',
fields: [{
name: 'src',
type: 'string'
}, {
name: 'caption',
type: 'string'
}]
});
Ext.create('Ext.data.Store', {
id: 'ListItemsStore',
model: 'ListItem',
data: [{
title: "One"
}, {
title: "Two"
}, {
title: "Three"
}, {
title: "Four"
}, {
title: "Three"
}, ]
});
var imageTpl = new Ext.XTemplate(
'<tpl for=".">',
'<div style="margin-bottom: 10px;" class="thumb-wrap">',
'<span>{title}</span>',
'</div>',
'</tpl>'
);
Ext.create('Ext.view.View', {
store: Ext.data.StoreManager.lookup('ListItemsStore'),
tpl: imageTpl,
itemSelector: 'div.thumb-wrap',
emptyText: 'No images available',
// Search Record Field
searchRecordField: 'title',
renderTo: Ext.getBody()
});
}
});
Using Ext JS 7.1 Modern, I have prepared an example to show the problem.
When I have remote filters on my main store, binding the dataview.List to a ChainedStore correctly handles my local filtering. However, when I also add a PullRefresh plugin to the list, I get an error during pull refresh. I see from the source code that the plugin doesn't consider the possibility that a list's store can be a ChainedStore.
I have tried to explain the problem with a Sencha Fiddle and also attached the code below.
I have temporarily solved the problem by overriding the fetchLatest and onLatestFetched methods of Ext.dataview.pullrefresh.PullRefresh plugin, to use the source store if the list's store is a ChainedStore. But I believe the source code must be updated to handle this case.
app.js
Ext.define('App.model.Test', {
extend: 'Ext.data.Model',
fields: ['id', 'firstName', 'lastName']
});
Ext.define('App.store.Test', {
extend: 'Ext.data.Store',
alias: 'store.teststore',
model: 'App.model.Test'
});
Ext.define('App.viewmodel.Test', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.test',
data: {
query: ''
},
stores: {
test: {
type: 'teststore',
autoLoad: true,
proxy: {
type: 'ajax',
url: 'names.json',
reader: {
type: 'json',
rootProperty: 'data'
}
},
remoteFilter: true,
filters: {
property: 'id',
value: 1
}
},
chained: {
type: 'chained',
autoLoad: true,
source: '{test}'
}
}
});
Ext.define('App.controller.TestController', {
extend: 'Ext.app.ViewController',
alias: 'controller.testcontroller',
doSearch: function (field) {
var list = this.lookup('list'),
store = list.getStore(),
value = field.getValue();
if (Ext.isEmpty(value)) {
store.removeFilter('firstName')
} else {
store.filter([{
property: 'firstName',
value: value,
operator: 'like'
}])
}
}
});
Ext.define('App.dataview.TestList', {
extend: 'Ext.dataview.List',
xtype: 'testlist',
viewModel: {
type: 'test'
},
plugins: [{
type: 'pullrefresh',
mergeData: false
}],
emptyText: 'Name not found',
bind: {
store: '{chained}'
},
itemTpl: '<div class="contact">{id} <b>{firstName} {lastName}</b></div>'
});
Ext.define('App.MainView', {
extend: 'Ext.Panel',
controller: 'testcontroller',
fullscreen: true,
viewModel: {
type: 'test'
},
items: [{
xtype: 'searchfield',
ui: 'solo',
placeholder: 'Search names',
listeners: {
buffer: 500,
change: 'doSearch'
},
bind: {
value: '{query}'
}
}, {
reference: 'list',
xtype: 'testlist'
}]
})
Ext.application({
name: 'App',
mainView: 'App.MainView'
});
names.json
var data = [{
id: 1,
firstName: 'Peter',
lastName: 'Venkman'
}, {
id: 2,
firstName: 'Raymond',
lastName: 'Stantz'
}, {
id: 3,
firstName: 'Egon',
lastName: 'Spengler'
}, {
id: 4,
firstName: 'Winston',
lastName: 'Zeddemore'
}]
var results = data.filter(function(record) {
if (params.filter) {
return record.id > params.filter[0].value
}
})
return {
"success": true,
"data": results
}
App.override.dataview.pullrefresh.PullRefresh:
Ext.define('App.override.dataview.pullrefresh.PullRefresh', {
override: 'Ext.dataview.pullrefresh.PullRefresh',
privates: {
fetchLatest: function() {
const store = this.getStore().isChainedStore ? this.getStore().getSource() : this.getStore()
store.fetch({
page: 1,
start: 0,
callback: this.onLatestFetched,
scope: this
});
},
onLatestFetched: function(newRecords, operation, success) {
var me = this,
list = me.getList(),
store = list.getStore().isChainedStore ? list.getStore().getSource() : list.getStore(),
length, toInsert,
oldRecords, newRecord, oldRecord, i;
if (success) {
if (me.getMergeData()) {
oldRecords = store.getData();
toInsert = [];
length = newRecords.length;
for (i = 0; i < length; i++) {
newRecord = newRecords[i];
oldRecord = oldRecords.getByKey(newRecord.getId());
if (oldRecord) {
oldRecord.set(newRecord.getData());
}
else {
toInsert.push(newRecord);
}
}
store.insert(0, toInsert);
}
else {
store.loadRecords(newRecords);
}
me.setLastUpdated(new Date());
}
me.setState('loaded');
list.fireEvent('latestfetched', me, toInsert || newRecords);
if (me.getAutoSnapBack()) {
me.snapBack(true);
}
}
}
})
Thanks in advance
Since this post, instead of being a question, was a bug report with a possible solution, it has been posted to Ext JS 7.x Community Forums\Ext JS 7.x Bugs.
The above solution, that overwrites the plugin where source store is needed, works if anyone comes across the same issue.
So the main question is: Does any one know how I need to change my calling method to be compatible with Sencha Touch 2?
Description:
I have a large Extjs application with a C# backend. I already have a mobile site with pages, but none of them require submitted form data. The trouble came when trying to create a login page.
On the desktop, using extjs and Ext.direct, there are login forms and they work fine, but the same coding patterns don't seem to work with Sencha Touch 2.
Ext.define('PF.view.phone.login.Form', {
extend: 'Ext.form.Panel',
alias: 'widget.phoneloginform',
requires: ['Ext.form.FieldSet', 'Ext.form.Password', 'Ext.Label', 'Ext.Img'],
autoheight: true,
initialize: function () {
this.build();
this.callParent();
Ext.apply(this, {
api: {
submit: LoginActionImpl.login
}
});
scope: this
},
config: {
title: 'Login',
items: [
{
xtype: 'label',
html: 'Client Login',
itemId: 'clientLoginLabel',
style: 'text-align: left; font-size: 1.1em',
margin: '10 0 20 0'
}, {
xtype: 'fieldset',
/*Temporary style*/
margin: '10 17% 10 17% ',
items: [
{
xtype: 'textfield',
itemId: 'loginUsername',
name: 'loginUsername',
label: 'Username',
required: true
}, {
xtype: 'passwordfield',
itemId: 'loginPassword',
name: 'loginPassword',
label: 'Password',
required: true
}, {
xtype: 'toolbar',
layout: {
pack: 'right'
},
ui: 'plain',
items: [{
xtype: 'button',
itemId: 'loginButton',
text: 'Login',
ui: 'confirm'
}, {
xtype: 'button',
itemId: 'clearButton',
text: 'Clear',
ui: 'decline'
}]
}
]
}
]
},
build: function () {
this.down('#clearButton').setHandler(this.clear);
this.down('#loginButton').setHandler(this.login);
},
clear: function (btn, evt) {
btn.up('phoneloginform').reset();
},
login: function (btn, evt) {
/* Need help here */
/* Below is what would normally be used in the rest of the application */
this.submit({
success: this.onLoginSuccess,
failure: function (form, action) {
this.onLoginFailure(action.result);
},
scope: this
});
},
onLoginSuccess: function (arguments) {
Ext.dispatch('login/success');
},
onLoginFailure: function (result) {
// Here we need to check the actual results to determine why the login failed.
//
// A return code of -2 indicates account locked - otherwise we just assume an invalid attempt.
//
if (result.returnCode == -2) {
this.getComponent("loginUsername").markInvalid(t('ptl_mbl_user_status_locked_password'));
this.getComponent("loginUsername").markInvalid(t('ptl_mbl_user_status_locked_password'));
}
else {
this.getComponent("loginUsername").markInvalid(t('wms_username_password_incorrect_error'));
this.getComponent("loginPassword").markInvalid(t('wms_username_password_incorrect_error'));
}
Ext.dispatch('login/fail', result.returnCode);
},
getUsername: function () {
if (this.username) {
this.username = this.getValues()['#loginUsername'];
}
},
getPassword: function () {
if (this.password) {
this.password = this.getValues()['#loginPassword'];
}
}
});
The method of the C# method is:
[DirectMethodForm]
public JObject login(HttpRequest request)
{
String username = request["loginUsername"];
String password = request["loginPassword"];
this.setDB(request["db"]);
int loginReturnCode = this.getUserDetailsService().login(username, password, this.getDB(), true);
JObject jsonObject;
if (loginReturnCode == 0)
{
jsonObject = new JObject(
new JProperty("loginUsername", username),
new JProperty("loginPassword", password),
new JProperty("db", this.getDB()),
new JProperty("success", true));
}
else
{
jsonObject = new JObject(
new JProperty("errors", new JArray(new[] { loginReturnCode.ToString() })),
new JProperty("returnCode", loginReturnCode),
new JProperty("failed", true));
}
return jsonObject;
}
and what I'm trying to reuse so that can't be changed.
Stepping through the code in the browser, the issue comes during the "urlAppend" method in the "sencha-touch-all.js" file where the "url" parameter is null, so the call never reaches the C# code.
urlAppend : function(url, string) {
if (!Ext.isEmpty(string)) {
return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
}
return url;
},
However, I've never needed to set that variable before, in using Ext.direct. I don't have a separate web service, so actual web service calls are not available.
Thanks in advance.
I checked out some other questions posted previously but couldn't find a relevant answer. So here I am posting again: Here's my code:
Ext.create('Rally.data.WsapiDataStore',{
autoLoad: true,
model: 'HierarchicalRequirement',
limit: '500',
fetch: ['ObjectID','DirectChildrenCount','FormattedID'],
filters:[{property: 'DirectChildrenCount', operator: '=', value: 0}], //tried both "0",'0'
listeners: {
load: function(store,data,success){
var data_length = data.length;
console.log("Number = ", data);
}
}
});
Result is an empty array. Furthermore, the result did not contain the field "DirectChildrenCount" at all, even though I am fetching it.
I tried your code without any changes, and it returned 300+ items. DirectChildrenCount was included in the result. It was tested against "2.0rc2" and "2.0rc1" with the same result. Here is screenshot from Chrome's DevTools:
Here is another short app where a grid of stories updated since yesterday is built, filtered by the same condition.
{property: 'DirectChildrenCount', operator: '=', value: 0}
js file is below:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
var millisecondsInDay = 86400000;
var currentDate = new Date();
var startDate = new Date(currentDate - millisecondsInDay);
var startDateUTC = startDate.toISOString();
Rally.data.ModelFactory.getModel({
type: 'UserStory',
success: function(model) {
this.grid = this.add({
xtype: 'rallygrid',
itemId: 'grid',
model: model,
columnCfgs: [
'FormattedID',
'Name',
'Owner',
'LastUpdateDate'
],
storeConfig: {
filters: [
{
property: 'LastUpdateDate',
operator: '>',
value: startDateUTC
},
{
property: 'DirectChildrenCount',
operator: '=',
value: 0
},
]
}
});
},
scope: this
});
console.log('items updated since', startDateUTC);
}
});
I've got a problem I really can't solve.
Here's my goal: I want to add dynamically Tabs to a TabPanel.
I've created my own component called EmpruntsTabPanel()
And when I create it I just have to give an array and EmpruntsTabPanel() creates count(array)) tabs.
This works perfectly.
So I've done another component, DossierPanel.
This one, once it is loaded, creates dynamically EmpruntsTabPanel().
This works perfectly.
Errr well. This works perfectly the first time it's loaded.
I've made a button to re-load DossierPanel (see code below).
I call exactly the same code (because it's in the same event), and I've checked: it's the same URL, and the result is the same.
So:
why does it works the first time, if the reload doesn't ?
on the opposite, if it works the
first time, why not the second time?
Maybe this has something to do with the code
var res = action.result;
var tab = Ext.getCmp('tab_emprunts_'+this.id_dossier);
tab.removeAll(true);
tab.add( new EmpruntsTabPanel( res.data.emprunts ) );
Here's my component (I've removed a lot of textfield components for clarity):
DossierPanel = Ext.extend(Ext.form.FormPanel, {
closable: true,
autoScroll:true,
initComponent : function(){
var n = this.id_dossier;
this.title = n;
this.id = 'id_dossier_'+this.id_dossier;
this.bodyStyle = 'padding:15px';
this.labelWidth = 150;
this.items = [{
xtype:'tabpanel',
plain:true,
activeTab: 0,
/* (!) Pour qu'un onglet qui n'est pas affiché soit
* quand même calculé, il faut faire :
* deferredRender: false
* C'est très important lorsqu'on fait des fiches
* avec plusieurs onglets.
*/
deferredRender: false,
defaults:{bodyStyle:'padding:10px'},
items:[{
title:'Détails personnels',
layout:'form',
autoHeight: true,
defaults: {width: '99%'},
defaultType: 'textfield',
items: [{
xtype:'datefield',
fieldLabel: 'Date premier contact ',
name: 'DATE1ERCONTACTJMA',
readOnly: true,
format:'d/m/Y'
}]
},{
title:'Adresse',
layout:'form',
autoHeight: true,
defaults: {width: '95%'},
defaultType: 'textfield',
items: [{
fieldLabel: 'Adresse 1 ',
name: 'ADRESSE1'
}]
},{
id: 'tab_emprunts_'+this.id_dossier,
title:'Emprunts',
layout:'form',
autoHeight: true,
defaults: {width: '99%'},
defaultType: 'textfield',
items: []
}]
}];
this.buttonAlign = 'left';
this.buttons = [{
text: 'Recharger',
handler: function() {
this.getForm().load( {
url: '/ws/cellulemedicale/jsonEditDossier.php',
params: {
id_dossier: this.id_dossier
},
waitTitle: 'Patientez',
waitMsg: 'Rafraichissement',
failure:function(form, action) {
},
scope: this
} );
},
scope: this
}];
this.on('actioncomplete', function (form,action) {
if (action.type=='load') {
if(typeof action.result != 'undefined') {
console.log( 'load finished' );
console.log( res.data.emprunts );
var res = action.result;
var tab = Ext.getCmp('tab_emprunts_'+this.id_dossier);
tab.removeAll(true);
tab.add( new EmpruntsTabPanel( res.data.emprunts ) );
}
}
});
DossierPanel.superclass.initComponent.call(this);
}
});
Where is the code that does the initial loading?
console.log( res.data.emprunts ); is before you declare var res = action.result; Is this correct? (You may not be seeing the true result in the log)
At a high level, if something is working as expected on load but not after everything has rendered, I would look at if I need to call form/container .doLayout() after doing the remove / add magic from the form.
Let me know if this helps.