onAfterRendering hook for smartform in UI5 - sapui5

In my app i have an XML view that consists of a smartform. I have a need to access an input element(via sap.ui.getCore().byId()) that becomes available after the smartform is parsed and rendered.
The onAfterRendering in the controller for my view triggers as soon as the view is rendered(i get all my non-smartform elements like title etc.), but before the smartform is parsed and rendered. A rudimentary test via an alert also proved this visually.
Is there any event that is triggered after the smartform has rendered which i can hook into to access my input element?
The developer guide walkthrough is extending the smartform and thus has its init method, but in my case i am extending the basecontroller and my init is for the page view.
Thanks for any pointers.
My View:
<mvc:View
controllerName="myns.controller.Add"
xmlns:mvc="sap.ui.core.mvc"
xmlns:semantic="sap.m.semantic"
xmlns:smartfield="sap.ui.comp.smartfield"
xmlns:smartform="sap.ui.comp.smartform"
xmlns="sap.m">
<semantic:FullscreenPage
id="page"
title="{i18n>addPageTitle}"
showNavButton="true"
navButtonPress="onNavBack">
<semantic:content>
<smartform:SmartForm
id="form"
editable="true"
title="{i18n>formTitle}"
class="sapUiResponsiveMargin" >
<smartform:Group
id="formGroup"
label="{i18n>formGroupLabel}">
<smartform:GroupElement>
<smartfield:SmartField
id="nameField"
value="{Name}" />
</smartform:GroupElement>
</smartform:Group>
</smartform:SmartForm>
</semantic:content>
<semantic:saveAction>
<semantic:SaveAction id="save" press="onSave"/>
</semantic:saveAction>
<semantic:cancelAction>
<semantic:CancelAction id="cancel" press="onCancel"/>
</semantic:cancelAction>
</semantic:FullscreenPage>
My Controller:
sap.ui.define([
"myns/controller/BaseController",
"sap/ui/core/routing/History",
"sap/m/MessageToast"
],function(BaseController, History, MessageToast){
"use strict";
return BaseController.extend("myns.controller.Add",{
onInit: function(){
this.getRouter().getRoute("add").attachPatternMatched(this._onRouteMatched, this);
},
onAfterRendering: function(){
//I tried my sap.ui.getCore().byId() here but does not work
//An alert shows me this triggers before the smartform is rendered but
//after all the other non-smartform elements have rendered
},
_onRouteMatched: function(){
// register for metadata loaded events
var oModel = this.getModel();
oModel.metadataLoaded().then(this._onMetadataLoaded.bind(this));
},
_onMetadataLoaded:function(){
//code here....
},
onNavBack: function(){
//code here....
}
});
});

You can look for when SmartForm is added to the DOM with DOMNodeInserted event of jQuery.
For this you can use it's id to identify the SmartForm has been added to the DOM.
Every UI5 element gets some prefix after it has been added to the DOM.
for e.g. __xmlview0--form.
So to make sure required form is added you can split the id of added element, then compare it with id which you have given.
Although it's not optimal solution, but you can try.
onAfterRendering: function() {
$(document).bind('DOMNodeInserted', function(event) {
var aId = $(event.target).attr("id").split("--");
var id = aId[aId.length - 1];
if (id) {
if (id == "form") {
// smart form fields are accessible here
$(document).unbind("DOMNodeInserted");
}
}
})
}

