Extend control with HBox container and inherited but customized event - sapui5

The idea is to have a HBox container under the MultiComboBox control to which selected tokens will be pushed. I have followed different tutorials and couldn't get a success. A multiComboBox is simply now shown.
The idea:
Simplified (testing) implementation of custom control:
sap.ui.define([
'sap/m/MultiComboBox',
'sap/m/HBox'
], function (MultiComboBox, HBox) {
'use strict';
/**
* Constructor for a new MultiCombobox with tokens.
*/
return MultiComboBox.extend('drex.control.DropDownWithTags', {
metadata: {
aggregations: {
_tokensContainer: { type: 'sap.m.HBox', multiple: false }
},
},
init: function () {
MultiComboBox.prototype.init.apply(this, arguments);
this.setAggregation('_tokensContainer', new HBox());
},
_addToken: function () {
this.getAggregation('_tokensContainer').insertItem({text: 'test'});
},
_handleSelectionLiveChange: function(oControlEvent) {
this._addToken();
MultiComboBox.prototype._handleSelectionLiveChange.apply(this, arguments);
},
renderer: function (rm, DropDownWithTags) {
rm.write('<div');
rm.writeControlData(DropDownWithTags);
rm.write('>');
rm.renderControl(DropDownWithTags.getAggregation('_tokensContainer'));
rm.write('</div>');
}
});
});
XML (no change, except for a name, could that be a problem?). Adding HBox aggregation to it does not help.
<drex:DropDownWithTags
items="{
path: 'diseaseList>/allDiseases'
}"
selectedKeys="{filterModel>/disease}"
selectionFinish="onSelectDisease">
<core:Item key="{diseaseList>id}" text="{diseaseList>Name}"/>
</drex:DropDownWithTags>
Any idea why it happens ? I cannot see my mistake.

there are many ways to do this. here is one way
sap.ui.define([
'sap/ui/core/Control',
'sap/ui/core/Item',
'sap/m/MultiComboBox',
'sap/m/HBox',
'sap/m/Text'
], function (Control, Item, MultiComboBox, HBox, Text) {
Control.extend('DropDownWithTags', {
metadata: {
aggregations: {
combo: { type: 'sap.m.MultiComboBox', multiple: false },
_hbox: { type: 'sap.m.HBox', multiple: false }
},
},
init: function () {
Control.prototype.init.apply(this, arguments);
this.setAggregation('_hbox', new HBox({
items: [
]
}));
},
renderer: function (rm, oControl) {
rm.write('<div');
rm.writeControlData(oControl);
rm.write('>');
rm.write('<div>');
rm.renderControl(oControl.getAggregation('combo'));
rm.write('</div>');
rm.write('<div>');
rm.renderControl(oControl.getAggregation('_hbox'));
rm.write('</div>');
rm.write('</div>');
},
onAfterRendering: function() {
var combo = this.getAggregation('combo')
var hbox = this.getAggregation('_hbox');
combo.attachEvent("selectionChange", function() {
hbox.destroyItems();
var text = this.getSelectedItems().map(function(item) {
return item.getText();
});
if (text.length > 0) {
hbox.addItem(new Text({ text: text.join(",")}))
}
})
}
});
var combo = new DropDownWithTags({
combo: new MultiComboBox({
items: [
new Item({
key: "test",
text: "test"
}),
new Item({
key: "test1",
text: "test1"
})
]
})
});
combo.placeAt("content")
});

Related

restrict addEventDelegate to parent hbox only

