Qlik Sense Extenstions - Unable to reference Ref in properties panel (bug?) - qliksense

I have the following Extension Code with a properties brought out into a separate file. As per the documentation you should be able to refence props.myTextBox within the layout object, however when using the uses: "dimensions", to build the properties section, this is not returned, is this intended or a bug?
//Properties Panel setup
define( [], function () {
'use strict';
// *****************************************************************************
// Dimensions & Measures
// *****************************************************************************
var myTextBox = {
ref: "props.myTextBox",
label: "My text box",
type: "string"
};
var dimensions = {
uses: "dimensions",
items: {
myTextBox2: myTextBox, //Problem Here?
},
};
var measures = {
uses: "measures",
min: 1,
max:20
};
// *****************************************************************************
// Appearance section
// *****************************************************************************
var appearanceSection = {
uses: "settings"
};
// *****************************************************************************
// Main properties panel definition
// Only what is defined here is returned from properties.js
// *****************************************************************************
return {
type: "items",
component: "accordion",
items: {
dimensions: dimensions,
measures: measures,
appearance: appearanceSection,
}
}
});
paint function
paint: function ($element,layout) {
console.log(layout.props.myTextBox) //Undefined?
}

Think that the issue is that the ref should be defined as: ref: "qDef.myTextBox"
var myTextBox = {
ref: "qDef.myTextBox",
label: "My text box",
type: "string"
};
Once this is added then you can access myTextBox property in the layout for each dimension
P.S. After making a change in the definitions make sure to re-add the extension object on the sheet.

Related

Bind CSS class of a UI5 control programatically to a model value

Is there a way to bind the class attribute of a ui5-input-template inside a sap.ui.table.Table to a model value?
What I tried so far is:
[
{
label: 'arow',
disabled: true,
class: 'myClass1',
data: [
{
value: 'rowVal1'
}
]
},
// ...
]
and
myTable.bindColumns("/columns", function (index: string, context: any) {
let indParts: string[] = index.split("-");
let ind = +indParts[indParts.length - 1];
var colLabel = context.getProperty().label;
let template = new sap.m.Input({
value: `{data/${ind}/value}`,
class: '{= ${class} }',
enabled: '{= !${disabled} && !${data/' + ind + '/disabled} }',
});
// template.addStyleClass('{class}');
// template.setClass('{class}');
let column = new sap.ui.table.Column({
label: colLabel,
width: `{width}`,
template: template,
});
return column;
});
myTable.bindRows("/rows");
It seems as if I cannot use the model binding here, only add static class values when I create the template. Is this right?
As suggested in the comment, one of the solutions is to enhance the control's set of properties with your own property to allow binding the style class.
Here is a working sample: https://embed.plnkr.co/ik9PIdHKvK8udpQt
And here a snippet from the control extension:
sap.ui.define([
"sap/m/Input",
"sap/m/InputRenderer",
], function(Input, InputRenderer) {
"use strict";
return Input.extend("demo.control.Input", {
metadata: {
properties: {
"styleClass": {
type: "string",
defaultValue: null,
bindable: true,
}
}
},
renderer: { // will be merged with the parent renderer (InputRenderer)
apiVersion: 2, // enabling semantic rendering (aka. DOM-patching)
// Implement the hook method from the parent renderer
addOuterClasses: function (oRenderManager, oInput) {
InputRenderer.addOuterClasses.apply(this, arguments);
oRenderManager
.class("demoControlInput") // Standard CSS class of demo.control.Input
.class(oInput.getStyleClass()); // Custom CSS class defined by application
},
},
});
});
As documented in the topic Extending Input Rendering, some base controls allow overwriting existing methods from the renderer. If you look at the sap.m.InputRenderer, for example, you can see that the renderer provides multiple hooks to be overwritten by subclasses such as the addOuterClasses.
And since styleClass in our customer control is a valid ManagedObject property, binding in JavaScript ("programmatically") also works:
new Input({ // required from "demo/control/Input"
// ...,
styleClass: "{= ${class}}"
});

GoldenLayout hide/show component (again)

