Custom xtypes as a cell in ext.listview - sencha-touch

I am using sencha touch 2 and not getting help inside sencha forum, so I hope you guys can help me.
I want to create a list with custom items. In this custom item i want to have a horizontal scrollable listview with buttons as items.
I tried to do it component.DataItem but it does no work for me.
I tried also to add an custom xtype als an item in a list, but this does not work.
I think this is a simple task but sencha touch makes it a challenge for me.
So please help me and show me, how can I get a view like shown in this picture.

Instead of a standard list you are going to want to use Component DataView. Essentially, you are going to need to first define an Ext.dataview.component.DataItem, which is then implemented into the DataView. Below is a simple example of a buttons in a DataView as referenced from the DataView guide: http://docs.sencha.com/touch/2-0/#!/guide/dataview
First create the DataItem:
Ext.define('MyApp.view.DataItemButton', {
extend: 'Ext.dataview.component.DataItem',
requires: ['Ext.Button'],
xtype: 'dataitembutton',
config: {
nameButton: true,
dataMap: {
getNameButton: {
setText: 'name'
}
}
},
applyNameButton: function(config) {
return Ext.factory(config, Ext.Button, this.getNameButton());
},
updateNameButton: function(newNameButton, oldNameButton) {
if (oldNameButton) {
this.remove(oldNameButton);
}
if (newNameButton) {
this.add(newNameButton);
}
}
});
We must extend Ext.dataview.component.DataItem for each item. This is an abstract class which handles the record handling for each item.
Below the extend we require Ext.Button. This is simply because we are going to insert a button inside our item component.
We then specify the xtype for this item component.
Inside our config block we define nameButton. This is a custom configuration we add to this component which will be transformed into a button by the class system. We set it to true by default, but this could also be a configuration block. This configuration will automatically generate getters and setters for our nameButton.
Next we define the dataMap. The dataMap is a map between the data of a record and this view. The getNameButton is the getter for the instance you want to update; so in this case we want to get the nameButton configuration of this component. Then inside that block we give it the setter for that instance; in this case being setText and give it the field of the record we are passing. So, once this item component gets a record it will get the nameButton and then call setText with the name value of the record.
Then we define the apply method for our nameButton. The apply method uses Ext.factory to transform the configuration passed into an instance of Ext.Button. That instance is then returned, which will then cause updateNameButton to be called. The updateNameButton method simply removes the old nameButton instance if it exists, and adds the new nameButton instance if it exists.
Now create the DataView:
Ext.create('Ext.DataView', {
fullscreen: true,
store: {
fields: ['name', 'age'],
data: [
{name: 'Jamie Avins', age: 100},
{name: 'Rob Dougan', age: 21},
{name: 'Tommy Maintz', age: 24},
{name: 'Jacky Nguyen', age: 24},
{name: 'Ed Spencer', age: 26}
]
},
useComponents: true,
defaultType: 'dataitembutton'
});
In your case, rather than using a button for the DataItem, you'll want to use a horizontal scrolling list. Here is an example that I found from this answer: Horizontal scrolling list
var list = Ext.create('Ext.DataView',{
store: store,
itemTpl: new Ext.XTemplate('<img src="{icon}" />'),
inline: { wrap: false },
scrollable: {
direction: 'horizontal',
directionLock: true
}
});
Note that you will probably have to use components in the second dataview as well in order to achieve your buttons with image

Related

Ext JS 4: Getters and setters in view

