SAPUI5 Custom MessagePopoverItem Template - javascript

I have a custom SAPUI5 application that used SAPUI5 version 1.40.11.
Since this version has been removed from CDN recently, I had to use a long-term maintenance available compatible version for the app. It is - version - 1.38.55.
I'm having an issue with MessagePopoverItem as the subtitle property is not available for this version.
So I'm trying to customize the template of the MessagePopoverItem to display the subtitle field.
While I'm trying to do this via a custom control I received the following error as it looks for a renderer.js from CDN without looking at it from the local location.
Cannot load from
https://sapui5.hana.ondemand.com/1.38.55/resources/sap/m/MessagePopoverItemRenderer.js
This is my custom controller and it's renderer.js
And I followed the below link
TooltipBaseRenderer.js: 404 - NOT FOUND
sap.ui.define([
"sap/m/MessagePopoverItem",
"./CustomMessagePopupItemRender",
], function (MessagePopoverItem,CustomMessagePopupItemRender) {
"use strict";
return MessagePopoverItem.extend("demoapp.customControls.CustomMessagePopoverItem", {
renderer:CustomMessagePopupItemRender,
metadata : {
properties: {
subtitle: {
type: "string",
defaultValue: ""
}
}
},
onAfterRendering: function() {
if(MessagePopoverItem.prototype.onAfterRendering) {
MessagePopoverItem.prototype.onAfterRendering.apply(this, arguments);
}
//...check if there is a highlight and tooltip
if(this.getTitle() !== "") {
sapMLIBContent
var oHl = this.$().find(".sapMLIBContent");
var ss = oHl;
}
}
});
});
Renderer.js
sap.ui.define([
"sap/ui/core/Renderer",
], function(Renderer) {
"use strict";
return Renderer.extend("demoapp.customControls.CustomMessagePopupItemRender", {
apiVersion: 2,
render: function(renderManager, control) {
// Issue: render function is called twice.
// See: https://github.com/SAP/openui5/issues/3169
// Update: fixed since 1.88
const child = control.getAggregation("content");
if (child && child.isA("sap.ui.core.Control")) {
renderManager.openStart("div", control)
.accessibilityState(control, { role: "tooltip" })
.style("max-width", "95vw")
.style("width", control.getWidth())
.openEnd()
.renderControl(child)
.close("div");
} else {
renderManager.openStart("span", control)
.accessibilityState(control, { role: "tooltip" })
.style("max-width", "90vw")
.style("width", control.getWidth())
.style("padding", "0.5rem")
.class("sapThemeBaseBG-asBackgroundColor")
.openEnd()
.text(control.getText())
.close("span");
}
},
});
});
Is this possible to customize the item template of the MessagePopover?

TooltipBaseRenderer.js: 404 - NOT FOUND is a completely different issue than the issue in this question. The element sap.m.MessagePopoverItem which extends sap.ui.core.Item, is not a sap.ui.core.Control but an sap.ui.core.Element. It is not supposed to have a renderer by definition.
sap.ui.core.TooltipBase, on the other hand, is a sap.ui.core.Control. It's a different issue.
Instead, you'll have to:
Extend sap.m.MessagePopoverItem by the subTitle property but without a renderer since it's not a control.
Extend the control that actually renders items using the information from your extended MessagePopoverItem element.