I have an issue with showing/hiding a component similar to this question:
GoldenLayout, how to hide/show component?
My layout is as follows:
let config: Config = {
settings: {
showCloseIcon: false,
showPopoutIcon: false
},
content: [
{
type: 'column',
content: [
{
type: 'row',
height: 25,
content: [
{
title: 'A react component',
type: 'react-component',
component: 'searchContainer'
}
],
},
{
type: 'row',
height: 75,
content: [
{
title: 'A react component',
type: 'react-component',
component: 'leftContainer'
},
{
title: 'Another react component',
type: 'react-component',
component: 'rightContainer'
}
],
},
],
}],
};
I have a hideSearchBar and showSearchBar functions which look like this:
function hideSearchBar() {
let container: ContentItem = layout.root.contentItems[0];
container.contentItems[1].config.height = 100;
container.contentItems[1].contentItems[0].config.height = 100;
container.contentItems[1].contentItems[1].config.height = 100;
container.config.height = 0;
container.contentItems[0].element.hide();
layout.updateSize();
//layout.updateSize($(window).width(), $(window).height());
}
function showSearchBar() {
let container: ContentItem = layout.root.contentItems[0];
container.contentItems[0].element.show();
layout.updateSize();
}
The showSearchBar works perfectly and shows both rows of the grid correctly.
The hideSearchBar hides the top row correctly but leaves the second row does not take up the whole screen. I have tried setting the config.height to 100 in various places but cannot get it to work - there is a gap the size of the top row at the bottom of the screen.
Any help much appreciated.
I solved this with a different layout config where search bar was initially set to 0:
let config: Config = {
settings: {
showCloseIcon: false,
showPopoutIcon: false
},
content: [
{
type: 'column',
content: [
{
type: 'row',
height: 0,
content: [
{
title: 'A react component',
type: 'react-component',
component: LayoutComponent.SearchContainer
}
],
},
{
type: 'row',
height: 100,
content: [
{
title: 'A react component',
type: 'react-component',
component: LayoutComponent.WindowContainer
},
{
title: 'Another react component',
type: 'react-component',
component: LayoutComponent.CollectionContainer
}
],
},
],
}],
};
showSearchBar looks like this:
function showSearchBar() {
let container: ContentItem = layout.root.contentItems[0];
if (searchRowHeight == 0) {
container.contentItems[0].config.height = SEARCH_HEIGHT;
}
else {
container.contentItems[0].config.height = searchRowHeight;
container.contentItems[1].config.height = containerRowHeight;
}
container.contentItems[0].element.show();
layout.updateSize();
}
and hideSearchBar looks like this:
function hideSearchBar() {
let container: ContentItem = layout.root.contentItems[0];
container.contentItems[0].config.height = 0;
container.contentItems[1].config.height = 100;
container.contentItems[0].element.hide();
layout.updateSize();
}
In summary, the config made the searchBar hidden and when it was opened, heights were readjusted.
I use an event listener to check for height changes:
layout.on('stateChanged', () => {
let updateConfig: Config = layout.toConfig();
if (updateConfig.content[0].content[0].height != 0) {
searchRowHeight = updateConfig.content[0].content[0].height;
containerRowHeight = updateConfig.content[0].content[1].height;
}
HTH
Extending #jmc42's answer. Pretty good work-around but once thing it doesn't do is hide the splitter when expanding on pane to 100% and the collapsing the other to 0%.
As a work-around, I thought of 2 choices:
When the pane gets hidden, get the adjacent element representing the splitter bar within the same div and hide it.
When the pane gets hidden, and you detect a resize, always re-apply the expand the top pane to 100% and the bottom pane to 0%.
I opted for option 2 as it was simpler to implement and what I have is:
if (updateConfig.content[0].content[0].height != 0) {
searchRowHeight = updateConfig.content[0].content[0].height;
containerRowHeight = updateConfig.content[0].content[1].height;
}
else {
let container = gbl_Layout.root.contentItems[0].contentItems[0];
container.contentItems[0].config.height = 100;
container.contentItems[1].config.height = 0;
layout.updateSize();
}
My 'if' statement condition is more complex that the one above as I'm performing other checks but that will give you the gist of it. Works pretty well for me.

How to access component model from outside

I have created a shell-in-shell construct in the index.html:
sap.ui.getCore().attachInit(function () {
// create a new Shell that contains the root view
var oShell = new sap.m.Shell({
id: "appShell",
app: new sap.ui.core.ComponentContainer({
name: "internal_app",
height: "100%"
})
});
// load the view that contains the unified shell
var oAppShellView = sap.ui.view({
type: sap.ui.core.mvc.ViewType.XML,
viewName: "internal_app.view.AppShell"
});
// access the unified shell from the view
var oUnifiedShell = oAppShellView.byId("unifiedShell");
// place the app shell in the unified shell
oUnifiedShell.addContent(oShell);
oAppShellView.placeAt("content");
});
In addition, a default model has been defined in manifest.json:
....
},
"models": {
"": {
"type": "sap.ui.model.json.JSONModel"
}
},
....
In the controller of the view internal_app.view.AppShell (which has been created by the code snippet above) I would now like to access the default model but neither this.getModel() nor this.getOwnerComponent().getModel() (getModel() and getOwnerComponent() return undefined) worked. I assume that the AppShell controller does not have an owner. But how can I access the default model in the onInit of that controller?
The app structure in your case is somewhat unusual - Nevertheless, you can always access the model, defined in manifest.json, as long as you can access the inner component.
Assuming this is referencing the controller of the internal_app.view.AppShell, you can get the default model like this:
onInit: function() {
var innerShell = sap.ui.getCore().byId("appShell"); // only if the app is standalone
this.componentLoaded(innerShell.getApp()).then(this.onComponentCreated.bind(this));
},
componentLoaded: function(componentContainer) {
var component = componentContainer.getComponent();
return component ? Promise.resolve(component) : new Promise(function(resolve) {
componentContainer.attachEventOnce("componentCreated", function(event) {
resolve(event.getParameter("component"));
}, this);
}.bind(this));
},
onComponentCreated: function(component) {
var myDefaultModel = component.getModel(); // model from manifest.json
// ...
}

Refresh custom control in sapui5 when model change

I've a custom control which have multiple properties inserted in Detail View page. I've binded data with these properties. Scenario is I've two pages one is list view and then detail view. I've to navBack from detail page and select diff product from main page.Detail view page show diff products detail according to selected product. everything works fine. but problem is that my custom control doesn't update values and other page have updated values.
<custom:product topic="" subTopic="{product>name}" id="productDetial"></custom:product>
I've used one methond this.getView().byId("productDetail").rerender(); but it doesn't update my Inner HTML of control.
the control code. might be some typos error.as I've changed some variables name and remove unwanted code. the purpose is to show the methods which I've used and how I did
sap.ui.define([
"sap/m/Label",
"sap/m/Button",
"sap/m/CustomTile"
], function(Label, Button, CustomTile) {
"use strict";
jQuery.sap.declare("testProduct.control.product");
return CustomTile.extend("testProduct.control.product", {
metadata: { // the Control API
properties: {
name: {
type: "string",
defaultValue: "--"
},
subTopic: {
type: "string",
defaultValue: "--"
}
}
},
init: function() {
},
rerender: function(oRM, oControl) {
},
renderer: function(oRM, oControl) {
oRM.write('<div class=" sapMTile customTileCourseDetail">');
oRM.write('<div class="leftTileYourScore">');
if (oControl.getSubTopic() !== "" && oControl.getSubTopic() !== undefined) {
oRM.writeEscaped(oControl.getSubTopic());
} else {
oRM.write(" ");
}
oRM.write('</div>');
oRM.write('</div>
}
});
});
Yo just need to add a setter function in you control. When the binding is refreshed/changes, UI5 will trigger a setter method specific to the property. So in you case for the property subTopic it expects a method setSubTopic. This method should define you own logic to update said property in the UI layer according to your needs.
Here is part of the code you need to add, you will also have to tweak the initial rendering logic a bit.
renderer: function (oRM, oControl) {
//oRM.write('<div class=" sapMTile customTileCourseDetail">');
oRM.write("<div")
oRM.writeControlData(oControl);
oRM.addClass("sapMTile customTileCourseDetail");
oRM.writeClasses();
oRM.write(">");
oRM.write('<div class="leftTileYourScore">');
if (oControl.getSubTopic() !== "" && oControl.getSubTopic() !== undefined) {
oRM.writeEscaped(oControl.getSubTopic());
} else {
oRM.write(" ");
}
oRM.write('</div>');
oRM.write('</div>');
},
setSubTopic: function(sText){
this.setProperty("subTopic", sText, true);
$("#"+this.sId+" .leftTileYourScore").html(sText);
}

EmberJS: Observer Not Being Triggered on Computed Property

I am building a handelbars helper that renders a checkbox group. My goal is to display a checkbox group with something like this and get two-way binding on selectedOptions:
{{form-checkboxGroup options=allOptions selectedOptions=selectedOptions}}
I've used this pattern successfully with other form components and it's a big win. I'm able to render my allOptions and selectedOptions values as a checkbox group, but it's the two-way binding that's tripping me up. Any idea what I'm missing?
By the way, I'm using ember-cli, but that doesn't affect anything relating to this issue.
Here's my setup:
Handlebars Helper: helpers/form-checkbox-group.js
The sole purpose of this file is to link the Handelbars expression {{form-checkboxGroup}} to the view and template below.
import FormCheckboxGroupView from 'my-app/views/util/form/form-checkbox-group';
export default Ember.Handlebars.makeBoundHelper(function( options ) {
return Ember.Handlebars.helpers.view.call(this, FormCheckboxGroupView, options);
});
CheckboxGroup Handlebars Template: templates/util/form/form-checkbox-group.hbs
...
{{#each user in view.combinedOptions}}
{{input type="checkbox" name="view.fieldName" checked=user.checked }} {{user.name}}
{{/each}}
...
CheckboxGroup View: views/util/form/form-checkbox-group.js
...
export default FormCheckboxGroupView = Ember.View.extend( FormFieldMixin, {
templateName: 'util/form/form-checkbox-group',
selectedOptions: function() {
console.log("When triggered this could update view.selectedOptions");
}.observes('view.combinedOptions.#each.checked'),
// combines the "options" and "selected options" into a single array of "combinedOptions" explicitly indicating what's checked
combinedOptions: function() {
...
// sample result of combinedOptions:
// { name: "Johnny Five", id: "12", checked: true }
return combinedOptions;
}.property('view.options', 'view.selectedOptions')
});
And finally, to actually use my Handlebars helper, here's the consuming page's template and corresponding controller:
Consuming Page: templates/my-page.hbs
{{form-checkboxGroup options=allUsersArray selectedOptions=selectedUsersArray fieldName="selectedProvidersArray" }}
Backing Controller for Consuming Page: controllers/my-page.js
export default MyPageController = Ember.Controller.extend( FormMixin, {
allUsersArray: [
{ name: 'Bill Huxtable', id: 'billy' },
{ name: 'Samantha Jones', id: 'jones' },
{ name: 'Tony Pepperoni', id: 'tonyp' },
{ name: 'Ridonk Youliss', id: 'silly' }
],
selectedUsersArray: [
{ name: 'Tony Pepperoni', id: 'tonyp' },
{ name: 'Ridonk Youliss', id: 'silly' }
],
...
});
So, all of this successfully renders the checkbox group nicely, but my efforts to capture the fact that a checkbox has been newly selected by using observes("view.combinedOptions.#each.checked') is not working.
Any idea on how I can this up for two-way binding? Thanks in advance for assistance!
No jsbin so I'm flying blind, but try this:
selectedOptions: function() {
console.log("When triggered this could update view.selectedOptions");
}.observes('combinedOptions.#each.checked')
view.property is how you access view from template. You don't need that from the view itself (unless you have view property on your view).