My final solution (for now and uses the accepted answer provided by #Dopedev):
(in controller for the nested view containing the smartform)
onAfterRendering: function() {
$(document).bind('DOMNodeInserted', function(event) {
var elem = $(event.target);
var aId = elem.attr("id").split("--");
var id = aId[aId.length - 1];
if (id) {
if (id == "nameField") {
elem.find("input").on({
focus: function(oEvent) {
//code here;
},
blur: function(oEvent) {
//code here;
}
});
/*
elem.find("input").get(0).attachBrowserEvent("focus", function(evt) {
//code here
}).attachBrowserEvent("blur", function(ev) {
//code here
});
*/
$(document).unbind("DOMNodeInserted");
}
}
});
}

Related

What is the event for SAPUI5 SmartTable when loading data is failed?

I need to run a code when the smart table cannot read the data from back-end. What kind of event I have to add to the smart table?
For example something like:
oSmartTable.getBinding("items").attachEventOnce("dataFailed", ....
We don't have such an event, but I need something like this.
If we have a SmartTable with a table inside like this, we can use its dataRequested event:
<smartTable:SmartTable ..... dataRequested="onDataRequested">
<m:Table id="table" ...>
....
</m:Table>
</smartTable:SmartTable>
By using this event we try to add dataReceived event of the table:
onDataRequested: function(oEvent){
var oTable = this.byId("table");
oTable.getBinding("items").attachEventOnce("dataReceived",function(oData) {
if(!oData.getParameter("data")){
// Do something here
}
}, this);
}
Here is a simple solution from UI5 1.56
In your view
<smartTable:SmartTable id="smartTable" entitySet="PRs" smartFilterId="smartFilterBar"
tableType="ResponsiveTable" beforeRebindTable=".onBeforeRebind">
In your Controller
onBeforeRebind: function (oEvent) {
var mBindingParams = oEvent.getParameter("bindingParams");
//Event handlers for the binding
mBindingParams.events = {
"dataReceived" : function(oEvent){
var aReceivedData = oEvent.getParameter('data');
},
//More event handling can be done here
};
}

onCloseDialog event not working in my Controller. What's wrong with my code?

I'm trying to learn SAPUI5 and following the walkthrough in SAPUI5 documentation. I'm currently in Step 17: Fragment Callbacks. I am not able to make the onCloseDialog event work. The code I double and triple checked and I could not find anything wrong. There is also no error in Chrome's console. Any insights?
Link to the guide I'm following:
https://sapui5.hana.ondemand.com/#/topic/354f98ed2b514ba9960556333428d35e
My code for:
HelloDialog.fragment.xml
<core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core">
<Dialog id="helloDialog" title="Hello {/recipient/name}">
<content>
<core:Icon src="sap-icon://hello-world" size="8rem" class="sapUiMediumMargin"/>
</content>
<beginButton>
<Button text="{i18n>dialogCloseButtonText}" press="onCloseDialog"/>
</beginButton>
</Dialog>
My code for:
HelloPanel.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
], function(Controller, MessageToast) {
"use strict";
return Controller.extend("sap.ui.demo.wt.controller.HelloPanel", {
onShowHello: function() {
// read msg from i18n model
var oBundle = this.getView().getModel("i18n").getResourceBundle();
var sRecipient = this.getView().getModel().getProperty("/recipient/name");
var sMsg = oBundle.getText("helloMsg", [sRecipient]);
// show message
MessageToast.show(sMsg);
},
onOpenDialog: function() {
var oView = this.getView();
var oDialog = oView.byId("helloDialog");
// create dialog lazily
if (!oDialog) {
// create dialog via fragment factory
oDialog = sap.ui.xmlfragment(oView.getId(), "sap.ui.demo.wt.view.HelloDialog");
oView.addDependent(oDialog);
}
oDialog.open();
},
onCloseDialog: function() {
this.getView().byId("helloDialog").close();
}
});
});
You missed to pass the controller reference when creating the dialog from the fragment.
In case of the Walkthrough step, it's the same controller object as the dialog caller, so this should be passed:
sap.ui.xmlfragment(oView.getId(), "sap.ui.demo.wt.view.HelloDialog", this);⚠️
API reference: sap.ui.xmlfragment⚠️
⚠️ sap.ui.*fragment is deprecated!
Instead, use one of the APIs mentioned in https://stackoverflow.com/a/64541325/5846045.
Documentation topic: Instantiation of Fragments

What is the need of $scope.closeModal?