I have managed to extend the sap.m.MessagePopoverItem as mentioned by #Boghyon Hoffmann.
Here's the answer
CustomMessagePopoverItem.js
sap.ui.define([
"sap/m/MessagePopoverItem",
], function (MessagePopoverItem) {
"use strict";
return MessagePopoverItem.extend("demoapp.customControls.CustomMessagePopoverItem", {
metadata : {
properties: {
subtitle: {
type: "string",
defaultValue: ""
}
}
}
});
});
CustomMessagePopover.js
sap.ui.define([
"sap/m/MessagePopover",
"sap/m/StandardListItem",
"sap/ui/core/IconPool",
"./CustomMessagePopoverItem"
], function (MessagePopover,StandardListItem,IconPool,CustomMessagePopoverItem) {
"use strict";
var MessagePopover = MessagePopover.extend("demoapp.customControls.CustomMessagePopover", {
metadata : {
aggregations: {
/**
* A list with message items
*/
items: {type: "demoapp.customControls.CustomMessagePopoverItem", multiple: true, singularName: "item"},
},
},
//Override following method to add the subtitle as the description of the Standard List Item
_mapItemToListItem :function (oMessagePopoverItem) {
if (!oMessagePopoverItem) {
return null;
}
var sType = oMessagePopoverItem.getType(),
oListItem = new StandardListItem({
title: oMessagePopoverItem.getTitle(),
icon: this._mapIcon(sType),
description :oMessagePopoverItem.getSubtitle(),
type: sap.m.ListType.Navigation
}).addStyleClass(CSS_CLASS + "Item").addStyleClass(CSS_CLASS + "Item" + sType);
oListItem._oMessagePopoverItem = oMessagePopoverItem;
return oListItem;
}
});
var CSS_CLASS = "sapMMsgPopover",
ICONS = {
back: IconPool.getIconURI("nav-back"),
close: IconPool.getIconURI("decline"),
information: IconPool.getIconURI("message-information"),
warning: IconPool.getIconURI("message-warning"),
error: IconPool.getIconURI("message-error"),
success: IconPool.getIconURI("message-success")
},
LIST_TYPES = ["all", "error", "warning", "success", "information"],
// Property names array
ASYNC_HANDLER_NAMES = ["asyncDescriptionHandler", "asyncURLHandler"],
// Private class variable used for static method below that sets default async handlers
DEFAULT_ASYNC_HANDLERS = {
asyncDescriptionHandler: function (config) {
var sLongTextUrl = config.item.getLongtextUrl();
if (sLongTextUrl) {
jQuery.ajax({
type: "GET",
url: sLongTextUrl,
success: function (data) {
config.item.setDescription(data);
config.promise.resolve();
},
error: function() {
var sError = "A request has failed for long text data. URL: " + sLongTextUrl;
jQuery.sap.log.error(sError);
config.promise.reject(sError);
}
});
}
}
};
});
Here I invoke the custom control from the controller
var oMessageTemplate = new demoapp.customControls.CustomMessagePopoverItem({
type: '{TYPE}',
title: 'My Title',
description: '{DESC}',
subtitle: '{DESC}'
});
var oMessagePopover2 = new demoapp.customControls.CustomMessagePopover({
items: {
path: '/Messages',
template: oMessageTemplate
}
});

Related

extend existing event in custom control ui5

I'd like to extend (not override) the existing UI5 event of MultiComboBox component.
I found that https://github.com/SAP/openui5/blob/master/src/sap.m/src/sap/m/MultiComboBox.js
has MultiComboBox.prototype._handleSelectionLiveChange, how do I actually extend it in my own custom control?
Here is what I've done:
sap.ui.define([
'sap/m/MultiComboBox',
'sap/m/HBox'
], function (MultiComboBox, HBox) {
return MultiComboBox.extend('TokenizedMultiComboBox', {
metadata: {
aggregations: {
_hbox: { type: 'sap.m.HBox', multiple: false }
}
},
init: function () {
MultiComboBox.prototype.init.apply(this, arguments);
this.setAggregation('_hbox', new HBox({
items: []
}));
},
onAfterRendering: function() {
const hbox = this.getAggregation('_hbox');
},
_handleSelectionLiveChange: function() {
// should my code go here or?
},
renderer: function (rm, oControl) {
sap.m.MultiComboBoxRenderer.render(rm, oControl);
rm.write('<div');
rm.writeControlData(oControl);
rm.write('>');
rm.write('<div>');
rm.renderControl(oControl.getAggregation('_hbox'));
rm.write('</div>');
rm.write('</div>');
},
})
})

