I'm quite new to SAPUI5 and JavaScript topic.
Currently I'm trying to develop a custom control. I'm trying to call a self defined function in the renderer method, but at runtime I always get this error:
Error: Uncaught TypeError: this.someFunction is not a function.
I used the the tutorial code from SAP to illustrate how my code is structured.
Can anyone answer. I'm quite sure it's related to JavaScript not UI5.
My code below:
sap.ui.define([
"sap/ui/core/Control",
"sap/m/RatingIndicator",
"sap/m/Label",
"sap/m/Button"
], function (Control, RatingIndicator, Label, Button) {
"use strict";
return Control.extend("sap.ui.demo.wt.control.ProductRating", {
metadata : {
properties : {
value: {type : "float", defaultValue : 0}
},
aggregations : {
_rating : {type : "sap.m.RatingIndicator", multiple: false, visibility : "hidden"},
_label : {type : "sap.m.Label", multiple: false, visibility : "hidden"},
_button : {type : "sap.m.Button", multiple: false, visibility : "hidden"}
},
events : {
change : {
parameters : {
value : {type : "int"}
}
}
}
},
init : function () {
this.setAggregation("_rating", new RatingIndicator({
value: this.getValue(),
iconSize: "2rem",
visualMode: "Half",
liveChange: this._onRate.bind(this)
}));
this.setAggregation("_label", new Label({
text: "{i18n>productRatingLabelInitial}"
}).addStyleClass("sapUiTinyMargin"));
this.setAggregation("_button", new Button({
text: "{i18n>productRatingButton}",
press: this._onSubmit.bind(this)
}));
},
setValue: function (iValue) {
this.setProperty("value", iValue, true);
this.getAggregation("_rating").setValue(iValue);
},
_onRate : function (oEvent) {
var oRessourceBundle = this.getModel("i18n").getResourceBundle();
var fValue = oEvent.getParameter("value");
this.setValue(fValue);
this.getAggregation("_label").setText(oRessourceBundle.getText("productRatingLabelIndicator", [fValue, oEvent.getSource().getMaxValue()]));
this.getAggregation("_label").setDesign("Bold");
},
_onSubmit : function (oEvent) {
var oResourceBundle = this.getModel("i18n").getResourceBundle();
this.getAggregation("_rating").setEnabled(false);
this.getAggregation("_label").setText(oResourceBundle.getText("productRatingLabelFinal"));
this.getAggregation("_button").setEnabled(false);
this.fireEvent("change", {
value: this.getValue()
});
},
renderer : function (oRM, oControl) {
oRM.write("<div");
oRM.writeControlData(oControl);
oRM.addClass("myAppDemoWTProductRating");
oRM.writeClasses();
oRM.write(">");
oRM.renderControl(oControl.getAggregation("_rating"));
oRM.renderControl(oControl.getAggregation("_label"));
oRM.renderControl(oControl.getAggregation("_button"));
oRM.write("</div>");
this.someFunction();
},
someFunction: function(){
//Do something
}
});
});
The control is perfectly fine, and so are the functions.
However, I think that since you are providing a renderer function, this context is not in the control itself anymore; you should use oControl instead.
So, your code should work if you use like this:
renderer : function (oRM, oControl) {
oRM.write("<div");
oRM.writeControlData(oControl);
oRM.addClass("myAppDemoWTProductRating");
oRM.writeClasses();
oRM.write(">");
oRM.renderControl(oControl.getAggregation("_rating"));
oRM.renderControl(oControl.getAggregation("_label"));
oRM.renderControl(oControl.getAggregation("_button"));
oRM.write("</div>");
oControl.someFunction();
},
someFunction: function(){
//Do something
}
Related
This is the component where I'm trying to put a Tooltip:
this.textFieldStreet = new Ext.form.TextField({
id : 'idTextFieldStreet',
fieldLabel : 'Street',
autoCreate : limitChar(30,30),
listeners : {
render : function(c){
Ext.QuickTips.register({
target : c.getEl(),
html : '' + Ext.getCmp('idTextFieldStreet').getValue()
}
});
}
}
});
In another .js I created the function that define every component like you see before and invoke the function as you see forward:
var componentFormCustomer = new ComponentFormCustomer();
Then I set value like:
componentFormCustomer.textFieldStreet.setValue('Some street info')
Now, here's the problem, I was looking for some ideas to do that and found nothing, I don't know if this is the right way to accomplish the tooltip. Help!
Solution:
Define show listener for created tooltip. In this listener get the value of textfield and update tooltip.
With this approach, the tooltip's content will change dynamically and will show the content of tooltip's target.
Ext.onReady(function(){
Ext.QuickTips.init();
var textFieldStreet = new Ext.form.TextField({
renderTo : Ext.getBody(),
id : 'idTextFieldStreet',
fieldLabel : 'Street',
value : 'Initial value',
bodyCfg : {
tag: 'center',
cls: 'x-panel-body',
html: 'Message'
},
listeners : {
render : function(c) {
new Ext.ToolTip({
target : c.getEl(),
listeners: {
'show': function (t) {
var value = t.target.getValue();
t.update(value);
}
}
});
}
}
});
var button = new Ext.Button({
renderTo : Ext.getBody(),
text : 'Change Tooltip',
handler : function () {
textFieldStreet.setValue('New value');
}
});
});
Notes:
Tested with ExtJS 3.4.1.
I have been pretty much beginner at this part of javascript and I would appreciate any ideas how could be solved this problem.
I use requirejs to define my own modules where I also use backbone.js.
Let say I have the main module where I initialize my Backbone view which is rendered without any problem. Also, the click event where is calling method createSchemeForm creates the form correctly. The problem raises up in a situation when I call cancel method by click and the modules which are defined for Backbone view (e.g. "unicorn/sla/dom/helper"...) are undefined but when I called method createSchemeForm at the beginning the modules were executed without any problem.
Thank you in advance for any suggestions.
Backbone view
define("unicorn/sla/view/scheme", [
"unicorn/sla/dom/helper",
"unicorn/soy/utils",
"unicorn/sla/utils"
], function (DOMHelper, soyUtils, jsUtils) {
return Backbone.View.extend({
el: 'body',
inputData: {},
btnSaveScheme: 'btn-save-sla-scheme',
btnCancel: 'btn-cancel-sla-scheme',
btnCreate: 'btn-create-sla-scheme',
btnContainer: '#sla-scheme-buttons-container',
schemeContent: '#sla-scheme-content-section',
btnSpinner: '.button-spinner',
events: {
'click #btn-create-sla-scheme' : "createSchemeForm",
'click #btn-cancel-sla-scheme' : "cancel"
},
initialize: function(){
console.log("The scheme view is initialized...");
this.render();
},
createSchemeForm: function () {
this.spin();
DOMHelper.clearSchemeContent();
DOMHelper.clearButtonsContainer();
//Get button
$btnSave = soyUtils.getButton({isPrimary: 'true', id: this.btnSaveScheme, label: 'Save'});
$btnCancel = soyUtils.getButton({isPrimary: 'false', id: this.btnCancel, label: 'Cancel'});
//Append new created buttons
DOMHelper.addContent(this.btnContainer, AJS.format("{0}{1}", $btnSave, $btnCancel));
//Call service to get entry data for scheme creation form
AJS.$.ajax({
url: AJS.format('{0}={1}',AJS.I18n.getText('rest-url-project-scheme-input-data'), jsUtils.getProjectKey()) ,
type: "post",
async: false,
context: this,
global: false,
}).done(function (data) {
this.inputData = data;
$slaSchemeForm = soyUtils.getSchemeCreateForm({slaScheme : data, helpText: AJS.I18n.getText("sla-time-target-tooltip-text")});
DOMHelper.addContent(this.schemeContent, $slaSchemeForm);
jsUtils.scroll(this.schemeContent, 'slow');
}).fail(function () {
jsUtils.callFlag('error', AJS.I18n.getText("message-title-error"), AJS.I18n.getText("sla-error-load-scheme-input-data"));
}).always(function () {
this.stopSpin();
});
},
spin: function () {
AJS.$('.button-spinner').spin();
},
stopSpin: function () {
AJS.$('.button-spinner').spinStop();
},
cancel: function () {
jsUtils.clearButtonsContainer();
jsUtils.clearSchemeContent();
$btnCreateScheme = soyUtils.getButton({isPrimary: 'false', id: this.btnCreate, label: 'Create SLA Scheme'});
DOMHelper.addContent(this.btnContainer, $btnCreateScheme);
DOMHelper.addContent(this.schemeContent, soyUtils.getSchemesTable(new Array())); // TODO - get current data from server instead of empty array
}
});
});
Main module where is Backbone view initialize
define("unicorn/sla/project/batch", [
"unicorn/sla/utils",
"unicorn/sla/data/operations",
"unicorn/sla/data/validator",
"unicorn/sla/dom/helper",
"unicorn/sla/model/confirm/message",
"unicorn/sla/view/scheme",
"exports"
], function (jsUtils, operations, validator, DOMHelper, ConfirmMessage, SchemeView, exports) {
//Load project batch
exports.onReady = function () {
$schemeView = new SchemeView();
$schemeView.render();
}
});
AJS.$(function () {
AJS.$(document).ready(function () {
require("unicorn/sla/project/batch").onReady();
});
});
I need to add a button to the taskbar quickstart, but i do not want to open a module window, for example a logout button that will show a confirm messagebox, i have tried like this:
getTaskbarConfig: function () {
var ret = this.callParent();
me = this;
return Ext.apply(ret, {
quickStart: [
{ name: 'Window', iconCls: 'icon-window', module: 'ext-win' },
{ name: 'Logout', iconCls:'logout', handler: me.onLogout}
]
});
},
onLogout: function () {
Ext.Msg.confirm('Logout', 'Are you sure you want to logout?');
},
And i changed the getQuickStart function of the TaskBar.js file to this:
getQuickStart: function () {
var me = this, ret = {
minWidth: 20,
width: Ext.themeName === 'neptune' ? 70 : 60,
items: [],
enableOverflow: true
};
Ext.each(this.quickStart, function (item) {
ret.items.push({
tooltip: { text: item.name, align: 'bl-tl' },
overflowText: item.name,
iconCls: item.iconCls,
module: item.module,
//handler: me.onQuickStartClick, **original code**
handler: item.handler == undefined ? me.onQuickStartClick : item.handler,
scope: me
});
});
return ret;
}
But it does not work, is there a way to add a simple button to the taskbar quickstart?
Thanks for your reply. I have solved the issue. In the TaskBar.js file i changed this line:
handler: item.handler == undefined ? me.onQuickStartClick : item.handler
for this one:
handler: item.handler ? item.handler : me.onQuickStartClick
Actually, for me, both do the same, but for any weird reason the code works with that change.
I am using extjs4 and Spring at server side. I need to integrate Google Places Auto-complete inside one of the extjs4 form. Is there any way this can be done. I am not sure weather we can integrate Google Auto-complete with extjs I have searched but not find anything more specific to my requirement. Please guide me ..... look at my code ...
Ext.define('abce.view.ReportMissing', {
extend : 'Ext.panel.Panel',
alias : 'widget.report_missing',
bodyPadding : 10,
autoScroll : true,
frame : true,
items : [{
id : 'report_form',
xtype : 'form',
frame : true,
defaultType : 'textfield',
items : [{
xtype : 'combobox',
store : new Ext.data.Store({
autoLoad : true,
//fields : ['memberName', 'email'],
proxy : {
type : 'ajax',
headers : {
'Content-Type' : 'application/json',
'Accept' : 'application/json'
},
url : 'http://maps.googleapis.com/maps/api/geocode/json?address=hyd+&sensor=false',
remoteSort : true,
method : 'GET',
reader : {
type : 'json',
successProperty : 'status'
}
}
})
}]
});
https://developers.google.com/places/documentation/autocomplete
Why not instead of use the sencha combobox, use a simple text input as suggest the google api autocomplete documentation.
(I first try with a just common textfield but it didn't work)
Then declare a panel or component with html as the following example, and then assign the render:
xtype: 'component',
html: '<div> <input id="searchTextField" type="text" size="50"> </div>',
listeners: {
render: function () {
var input = document.getElementById('searchTextField');
autocomplete = new google.maps.places.Autocomplete(input, { types: ['geocode'] });
autocomplete.addListener('place_changed', this.fillInAddress);
},
And result in this:
The proxy cannot be used to retrieve data from a URL on a different origin. See the limitations section of Ext.data.proxy.ajax for more information.
http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.data.proxy.Ajax
You will probably need to set up an endpoint on your server to proxy the request to Google if you want to use that API.
I was looking for a way to do the same, and I came up writing a custom proxy against the google map javascript library
Then I used this custom proxy in a regular combo box
ComboBox:
Ext.create('Ext.form.field.ComboBox', {
store: {
fields: [
{name: 'id'},
{name: 'description'}
],
proxy: 'google-places'
},
queryMode: 'remote',
displayField: 'description',
valueField: 'id',
hideTrigger: true,
forceSelection: true
});
Custom proxy: (inspired from Ext.data.proxy.Ajax)
Ext.define('com.custom.PlacesProxy', {
extend: 'Ext.data.proxy.Server',
alias: 'proxy.google-places',
constructor: function() {
this.callSuper();
this.autocompletePlaceService = new google.maps.places.AutocompleteService();
},
buildUrl: function() {
return 'dummyUrl';
},
doRequest: function(operation) {
var me = this,
request = me.buildRequest(operation),
params;
request.setConfig({
scope : me,
callback : me.createRequestCallback(request, operation),
disableCaching : false // explicitly set it to false, ServerProxy handles caching
});
return me.sendRequest(request);
},
sendRequest: function(request) {
var input = request.getOperation().getParams().query;
if(input) {
this.autocompletePlaceService.getPlacePredictions({
input: input
}, request.getCallback());
} else {
// don't query Google with null/empty input
request.getCallback().apply(this, [new Array()]);
}
this.lastRequest = request;
return request;
},
abort: function(request) {
// not supported by Google API
},
createRequestCallback: function(request, operation) {
var me = this;
return function(places) {
// handle result from google API
if (request === me.lastRequest) {
me.lastRequest = null;
}
// turn into a "response" ExtJs understands
var response = {
status: 200,
responseText: places ? Ext.encode(places) : []
};
me.processResponse(true, operation, request, response);
};
},
destroy: function() {
this.lastRequest = null;
this.callParent();
}
});
Note: I wrote this against ExtJs6 but it should basically work alike for ExtJs4.
I am using this article of architecture http://blog.extjs.eu/know-how/writing-a-big-application-in-ext/
In my one class of Dashboardgrid i have two functions are :
,linkRenderer : function (data, cell, record, rowIndex, columnIndex, store) {
if (data != null) {
return ''+ data +'';
}
return data;
},
resellerwindow : function (cityname) {
// render the grid to the specified div in the page
// resellergrid.render();
resellerstore.load();
wingrid.show(this);
}
when the click event of linkrendrer function is called it gives error
this.resellerwindow is not a function
where and how should i put resellerwindow function ?
My ResellerDashBoard Class
Application.DashBoardGrid = Ext.extend(Ext.grid.GridPanel, {
border:false
,initComponent:function() {
var config = {
store:new Ext.data.JsonStore({
// store configs
autoDestroy: true,
autoLoad :true,
url: 'api/index.php?_command=getresellerscount',
storeId: 'getresellerscount',
// reader configs
root: 'cityarray',
idProperty: 'cityname',
fields: [
{name: 'cityname'},
{name: 'totfollowup'},
{name: 'totcallback'},
{name: 'totnotintrested'},
{name: 'totdealsclosed'},
{name: 'totcallsreceived'},
{name: 'totcallsentered'},
{name: 'totresellerregistered'},
{name: 'countiro'},
{name: 'irotransferred'},
{name: 'irodeferred'}
]
})
,columns: [
{
id :'cityname',
header : 'City Name',
width : 120,
sortable : true,
dataIndex: 'cityname'
},
{
id :'countiro',
header : ' Total Prospect',
width : 100,
sortable : true,
dataIndex: 'countiro'
},
{
id :'irotransferred',
header : 'Calls Transfered By IRO',
height : 50,
width : 100,
sortable : true,
dataIndex: 'irotransferred'
},
{
id :'irodeferred',
header : ' Calls Deferred By IRO',
width : 100,
sortable : true,
dataIndex: 'irodeferred'
},
{
id :'totcallsentered',
header : ' Total Calls Entered',
width : 100,
sortable : true,
dataIndex : 'totcallsentered',
renderer : this.linkRenderer
},
{
id :'totfollowup',
header : ' Follow Up',
width : 100,
sortable : true,
dataIndex: 'totfollowup'
},
{
id :'totcallback',
header : ' Call Backs',
width : 100,
sortable : true,
dataIndex: 'totcallback'
},
{
id :'totnotintrested',
header : ' Not Interested',
width : 100,
sortable : true,
dataIndex: 'totnotintrested'
},
{
id :'totdealsclosed',
header : ' Deals Closed',
width : 100,
sortable : true,
dataIndex: 'totdealsclosed'
},
{
id :'totresellerregistered',
header : ' Reseller Registered',
width : 100,
sortable : true,
dataIndex: 'totresellerregistered'
}
]
,plugins :[]
,viewConfig :{forceFit:true}
,tbar :[]
,bbar :[]
,height : 350
,width : 1060
,title : 'Reseller Dashboard'
}; // eo config object
// apply config
Ext.apply(this, Ext.apply(this.initialConfig, config));
Application.DashBoardGrid.superclass.initComponent.apply(this, arguments);
} // eo function initComponent
/**
* It is the renderer of the links of cell
* #param data value of cell
* #param record object of data has all the data of store and record.id is unique
**/
,linkRenderer : function (data, cell, record, rowIndex, columnIndex, store) {
if (data != null) {
return ''+ data +'';
}
return data;
},
resellerwindow : function (cityname) {
// render the grid to the specified div in the page
// resellergrid.render();
resellerstore.load();
wingrid.show(this);
}
,onRender:function() {
// this.store.load();
Application.DashBoardGrid.superclass.onRender.apply(this, arguments);
} // eo function onRender
});
Ext.reg('DashBoardGrid', Application.DashBoardGrid);
Your scope is messed up, when the function in your <a> tag is called this does not point to your object where you defined the function but to your <a>-dom node.
It's pretty hard to call member functions from within a html fragment like the fragment returned by a grid renderer. I suggest you take a look at Ext.grid.ActionColumn to solve this problem. When you look at the code in this column type you should be able to write your own column type that renders a link instead of an icon like the ActionColumn.
Another option is using my Ext.ux.grid.ButtonColumn which doesn't render links but buttons in your grid.
more info on scope in ExtJS (and js in general): http://www.sencha.com/learn/Tutorial:What_is_that_Scope_all_about
this.resellerwindow is not a function
because 'this', in the onclick function is in fact a reference to the 'a' dom element;
In order to access the 'resellerwindow' function from the onclick handler, you need to make the function accessible from the global scope, where your handler is executed:
var globalObj =
{
linkRenderer : function (data, cell, record, rowIndex, columnIndex, store)
{
if (data != null)
return ''+ data +'';
return data;
},
resellerwindow : function (cityname)
{
// render the grid to the specified div in the page
// resellergrid.render();
resellerstore.load();
wingrid.show(this);
}
}
so use the globalObj.resellerwindow(......);
The problem is that this does not point to the class itself. Should you need to render the a element as a string instead of JavaScript object you will need to call a global function in which to call the resellerwindow function (after obtaining correct reference). However, I believe a much more efficient way would be to abandon the string and use JavaScript object instead. Then you can do something like the following:
var a = document.createElement("a");
a.onclick = this.resselerwindow;
If you use jQuery something like the following can be used:
return $("<a />").click(this.resselerwindow)[0];
instead of building and passing direct html, try these.
Create Anchor object
{ tag: 'a',
href: '#',
html: 'click me',
onclick: this.resellerWindow }
Make sure that, scope in linkRenderer is grid, by settings 'scope: this' in that column definition. So that this.resellerWindow refers to grid's function.
try returning created object.