ExtJS 4.2.1 XTemplate and subtemplates (statics) - javascript

I got a custom Ext.Component with a view XTemplates. I do need some of theese Templates outside of the view in my controller too.
Is it possible to refer to static members in functions of a XTemplate. Or is there another much better way???
something like this:
Ext.define('app.view.ApplicationHeader', {
extend: 'Ext.Component',
name: 'app-header',
xtype: 'app-header',
height: 67,
margin: 0,
statics: {
mainIconTpl: new Ext.XTemplate('someTemplate'),
navigationItemsTpl: new Ext.XTemplate( 'anotherTemplate'),
userInfoTpl: new Ext.XTemplate('userTemplate')
},
html: new Ext.XTemplate('... {[ this.renderMainIcons() ]} {[ this.renderUserInfo() ]} ...',
'... {[ this.renderNavigationBarItems() ]} ...',
{
me: this,
renderMainIcons: function () {
return view.static.mainIconTpl.apply(MR.Sitemap.Items);
},
renderUserInfo: function () {
return view.static.userInfoTpl.apply();
},
renderNavigationBarItems: function () {
return view.static.navigationItemsTpl.apply();
}
}).apply()
});
i also dont know how i could apply subtemplates which are members of the view. I declared them global right know which i really dont like to do.
please!

Your code is not working because the apply method of the main template is called before the class definition (i.e. the define method) is even called.
You can create your static template that uses the other static members of the class in the post-create function (see the last param of the define method).
Then in order for the template to be available, I would override the initComponent method and set the html property there.
Ext.define('app.view.ApplicationHeader', {
extend: 'Ext.Component',
name: 'app-header',
xtype: 'app-header',
height: 67,
margin: 0,
statics: {
mainIconTpl: new Ext.XTemplate('someTemplate'),
navigationItemsTpl: new Ext.XTemplate('anotherTemplate'),
userInfoTpl: new Ext.XTemplate('userTemplate')
},
initComponent: function() {
// Here, your statics are available, and you're in the scope of your
// class *instance*
this.html = this.self.viewTemplate.apply();
this.callParent(arguments);
}
}, function() {
// In the post create function, this is the class constructor
// (i.e. app.view.ApplicationHeader)
var cls = this;
// In fact, you could also create your sub templates here if you prefer
// e.g.
// cls.useInfoTpl = new Ext.XTemplate('userTemplate')
// So, viewTemplate will be a static property of the class
cls.viewTemplate = new Ext.XTemplate('... {[ this.renderMainIcons() ]} {[ this.renderUserInfo() ]} ...',
'... {[ this.renderNavigationBarItems() ]} ...', {
renderMainIcons: function() {
return cls.mainIconTpl.apply();
},
renderUserInfo: function() {
return cls.userInfoTpl.apply();
},
renderNavigationBarItems: function() {
return cls.navigationItemsTpl.apply();
}
});
});

According to the link, you should be able to put this directly in your XTemplate. No need for statics
{[ MyApp.tpls.someOtherTpl.apply(values) ]}
Multiple templates in Nested List
You could also try putting all of these XTemplates in initComponent instead since you're not injecting any values for XTemplate after initial component render. The apply() will just return you an HTML fragment which should be able to be appended anywhere within the XTemplate.
If you're trying to put logical or conditional tpl operators i.e. <tpl for="parent.someVar">...</tpl> in any of the sub XTemplates, then that's another problem so it all depends on what you're trying to accomplish.
Ext.define('app.view.ApplicationHeader', {
extend: 'Ext.Component',
name: 'app-header',
xtype: 'app-header',
height: 67,
margin: 0,
initComponent: function() {
var me = this,
me.mainIconTpl = new Ext.XTemplate('someTemplate'),
me.navigationItemsTpl = new Ext.XTemplate( 'anotherTemplate'),
me.userInfoTpl = new Ext.XTemplate('userTemplate');
me.tpl = new Ext.XTemplate(
'...', me.mainIconTpl.apply(MR.Sitemap.Items),
'...', me.navigationItemsTpl.apply(someValues),
'...', me.userinfoTpl.apply(someValues),
'...'
);
Ext.apply(me, {
html: me.tpl
});
me.callParent();
}
});

Related

How to split up code for better readability?