Here is my code..If I remove close modal function,there is no effect. If I click any where outside the modal, the modal closes. But I need this close modal function as I need to set a flag in it for further use. How can I proceed further?
$scope.$on('$ionicView.afterEnter', function() {
$scope.openModal();
}
$ionicModal.fromTemplateUrl("settings/settingsModal.html", {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.modal = modal;
});
$scope.openModal = function(){
$scope.modal.show();
}
$scope.closeModal = function(){
$scope.modal.hide();
};
}
There are a two ways of implementing modal in Ionic. One way is to add separate template and the other is to add it on top of the regular HTML file, inside script tags. First thing we need to do is to connect our modal to our controller using angular dependency injection. Then we need to create modal. We will pass in scope and add animation to our modal.
After that we are creating functions for opening, closing, destroying modal and the last two functions are place where we can write code that will be triggered if modal is hidden or removed. If you don't want to trigger any functionality when modal is removed or hidden you can delete the last two functions.
Controller's Code:
.controller('MyController', function($scope, $ionicModal) {
$ionicModal.fromTemplateUrl('my-modal.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.modal = modal;
});
$scope.openModal = function() {
$scope.modal.show();
};
$scope.closeModal = function() {
$scope.modal.hide();
};
//Cleanup the modal when we're done with it!
$scope.$on('$destroy', function() {
$scope.modal.remove();
});
// Execute action on hide modal
$scope.$on('modal.hidden', function() {
// Execute action
});
// Execute action on remove modal
$scope.$on('modal.removed', function() {
// Execute action
});
});
HTML Code :
<script id = "my-modal.html" type = "text/ng-template">
<ion-modal-view>
<ion-header-bar>
<h1 class = "title">Modal Title</h1>
</ion-header-bar>
<ion-content>
<button class = "button icon icon-left ion-ios-close-outline"
ng-click = "closeModal()">Close Modal</button>
</ion-content>
</ion-modal-view>
</script>
There are also other options for modal optimization. I already showed how to use scope and animation. The table below shows other options.
Modal options
The close modal function is meant for situations where you would like to close the modal manually. For example after a certain time it has been open or if something happens/the user does something for example presses a button.
There are ways of listening to when the modal is hidden/removed which will suit your situation and needs. For example:
// Execute action on hide modal
$scope.$on('modal.hidden', function() {
// Execute action
console.log('modal was hidden');
});
// Execute action on remove modal
$scope.$on('modal.removed', function() {
// Execute action
console.log('modal was removed');
});
With these you should be able to do what I understood you are wanting to do.
Straight from the documentation: http://ionicframework.com/docs/api/service/$ionicModal/

ionic how to call function when specific view load?