I've been thinking about this problem for a while, and I can't seem to come up with a reasonable solution. What I would like to do is create getters/setters for a textfield/its value in my view. I realize that the preferred Ext JS way is using a reference within the controller and getting it that way, but that doesn't feel very object-oriented to me. I'd also have to wrap these getters and setters because I want to output a message if the getter returns undefined. What I'd like to do is create my own getters/setters or somehow override the default getters/setters. Here are some ways I was thinking of accomplishing this.
I was thinking I could use the config {}, but that appears to only work for variables I want to define. I then was thinking of using an id somehow, but the community seems split on whether that's a good practice or not. Which leads to my current solution... wrapping. Here's my code:
LoginWindow
Ext.define('MyApp.view.LoginWindow', {
extend: 'Ext.window.Window',
alias: 'widget.loginWindow',
autoShow: true,
closable: false,
border: 0,
plain: true,
allowBlank: false,
title: "Enter your username",
modal: true,
config: {
buttons: [{
text: "Ok"
}],
items: [{
xtype: 'textfield',
fieldLabel: 'Username',
id: 'loginUserInput',
name: 'loginUserInput',
msgTarget: 'under',
validator: function(value) {
if (Ext.isEmpty(value)) {
return "You need to enter a username.";
}
return true;
}
}]
},
constructor: function(config) {
this.callParent(config);
},
getButton: function() {
console.log('here');
}
});
MyController
Ext.define('MyApp.controller.Chat', {
extend: 'Ext.app.Controller',
requires: [
'Views.ChatModule.view.LoginWindow'
],
refs: [{
ref: 'loginWindow',
selector: 'loginWindow',
xtype: 'loginWindow',
autoCreate: true
}, {
ref: 'loginUserInput',
selector: '#loginUserInput'
}],
init: function() {
// The events controller oversees
this.control({
'loginWindow button[text="Ok"]': {
'click': this.onSubmitLoginWindow
}
});
},
getLoginUserInputValue: function() {
var loginUserInput = this.getLoginUserInput();
if (loginUserInput) {
var username = loginUserInput.getValue();
if (username) {
console.log(username);
} else {
console.warn("username is undefined");
}
}
console.warn("loginUserInput is undefined");
},
onSubmitLoginWindow: function(button, event, eOpts) {
this.getLoginUserInputValue();
}
});
This works, and I realize it's a very nit-picky thing, but it just doesn't feel right to have the getter in the controller. I feel like it'd be more object-oriented if it was in the Window. However, if I put it in the Window, I believe my only option is to lean on ids or manually create the textfield in the Window's initComponent--which would involve saving off a reference of the textfield in there, but that seems a bit inefficient... as I would have to make a call to doLayout as well.
Just to reiterate, I'd love to have the getters/setters in the Window, and I'm looking for a quick way to reference it, similar to how the controller references objects. I believe the main answer will be to use ids and making a call to Ext.ComponentQuery.query('#loginUserInput') in the Window, but I'd like to know if there were any better approaches out there... like overriding the auto generated getters/setters or adding a simple getter/setter for an input's value.
Cross-post from the Sencha forums.
Edit
I guess I was a bit unclear with what I want. As a more general statement, instead of jamming all things related to my view in the controller, I'd like to store it all in the view itself, which includes things like getters/setters. One of these getters/setters just so happens to be the loginUserInput getter.
Using a model is an interesting idea, but I feel like that would be a whole lot of overhead for singleton values. I'm basically looking for something like Java's setters/getters in the LoginWindow view... and hopefully something as simple as (or close to) Java's.
The idea of including (encapsulating) it in the view makes the controller a bit cleaner, and if I delete the view, I'm deleting its functions as well, so I don't have to go hunting for the functions in the controller... all I have to worry about is removing the references (which should be minimal).
I think that the "OO" way that you're looking is to work with a Ext.data.Model for your form. If you look at the Ext.form.Basic you have methods to manipulate a model (called record) and also get the object with the values of your view. So you need:
When you create your form, use loadRecord() to bind your form to a Model.
At any time you need, use getValues() to retrieve the values of your form fields.
When submiting your form, use getRecord() and getValues() to sync your record.
Ext.define('MyApp.model.Login',{
fields : [{
name: 'username',
type: 'string'
},{
name: 'password',
type: 'string'
}]
});
Ext.define('MyApp.controller.Login',{
...
refs : [{
selector: 'window form',
ref: 'formPanel'
}],
...
openForm : function() {
//load your form and then bind the new record
var formPanel = this.getFormPanel(), //Ext.form.Panel
form = formPanel.getForm(); //Ext.form.Basic
form.loadRecord(Ext.create('MyApp.model.Login'));
},
save : function() {
//get the values in the view
var form = this.getFormPanel().getForm(),
vals = form.getValues(),
record = form.getRecord();
console.log(vals); //see the object representation of your view here
record.set(vals); //update your model
//do whatever you need with your model
}
...
});
This is an good example when you need to save the form data. In the login I think you can work directly with getValues() without binding it to a Ext.data.Model.
I am not quite certain what problem you are trying to solve to be honest with you.
If you do not like controllers listening to buttons within your window, you can have button handlers witin your view definition fire custom events that controllers can listen on. Use fireEvent method. And by the way initConfig is a recommended way to setup your views. You can break it up into methods if you wish, 'this' reference is available and is the View component being instantiated.
If you need to find inner components within the View there are many methods available from up /down to nextSibling and query .
For Components:
• Ext.getCmp(id)
• Ext.ComponentQuery.query()
• up()
• down()
• nextSibling()
• previousSibling()
• child()
• previousNode()
plus various find.. Methods
EDIT
I think I understood what you mean by getter and setters. Ext forms have the fields finders to make it easy to get and set data to individual fields. See these SO questions: Best way to access adjacent components / fields and EXT.JS getting a list of items from a fieldset
Also like Sergio said there is getRecord getValues and setRecord methods on the form to deal with data binding. Thats it.
EDIT2
The best starting point guide that shows clear and claen MVC patterns as well as form handling. http://docs.sencha.com/extjs/4.1.3/#!/guide/application_architecture
My thoughts are something like this:
...
items: [],
constructor: function(config) {
this.loginUserInput = Ext.create('Ext.form.field.Text', {
fieldLabel: 'Username',
id: 'loginUserInput',
name: 'loginUserInput',
msgTarget: 'under',
validator: function(value) {
if (Ext.isEmpty(value)) {
return "You need to enter a username.";
}
return true;
}
});
this.items.push(this.loginUserInput);
this.callParent(config);
},
getLoginUserInput: function() {
var loginUserInput = this.loginUserInput;
if (!loginUserInput) {
console.warn("LoginWindow::getLoginUserInput: loginUserInput is undefined");
}
return loginUserInput;
}
So instead of letting Ext do its magic, I am now instantiating the object on my own, which then allows me to store away a reference of it, so I can easily access it in my getter. I just wonder if this is creating any sort of performance hit. It doesn't seem like it'd be that much worse... it actually seems like it'd be a bit better because I'm not referencing this object by its ID, and I don't have to go searching for it when I need it.