I want to clean up my code for better readability and put some code in extra js-files but nothing I've tried has worked.
It's a SubApp and part of a larger Project (Shopware) that is still using ExtJs 4.1.
I have a "main/window.js" that extends 'Ext.window.Window'.
initComponent looks like this:
initComponent: function () {
//...
me.dockedItems = [
me.createTopToolbar(),
];
//...
}
createTopToolbar is a Method inside "main/window.js" that return a Ext.toolbar.Toolbar with some elements.
My goal is to put this method in an extra file.
I tried to create a new static/singleton class like this
Ext.define('myapp.myplugin.view.main.Toptoolbar', {
singleton: true,
createTopToolbar: function(){
// ...
return toolbar;
}
But inside "main/window.js" i cannot call it using myapp.myplugin.view.main.Toptoolbar.createTopToolbar(), or main.Toptoolbar.createTopToolbar()
In app.js i tried to include it like this
views: [
'main.Window',
'main.Toptoolbar',
],
but it doesnt work.
I have no experience with ExtJs and search for hours...hope someone can help me.
Thank you
Edit
To answer the question why i'm building the toolbar within a function.
The whole functions looks like this:
createTopToolbar: function () {
var me = this;
var shopStore = Ext.create('Shopware.apps.Base.store.Shop');
shopStore.filters.clear();
shopStore.load({
callback: function(records) {
shopCombo.setValue(records[0].get('id'));
}
});
var shopCombo = Ext.create('Ext.form.field.ComboBox', {
name: 'shop-combo',
fieldLabel: 'Shop',
store: shopStore,
labelAlign: 'right',
labelStyle: 'margin-top: 2px',
queryMode: 'local',
valueField: 'id',
editable: false,
displayField: 'name',
listeners: {
'select': function() {
if (this.store.getAt('0')) {
me.fireEvent('changeShop');
}
}
}
});
var toolbar = Ext.create('Ext.toolbar.Toolbar', {
dock: 'top',
ui: 'shopware-ui',
items: [
'->',
shopCombo
]
});
return toolbar;
}
And i dont want the whole code inside my "main/window.js".
I'm not sure using the xtype solution provided by Jaimee in this context because i dont realy extend 'Ext.toolbar.Toolbar'. I just need a "wrapper" for my "shopCombo" code and return 'Ext.toolbar.Toolbar' with shopCombo as an item.
Create the toolbar as you would any other view, and create an 'xtype' to add it to your window.
Ext.define('myapp.myplugin.view.main.Toptoolbar', {
extends: 'Ext.toolbar.Toolbar',
xtype: 'mytoptoolbar',
dock: 'top',
ui: 'shopware-ui',
items :[
'->',
{
xtype: 'combobox',
name: 'shop-combo',
fieldLabel: 'Shop',
store: 'shopStore',
labelAlign: 'right',
labelStyle: 'margin-top: 2px',
queryMode: 'local',
valueField: 'id',
editable: false,
displayField: 'name',
listeners: {
'select': function() {
if (this.store.getAt('0')) {
me.fireEvent('changeShop');
}
}
}
}]
});
And then simply add it to your window's docked items like you would any other component:
initComponent: function () {
//...
me.dockedItems = [
{
xtype: 'mytoptoolbar'
}
];
//...
}
And the load listener can be added to the store.
Ext.define('Shopware.apps.Base.store.Shop', {
// ....
listeners:
{
load: function() {
Ext.ComponentQuery.query("combobox[name='shop-combo']")[0].setValue(...)
}
}
However, I'd suggest just setting a starting value for the combobox if it's a predictable value. You'll need to add the store to your controller's stores config if you haven't already.
You could use a factory pattern like so:
Ext.define('ToolbarFactory', {
alias : 'main.ToolbarFactory',
statics : {
/**
* create the toolbar factory
*/
factory : function() {
console.log('create the toolbar');
var toolbar = ....;
return toolbar;
}
}
});
Then you can create the Toolbar this way:
initComponent: function () {
//...
me.dockedItems = [
ToolbarFactory.factory();
];
//...
}
Depending on your build process you may need to add a requires statement.

ExtJS 5: Initial ViewModel data not firing formula

I have a ViewModel that contains some initial data... this initial data is based off of a global variable that I have created. In the ViewModel, I have a formula that does some logic based on the data set from the global variable. The interesting thing is, this formula does not fire when the ViewModel is created. I'm assuming this is because the Something.Test property does not exist, so the ViewModel internals have some smarts to not fire the method if that property does not exist.
If the property doesn't exist, how do I fire the formula anyway? I know I could look for Something check to see if it has the property Test, but I'm curious why this example wouldn't work. Here's the example:
Ext.application({
name : 'Fiddle',
launch : function() {
// Define global var Something
Ext.define('Something', {
singleton: true
});
Ext.define('MyViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.myView',
data: {
Something: window.Something
},
formulas: {
testSomething: function(getter) {
console.log('here', getter('Something.Test'));
return getter('Something.Test');
},
myTitle: function(getter) {
return 'My Title';
}
}
});
Ext.define('MyView', {
extend: 'Ext.panel.Panel',
bind: {
title: '{myTitle}'
},
viewModel: {
type: 'myView'
}
});
var view = Ext.create('MyView', {
renderTo: Ext.getBody()
});
// This will fire the ViewModel formula
//view.getViewModel().set('Something', window.Something);
console.log(Something, window.Something)
}
});
You can workout some logic to handle when Something.Test is not available, something like:
data: {
Something: window.Something && window.Something.Test || {Test: null}
},
formulas: {
testSomething: function(get) {
var val = get('Something.Test');
console.log('Test');
return val;
},
myTitle: function(getter) {
return 'My Title';
}
}

Call `show` on Marionette LayoutView region

I have this Layout View:
var appLayoutView = Backbone.Marionette.LayoutView.extend({
template: function() {
return "some template string";
},
regions: {
notify: "[data-region='Notify']"
},
onShow: function() {
this.regions.notify.show(new notifyView());
}
});
Which I call like so:
mainLayout.app.show(appLayout);
So ideally, I'd like, when I run the above line (essentially when the layout view is put into the DOM) for the notifyView to be rendered into the "notify" region. However this.regions.notify is just a string. How can I achieve what I'm trying to do here? Basically having the render logic for "notify" inside the Layout View class, and not controlled from the invocation line.
I can't find any docs that show where this got added, but LayoutView should have a getRegion method :
https://github.com/marionettejs/backbone.marionette/blob/master/src/marionette.layoutview.js#L74
so your code would look like :
var appLayoutView = Backbone.Marionette.LayoutView.extend({
template: function() {
return "some template string";
},
regions: {
notify: "[data-region='Notify']"
},
onShow: function() {
this.getRegion('notify').show(new notifyView());
}
});

Add onLoad Listener to Sencha Touch List

I want to perform selection operation on List after the data has been loaded, because based on the data which I received I have to select one cell in that list and also need to update the detail view base on that.
Ext.define('WaxiApp.view.ProductViews.ProductList', {
extend: 'Ext.Container',
alias: "widget.ProductList",
requires: [
'Ext.Img',
],
config: {
layout: Ext.os.deviceType == 'Phone' ? 'fit' : {
type: 'hbox',
pack:'strech'
},
cls: 'product-list',
items: [{
xtype: 'list',
id:'product-list-view',
width: '100%',
height:'100%',
store: 'ProductsList',
infinite: true,
plugins: 'sortablelist',
itemCls: 'productList',
itemId:"product-item",
itemTpl: new Ext.XTemplate(
'<div class="list-content-div ',
'<tpl if="this.needSortable(isNeedInventry)">',
Ext.baseCSSPrefix + 'list-sortablehandle',
'</tpl>',
'">',
'<b>{UpcCode} {Name}</b>',
'<tpl if="isActive">',
'</div>',
'</tpl>',
{
// XTemplate configuration:
compiled: true,
// member functions:
needSortable: function (isneedInventry) {
return !isneedInventry;
},
}),
masked: { xtype: 'loadmask',message: 'loading' },
onLoad: function (store) {
this.unmask();
console.log('list loaded');
this.fireEvent("productListLoadedCommand", this,store);
},
}
],
listeners: [
{
delegate: "#product-list-view",
event: "itemtap",
fn: function (list, index, target, record) {
console.log(index);
console.log('list selection command fired');
this.fireEvent("productListSelectedCommand", this,index,record);
}
}
],
style: 'background-image: -webkit-gradient(linear, left top, right bottom, color-stop(0, #FDFDFD), color-stop(1, #DBDBDB));background-image: linear-gradient(to bottom right, #FDFDFD 0%, #DBDBDB 100%);'
}//End of config
});//End of Define
Above this actual view I used to display the list. My problem is I tried onLoad() method it work but i want do everything in my Controller to make it more clear.
As you saw my itemTap event has been handled in Controller by firing event. But same is not working for load event.
As mentioned by #Jimmy there is no onLoad method on list. However there are a few ways to work around it. My understanding of what you want to achieve is that when the store backing the list is loaded, you want an event to be fired from the ProductList instance (not the list) such that in the controller you can configure the control to be:
control: {
ProductList: {
productListSelectedCommand: 'productListSelectCommand',
productListLoadedCommand: 'productListLoadedCommand'
}
}
If so then we can modify the listeners in your existing ProductList class to do the following:
listeners: [
{
delegate: "#product-list-view",
event: "itemtap",
fn: function (list, index, target, record) {
console.log(index);
console.log('list selection command fired');
this.fireEvent("productListSelectedCommand", this,index,record);
}
},
{
delegate: "#product-list-view",
event: "show",
fn: function (list) {
var store = list.getStore();
var handler = function() {
list.unmask();
console.log('list loaded');
this.fireEvent("productListLoadedCommand", this, store);
};
if (store.isLoaded()) {
handler.apply(this)
} else {
list.getStore().on('load', handler, this);
}
}
}
]
What this is does is to what for the list to be shown and then get it's store, if the store has loaded then invoke the handler, otherwise register a load listener directly on it. Note that the this object here will be the ProductList not the product-list-view.
Per the Sencha Touch documentation, I do not see an onLoad function for Ext.dataview.List. However, there is a load event listener for the Ext.data.Store, which the list contains. So, your event listener should probably be on the data store, not necessarily the list itself.
Inside your controller's launch method, you could setup a listener for the Store's load event like so:
launch: function () {
// your store should be setup in your Ext.application
Ext.getStore('NameOfStore').on('load', this.productListLoadedCommand);
},
productListLoadedCommand: function(store, records, successful, operation, eOpts) {
// do some after load logic
}
You should set up your event listener for your list in the controller as well. There should be no need for you to create a listener in the view config only to call a fireEvent method in the Controller. Instead, do all event handling in the controller. To get a handle on your list in the Controller, add a xtype: 'productlist' inside the Ext.define for your WaxiApp.view.ProductViews.ProductList. Then, add your list to the Controller's config as a ref and attach the itemtap event for the list in the control like so:
config: {
ref: {
productList: 'productlist'
},
control: {
productList: {
itemtap: 'productListSelectCommand'
}
}
},
productListSelectCommand: function (list, index, target, record, e, eOpts) {
// do some itemtap functionality
}
In the end, your controller might look something like this:
Ext.define('MyApp.controller.Controller', {
extend: 'Ext.app.Controller',
requires: [
// what is required
],
config: {
ref: {
productList: 'productlist'
},
control: {
productList: {
itemtap: 'productListSelectCommand'
}
}
},
launch: function () {
// your store should be setup in your Ext.application
Ext.getStore('NameOfStore').on('load', this.productListLoadedCommand);
},
productListLoadedCommand: function(store, records, successful, operation, eOpts) {
// do some after load logic
// this.getProductList() will give you handle of productlist
},
productListSelectCommand: function (list, index, target, record, e, eOpts) {
// do some itemtap functionality
}
}
Finally, don't forget to add a xtype: 'productlist' inside the Ext.define for your WaxiApp.view.ProductViews.ProductList. I'm not sure of your overall experience with Sencha Touch application design, but here is a good reference for understanding their view, model, store, controller structure.
I found the solution exactly how to handle this scenario and posted my
own solution.
ProductList.js
Ext.define('WaxiApp.view.ProductViews.ProductList', {
extend: 'Ext.Container',
alias: "widget.ProductList",
requires: [
'Ext.Img',
],
initialize: function () {
this.add([
{
xtype: 'list',
id: 'product-list-view',
store: 'ProductsList',
masked: { xtype: 'loadmask', message: 'loading' },
//onLoad is not a listener it's private sencha touch method used to unmask the screen after the store loaded
onLoad: function (store) {
this.unmask();//I manually unmask, because I override this method to do additional task.
console.log('list loaded');
this.fireEvent("productListLoadedCommand", this, store);
}
,
//Painted is event so added it to listener, I saw fee document state that, add event like Painted and show should to added to the
//Component itslef is best practice.
listeners: {
order: 'after',
painted: function () {
console.log("Painted Event");
this.fireEvent("ProductListPaintedCommand", this);
},
scope: this
//This is also very important, because if we using this in card layout
//and changing the active view in layout cause this event to failed, so
//setting scope to this will help to receive the defined view instead of this list component.
}
}]);
},
config: {
listeners: [
{
delegate: "#product-list-view",
event: "itemtap",
fn: function (list, index, target, record) {
console.log(index);
console.log('list selection command fired');
this.fireEvent("productListSelectedCommand", this, index, record);
}
}
],
}//End of config
});//End of Define
ProductViewController.js
/// <reference path="../../touch/sencha-touch-all-debug.js" />
Ext.define("WaxiApp.controller.ProductsViewController", {
extend: "Ext.app.Controller",
listStoreNeedLoad:true,
config: {
refs: {
ProductContainer: "ProductList",
ProductList: "#product-list-view",
ProductDetailView:"ProductDetail"
},
control: {
ProductContainer:{
productListSelectedCommand: "onProductListSelected",
ProductListPaintedCommand: "onProductListPainted"
},
ProductList:{
productListLoadedCommand: "onProductListLoaded"
}
}//End of control
},//End of config
// Transitions
getstore:function(){
return Ext.ComponentQuery.query('#product-list-view')[0].getStore();
},
onProductListPainted:function(list)
{
//Check for if need to load store because painted event called every time your view is painted on screen
if (this.listStoreNeedLoad) {
var store = this.getstore();
this.getstore().load();
}
},
onProductListLoaded:function(list,store)
{
this.listStoreNeedLoad = false;
var index = 0;
//Iterate through record and set my select Index
store.each(function(record,recordid){
console.info(record.get("isNeedInventry"));
if (record.get("isNeedInventry")) {
return false;
}
index++;
});
console.log('list load event fired');
if(Ext.os.deviceType.toLowerCase()!='phone')
{
this.setRecord(store.getAt(index));
list.select(index);
}
}
})//End of define

Backbone.Marionette view with subviews

What is the apropriate aproach to setup a view in a Backbone.Marionete environment to have a list of subviews, without manually rendering them, and consume as least as possible memmory.
The view with child views is rendered based on a template, and is a part of a tab control tabs. The tamplete for the tab view has divs, which are used as a placholders for child controls ( two collection views and two helper controls )
Several aproaches I've made already:
1) Create view instances in render method and, attach them to a propper el hardcoding the selectors in render method.
2) Extend a marionete layout and declare a regions for each view.
var GoalsView = Marionette.Layout.extend({
template: '#goals-view-template',
regions: {
content: '#team-goals-content',
homeFilter: '#team-goals-home-filter',
awayFilter: '#team-goals-away-filter'
},
className: 'team-goals',
initialize: function () {
this.homeFilterView = new SwitchControlView({
left: { name: 'HOME', key: 'home' },
right: { name: 'ALL', key: 'all' },
});
this.awayFilterView = new SwitchControlView({
left: { name: 'AWAY', key: 'away' },
right: { name: 'ALL', key: 'all' },
});
this.сontentView = new GoalsCollecitonView({
collection: statsHandler.getGoalsPerTeam()
});
},
onShow: function () {
this.content.show(this.сontentView);
this.homeFilter.show(this.homeFilterView);
this.awayFilter.show(this.awayFilterView);
}
});
This is the cool way, but I am worried about the overhead for maintaing regions collection which will always display single view.
3) I extended marionette item view with the following logic:
var ControlsView = Marionette.ItemView.extend({
views: {},
onRender: function() {
this.bindUIElements();
for (var key in this.ui) {
var view = this.views[key];
if (view) {
var rendered = view.render().$el;
//if (rendered.is('div') && !rendered.attr('class') && !rendered.attr('id')) {
// rendered = rendered.children();
//}
this.ui[key].html(rendered);
}
}
}
});
Which allowed me to write following code
var AssistsView = ControlsView.extend({
template: '#assists-view-template',
className: 'team-assists',
ui: {
content: '#team-assists-content',
homeFilter: '#team-assists-home-filter',
awayFilter: '#team-assists-away-filter'
},
initialize: function () {
this.views = {};
this.views.homeFilter = new SwitchControlView({
left: { name: 'HOME', key: 'home' },
right: { name: 'ALL', key: 'all' },
});
this.views.awayFilter = new SwitchControlView({
left: { name: 'AWAY', key: 'away' },
right: { name: 'ALL', key: 'all' },
});
this.views.content = new AssistsCollecitonView({
collection: statsHandler.getAssistsPerTeam()
});
}
});
But it will leak memmory for sure, and I not feel like I will be able to write proper code to handle memmory leaks.
So in general, what I want, is to have a nice declarative way to create a view with other views as controls on it, with protection agains memmory leaks and least memmory consumption possible...
P.S. sorry for the wall of text
Why don't you simply use a layout and display your views within the layout's regions? You can see an example here: https://github.com/davidsulc/marionette-gentle-introduction/blob/master/assets/js/apps/contacts/list/list_controller.js#L43-L46

Categories

Resources