how to set a variable that is shared between multiple controllers - sapui5

Is it a good idea to set a js file(in util folder) which return a global object which will keep a list of different map of variables .
So in the index.js I can pass the app variable and followed by accessing it from controller.
sap.ui.define(["sap/ui/base/ManagedObject"], function (ManagedObject) {
"use strict";
let globals = {};
return globals;
});
and in index.js
sap.ui.define(
[
"sap/ui/base/ManagedObject",
"zap/util/globals",
],
function (ManagedObject, globals) {
"use strict";
let oApp = new sap.m.App();
globals.oApp = oApp;
});
and in controller event handler
sap.ui.define(
["sap/ui/core/mvc/Controller", "zap/util/globals"],
function (Controller, globals) {
"use strict";
return Controller.extend("zap.controller.Master", {
onListPress: function (oEvt) {
let oApp = globals.oApp;
}
});
});

You can create a model, and load it in the manifest.json, so it would be accessible from each part of your application.
{
"models": {
"globalModel": {
"type": "sap.ui.model.json.JSONModel",
"uri": "Models/GlobalModel.json"
}
}
}`

Related

Dialog is not a constructor ui5

I get an error while using an reusing a dialog in sapui5:
Dialog is not a constructor
I want to create a dialog fragment. dialog.js contains all functions and after that make it global in the component.js
sap.ui.define([
"sap/ui/base/Object"
], function (Object) {
"use strict";
return Object.extend("tmp.Zprojetyousra.controller.Dialog", {
constructor : function (oView) {
this._oView = oView;
},
open : function () {
var oView = this._oView;
var oDialog = oView.byId("dialog");
// create dialog lazily
if (!oDialog) {
var oFragmentController = {
onCloseDialog : function () {
oDialog.close();
}
};
// create dialog via fragment factory
oDialog = sap.ui.xmlfragment(oView.getId(), "tmp.Zprojetyousra.view.Dialog", oFragmentController);
// connect dialog to the root view of this component (models, lifecycle)
oView.addDependent(oDialog);
}
oDialog.open();
}
});
});
HTML:
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core">
<Dialog
id="dialog"
title="Hello dialogs}">
<content>
<core:Icon
src="sap-icon://hello-world"
size="8rem"
class="sapUiMediumMargin"/>
</content>
<beginButton>
<Button
text="{i18n>dialogCloseButtonText}"
press="onCloseDialog"/>
</beginButton>
</Dialog>
</core:FragmentDefinition>
JS:
sap.ui.define([
"sap/ui/core/UIComponent",
/* "tmp/Zprojetyoussra/model/models", peut influencer sur le code */
"sap/ui/model/json/JSONModel",
"tmp/Zprojetyoussra/controller/Dialog" ,
"sap/ui/Device"
/// device toujours doit etre a la fin des dependecies
], function (UIComponent, JSONModel, Device, Dialog) {
"use strict";
return UIComponent.extend("tmp.Zprojetyoussra.Component", {
metadata: {
// rootView: "tmp.Zprojetyoussra.view.tesstview"
manifest: "json"
},
init: function () {
// call the base component's init function
UIComponent.prototype.init.apply(this, arguments);
// set data model
var oData = {
recipient : {
name : "World"
}
};
// without this.getview
var oModel = new sap.ui.model.json.JSONModel(oData);
this.setModel(oModel);
// set i18n model
var i18nModel = new sap.ui.model.resource.ResourceModel({
bundleName : "tmp.Zprojetyoussra.i18n.i18n"
});
this.setModel(i18nModel, "i18n");
/* eslint-disable no-alert */
// debug code to show an alert for missing destination or CORS issues in the tutorial (see step 26 for details)
this.getModel("invoice").attachEventOnce("metadataFailed", function(oEvent) {
alert("Request to the OData remote service failed.\nRead the Walkthrough Tutorial Step 26 to understand why you don't see any data here.");
});
// set device model
var oDeviceModel = new JSONModel(Device);
oDeviceModel.setDefaultBindingMode("OneWay");
this.setModel(oDeviceModel, "device");
this._dialog= new Dialog();
// create the views based on the url/hash
this.getRouter().initialize();
}
});
});
Error:
You have a wrong order of your imports in the controller define section. In the array you have:
UIComponent, JSONModel, Dialog, Device
but in the function definition below you have:
UIComponent, JSONModel, Device, Dialog
So the Dialog variable holds Device namespace which doesn't have a controller.
As #vasek said put your import in the right order use this syntax in your js file :
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel",
"tmp/Zprojetyoussra/controller/Dialog" ,
"sap/ui/Device"
], function (UIComponent, JSONModel, Dialog, Device) {
"use strict";
return UIComponent.extend("tmp.Zprojetyoussra.Component", {
metadata: {
// rootView: "tmp.Zprojetyoussra.view.tesstview"
manifest: "json"
},

Error while setting model inside onInit() of root view - no error / no log

I am trying to set a new JSONModel on the Main.view.xml (root view).
But it seems like it is stoping at .setModel(). The console.log("after") is not logging.
sap.ui.define([
"sap/ui/core/mvc/Controller",
"jquery.sap.global",
"sap/m/MessageToast",
"sap/ui/model/json/JSONModel"
], function (Controller, JSONModel, MessageToast) {
"use strict";
return Controller.extend("sap.ui.bookedTimes.wt.controller.Main", {
onInit : function () {
var jModel = this.getOwnerComponent().getModel("zCatsTestJ");
var that = this;
jModel.attachRequestCompleted(function() {
console.log(that.getView());
var oViewModel= new JSONModel({workdate: "test"});
console.log("before");
that.getView().setModel(oViewModel, "view");
console.log("after");
console.log(that.getView().getModel("view"));
});
},
});
});
Error in console
Entry in manifest.json:
"sap.ui5": {
"rootView" : {
"viewName":"sap.ui.bookedTimes.wt.view.Main",
"id": "mainview",
"type": "XML"
},
Is there a problem in onInit() of the root view?
Update:
I should have added the part of the xml.view. I changed the view name to "view1" and everything from the controller was logged. The problem was that my view was still expecting a date
<Text text="{ path: 'view1>/workdate', type: 'sap.ui.model.type.Date', formatOptions: { pattern: 'dd.MM.yyyy' } }" />
After changing this to text it was working. Anyway the initial problem was the order of the definitions
Thanks guys
It looks like your imports are off. Try fixing it like this (pay attention to the define([]) block)
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/m/MessageToast"
], function (Controller, JSONModel, MessageToast) {
"use strict";
return Controller.extend("sap.ui.bookedTimes.wt.controller.Main", {
onInit : function () {
var jModel = this.getOwnerComponent().getModel("zCatsTestJ");
var that = this;
jModel.attachRequestCompleted(function() {
console.log(that.getView());
var oViewModel= new JSONModel({workdate: "test"});
console.log("before");
that.getView().setModel(oViewModel, "view");
console.log("after");
console.log(that.getView().getModel("view"));
});
},
});
});
Now you should have JSONModel correctly imported and shouldn't see any errors.

How to access other function in UI5 custom control/type?

I defined many custom type in Utils.js: http://plnkr.co/edit/BGqDoWEC7DTmomEFHygd?p=catalogue
I want to add trim() in parseValue
parseValue: function (oValue) {
if(oValue !== null) {
return oValue.trim();
}
return oValue;
},
Copy and paste this function is kind of dumb, so I want to do sth. like :
trimString : function(oValue) {
if(oValue !== null) {
return oValue.trim();
}
return oValue;
},
mandatoryValueType : SimpleType.extend("text", {
formatValue: function (oValue) {
return oValue;
},
parseValue: this.trimString,
validateValue: function (oValue) {
if (!oValue) {
throw new ValidateException(Utils.i18n("MANDATORY_VALIDATE_ERROR"));
}
}
}),
But scope in mandatoryValueType seems can not access to this.trimString, what can I do?
this scope in parseValue function, no trimString function:
Another working sample : http://plnkr.co/edit/ahS6NlUHHL0kdvdddvgd?p=preview
Reference: https://sapui5.hana.ondemand.com/#/sample/sap.m.sample.InputChecked/preview
Maybe you can put the trimString function outside the var Utils object, and set it as a global function.
here is the example: http://plnkr.co/edit/u64DEP0RnT5CJADZyXJg?p=catalogue
edit:
corrected by #boghyon, the trimString function should be a function in a closure, instead of a global function :)
in this case the this keyword contains the object which invokes the function: the controller which invokes mandatoryValueType of utils.js.
initialise in utils.js in the upper scope of mandatoryValueType a var that = this. then use inside of mandatoryValueType instead of this.trimString the that.trimString.
edit
self reference to a property of an object in a literal declaration of an object:
var Utils = {
...
trimString: function() {
...
},
...
parseValue: function() {
this.trimString();
}
..
}

Not able to initilize the JS view in openui5

Not able to initilize the below JS view in index page. Could you please help me?
I m getting this error msg "Uncaught (in promise) TypeError: t.createContent is not a function"
Plunker link:
https://plnkr.co/edit/7v0CN93aDrAOY9WqU269?p=preview
app.view.js:
sap.ui.jsview("app",{
getControllerName:function(){
return "app";
},
createContent:function(oContoller){
var oButton = new sap.m.Button(this.createId("helloButton"),{
text:"Click Me"
});
return oButton;
}
});
change the view code like this
sap.ui.jsview("view.js.app", {
getControllerName: function() {
return "view.js.app";
},
createContent: function(oContoller) {
var oButton = new sap.m.Button(this.createId("helloButton"), {
text: "Click Me"
});
return oButton;
}
});
Also add a controller file app.controller.js and paste below code.
sap.ui.controller("view.js.app", {
onInit: function() {
}
});
You are specifying the resourceroots as "view.js" in the index file.
So while instantiating use below code
var view = sap.ui.view({
id:"idApp1",
type:sap.ui.core.mvc.ViewType.JS,
viewName:"view.js.app"
});
And in the view add these changes,
sap.ui.jsview("view.js.app",{
getControllerName:function(){
return "view.js.app";
},
createContent:function(oContoller){
var oButton = new sap.m.Button(this.createId("helloButton"),{
text:"Click Me"
});
return oButton;
}
});
And add one controller file named app as below,
sap.ui.define(["sap/ui/core/mvc/Controller"],
function (Controller) {
"use strict";
return Controller.extend("view.js.app", {
});
});
Here is the working link.

Pass a controller to $ionicModal

I am wondering if you can pass a controller to the $ionicModal service. Something like.
$ionicModal.fromTemplateUrl('templates/login.html', {
scope: $scope,
controller: 'MyModalCotroller'
})
A little context: I would like to have a modal that is distributed across the app and I dont want to repeat all the methods (hide, show, buttons inside the modal) in every controller and I would like to remove the methods from the 'Main Controller' to keep things clean. This would encapsulate the functionality of the modal.
Is there a way to do this.?
Thanks
Just add the controller you want to use in the body of the html of the modal. I created a fiddle to show you an example based off the one provided in the ionic docs: http://jsfiddle.net/g6pdkfL8/
But basically:
<-- template for the modal window -->
<ion-modal-view>
<ion-content ng-controller="ModalController">
...
</ion-content>
<ion-modal-view>
There's no direct way of doing so in ionic. However, if you really want to have some common code being segregated at one place,
You can use services to do so. Here' how.
In your modal declaration, pass scope as null, also the modal declaration should move in a service.
app.service('utilsService', function($ionicModal) {
this.showModal = function() {
var service = this;
$ionicModal.fromTemplateUrl('templates/login.html', {
scope: null,
controller: 'MyModalCotroller'
}).then(function(modal) {
service.modal = modal;
service.modal.show();
});
};
this.hideModal = function() {
this.modal.hide();
};
});
All your common methods will also move down into the same service.
Add the reference to this service into your controller's scope.
app.controller('indexController', function($scope, utilsService) {
$scope.utilsService = utilsService;
});
Now, you can call all the common methods from the view directly using this service.
e.g. <button ng-click="utilsService.hideModal()">Hide modal</button>
Based on this question and other needs I create a service that can be useful.
Anyway use the CodePen code, this updated, improved and it makes available the parameter 'options' of $ionicModal.
See this post: Ionic modal service or see in operation: CodePen
(function () {
'use strict';
var serviceId = 'appModalService';
angular.module('app').factory(serviceId, [
'$ionicModal', '$rootScope', '$q', '$injector', '$controller', appModalService
]);
function appModalService($ionicModal, $rootScope, $q, $injector, $controller) {
return {
show: show
}
function show(templateUrl, controller, parameters) {
// Grab the injector and create a new scope
var deferred = $q.defer(),
ctrlInstance,
modalScope = $rootScope.$new(),
thisScopeId = modalScope.$id;
$ionicModal.fromTemplateUrl(templateUrl, {
scope: modalScope,
animation: 'slide-in-up'
}).then(function (modal) {
modalScope.modal = modal;
modalScope.openModal = function () {
modalScope.modal.show();
};
modalScope.closeModal = function (result) {
deferred.resolve(result);
modalScope.modal.hide();
};
modalScope.$on('modal.hidden', function (thisModal) {
if (thisModal.currentScope) {
var modalScopeId = thisModal.currentScope.$id;
if (thisScopeId === modalScopeId) {
deferred.resolve(null);
_cleanup(thisModal.currentScope);
}
}
});
// Invoke the controller
var locals = { '$scope': modalScope, 'parameters': parameters };
var ctrlEval = _evalController(controller);
ctrlInstance = $controller(controller, locals);
if (ctrlEval.isControllerAs) {
ctrlInstance.openModal = modalScope.openModal;
ctrlInstance.closeModal = modalScope.closeModal;
}
modalScope.modal.show();
}, function (err) {
deferred.reject(err);
});
return deferred.promise;
}
function _cleanup(scope) {
scope.$destroy();
if (scope.modal) {
scope.modal.remove();
}
}
function _evalController(ctrlName) {
var result = {
isControllerAs: false,
controllerName: '',
propName: ''
};
var fragments = (ctrlName || '').trim().split(/\s+/);
result.isControllerAs = fragments.length === 3 && (fragments[1] || '').toLowerCase() === 'as';
if (result.isControllerAs) {
result.controllerName = fragments[0];
result.propName = fragments[2];
} else {
result.controllerName = ctrlName;
}
return result;
}
} // end
})();
Usage:
appModalService
.show('<templateUrl>', '<controllerName> or <controllerName as ..>', <parameters obj>)
.then(function(result) {
// result from modal controller: $scope.closeModal(result) or <as name here>.closeModal(result) [Only on template]
}, function(err) {
// error
});
You can use another service to centralize the configuration of all modals:
angular.module('app')
.factory('myModals', ['appModalService', function (appModalService){
var service = {
showLogin: showLogin,
showEditUser: showEditUser
};
function showLogin(userInfo){
// return promise resolved by '$scope.closeModal(data)'
// Use:
// myModals.showLogin(userParameters) // get this inject 'parameters' on 'loginModalCtrl'
// .then(function (result) {
// // result from closeModal parameter
// });
return appModalService.show('templates/modals/login.html', 'loginModalCtrl as vm', userInfo)
// or not 'as controller'
// return appModalService.show('templates/modals/login.html', 'loginModalCtrl', userInfo)
}
function showEditUser(address){
// return appModalService....
}
}]);
Create a directive to be used inside the modal and inside the directive you can assign the modal it's own controller and scope. If someone wants some example code I can put something up.
I was looking for a simple way to attach a controller to a modal instance and manage all modals with a single service. Also, I wanted the modal to have it's own isolated child scope. I wasn't satisfied with using ng-controller and I found other answers to be overly complicated to the point where you could easily loose track of scope and end up with circular or unidentifiable dependencies. I created the following service for my purposes.
You can pass an optional parentScope parameter to explicitly assign a parent scope to the created modal scope.
You could easily modify the instantiateModal method to accept $ionicModal options as an argument - I just didn't have the need for it.
BTW - I'm using the Webpack babel-loader for transpilation and the html-loader to load the templates. But, in it's simplest form, it's just a basic service.
/**
* nvModals
* #description A modal manager. Attaches a specified controller to an $ionicModal instance.
*/
import myModalTemplate from '../common/modals/my-modal.html';
import otherModalTemplate from '../common/modals/other-modal.html';
let nvModals = function (
$rootScope,
$controller,
$ionicModal
) {
var _self = this;
_self.methods = {
/**
* Instantiate and show a modal
*/
showMyModal: (parentScope) => {
var parentScope = parentScope || null;
_self.methods.instantiateModal('MyModalController', myModalTemplate, parentScope)
.show();
},
/**
* Instantiate and show another modal
*/
showOtherModal: (parentScope) => {
var parentScope = parentScope || null;
_self.methods.instantiateModal('OtherModalController', otherModalTemplate, parentScope)
.show();
},
/**
* Instantiate a new modal instance
*
* #param {object} controller Controller for your modal
* #param {string} template Template string
* #param {object} parentScope Optional parent scope for the modal scope
* #return {object} Modal instance
*/
instantiateModal: (controller, template, parentScope) => {
var modalScope;
if(parentScope) {
modalScope = $rootScope.$new(false, parentScope);
} else {
modalScope = $rootScope.$new(false);
}
$controller(controller, {
'$scope': modalScope
});
modalScope.modal = $ionicModal.fromTemplate(template, {
scope: modalScope,
animation: 'slide-in-up'
});
modalScope.$on('modal.hidden', (evt) => {
evt.targetScope.$destroy();
if (evt.targetScope.modal) {
evt.targetScope.modal.remove();
}
});
modalScope.hideModal = function () {
modalScope.modal.hide();
};
return modalScope.modal;
}
};
return _self.methods;
};
nvModals.$inject = [
'$rootScope',
'$controller',
'$ionicModal'
];
export default nvModals;
In your controller...
$scope.modals = nvModals;
In the associated template
ng-click="modals.showMyModal()"
In the modal template
ng-click="hideModal()"
Ok, I have seen a lot of different solutions to better handling Ionic modals because of the lack of a controller option or something similar.
After playing with React for a while I came up with another option, more declarative in my opinion. Is in ES6 and just a prototype but you can have an idea:
(function() {
'use strict';
#Inject('$scope', '$ionicModal', '$transclude', '$rootScope')
class Modal {
constructor() {
let { animation, focusFirstInput, backdropClickToClose, hardwareBackButtonClose } = this;
$transclude((clone, scope) => {
let modal = this.createModalAndAppendClone({
scope,
animation,
focusFirstInput,
backdropClickToClose,
hardwareBackButtonClose
}, clone);
this.setupScopeListeners(modal.scope);
this.createIsOpenWatcher();
this.addOnDestroyListener();
this.emitOnSetupEvent(modal.scope);
});
}
setupScopeListeners(scope) {
scope.$on('modal.shown', this.onShown);
scope.$on('modal.hidden', this.onHidden);
scope.$on('modal.removed', this.onRemoved);
}
addOnDestroyListener() {
this.$scope.$on('$destroy', () => {
this.removeModal();
});
}
createIsOpenWatcher() {
this.isOpenWatcher = this.$scope.$watch(() => this.isOpen, () => {
if (this.isOpen) {
this.modal.show();
} else {
this.modal.hide();
}
});
}
emitOnSetupEvent(scope) {
this.onSetup({
$scope: scope,
$removeModal: this.removeModal.bind(this)
});
}
createModalAndAppendClone({
scope = this.$rootScope.$new(true),
animation = 'slide-in-up',
focusFirstInput = false,
backdropClickToClose = true,
hardwareBackButtonClose = true
}, clone) {
let options = {
scope,
animation,
focusFirstInput,
backdropClickToClose,
hardwareBackButtonClose
}
this.modal = this.$ionicModal.fromTemplate('<ion-modal-view></ion-modal-view>', options);
let $modalEl = angular.element(this.modal.modalEl);
$modalEl.append(clone);
return this.modal;
}
removeModal() {
this.modal.remove();
this.isOpenWatcher();
}
}
function modal() {
return {
restrict: 'E',
transclude: true,
scope: {
'onShown': '&',
'onHidden': '&',
'onRemoved': '&',
'onSetup': '&',
'isOpen': '=',
'animation': '#',
'focusFirstInput': '=',
'backdropClickToClose': '=',
'hardwareBackButtonClose': '='
},
controller: Modal,
bindToController: true,
controllerAs: 'vm'
}
}
angular
.module('flight')
.directive('modal', modal);
})();
And then you can use it like this:
<modal is-open="vm.isOpen" on-shown="vm.onShown()" on-hidden="vm.onHidden()" on-removed="vm.onRemoved()" on-setup="vm.onSetup($scope, $removeModal)">
<div class="bar bar-header bar-clear">
<div class="button-header">
<button class="button button-positive button-clear button-icon ion-close-round button-header icon" ng-click="vm.closeModal()"></button>
</div>
</div>
<ion-content class="has-header">
<create-flight-form on-submit="vm.submit()"></create-flight-form>
</ion-content>
</modal>
You open and close the modal with a boolean value bind to is-open and then register callbacks for the different events.