Pass value to view on create

Im trying to make a custom view and pass a value when I add it in another item using the xtype. It looks simple because I dont need to use stores or anything like that, its just static values but I cant achieve it :(
My idea is to place this in a component (the parent of my custom component):
...
items: [{
xtype: 'myNewComponent',
car: 'Renault'
}]
...
And then in my custom component get the value:
Ext.define('myNewComponent', {
extend: 'Ext.Panel',
xtype: 'myNewComponent',
config: {
items: [{
html: 'This is my car: ' + this.config.car
}]
}
});
I think that Im not understanding something, could you help me?
Thanks!
There are 2 things you need to do.
Firstly, you must create a the new config in your custom component. Doing this is as simple as adding it into the config object of your class:
Ext.define('myNewComponent', {
extend: 'Ext.Panel',
xtype: 'myNewComponent',
config: {
car: null
}
});
null here is merely the default value if you do not change it when you create the component.
Now we want to use this new config. What you have done will not work as the scope of this.config.car is the DOM window. You will need to create the item using a function of your class. You can achieve this by using the new updateCar method of your new car config. This method is called anytime you update that config. In your case, that is when you first create your custom component.
Ext.define('myNewComponent', {
...
updateCar: function(newCar) {
this.add({
html: 'This is my car: ' + newCar
});
}
});
You can find out more about how the config system works here: http://docs.sencha.com/touch/2-1/#!/guide/class_system

How to perform View-Controller separation when using an "actioncolumn" (Ext.grid.column.Action)

In ExtJS 4, I have a grid that contains an action column. Whenever that action is triggered, I want to execute "my action".
Without MVC, this would look like this:
/* ... */
{
xtype: 'gridpanel',
columns: [
/* ... */
{
xtype: 'actioncolumn',
items: [{
handler: function(grid, rowIndex, colIndex) {
// my action
}
}]
}
]
}
Now I want to introduce the View-Controller separation. So I have to move the handler from the View to the Controller.
But how does the controller register its method to the action column? Looking at the ExtJS 4.1 actioncolumn docs, I can't find any event I could listen to. I also can't find a method to set the action column's handler afterwards.
So how can I achieve a clean View-Controller separation when using an actioncolumn?
Are actioncolumns not yet ready for MVC?
The problem is not in actioncolumn but in its items which are not ExtJs Widgets. This items are simple images. That's why we cannot assign handlers in control in such a way:
this.control({
'mygrid actioncolumn button[type=edit]' : this.onEdit
This way, however, would be the best one.
Unfortunately this way is impossible. But There is another way, which is almost as clean as the preferred one: make actioncolumn handler to fire grid's custom event (created by you):
// view
{
xtype: 'actioncolumn',
items: [{
icon: 'http://cdn.sencha.io/ext-4.1.0-gpl/examples/shared/icons/fam/cog_edit.png',
tooltip: 'Edit',
handler: function(grid, rowIndex, colIndex) {
// fire custom event "itemeditbuttonclick"
this.up('grid').fireEvent('itemeditbuttonclick', grid, rowIndex, colIndex);
}},
// controller
init: function() {
this.control({
'viewport > testpanel': {
itemeditbuttonclick: this.onEdit,
itemdeletebuttonclick: this.onDelete
}
});
},
Example
Here is demo.
This post explains an even simpler method than the accepted answer, if you only happen to have one type of actioncolumn item in your grid:
http://mitchellsimoens.com/actioncolumn-and-mvc/
Basically: just listen for the actioncolumn's click event in your controller. However, this doesn't work if you need to distinguish between multiple actioncolumn types.

Displaying a text on a view

The code below is part of my controller function;
success: function (response) {
var text = response.responseText;
var result = Ext.decode(response.responseText);
var indexPanel = Ext.create('app.view.PersonDetails');
Ext.getCmp('mainView').push({
xtype:'person',
data: result
});
}
The code below, is the view, which i am passing values from my Controller function (above).
The code below, demonstrates hard coded data in that view (Hard coded text), but what i want to do is to display the data: result that i am passing from Controller function (above) to be displayed in the following view. How can i do this ?
Ext.define('app.view.UserInformation',{
extend:'Ext.Panel',
xtype:'person',
config: {
title:'Person details',
html:['Hard coded text'].join("")
}
});
UPDATE
The result contains several values like;
result.name, result.age. result.gender
I will be passing result to the other view.
1.) from the View, how can i add a button ? and wen the user clicks on that button how can i fetch the result.age field and do a if condition to check if the age is below 10 ?
2.) Imagine, if there's a field called, result.imageurl, how could i display the image on the other view (in a frame) ?
UPDATE2
Ext.getCmp('mainpanel').push({
title: 'Hello ' ,
xtype:'person'
});
Ext.getCmp('idOfTheView').setRecord(result.first_name);
Your question is Sencha Touch alone, not relevant to PhoneGap. :)
Suppose that your view has an id: view_id
Then in your controller function:
Ext.getCmp('view_id').setHtml(what you want to put into your view)
Updated answer:
Your question consists of several sub-questions. I'm afraid that the scope you're asking is too wide but I will answer the most important part.
(from my own application):
Ext.define('rs.view.ProductInfo', {
extend: 'Ext.Container',
xtype: 'ProductInfo',
id: 'product-info',
cls: 'product-info',
config: {
items: [
{
xtype: 'panel',
styleHtmlContent: true,
id: 'product-info-header',
tpl: [
'<div class="product-info-header">',
'<img src={image} width="100px" height="100px"/>',
'<h3>{name}</h3>',
'<h4>Price: {price}</h4>',
'<h4>Seller: {sellerUsername}</h4>',
'</div>',
],
},
],
}
});
Note that I defined a Model with attributes, {image},{name},{price},{sellerUsername}, then in the code snippet above, you can see that I use them in tpl config as if normal use in Ext.List (with store config). So how can I do it?
First, you have to define a model describing your result. obviously.
Second, define tpl in your view, I believe you can figure it out from the example above.
Finally, use this (assume that you've written the result received from server into your Model instance which I've mentioned in first step):
Ext.getCmp('your_view_id').setRecord(your_model_instance)
100% work warranty because I've used this many times. Hope it helps. Should you have any question, please leave a comment.

Extjs4 - Reloading store inside itemselector

I have an itemselecor inside a grid, and i have an combobox that should reload only the store inside the itemselector( the value fields should remain intact)
Here is the itemselector
var grid = Ext.widget('form', {
id: 'grid',
title: '',
width: 600,
bodyPadding: 10,
renderTo: 'itemselectorproduto',
items: [{
xtype: 'itemselector',
name: 'itemselector',
id: 'itemsel',
anchor: '100%',
imagePath: '/ux/images/',
store: store,
displayField: 'Nome',
valueField: 'ID',
value: vitrine,
allowBlank: true,
msgTarget: 'side'
}]
});
I tried to call the normal store.load(), but it have no effect and it shows no error on the console
If needed i will post more of the code, but i think just this should be enough
Thanks,
Looking through the code if ItemSelector it doesn't look like it supports any changes in the store once it's been binded. It basically creates local copies of the data. And if you call bindStore method to assign different store - it will erase your selection.
You can always improve code in ItemSelector to allow such behavior. It should not be a problem. You can subscribe to datachanged or load event of the store when binding to it, and then handle situation when store data is changed.
Actually i think the best way to do such thing is using ItemSelector`s "populateFromStore". Sure except of extending item selector. In case of extending you should look on the "onBindStore" function of the ItemSelector.
onBindStore: function(store, initial) {
var me = this;
if (me.fromField) {
me.fromField.store.removeAll()
me.toField.store.removeAll();
// Add everything to the from field as soon as the Store is loaded
if (store.getCount()) {
me.populateFromStore(store);
} else {
me.store.on('load', me.populateFromStore, me);
}
}
}
So as you see in case of the empty store it hooks to the load event. And it should be like this:
onBindStore: function(store, initial) {
var me = this;
if (me.fromField) {
me.fromField.store.removeAll()
me.toField.store.removeAll();
me.store.on('load', me.populateFromStore, me);
// Add everything to the from field as soon as the Store is loaded
if (store.getCount()) {
me.populateFromStore(store);
}
}
}