i need to show /hide buttons at specific ion view
and this buttons appearance depend on function
as my sample:
.controller('IntroCtrl', function ($scope, $state, $ionicSlideBoxDelegate) {
$scope.showalbums=false;
$scope.showalbums_new=true;
checkfolders();
if(hasalbums==1)
{
$scope.showalbums=true;
$scope.showalbums_new=false;
}
and in html page:
<i class="ion-images font-ion margin-right-8" ng-click="myAlbums()" ng-show="showalbums"></i>
<button class="button button-positive button-clear no-animation"
ng-click="showAlert2()" ng-if="slideIndex == 2" ng-show="showalbums_new" >
and my method in js:
var hasalbums=0;
function checkfolders()
{
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0,
function(fileSystem){ // success get file system
directoryEntry = fileSystem.root;
if( !directoryEntry.isDirectory ) {
hasalbums=0;
}
currentDir = directoryEntry; // set current directory
directoryEntry.getParent(function(par){ // success get parent
parentDir = par; // set parent directory
}, function(error){ // error get parent
hasalbums=0;
});
var directoryReader = directoryEntry.createReader();
directoryReader.readEntries(function(entries){
alert(hasalbums);
if(entries.length>0)
{
hasalbums=1;
}else{
hasalbums=0;
}
}, function(error){
hasalbums=0;
});
}, function(evt){ // error get file system
hasalbums=0;
}
);
alert(hasalbums);
}
but method not called and can't show/hide buttons as i need
while this function is working correctly
In Ionic, controllers normally only load once. So we need to use ionic's lifecycle events to trigger some logic in your controller everytime you arrive at this view. Example:
.controller('IntroCtrl', function ($scope, $state, $ionicSlideBoxDelegate) {
$scope.$on('$ionicView.loaded', function () {
checkfolders();
});
Now your checkfolders() method will be executed everytime you arrive at this view, after your content has loaded. Refer this for other lifecycle events in ionic: http://ionicframework.com/docs/api/directive/ionView/

Passing Data from Master to Detail Page

I watched some tutorials about navigation + passing data between views, but it doesn't work in my case.
My goal is to achieve the follwing:
On the MainPage the user can see a table with products (JSON file). (Works fine!)
After pressing the "Details" button, the Details Page ("Form") is shown with all information about the selection.
The navigation works perfectly and the Detail page is showing up, however the data binding doesnt seem to work (no data is displayed)
My idea is to pass the JSON String to the Detail Page. How can I achieve that? Or is there a more elegant way?
Here is the code so far:
MainView Controller
sap.ui.controller("my.zodb_demo.MainView", {
onInit: function() {
var oModel = new sap.ui.model.json.JSONModel("zodb_demo/model/products.json");
var mainTable = this.getView().byId("productsTable");
this.getView().setModel(oModel);
mainTable.setModel(oModel);
mainTable.bindItems("/ProductCollection", new sap.m.ColumnListItem({
cells: [new sap.m.Text({
text: "{Name}"
}), new sap.m.Text({
text: "{SupplierName}"
}), new sap.m.Text({
text: "{Price}"
})]
}));
},
onDetailsPressed: function(oEvent) {
var oTable = this.getView().byId("productsTable");
var contexts = oTable.getSelectedContexts();
var items = contexts.map(function(c) {
return c.getObject();
});
var app = sap.ui.getCore().byId("mainApp");
var page = app.getPage("DetailsForm");
//Just to check if the selected JSON String is correct
alert(JSON.stringify(items));
//Navigation to the Detail Form
app.to(page, "flip");
}
});
Detail Form View:
<mvc:View xmlns:core="sap.ui.core" xmlns="sap.m" xmlns:f="sap.ui.layout.form" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mvc="sap.ui.core.mvc" controllerName="my.zodb_demo.DetailsForm">
<Page title="Details" showNavButton="true" navButtonPress="goBack">
<content>
<f:Form id="FormMain" minWidth="1024" maxContainerCols="2" editable="false" class="isReadonly">
<f:title>
<core:Title text="Information" />
</f:title>
<f:layout>
<f:ResponsiveGridLayout labelSpanL="3" labelSpanM="3" emptySpanL="4" emptySpanM="4" columnsL="1" columnsM="1" />
</f:layout>
<f:formContainers>
<f:FormContainer>
<f:formElements>
<f:FormElement label="Supplier Name">
<f:fields>
<Text text="{SupplierName}" id="nameText" />
</f:fields>
</f:FormElement>
<f:FormElement label="Product">
<f:fields>
<Text text="{Name}" />
</f:fields>
</f:FormElement>
</f:formElements>
</f:FormContainer>
</f:formContainers>
</f:Form>
</content>
</Page>
</mvc:View>
Detail Form Controller:
sap.ui.controller("my.zodb_demo.DetailsForm", {
goBack: function() {
var app = sap.ui.getCore().byId("mainApp");
app.back();
}
});
The recommended way to pass data between controllers is to use the EventBus
sap.ui.getCore().getEventBus();
You define a channel and event between the controllers. On your DetailController you subscribe to an event like this:
onInit : function() {
var eventBus = sap.ui.getCore().getEventBus();
// 1. ChannelName, 2. EventName, 3. Function to be executed, 4. Listener
eventBus.subscribe("MainDetailChannel", "onNavigateEvent", this.onDataReceived, this);)
},
onDataReceived : function(channel, event, data) {
// do something with the data (bind to model)
console.log(JSON.stringify(data));
}
And on your MainController you publish the Event:
...
//Navigation to the Detail Form
app.to(page,"flip");
var eventBus = sap.ui.getCore().getEventBus();
// 1. ChannelName, 2. EventName, 3. the data
eventBus.publish("MainDetailChannel", "onNavigateEvent", { foo : "bar" });
...
See the documentation here: https://openui5.hana.ondemand.com/docs/api/symbols/sap.ui.core.EventBus.html#subscribe
And a more detailed example:
http://scn.sap.com/community/developer-center/front-end/blog/2015/10/25/openui5-sapui5-communication-between-controllers--using-publish-and-subscribe-from-eventbus
Even though this question is old, the scenario is still valid today (it's a typical master-detail / n-to-1 scenario). On the other hand, the currently accepted solution is not only outdated but also a result of an xy-problem.
is there a more elegant way?
Absolutely. Take a look at this Flexible Column Layout tutorial: https://sdk.openui5.org/topic/c4de2df385174e58a689d9847c7553bd
No matter what control is used (App, SplitApp, or FlexibleColumnLayout), the concept is the same:
User clicks on an item from the master.
Get the binding context from the selected item by getBindingContext(/*modelName*/).
Pass only key(s) to the navTo parameters (no need to pass the whole item context).
In the "Detail" view:
Attach a handler to the patternMatched event of the navigated route in the Detail controller's onInit.
In the handler, create the corresponding key, by which the target entry can be uniquely identified, by accessing the event parameter arguments in which the passed key(s) are stored. In case of OData, use the API createKey.
With the created key, call bindObject with the path to the unique entry in order to propagate its context to the detail view.
The relative binding paths in the detail view can be then resolved every time when the detail page is viewed. As a bonus, this also enables deep link navigation or bookmark capability.
You can also set local json model to store your data, and use it in the corresponding views. But be sure to initialize or clear it in the right time.