Custom Control - how to encapsulate aggregation in another control - javascript

I might be going around this wrong, but I want to create a custom control that is a subclass of sap.m.Dialog. This new control will have an aggregation 'leftImage' which will then be placed in a HorizontalLayout (private variable). However, this causes an error possibly because the 'leftImage' is already a dependent of my control.
So how do I encapsulate an aggregation on another control?

sap.m.Dialog already has a "content" aggregation which holds the controls to be rendered, so you just need to add your custom bits there.
So, a pattern like this should work:
sap.ui.define([
"jquery.sap.global",
"sap/m/Dialog",
"sap/m/Image",
"sap/ui/layout/HorizontalLayout"
], function(jQuery, Dialog, Image, HorizontalLayout) {
"use strict";
var MyDialog = Dialog.extend("sap.ui.mylib.MyDialog", {
metadata: {
library: "sap.ui.mylib",
associations: {
leftImage: {type: "sap.m.Image", multiple: false}
}
},
renderer: {
// inherit rendering from sap.m.Dialog
}
});
MyDialog.prototype.init = function() {
if (Dialog.prototype.init) {
Dialog.prototype.init.apply(this, arguments);
}
var oImage = new Image({
src: '...'
}),
oHorizontalLayout = new HorizontalLayout({
content: [
oImage
]
});
this.addContent(oHorizontalLayout);
};
return MyDialog;
}, /* bExport= */ true);
Define leftImage as an association because it will be aggregated by the HorizontalLayout. It can be accessed later with:
var oLeftImage = sap.ui.getCore().byId(oMyDialog.getLeftImage());
You may also want to define the HorizontalLayout as an association so you can add more content to that as well.

Related

How to process two sets from different models in one custom control

Aim:
I'd like to have two models(sets of data) passed to the custom control with a predefined search field, in which later on I can execute filtering.
I'm a newbie in OpenUi5, so I might be doing something wrong and stupid here. I've started with a simplified task of passing data from the frontend to my custom control and experiencing troubles.
Background of the simplified idea:
Create a custom control with an aggregation foo , the value to it will be provided from the view.
Also create another aggregation element _searchField which will be populated with the data provided from the view.
Fire the onSuggestTerm everytime user types in a _searchField.
Custom control code:
function (Control) {
var DropDownListInput = Control.extend('xx.control.DropDownListInput', {
metadata: {
defaultAggregation: 'foo',
aggregations: {
foo: { type: 'sap.m.SuggestionItem', multiple: true, singularName: 'suggestionItem' },
_searchField: { type: 'sap.m.SearchField', multiple: false, visibility: 'hidden' }
}
}
});
DropDownListInput.prototype.init = function () {
var that = this;
this.onSuggestTerm = function (event) {
var oSource = event.getSource();
var oBinding = that.getAggregation('foo');
oBinding.filter(new sap.ui.model.Filter({
filters: new sap.ui.model.Filter('DISEASE_TERM', sap.ui.model.FilterOperator.Contains, ' Other')
}));
oBinding.attachEventOnce('dataReceived', function () {
oSource.suggest();
});
};
this.setAggregation('_searchField', new sap.m.SearchField({
id: 'UNIQUEID1',
enableSuggestions: true,
suggestionItems: that.getAggregation('foo'),
suggest: that.onSuggestTerm
}));
};
return DropDownListInput;
}, /* bExport= */true);
I'm not providing Renderer function for control here, but it exists and this is the most important excerpt from it:
oRM.write('<div');
oRM.writeControlData(oControl);
oRM.write('>');
oRM.renderControl(oControl.getAggregation('_searchField'));
oRM.write('</div>');
Passing the data to this control from the xml frontend:
<xx:DropDownListInput
id="diseaseTermUNIQUE"
foo='{path: db2>/RC_DISEASE_TERM/}'>
<foo>
<SuggestionItem text="{db2>DISEASE_TERM}"
key="{db2>DISEASE_TERM}" />
</foo>
</xx:DropDownListInput>
The code fails to run with this error Cannot route to target: [object Object] -
and I have no idea what's wrong here..
The problem is that you forgot to provide single quotes in your path:
foo="{path: 'db2>/RC_DISEASE_TERM/'}"

ExtJS: settings properties via a function on the Prototype: is it a safe pattern?