This is a custom control created for my previous question Custom font for currency signs
I have two span elements coming next to each other. They sit in the FormattedText. The FormattedText itself sits in the HBox.
I want popover fired when user mouses over/out from the hbox. Unfortunately, as I have 2 spans this fires separately when user hovers overs them (thus showing 2 separate popovers, when in fact it should be one). My assumption is that this causes because onmouseover/out is attached to both spans under the hood. Can I restrict these events to hbox only?
sap.ui.define([
'sap/ui/core/Control',
'sap/m/FormattedText',
'sap/m/HBox',
], function (Control, FormattedText, HBox) {
return Control.extend('drex.control.TherapyCosts', {
metadata: {
properties: {
rank: {
type: 'int',
defaultValue: 0
},
},
aggregations: {
_rankedTherapyCost: {type: 'sap.m.FormattedText', multiple: false, singularName: '_rankedTherapyCost'},
_popover: {type: 'sap.m.Popover', multiple: false, singularName: '_popover'},
_hbox: {type: 'sap.m.HBox', multiple: false}
}
},
init: function () {
Control.prototype.init.apply(this, arguments);
},
onBeforeRendering: function () {
const highlighedCurrency = this.getCurrency().repeat(this.getRank());
const fadedCurrency = this.getCurrency().repeat(7 - this.getRank());
const _popover = new sap.m.Popover({
showHeader: false,
placement: 'VerticalPreferredBottom',
content: new sap.m.Text({text: 'test'})
});
this.setAggregation('_popover', _popover);
const _formattedText = new FormattedText({
htmlText:
`<span class="currencyHighlighted">${highlighedCurrency}</span>` +
`<span class="currencyFaded">${fadedCurrency}</span>`
});
this.setAggregation('_rankedTherapyCost', _formattedText);
const _hbox = new HBox(
{ items: [this.getAggregation('_rankedTherapyCost')]})
.addEventDelegate({
onmouseover: () => {
this.getAggregation('_popover').openBy(this);
},
onmouseout: () => {
this.getAggregation('_popover').close()
}
});
this.setAggregation('_hbox', _hbox)
},
renderer: function (rm, oControl) {
const _hbox = oControl.getAggregation('_hbox');
rm.write('<div');
rm.writeControlData(oControl);
rm.write('>');
rm.renderControl(_hbox);
rm.write('</div>');
}
});
});
Here is the video of the issue
https://streamable.com/fjw408
The key here is the mouseout event is triggered when the mouse moves out of any child element of the element that is listening for the event. In this case, for example, moving from the emphasised text to the faded text, you get a mouseout event when moving off the emphasised text, which closes the popup, then a mouseover event on the faded text which opens it again.
Since you opening an already open popup doesn't do anything, you only need add a line on mouseout to inspect the element that you've gone to. If it is not a child of the current element, only then close the popover.
if (!this.getDomRef().contains(e.toElement)) {
popOver.close()
}
Building on D.Seah's answer, I've added this to the JSBin. I personally don't like using onBeforeRendering and onAfterRendering like this, so I've refactored a little to construct everything on init and simply change the controls on property change. This way, you're doing less on rendering for performance reasons.
sap.ui.define([
'sap/ui/core/Control',
'sap/m/FormattedText',
], function (Control, FormattedText) {
Control.extend('TherapyCosts', {
metadata: {
properties: {
rank: {
type: 'int',
defaultValue: 0
},
currency: {
type: "string",
defaultValue: "$"
}
},
aggregations: {
_highlighted: {type: 'sap.m.FormattedText', multiple: false},
_faded: {type: 'sap.m.FormattedText', multiple: false},
_popover: {type: 'sap.m.Popover', multiple: false, singularName: '_popover'}
}
},
init: function () {
Control.prototype.init.apply(this, arguments);
this.addStyleClass('therapy-cost');
const highlight = new FormattedText();
highlight.addStyleClass("currency-highlight");
this.setAggregation('_highlighted', highlight);
const faded = new FormattedText();
faded.addStyleClass("currency-faded");
this.setAggregation('_faded', faded);
const _popover = new sap.m.Popover({
showHeader: false,
placement: 'VerticalPreferredBottom',
content: new sap.m.Text({text: 'test'})
});
this.setAggregation('_popover', _popover);
},
_changeAggr: function () {
const highlighedCurrency = this.getCurrency().repeat(this.getRank());
const highlight = this.getAggregation("_highlighted");
highlight.setHtmlText(highlighedCurrency);
const fadedCurrency = this.getCurrency().repeat(7 - this.getRank());
const faded = this.getAggregation("_faded");
faded.setHtmlText(fadedCurrency);
},
setRank: function(rank) {
if (this.getProperty("rank") !== rank) {
this.setProperty("rank", rank);
this._changeAggr();
}
},
setCurrency: function(curr) {
if (this.getProperty("currency") !== curr) {
this.setProperty("currency", curr);
this._changeAggr();
}
},
renderer: function (rm, oControl) {
rm.write('<div');
rm.writeControlData(oControl);
rm.writeClasses(oControl);
rm.writeStyles(oControl);
rm.write('>');
rm.renderControl(oControl.getAggregation('_highlighted'));
rm.renderControl(oControl.getAggregation('_faded'));
rm.write('</div>');
},
onmouseover: function (e) {
const popOver = this.getAggregation('_popover');
popOver.openBy(this);
},
onmouseout: function (e) {
if (!this.getDomRef().contains(e.toElement)) {
const popOver = this.getAggregation('_popover');
popOver.close();
}
}
});
(new TherapyCosts({
rank: 5,
currency: "£"
})).placeAt('content')
});
.therapy-cost {
display: inline-flex;
flex-direction: row;
}
.therapy-cost .currency-highlighted {
color: black;
}
.therapy-cost .currency-faded {
color: lightgrey;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<script
src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-theme="sap_bluecrystal"
data-sap-ui-xx-bindingSyntax="complex"
data-sap-ui-libs="sap.m"></script>
</head>
<body class="sapUiBody sapUiSizeCompact">
<div id='content'></div>
</body>
</html>
https://jsbin.com/gukedamemu/3/edit?css,js,output
i think you can do without the hbox; by just having two formatted text or any sapui5 controls will be ok too.
sap.ui.define([
'sap/ui/core/Control',
'sap/m/FormattedText',
], function (Control, FormattedText) {
Control.extend('TherapyCosts', {
metadata: {
properties: {
rank: {
type: 'int',
defaultValue: 0
},
currency: {
type: "string",
defaultValue: "$"
}
},
aggregations: {
_highlighted: {type: 'sap.m.FormattedText', multiple: false},
_faded: {type: 'sap.m.FormattedText', multiple: false},
_popover: {type: 'sap.m.Popover', multiple: false, singularName: '_popover'}
}
},
init: function () {
Control.prototype.init.apply(this, arguments);
this.addStyleClass('therapy-cost')
},
onBeforeRendering: function () {
const highlighedCurrency = this.getCurrency().repeat(this.getRank());
const highlight = new FormattedText({
htmlText: highlighedCurrency
});
this.setAggregation('_highlighted', highlight);
const fadedCurrency = this.getCurrency().repeat(7 - this.getRank());
const faded = new FormattedText({
htmlText: fadedCurrency
});
this.setAggregation('_faded', faded);
const _popover = new sap.m.Popover({
showHeader: false,
placement: 'VerticalPreferredBottom',
content: new sap.m.Text({text: 'test'})
});
this.setAggregation('_popover', _popover);
},
renderer: function (rm, oControl) {
rm.write('<div');
rm.writeControlData(oControl);
rm.writeClasses(oControl);
rm.writeStyles(oControl);
rm.write('>');
rm.renderControl(oControl.getAggregation('_highlighted'));
rm.renderControl(oControl.getAggregation('_faded'));
rm.write('</div>');
},
onAfterRendering: function () {
const popOver = this.getAggregation('_popover');
const highlighted = this.getAggregation("_highlighted");
highlighted.$().addClass("currency-highlighted");
highlighted.$().hover(function() {
popOver.openBy(highlighted);
}, function() {
popOver.close();
});
const faded = this.getAggregation("_faded");
faded.$().addClass("currency-faded");
},
});
(new TherapyCosts({
rank: 5
})).placeAt('content')
});
https://jsbin.com/razebuq/edit?css,js,output

How to navigate to another XML page when the user click on message popup button

View1.Controller.js
onClickRUSSIA: function() {
var dialog = new Dialog({
title: 'Default Message',`enter code here`
type: 'Message',
content: new Text({
text: 'Country : RUSSIA \n Contenent : ASIA \n language:RUSSIAN.'
}),
beginButton: new Button({
text: 'OK',
press: function() {
dialog.close();
}
}),
endButton: new Button({
text: 'More Info',
press: function() {
//this.getRouter().navTo("mohan");
var router = sap.ui.core.UIComponent.getRouterFor();
router.navTo("View2");
}
}),
afterClose: function() {
dialog.destroy();
}
});
dialog.open();
},
Component.js
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/Device",
"Test1/model/models"
], function(UIComponent, Device, models) {
"use strict";
return UIComponent.extend("Test1.Component", {
metadata: {
//rootView: "test1.webapp.view.App",
manifest: "json",
routing: {
config: {
viewType: "XML",
viewPath: "test1.webapp.view",
controlId: "rootControl",
controlAggregation: "pages",
//routerClass: "Test1.controller",
transition: "slide"
},
routes: [
{
pattern: "",
name: "default",
view: "view1"
}, {
pattern: "mohan",
name: "view2",
View: "view2"
}
]
// targets: {
// page1: {
// viewName: "View1",
// viewLevel: 0
// },
// page2: {
// viewName: "View2",
// viewLevel: 1
// }
// }
}
},
init: function() {
// call the base component's init function
UIComponent.prototype.init.apply(this, arguments);
jQuery.sap.require("sap.m.routing.RouteMatchedHandler");
jQuery.sap.require("sap.ui.core.routing.HashChanger");
sap.ui.core.UIComponent.prototype.init.apply(this, arguments);
this._router = this.getRouter();
// set the device model
this.setModel(models.createDeviceModel(), "device");
//initlialize the router
this._routeHandler = new sap.m.routing.RouteMatchedHandler(this._router);
this._router.initialize();
}
});
});
You will have to get the router for your controller/view. As this is not your controller inside endButton.press(), you have to use a helper variable that.
You have to give Router.navTo() the name of the route to navigate to. So it has to be view2 instead of View2.
var that = this;
var dialog = new Dialog({
...
endButton: new Button({
text: 'More Info',
press: function() {
var router = sap.ui.core.UIComponent.getRouterFor(that);
router.navTo("view2");
}
}),
...
this.getOwnerComponent().getTargets().display("page1");

How to pass selected Value form Popup to normal controller page in ionic framework

How to pass selected Value form Popup to normal controller page in ionic framework
`$scope.showprofpopup = function()
{
$scope.data = {}
var myPopup = $ionicPopup.show
({
templateUrl: 'templates/popover.html',
title: 'Please Choose Category',
scope: $scope,
buttons: [ { text : 'Cancel' }, { text: 'Select', type: 'button-dark', onTap: function(e) { return $scope.data; } }, ]
});
myPopup.then(function(res)
{
//$scope.create(res.category);
//$state.go('app.userdetails');
//$scope.contactMessage = { text: res };
if(!res.category)
{
$ionicLoading.show({ template: '<ion-spinner icon="android"></ion-spinner>', animation: 'fade-in', showBackdrop: true, maxWidth: 100,showDelay: 50 });
$scope.showprofpopup();
$timeout(function () { $ionicLoading.hide(); }, 3000);
//$ionicPopup.alert({ title: "Please Choose Category" });
}
else
{
$scope.SelectedProfessional = { text: res.category};
//alert(res.category);
$state.go('app.userdetails');
}
});
};`
I want to send the result re.category to app.userdetails page.kindly anyone help me.
using $stateParams
$state.go('app.userdetails',{'category': res.category});

How to build a quantity plus / minus composite control?

I try to build an input field for quantities with plus and minus buttons. More like to understand how composite controls are working. I found this documentation.
I thought I could use the new control like this:
new ex.perimental.Input({
width: "14em",
editable: true,
input: new sap.m.Input({
width: "8em",
value: {
path: "model>Quantity",
type: new sap.ui.model.type.Integer({}, {
minimum:0
})
},
description: "{model>Unit}"
}),
})
The control code looks like this:
sap.ui.define([
"sap/ui/core/Control",
"sap/m/Button",
"sap/m/Input"
], function(Control, Button, Input) {
"use strict";
return Control.extend("ex.perimental.Input", {
metadata: {
properties: {
width: {
type: "string",
defaultValue: "14em",
},
editable: {
type: "boolean",
defaultValue: false,
},
},
aggregations: {
_increaseButton: {
type: "sap.m.Button",
multiple: false,
visibility: "hidden",
},
_decreaseButton: {
type: "sap.m.Button",
multiple: false,
visibility: "hidden",
},
input: { type: "sap.m.Input", multiple: false },
_hBox: { type: "sap.m.HBox", multiple: false, visibility: "hidden" },
},
events: {
increase: {},
decrease: {},
},
},
_onDecrease: function(oEvent) {
var oResourceBundle = this.getModel("i18n").getResourceBundle();
var oldValue = this.getAggregation("input").getValue();
var newValue = 0;
if (!isNaN(Number(oldValue))) {
newValue = Number(oldValue) - 1;
}
oInput.setValue(newValue);
this.fireEvent("decrease", {
oldValue: oldValue,
newValue: newValue,
});
},
_onIncrease: function(oEvent) {
var oResourceBundle = this.getModel("i18n").getResourceBundle();
var oldValue = this.getAggregation("input").getValue();
var newValue = 0;
if (!isNaN(Number(oldValue))) {
newValue = Number(oldValue) + 1;
}
oInput.setValue(newValue);
this.fireEvent("increase", {
oldValue: oldValue,
newValue: newValue,
});
},
init: function() {
this.setAggregation(
"_decreaseButton",
new Button({
icon: "sap-icon://sys-minus",
press: this._onDecrease.bind(this),
})
);
this.setAggregation(
"_increaseButton",
new Button({
icon: "sap-icon://sys-add",
press: this._onIncrease.bind(this),
})
);
this.setAggregation(
"_hBox",
new sap.m.HBox({
items: [
this.getAggregation("_decreaseButton"),
this.getAggregation("_increaseButton"),
],
})
);
},
setEditable: function(sValue) {
debugger;
// aggregations will be null now
// I assume because they are reused in the HBox control
// this.getAggregation("_increaseButton").setEditable(sValue);
// this.getAggregation("_decreaseButton").setEditable(sValue);
// this.getAggregation("input").setEditable(sValue);
},
setWidth: function(sValue) {
this.getAggregation("_hBox").setWidth(sValue);
},
setInput: function(oInput) {
this.setAggregation("input", oInput);
var oHBox = this.getAggregation("_hBox");
oHBox.insertItem(oInput, 1);
},
renderer: function(oRenderManager, oControl) {
oRenderManager.write("<div");
oRenderManager.writeControlData(oControl);
oRenderManager.addClass("myStyle");
oRenderManager.writeClasses();
oRenderManager.write(">");
oRenderManager.renderControl(oControl.getAggregation("_hBox"));
oRenderManager.write("</div>");
}
});
});
It will be rendered but the setEditable is not working.
The buttons (used inside the HBox control again) are not reachable via getAggregation. Also the input field (set from outside) can't be accessed.
Not sure how to do it right. Anyone an idea?
Edit2
This is the latest version but still not working.
I am asking me how to put the externally defined input control into the right place inside the inner Hbox control and be able to access this control in methods like setEditable?
sap.ui.define([
"sap/ui/core/Control",
"sap/m/Button",
"sap/m/Input"
], function(Control, Button, Input) {
"use strict";
return Control.extend("ex.perimental.Input", {
metadata: {
properties: {
width: {
type: "string",
defaultValue: "14em",
},
editable: {
type: "boolean",
defaultValue: false,
},
},
aggregations: {
_hBox: { type: "sap.m.HBox", multiple: false, visibility: "hidden" },
},
associations: {
input: { type: "sap.m.Input", multiple: false, singularName: "input" },
},
events: {
increase: {},
decrease: {},
},
},
_onDecrease: function(oEvent) {
var oResourceBundle = this.getModel("i18n").getResourceBundle();
var oldValue = this._input.getValue();
var newValue = 0;
if (!isNaN(Number(oldValue))) {
newValue = Number(oldValue) - 1;
}
this._input.setValue(newValue);
this.fireEvent("decrease", {
oldValue: oldValue,
newValue: newValue,
});
},
_onIncrease: function(oEvent) {
var oResourceBundle = this.getModel("i18n").getResourceBundle();
var oldValue = this._input.getValue();
var newValue = 0;
if (!isNaN(Number(oldValue))) {
newValue = Number(oldValue) + 1;
}
this._input.setValue(newValue);
this.fireEvent("increase", {
oldValue: oldValue,
newValue: newValue,
});
},
init: function() {
this._decreaseButton = new Button({
icon: "sap-icon://sys-minus",
press: this._onDecrease.bind(this),
});
this._increaseButton = new Button({
icon: "sap-icon://sys-add",
press: this._onIncrease.bind(this),
});
this.setAggregation(
"_hBox",
new sap.m.HBox({
items: [
this._decreaseButton,
this.getAssociation("input"),
this._increaseButton,
],
})
);
},
setEditable: function(sValue) {
var bEditable = false;
if (sValue === true) {
bEditable = true;
}
this._decreaseButton.setEnabled(bEditable);
this._increaseButton.setEnabled(bEditable);
// seems not always working
this._input.setEditable(bEditable);
},
setWidth: function(sValue) {
this.getAggregation("_hBox").setWidth(sValue);
},
setInput: function(oInput) {
this.setAssociation("input", oInput);
this._input = oInput;
var oHBox = this.getAggregation("_hBox");
oHBox.insertItem(oInput, 1);
},
renderer: function(oRenderManager, oControl) {
oRenderManager.write("<div");
oRenderManager.writeControlData(oControl);
oRenderManager.addClass("myStyle");
oRenderManager.writeClasses();
oRenderManager.write(">");
oRenderManager.renderControl(oControl.getAggregation("_hBox"));
oRenderManager.write("</div>");
}
});
});
I still have problems with the association handling (updated code) I guess handling the association should be done different? Sometimes the input field is still null.
a control can only be aggregated by one control at a time.
This is the difference between associations (control may be at multiple at the same time) and aggregations.
What you can do in your init is:
this._decreaseButton = new Button (...)
Basically you only need one aggregation for your HBox.
If your buttons are then aggregated by the HBox they will know the models of the parent and also be destroyed.
The only thing you need to check if your Root control is a Ancestor of the created controls (use myControl.getParent()).
best regards,
Tobias

Changed values of collection not reflected in backbone.js

When i click on submit button i can't see the changes made to collection although on every click of checkbox change event is fired
//Model
Wine = Backbone.Model.extend({
// Toggle the `IsSelected` state of this todo item.
toggle: function () {
debugger;
this.save({
IsSelected: !this.get('IsSelected')
});
}
});
//Collection
WineCollection = Backbone.Collection.extend({
model: Wine,
url: "http://localhost/Industries/data/data.json"
});
//This is a list view collection and individual List item and here we bind the click event of checkbox and in Submit button here we check the the changed values
WineListView = Backbone.View.extend({
el: $('#wineList'),
initialize: function () {
alert($('#divBtnSubmit'));
wineList.bind("reset", this.render, this);
},
render: function () {
wineList.each(function (wine) {
$(this.el).append(new WineListItemView({ model: wine }).render().el);
}, this);
return this;
}
});
//Submit button
WinedivBtnSubmit = Backbone.View.extend({
el: $('#divBtnSubmit'),
initialize: function () {
this.render();
},
render: function () {
// Load the compiled HTML into the Backbone "el"
var template = _.template($("#save-div").html(), {});
this.$el.append(template);
},
events: {
"click #divBtnSubmit1": "saveWine"
},
saveWine: function () {
debugger;
wineList.each(function (wine) {
alert(wine.get('ID') + '<<>>' + wine.get('IsSelected'));
});
return false;
// alert(this.modelmodels);
}
});
//Indiviual list item
WineListItemView = Backbone.View.extend({
tagName: "li",
template: _.template($('#wine-list-item').html()),
initialize: function () {
},
events:
{
'click .toggle': 'toggleVisible'
},
toggleVisible: function () {
this.model.toggle();
},
render: function (eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
var AppRouter = Backbone.Router.extend({
routes: {
"": "list",
"wines/:id": "wineDetails"
},
initialize: function () {
},
list: function () {
// this.wineList = new WineCollection();
this.wineListView = new WineListView();
this.btn = new WinedivBtnSubmit();
// wineList.fetch();
},
wineDetails: function (id) {
this.wine = this.wineList.get(id);
this.wineView = new WineView({ model: this.wine });
this.wineView.render();
}
});
wineList = new WineCollection();
wineList.fetch();
// on every click of checkbox this event is fired
wineList.bind("change", function () {
alert('list changed');
});
var app = new AppRouter();
Backbone.history.start();