I have a problem in SAP WebIDE to Display XS OData Service on UI5 Application

Im creating an app to display recommendation results for apps to users but cannot display it on the app.
The error shows
Display property undefined in my Base Controller file
Error Code is getOwnerComponent("myComp").getTargets().display(to) is in appview.controller.js file (controller file)
NOTE :if i use method 1 for the component container , i can initialise router but error is targets undefined as component id is not found.
However if i use method 2 for the component container , i can getOwnerComponent but display undefined as i cannot initialise router.
Any Ideas?
appview.controller.js
handlePressOpenMenu: function(oEvent) {
var oButton = oEvent.getSource();
// create menu only once
if (!this._menu) {
this._menu = sap.ui.xmlfragment("apps.html.fragment.view_menu", this);
this.getView().addDependent(this._menu);
}
var eDock = sap.ui.core.Popup.Dock;
this._menu.open(this._bKeyboard, oButton, eDock.BeginTop, eDock.BeginBottom, oButton);
},
handleMenuItemPress: function(oEvent) {
if (oEvent.getParameter("item").getSubmenu()) {
return;
}
var to = oEvent.getParameter("item").data("to");
if (to) {
this.getOwnerComponent("myComp").getTargets().display(to);
}
},
manifest.json
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"async": true,
"viewPath": "apps.html",
"controlAggregation": "pages",
"controlId": "idAppControl",
"transition": "slide"
},
"routes": [{
"name": "appview",
"pattern": "",
"target": ["appview"]
}],
"targets": {
"appview": {
"clearAggregation": true,
"viewName": "appview"
},
"xsodata.collaborative": {
"clearAggregation": true,
"viewName": "xsodata.collaborative"
},
"xsodata.contentbased": {
"clearAggregation": true,
"viewName": "xsodata.contentbased"
},
"xsjs.apl_recommendation": {
"clearAggregation": true,
"viewName": "xsjs.apl_recommendation"
},
"xsjs.pal_apriori": {
"clearAggregation": true,
"viewName": "xsjs.pal_apriori"
}
}
}
}
}
}
component.js
return UIComponent.extend("apps.html.Component", {
metadata: {
manifest: "json"
},
/**
* The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
* #public
* #override
*/
init: function () {
// call the base component's init function
sap.ui.core.UIComponent.prototype.init.apply(this, arguments);
// set the device model
this.setModel(models.createDeviceModel(), "device");
// initialise router
this.getRouter().initialise();
}
});
});
index.html Method 1
sap.ui.getCore().attachInit(function() {
new sap.m.Shell({
app: new sap.ui.core.ComponentContainer({
id:"myComp",
height : "100%",
name : "movielens.html",
propagateModel:true
}).placeAt("content")
});
index html Method 2
sap.ui.getCore().attachInit(function() {
var oComp = sap.ui.getCore().createComponent({
name:"apps.html",
id:"myComp",
height:"100%",
propagateModel:true
});
new sap.m.Shell({
app: new sap.ui.core.ComponentContainer({
component:oComp
}).placeAt("content")
});
the exepcted result is supposed to be display of recommendation based on user selection.

Jquery Context Menu ajax fetch menu items

I have a jquery context menu on my landing page where I have hardcode menu items. Now I want to get the menu items from server. Basically the idea is to show file names in a specified directory in the context menu list and open that file when user clicks it...
This is so far I have reached..
***UPDATE***
C# code
[HttpPost]
public JsonResult GetHelpFiles()
{
List<Manuals> manuals = null;
var filesPath = Server.MapPath(#"\HelpManuals");
var standardPath = new DirectoryInfo(filesPath);
if (standardPath.GetFiles().Any())
{
manuals = standardPath.GetFiles().Select(x => new Manuals
{
Name = GetFileNamewithoutExtension(x.Name),
Path = x.Name
}).ToList();
}
return Json(manuals, JsonRequestBehavior.AllowGet);
}
private string GetFileNamewithoutExtension(string filename)
{
var extension = Path.GetExtension(filename);
return filename.Substring(0, filename.Length - extension.Length);
}
JavaScript Code
$.post("/Home/GetHelpFiles", function (data) {
$.contextMenu({
selector: '#helpIcon',
trigger: 'hover',
delay: 300,
build: function($trigger, e) {
var options = {
callback: function(key) {
window.open("/HelpManuals/" + key);
},
items: {}
};
$.each(data, function (item, index) {
console.log("display name:" + index.Name);
console.log("File Path:" + index.Path);
options.items[item.Value] = {
name: index.Name,
key: index.Path
}
});
}
});
});
Thanks to Matt. Now, the build function gets fire on hover.. but im getting illegal invocation... and when iterating through json result, index.Name and this.Name gives correct result. But item.Name doesn't give anything..
to add items to the context menu dynamically you need to make a couple changes
$.contextMenu({
selector: '#helpIcon',
trigger: 'hover',
delay: 300,
build: function($trigger, e){
var options = {
callback: function (key) {
var manual;
if (key == "adminComp") {
manual = "AdminCompanion.pdf";
} else {
manual = "TeacherCompanion.pdf";
}
window.open("/HelpManuals/" + manual);
},
items: {}
}
//how to populate from model
#foreach(var temp in Model.FileList){
<text>
options.items[temp.Value] = {
name: temp.Name,
icon: 'open'
}
</text>
}
//should be able to do an ajax call here but I believe this will be called
//every time the context is triggered which may cause performance issues
$.ajax({
url: '#Url.Action("Action", "Controller")',
type: 'get',
cache: false,
async: true,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (_result) {
if (_result.Success) {
$.each(_result, function(item, index){
options.items[item.Value] = {
name: item.Name,
icon: 'open'
}
});
}
});
return options;
}
});
so you use build and inside of that define options and put your callback in there. The items defined in there is empty and is populated in the build dynamically. We build our list off of what is passed through the model but I believe you can put the ajax call in the build like I have shown above. Hopefully this will get you on the right track at least.
I solved this problem the following way.
On a user-triggered right-click I return false in the build-function. This will prevent the context-menu from opening. Instead of opeing the context-menu I start an ajax-call to the server to get the contextMenu-entries.
When the ajax-call finishes successfully I create the items and save the items on the $trigger in a data-property.
After saving the menuItems in the data-property I open the context-menu manually.
When the build-function is executed again, I get the items from the data-property.
$.contextMenu({
build: function ($trigger, e)
{
// check if the menu-items have been saved in the previous call
if ($trigger.data("contextMenuItems") != null)
{
// get options from $trigger
var options = $trigger.data("contextMenuItems");
// clear $trigger.data("contextMenuItems"),
// so that menuitems are gotten next time user does a rightclick
// from the server again.
$trigger.data("contextMenuItems", null);
return options;
}
else
{
var options = {
callback: function (key)
{
alert(key);
},
items: {}
};
$.ajax({
url: "GetMenuItemsFromServer",
success: function (response, status, xhr)
{
// for each menu-item returned from the server
for (var i = 0; i < response.length; i++)
{
var ri = response[i];
// save the menu-item from the server in the options.items object
options.items[ri.id] = ri;
}
// save the options on the table-row;
$trigger.data("contextMenuItems", options);
// open the context-menu (reopen)
$trigger.contextMenu();
},
error: function (response, status, xhr)
{
if (xhr instanceof Error)
{
alert(xhr);
}
else
{
alert($($.parseHTML(response.responseText)).find("h2").text());
}
}
});
// This return false here is important
return false;
}
});
I have finally found a better solution after reading jquery context menu documentation, thoroughly..
C# CODE
public JsonResult GetHelpFiles()
{
List<Manuals> manuals = null;
var filesPath = Server.MapPath(#"\HelpManuals");
var standardPath = new DirectoryInfo(filesPath);
if (standardPath.GetFiles().Any())
{
manuals = standardPath.GetFiles().Select(x => new Manuals
{
Name = GetFileNamewithoutExtension(x.Name),
Path = x.Name
}).ToList();
}
return Json(manuals, JsonRequestBehavior.AllowGet);
}
HTML 5
<div id="dynamicMenu">
<menu id="html5menu" type="context" style="display: none"></menu>
</div>
JavaScript Code
$.post("/Home/GetHelpFiles", function (data) {
$.each(data, function (index, item) {
var e = '<command label="' + item.Name + '" id ="' + item.Path + '"></command>';
$("#html5menu").append(e);
});
$.contextMenu({
selector: '#helpIcon',
trigger: 'hover',
delay: 300,
items: $.contextMenu.fromMenu($('#html5menu'))
});
});
$("#dynamicMenu").on("click", "menu command", function () {
var link = $(this).attr('id');
window.open("/HelpManuals/" + link);
});
Here's my solution using deferred, important to know that this feature is supported for sub-menus only
$(function () {
$.contextMenu({
selector: '.SomeClass',
build: function ($trigger, e) {
var options = {
callback: function (key, options) {
// some call back
},
items: JSON.parse($trigger.attr('data-storage')) //this is initial static menu from HTML attribute you can use any static menu here
};
options.items['Reservations'] = {
name: $trigger.attr('data-reservations'),
icon: "checkmark",
items: loadItems($trigger) // this is AJAX loaded submenu
};
return options;
}
});
});
// Now this function loads submenu items in my case server responds with 'Reservations' object
var loadItems = function ($trigger) {
var dfd = jQuery.Deferred();
$.ajax({
type: "post",
url: "/ajax.php",
cache: false,
data: {
// request parameters are not importaint here use whatever you need to get data from your server
},
success: function (data) {
dfd.resolve(data.Reservations);
}
});
return dfd.promise();
};

ExtJS controller probelem. Uncaught TypeError: Object [object Object] has no method 'setSortState'

T writing code using ExtJS4.0.1, MVC architecture. And when I develop main form I meet problem with search extension for web site.
When I was trying to create new widget in controller, I need render result in subpanel. and so when I write sample code I meet following problem:
**Uncaught TypeError: Object [object Object] has no method 'setSortState'**
I cannot understand why it gives that error message. I need some help to resolve that problem.
Below I want to show my code:
//Panel which is showing to user
Ext.define('Semantics.view.main.menuView', {
extend: 'Ext.panel.Panel',
layout: 'fit',
alias: 'widget.Menu',
title: "",
tbar: [
{
//search field
name:'mainSearchText',
id:'mainSearchText',
xtype: 'textfield',
defaultValue: 'Search',
height: 20
},
{
name:'mainSearchButton',
id:'mainSearchButton',
xtype: 'button',
text: 'Find',
height: 20
}
]
});
//controller for search request
Ext.define('Semantics.controller.main.mainController', {
extend: 'Ext.app.Controller',
views: ['main.menuView','mainSearch.MainSearchResultForm'],
refs: [
{ ref: 'menuPanel', selector: 'Menu' },
{ ref:'mainSearchText',selector:'#mainSearchText'},
{ref: 'mainSearchForm',selector:'#mainSearchForm'},
{ref:'MainSearchGrid',selector:'#MainSearchGrid'}
],
init: function () {
this.control({
'Menu': {
render: this.onPanelRendered
},
'Menu button[name="mainSearchButton"]': {
click: this.onButtonClick
}
});
},
onButtonClick: function (button) {
var me = this;
if(button.name=="mainSearchButton") {
var mtextFiled = me.getMainSearchText().getValue();
console.log(mtextFiled);
Ext.Ajax.request({
scope: this,
url: 'app/mainSearchT/findText/',
method: 'POST',
params: {
text: me.getMainSearchText().getValue()
},
success: function (result) {
mainPanel = me.getMenuPanel();
mainPanel.removeAll(true);
loadingMask = new Ext.LoadMask(mainPanel, { msg: "Loading" });
loadingMask.show();
var mname = 'MainSearchResultForm';
var start_info_panel = Ext.widget(mname);
mainPanel.items.add(start_info_panel);
loadingMask.hide();
mainPanel.doLayout(); //that line gives that error
},
failure: function (result) {
console.log(result);
}
});
}
},
onPanelRendered: function () {
}
});
I encountered the same error when I included unsupported xtype in Ext.grid.Panel columns. I had to remove the xtype, which resolved the problem.

How to dynamically generate options for RichCombo in CKEDITOR?

There is a form on my page with textarea (CKEDITOR) and select field <select id="_photogalleries" multiple="multiple"></select>. I'd like options in RichCombo to depend on the options that are selected in select with id #_photogalleries. Is there any way to regenerate RichCombo dynamically?
Thanks in advance.
CKEDITOR.plugins.add('style_plugin', {
requires: ['richcombo'],
init: function(editor) {
var pluginName = 'style_plugin';
var config = editor.config,
lang = editor.lang.format;
editor.ui.addRichCombo('photogalleries', {
label: "Фоторепортаж",
title: "Фоторепортаж",
voiceLabel: "Фоторепортаж",
className: 'cke_format',
multiSelect: false,
icon: CKEDITOR.plugins.getPath('style_plugin') + 'photo-list-horizontal.png',
panel: {
css: [config.contentsCss, CKEDITOR.getUrl(editor.skinPath + 'editor.css')],
voiceLabel: lang.panelVoiceLabel
},
init: function () {
this.startGroup("Фоторепортаж");
var list=this;
$("#_photogalleries option:selected").each(function(index, value){
console.log(index, value);
list.add("#HORIZONTAL_GALLERY_"+ $(value).val()+"#", "(Г) " + $(value).text(), "(Г) " + $(value).text());
list.add("#VERTICAL_GALLERY_"+ $(value).val()+"#", "(В) " + $(value).text(), "(В) " + $(value).text());
});
},
onClick: function (value) {
editor.focus();
editor.fire('saveSnapshot');
editor.insertHtml(value);
editor.fire('saveSnapshot');
}
});
}
});
This works for me and you dont have to keep a global variable.
CKEDITOR.plugins.add('systemdata', {
init: function (editor) {
var fnData = editor.config.fnData;
if (!fnData || typeof (fnData) != 'function')
throw "You must provide a function to retrieve the list data.";
editor.ui.addRichCombo('systemDataCmb',
{
allowedContent: 'abbr[title]',
label: "System Data",
title: "System Data",
multiSelect: false,
init: function () {
var self = this;
var content = fnData();
$.each(content, function(index, value) {
// value, html, text
self.add(value.name, value.name, value.name)
});
}
}
Then to set the function to get the data put this somewhere where you setup the ckeditor
CKEDITOR.replaceAll(function(element, config) {
config.startupFocus = true;
config.fnData = function() {
var returnData = null;
$.ajax({
url: "/GetData",
async: false,
data: { id: 1 },
}).done(function(result) { returnData= result; });
return returnData;
};
});
It assumes you bring back a json response that has an array of items that have a value property, that can be easily changed though.
I guess I found a solution that worked for me. It was to keep a list object in a global variable and then modify it when onchange event fires in the external select.
I solved this trouble with a single line:
YOURCOMBO.createPanel(editor);
For example:
var comboTeam = editor.ui.get("team");
comboTeam.createPanel(editor);//This is important, if not, doesnt works
Now you can add items to the combo
comboTeam.add("name","name","name");
comboTeam.add("name2","name2","name2");
comboTeam.add("name3","name3","name3");

Categories

Resources