I am working on a large ExtJS codebase written around ExtJS3 which has a lot of the following initComponent() pattern:
Ext.define('PVE.form.BackupModeSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.pveBackupModeSelector'],
initComponent: function() {
var me = this;
me.comboItems = [
['snapshot', gettext('Snapshot')],
['suspend', gettext('Suspend')],
['stop', gettext('Stop')]
];
me.callParent();
}
now I have started to set this properties directly on the prototype doing things like:
Ext.define('PVE.form.BackupModeSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.pveBackupModeSelector'],
comboItems: [
['snapshot', gettext('Snapshot')],
['suspend', gettext('Suspend')],
['stop', gettext('Stop')]
],
initComponent: function doStuff() {
console.log('something we really need to do stuff here' + this.comboItems);
}
});
This works with ExtJS5, but is it a safe pattern ? can I be sure comboItems is already set when I call initComponent ?
I know about the https://docs.sencha.com/extjs/5.1/core_concepts/classes.html#Configuration config Object but this seems overkill.
In the first way, you are forcing comboItems value.
In the second, comboItems become available in config, with a default value. That means that you can override it.
var ms = Ext.create('PVE.form.BackupModeSelector', {
comboItems: [
// ... someting else
]
});
It depends if you want this value exposed to change or not.

Pass config to a custom Ext.tree.Panel and then to a custom Ext.data.Store

How can I pass a variable to an extended Ext.tree.Panel, which in turn, will pass it a custom Ext.data.Store.
Here is my code:
Ext.define('CustomStore', {
extend: 'Ext.data.TreeStore',
alias: 'widget.customstore',
folderSort : true,
model : 'TreeNode',
autoLoad: true,
config: {
customParam: 'defaultVal'
},
...
proxy: {
url: '/some/url?param'+this.customParam,
...
}
});
Ext.define('CustomTree', {
extend: 'Ext.tree.Panel',
alias: 'widget.customtree',
config: {
customParam2: 'defaultVal'
},
store: new CustomStore({customParam: this.customParam2'}),
...
});
var tree = Ext.create('CustomTree', {customParam2: 'someVal'});
As you can see, I want to pass a value someVal to the tree, which should pass it down to the store, the proxy of the store then needs to pick it up and use in its load url.
Tried many things, to name a few: config, initConfig, constructor, initComponent but with no good outcome.
You've got the right ingredients but you don't mix them in the right order.
The problem here is that your store creation code:
new CustomStore({customParam: this.customParam2'})
gets called before the definition of CustomTree:
Ext.define('CustomTree', ...)
This is because new CustomStore(...) is used as argument to the define function. So, obviously, it is also called before the line that sets the value of customParam2:
var tree = Ext.create('CustomTree', {customParam2: 'someVal'});
So in order to make it work, you want to create your store when the constructor of CustomTree is called. But when working with components, it is best to override initComponent instead of the constructor. So here's how you should do it:
Ext.define('CustomTree', {
extend: 'Ext.tree.Panel',
alias: 'widget.customtree',
config: {
customParam2: 'defaultVal'
},
// remove that
// store: new CustomStore({customParam: this.customParam2'});
// ... and put it in there:
initComponent: function() {
// creates the store after construct
this.store = new CustomStore({customParam: this.customParam2});
// call the superclass method *after* we created the store
this.callParent(arguments);
}
...
});
As for initConfig, you have to call it in the constructor in order for you're config params to be applied. But in your case, you're extending from Ext.data.Store and Ext.tree.Panel, and their constructor already calls it, so you don't have to do it yourself.

backbone: initialize model subclasses with additional attributes

I'm new to backbone, so please bear with me. I would like to create a collection in which the models all have a handful of critical attributes which they share as well as a number of other attributes which they do not share. I thought the best way to do this would be to extend a superclass model (containing defaults for all of the shared attributes) such that when I instantiate a new subclass model, those attributes are initialized and additional attributes specific to the subclass are also added to the model. I don't know how to accomplish this, but here is the direction I've been working in:
app.Fruit = Backbone.Model.extend(
{
defaults: {
name: "none",
classification: "none",
color: "none"
},
initialize: function()
{
console.log("Fruit Initialized");
}
});
app.Apple = app.Fruit.extend(
{
url: "/php/Apple.php",
initialize: function()
{
console.log("Apple initialized");
// somehow fetch additional information from server
// and add sublcass-specific attributes to model
// (for example, in the case of an apple, an attribute called cultivar)
}
});
var FruitCollection = Backbone.Collection.extend(
{
model: function(attributes, options)
{
switch(attributes.name)
{
case "Apple":
return new app.Apple(attributes, options);
break;
default:
return new app.Fruit(attributes, options);
break;
}
}
// ...
});
app.fruitCollectionCurrent = new FruitCollection([
{name: "Apple"},
{name: "Orange"}
]);
// logs: Fruit Initialized
Any suggestions on how to properly initialize a subclass with additional attributes would be appreciated.
Thanks.
EDIT: THE SOLUTION
I thought I would post the code that ended up working for me... Arrived at it thanks to #Mohsen:
app.Fruit = Backbone.Model.extend(
{
defaults: {
name: "none",
classification: "none",
color: "none"
},
initialize: function()
{
console.log("Fruit Initialized");
}
});
app.Apple = app.Fruit.extend(
{
url: "/php/Apple.php",
initialize: function()
{
console.log("Apple initialized");
return this.fetch();
}
});
I didn't even really need the asynchronous call in the subclass because I wasn't fetching any additional data for Fruit (Fruit's attributes were just set in the constructor), only for Apple. What I was really looking for was the call to this.fetch() with the specified URL. Sorry if the question made things seem more complex...
Backbone is not easy to work with when it comes to hierarchy. I solve this problem by calling parent model/collection initializer inside of my child model/collection initializer.
Fruit = Backbone.Model.extend({
url: "/fruits:id",
initialize: function initialize () {
this.set('isWashed', false);
return this.fetch();
}
});
Apple = Fruit.extend({
url: "/fruits/apples:id"
initialize: function initialize () {
var that = this;
Fruit.prototype.initialize.call(this, arguments).then(function(){
that.fetch();
})
this.set("hasSeed", true);
}
});
Now your Apple model does have all properties of a Fruit.
Key line is Fruit.prototype.initialize.call(this, arguments);. You call initialize method of Fruit for Apple.
You can also use this.__super__ to access parent model:
this.__super__.prototype.initialize.call(this, arguments);

Using custom attributes in ExtJS Component

My application has been growing for the last year or so and I have finally started to split out common components by extending existing ones. I've found this to be really helpful - increasing speed of development and maintenance. My problem is that I haven't grasped the concept of using custom parameters on my components and was hoping that someone could offer some assistance in the following example. Specifically I don't seem to be able to access the custom parameter (myCustomParam) inside the proxy declared in the initComponent function:
MyEmployeeGrid = Ext.extend(Ext.grid.GridPanel, {
myCustomParam: "CompanyX",
initComponent:function() {
//Store Reader
var myReader = new Ext.data.JsonReader({
root : 'objectsToConvertToRecords',
idProperty: 'id',
fields : [
{name: 'id'},
{name: 'employee', allowBlank:false},
{name: 'department', allowBlank:false}
]
});
//Store Proxy
var dwrProxy = new Ext.ux.data.DwrProxy({
apiActionToHandlerMap : {
read : {
dwrFunction : EmployeeService.readCompanyEmployees,
getDwrArgsFunction: function(request, recordDataArray, oldRecordDataArray) {
return [myCustomParam];
}
}
}
});
//Setup Params for the grid
Ext.apply(this, {
store: new Ext.data.Store({
proxy: dwrProxy,
reader: myReader,
autoLoad : true,
autoSave: true
}),
columns: [{header: "Employee", dataIndex: 'employee'},
{header: "Department", dataIndex: 'department'}]
});
MyEmployeeGrid.superclass.initComponent.apply(this, arguments);
} // eo function initComponent
,onRender:function() {
MyEmployeeGrid.superclass.onRender.apply(this, arguments);
} // eo function onRender
});
Ext.reg('myemployeegrid', MyEmployeeGrid);
myCustomParam is a property of the object in question. It is never declared as a variable in its own right. You can see this by running the code through jslint.
You want this.myCustomParam.
If you're trying to pass in parameters, however, you might want to try this way instead:
MyGrid = Ext.extend(Ext.grid.GridPanel, {
constructor: function(cfg) {
cfg = cfg || {};
// ... do stuff ...
MyGrid.superclass.constructor.call(this, cfg);
}
});
I have a strong feeling this is to do with scope around the point at which getDwrArgsFunction is called. It possibly doesn't know what myCustomParam is (despite the fact this should possibly be this.myCustomParam, and that you'd want to set this after you'd applied any config) because it's not declared in the DwrProxy object and has no reference back to its owning MyEmployeeGrid object.
I'd debug/attempt to fix in the following way:
Rename myCustomParam to
this.myCustomParam and test
Set scope and test
Create getDwrArgsFunction using
createDelegate
(http://dev.sencha.com/deploy/dev/docs/source/Function.html#method-Ext.util.Functions-createDelegate)
and test
DISCLAIMER: Some of those will be no good depending on your DwrProxy etc (which we can't see the code for)
The proxy cannot access the variable because it is not defined in the scope of the proxy itself. I have been struggling with things like this in the past, I thought that components would act like closures and keep track of the variable, but they dont.
You can solve your problem by handing the scope of the grid to the handler function explicitly by making it a delegate, what you do have to change for that is making the variable you would like to access an instance variable instead of just a local variable.
Changes:
MyEmployeeGrid = Ext.extend(Ext.grid.GridPanel, {
myCustomParam: "defaultValue",
initComponent:function() {
...
//Store Proxy
var dwrProxy = new Ext.ux.data.DwrProxy({
apiActionToHandlerMap : {
read : {
dwrFunction : EmployeeService.readCompanyEmployees,
getDwrArgsFunction: function(request, recordDataArray, oldRecordDataArray) {
return [this.myCustomParam];
}.createDelegate(this)
}
}
});
var myGridInstance = new MyEmployeeGrid({ myCustomParam: 'ValueForThisInstance' });
Hope this helps you, good luck

Categories