Set the value of a global variable in a function - javascript

I'm currently working on an SAP Fiori app that consumes an OData service.
I created a method in my controller fonct that calculates a variable coming from my OData.
I want to capture this value and put it in a global variable every time the view is refreshed.
I created a global variable like this:
var boo1;
return Controller.extend("com.controller.Detail", {...});
and I passed boo1 as a parameter in my method fonct inside my onInit method but it is undefined.
Here's a snippet of my controller's code:
sap.ui.define([
"com/util/Controller"
], function(Controller) {
"use strict";
var boo1;
return Controller.extend("com.controller.Detail", {
onInit: function() {
this.fonct(boo1);
alert(boo1);
},
fonct: function(ovar) {
var that = this;
var oModel = that.getView().getModel();
oModel.read("/alertSet", {
success: function(data) {
var a = JSON.stringify(data);
var b = a.slice(332,-4);
ovar = b;
}
});
}
});
});

I think that what you want to do is simplier that what you are doing.
To save a global variable, get the Core object and set the variable as a new property of this object:
sap.ui.getCore().myGlobalVar = myCalculatedValue;
Then to use it in other view, get the property directly from the Core:
var mySavedVar = sap.ui.getCore().myGlobalVar
Then use the Router routeMatched event to handle your navigation and refresh the value.
Here a snippet: https://jsbin.com/bewigusopo/edit?html,output
<!DOCTYPE html>
<html><head>
<meta http-equiv='X-UA-Compatible' content='IE=edge' >
<meta charset="UTF-8" >
<title>test</title>
<script id='sap-ui-bootstrap'
src='https://sapui5.hana.ondemand.com/1.38.5/resources/sap-ui-core.js'
data-sap-ui-theme='sap_bluecrystal'
data-sap-ui-bindingSyntax="complex"></script>
<script id="view1" type="sapui5/xmlview">
<mvc:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc">
<core:ComponentContainer name='my.comp'/>
</mvc:View>
</script>
<script id="home" type="sapui5/xmlview">
<mvc:View xmlns="sap.m" xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc"
controllerName="my.controller1">
<Page>
<Input id="input" placeholder="Write a text to save it globally"/>
<Button text="Navigate to other view" press="onNavigate"/>
</Page>
</mvc:View>
</script>
<script id="add" type="sapui5/xmlview">
<mvc:View xmlns="sap.m" xmlns:f="sap.ui.layout.form" xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc"
controllerName="my.controller2">
<Page id="page" showNavButton="true" navButtonPress="onBack">
<HBox class="sapUiLargeMarginBegin">
<Label text="The global variable is:" class="sapUiSmallMarginEnd sapUiSmallMarginTop"/>
<Input id="inputResult"/>
</HBox>
</Page>
</mvc:View>
</script>
<script>
// jQuery.sap.declare("my.comp.Component");
sap.ui.define("my/comp/Component", ["sap/ui/core/UIComponent"], function(UIComponent) {
return UIComponent.extend("my.comp.Component", {
metadata : {
name : "GreatComponent",
version : "1.0",
includes : [],
dependencies : {
libs : ["sap.m"]
},
routing: {
config: {
routerClass: "sap.m.routing.Router",
viewType: "XML",
viewPath: "my",
controlId: "app",
transition: "slide",
controlAggregation: "pages"
},
routes: [
{
name: "home",
pattern: "",
target: "home"
},
{
name: "add",
pattern: "add",
target: "add"
}
],
targets: {
home: {
viewName: "Home",
title: "home"
},
add: {
viewName: "Add",
title: "add"
}
}
}
},
init: function() {
sap.ui.core.UIComponent.prototype.init.apply(this, arguments);
var oRouter = this.getRouter();
var oViews = oRouter.getViews();
this.runAsOwner(function() {
var myHome = sap.ui.xmlview({viewContent:jQuery('#home').html()});
oViews.setView("my.Home", myHome);
var myAdd = sap.ui.xmlview({viewContent:jQuery('#add').html()});
oViews.setView("my.Add", myAdd);
});
oRouter.initialize();
},
createContent : function() {
var componentData = this.getComponentData();
return new sap.m.App("app", {
});
}
});
});
sap.ui.define("my/controller1", [
"sap/ui/core/UIComponent"
],function(UIComponent) {
return sap.ui.controller("my.controller1", {
onInit: function() {
this.oRouter = UIComponent.getRouterFor(this.getView());
},
onNavigate: function() {
var sInputText = this.getView().byId("input").getValue();
sap.ui.getCore().myGlobalVar = sInputText;
console.log(sap.ui.getCore().myGlobalVar)
this.oRouter.navTo("add");
}
});
});
sap.ui.define("my/controller2", [
"sap/ui/core/UIComponent"
],function(UIComponent) {
return sap.ui.controller("my.controller2", {
onInit: function() {
this.oRouter = UIComponent.getRouterFor(this.getView());
this.oRouter.getRoute("add").attachPatternMatched(this._onObjectMatched, this);
},
_onObjectMatched: function(){
var sGlobalVariable = sap.ui.getCore().myGlobalVar;
console.log(sGlobalVariable);
this.getView().byId("inputResult").setValue(sGlobalVariable);
},
onBack: function(){
this.oRouter.navTo("home");
}
});
});
sap.ui.require(["my/comp/Component"], function(myComp) {
// instantiate the View
sap.ui.xmlview({viewContent:jQuery('#view1').html()}).placeAt('content');
});
</script>
</head>
<body class='sapUiBody'>
<div id='content'></div>
</body>
</html>
Other possibility is to set a global model, which will simplyfy your binding very much. Just create it and set it into the Core
//To set it
var oGlobalModel = new sap.ui.model.json.JSONModel();
sap.ui.getCore().setModel(oGlobalModel, "myGlobalModelID");
//To get it
var oMyGlobalModel = sap.ui.getCore().getModel("myGlobalModelID");

Please, avoid defining global variables (such as before sap.ui.define) especially if you are working on apps that are going to be placed into the Fiori Launchpad. This approach is considered harmful. This is also repeated in the documentation:
In control and class modules, you should not use global variables at all.(src)
We're using "use strict" purposely to disallow such practices.
Also, defining properties or adding models on sap.ui.getCore() is an anti-pattern for Fiori apps. Not only does this lead to the same flaws as defining global variables but models set on the core are not propagated to the views by default because components are meant to be used modularly. Therefore, defining such data to the corresponding component instead of the core is the way to go for Fiori apps.
==> FLP Best Practices
I created a global variable
var boo1;
return Controller.extend("com.controller.Detail", {...});
What you created though was not a global variable since boo1 was declared inside an anonymous function. Instead, boo1 was stored in a closure which allowed the prototype methods (onInit and fonc) to maintain the lexical environment within which boo1 was made accessible not only by the methods but also by other instances of com.controller.Detail. In terms of Java, we can say that boo1 is a private static variable. But it's not a global variable.
That being said..
I passed boo1 as a parameter in my method fonct inside my onInit method but it is undefined
The closure makes passing boo1 as an argument unnecessary (unless you want a hard copy of boo1 to maintain multiple states of it). The method fonct can access boo1 directly, even in the anonymous callbacks defined in fonct.
Now, you may be asking why boo1 was undefined. There are two reasons:
boo1 was undefined before calling fonct. If boo1 were an object instead of undefined, then ovar would have been a reference to the boo1 object and the change to any ovar property would have took place in the boo1 object. But undefined is not an object. So ovar doesn't know what boo1 is at all. Note: The evaluation strategy of JS is "Call by object-sharing".
alert(boo1) gets fired right after calling fonct. Unless oModel works synchronously (which I strongly recommend not to do so), the browser doesn't wait for the success callback to get fired in order to invoke alert(boo1) later. The alert is fired immediately, and then the success callback where boo1 should be manipulated.
Removing ovar and using boo1 instead updates boo1 properly in the success callback.
fonct: function(/*no ovar*/) {
//...
oModel.read("/alertSet", {
success: function(data) {
//...
boo1 = /*new value*/;
alert(boo1);
}.bind(this)
});
}

You should declare the global variable at global scope (i.e., before sap.ui.define).

Related

Backbone JS Button to open a new view, save values in form

Im new to backbone and I'm looking to a very simple 2 view configuration page usig backbone.
I have the following code;
define(
["backbone","...","..."],
function(Backbone, ... , ... ) {
var PopupView = Backbone.View.extend({
initialize: function initialize() {
Backbone.View.prototype.initialize.apply(this,arguments);
},
events: {
"click .save_conf_button": "save_conf",
},
render: function() {
this.el.innerHTML = this.get_popup_template();
return this;
},
save:conf: function save_conf() {
//get the field values from popup_template
//var items = jquery(....);
});
var ExampleView = Backbone.View.extend({
//Starting view
initialize: function initialize() {
Backbone.View.prototype.initialize.apply(this, arguments);
},
events: {
"click .setup_button": "trigger_setup", //Triggers final setup
"click .create_conf_button": "trigger_popup_setup", //This is the conf popup
},
render: function() {
this.el.innerHTML = this.get_start_html();
return this;
},
trigger_popup_setup: function trigger_popup_setup() {
console.log("Pop up");
//this.el.innerHTML = this.get_popup_template();
PopupView.render();
...
},
}); //End of exampleView
return ExampleView;
} // end of require asynch
); // end of require
E.g. The ExampleView is the starting view with a couple of fields and 2 buttons; create popup and save. Upon pressing the create_conf_button I want to render the popup view, however this does not seem to work as I expected. (Uncaught TypeError: PopupView.render is not a function)
I'm not sure how to proceed and additionally what the "best practice" is for generating these types of dialogs?
Additionally, keeping the values filled in on the previous page after returning from the popupview would be preferential.
Thanks for any help
try
new PopupView.render()
you have to create an instance to call the methods this way
#ashish is correct, you have to instantiate an instance of the PopupView before calling its render method. Currently, you have defined a blueprint for a view called PopupView, which will act as a constructor for newly created PopupView view instances. In order to use this defined view I would suggest storing it in ExampleView's render or initialize method:
// Example View's initialize method
initialize: function initialize() {
this.popUpView = new PopupView();
Backbone.View.prototype.initialize.apply(this, arguments);
},
then referencing it in your trigger_popup_setup function as follows:
trigger_popup_setup: function trigger_popup_setup() {
console.log("Pop up");
//this.el.innerHTML = this.get_popup_template();
this.popUpView.render();
...
},
As for storing state Backbone models are used for that :)
In general to nest subviews within a master view in Backbone you can do the following:
initialize : function () {
//...
},
render : function () {
this.$el.empty();
this.innerView1 = new Subview({options});
this.innerView2 = new Subview({options});
this.$('.inner-view-container')
.append(this.innerView1.el)
.append(this.innerView2.el);
}
In this example the master view is creating instances of it's subviews within its render method and attaching them to a corresponding DOM element.

How to listen to the events inside an object?

I am trying to listen to a click event and give the output accordingly.
However, I am a bit confuse on adding events inside an object.
I tried this.
var object1 = {
getVariables: {
button1: document.getElementById('button');
},
eventHandler: {
getVariables.button1.addEventListener('click', this.alertSomething);
},
alertSomething: function() {
alert('Cool');
}
};
Is this a correct way to listen to the events? If not, please help me correct it.
You'll have to save the reference to the variable somewhere in the object - and for the object to reference itself to achieve that, it'll have to use this somehow. (well, you could also pass in the whole object as a separate argument, but that's kinda odd and not often done)
It's somewhat tricky because when calling object1.getVariables.button1, the context of this is the object referenced by the getVariables property, but you likely want to put the information into somewhere more appropriate within object1 itself, not within getVariables. Let's store the variables in a storedVariables property.
We want the functions to be called with a reference to the outer object, not the getVariables or addEventHandler property, so we have to use call to pass a custom this to the functions:
const button = document.getElementById('button');
const object1 = {
storedVariables: {},
getVariables: {
button1: function() { this.storedVariables.button1 = document.getElementById('button') },
},
addEventHandler: {
button1: function() { this.storedVariables.button1.addEventListener('click', this.alertSomething); },
},
alertSomething: function() {
alert('Cool');
}
};
object1.getVariables.button1.call(object1);
object1.addEventHandler.button1.call(object1);
<div id="button">
some button
</div>
It would be notably less convoluted if there was a method such as getButton1 directly on object1 (and the same for addEventHandler), that way they could be called normally without having to customize their this.
Just bind object1 object to this variable.
<!DOCTYPE html>
<html>
<body>
<div id="button1">
Button-1
</div>
<script>
var object1 = {
button1: document.getElementById('button1'),
eventHandler: function() {
this.button1.addEventListener('click', this.alertSomething);
},
alertSomething: function() {
alert('Cool');
}
};
object1.eventHandler.call(object1);
</script>
</body>
</html>

Durandal widgets inner function scope and context

I have durandal widget and two syncfusion buttons:
view.html
<div class="group-btn-container">
<input type="checkbox" data-bind="ejToggleButton: toggleButtonNo" />
<input type="checkbox" data-bind="ejToggleButton: toggleButtonYes" />
</div>
viewmodel.js
define(['knockout', 'jquery','web/ej.togglebutton.min', 'common/ej.widget.ko.min'], function (ko, $) {
var ctor = function () { };
ctor.prototype.activate = function (settings) {
self = this;
var toggleBtnYes = {
toggleState: ko.observable(false),
click: function (args) {
self.toggleButtonNo.buttonOptions.toggleState(false);
args.model.toggleState(true);
}
}
self.toggleButtonYes = toggleBtnYes;
var toggleBtnNo = {
toggleState: ko.observable(true),
click: function (args) {
self.toggleButtonYes.buttonOptions.toggleState(false);
args.model.toggleState(true);
}
}
self.toggleButtonNo = toggleBtnNo;
};
return ctor;
});
Two buttons 'Yes' and 'No', should work like radio buttons, when one is turned on, another should be turned off.
Q1: I have a problem with viewodel context or scope.
Inside click function I do not see 'self', actually self is always some another widget because in one loop I render couple widgets, I do not know why self is not my function ctor ?
Q2: Also when I want to approach to object 'toggleBtnNo' or 'toggleBtnYes' directly from click function, they are not defined. Why I cannot see the objects from global function ctor in my inner, 'click' function?
I just do not know how to approach to the button (on one or other way) from my click function how I can change it state ?
From the code snippet you have provided, it seems you are trying to update the toggleButton’s state by assigning values to the model. With this, the model values is just assigned and is not reflected in ToggleButton control.
Rather, for the ToggleButton control to be updated, you need to update the value in the observable variable (self.toggleButtonNo.toggleState) in the module and call ko.applyBindings method, so that the model values will be updated the ToggleButton control.
Please refer the code snippet provided below.
HTML
<input id="yes" type="checkbox" data-bind="ejToggleButton: {toggleState: toggleButtonYes.toggleState , defaultText: settings.defaultText, activeText: settings.activeText, click: toggleButtonYes.click}" />
<input id="no" type="checkbox" data-bind="ejToggleButton: {toggleState: toggleButtonNo.toggleState, defaultText: settings.defaultText, activeText: settings.activeText, click: toggleButtonNo.click}" />
Script
define('mo1', ['knockout', 'jquery'], function (ko, $) {
return function () {
self = this;
self.settings = {
defaultText: "No",
activeText: "Yes",
};
var toggleBtnYes = {
toggleState: ko.observable(true),
click: function (args) {
self.toggleButtonNo.toggleState(false);
self.toggleButtonYes.toggleState(true);
ko.applyBindings(self);
}
}
self.toggleButtonYes = toggleBtnYes;
var toggleBtnNo = {
toggleState: ko.observable(false),
click: function (args) {
self.toggleButtonYes.toggleState(false);
self.toggleButtonNo.toggleState(true);
ko.applyBindings(self);
}
}
self.toggleButtonNo = toggleBtnNo;
};
});
This was the worst mistake ever. I was looking at that million times and did not notice. I am ashamed.
I did not put 'var' in front of self.
self = this;
should be:
var self = this;
It caused that self goes to the global scope and every time some other widget overwrites value of self.

Handling "this" with RequireJS and JQuery onclick events

I am updating an app to use RequireJS and move functions/variables out of the global context. I am not sure how to structure things when using JQuery click handlers so that my functions have access to the data-* attributes on the HTML object that triggered the click AND have access to properties inside of my module.
Below is a simple test case I put together to demonstrate what I am trying to do.
I want a function called on the click event for the div that is able to access both the data-value attribute on the div that triggered it and the packageVar property inside the module I'm loading with RequireJS.
Here is my index.html:
<!DOCTYPE html>
<head>
<title>Test.Next</title>
</head>
<body>
<script data-main="js/test-require" src="js/require.js"></script>
<div id="test-no-bind" data-value="value">Test No Bind</div>
<div id="test-bind" data-value="value">Test Bind</div>
</body>
</html>
here is the test-require.js:
requirejs.config({
"baseUrl": 'js',
"paths": {
"jquery": "https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min",
"domReady": 'vendor/domReady'
}
});
requirejs(['maintest','domReady!'], function(main) {
main.initialize();
});
And finally my maintest.js:
define(['jquery','domReady!'], function($) {
return {
'packageVar' : 'test',
buttonClick : function() {
console.log($(this).attr("data-value"));
console.log(this.packageVar);
},
initialize: function() {
$("#test-no-bind").click(this.buttonClick);
$("#test-bind").click(this.buttonClick.bind(this));
}
};
});
The test-no-bind div results in:
value
undefined
The test-bind div results in:
undefined
test
You're trying to access a properties from two different objects using the same context this. So in the no-bind case you're getting correctly the div element value for data-value attribute because inside jquery click function the this context it's the element where the event is produced, however you're getting and undefined for the other object property because the element doesn't have the packageVar property. For bind case where you are using bind function you're passing your object as this context so you've a value for packageVar but not for element attribute.
Try using the correct context to call buttonClick and passing the element context to the function in order to get each value correctly from his source:
define(['jquery','domReady!'], function($) {
return {
'packageVar' : 'test',
buttonClick : function(element) {
console.log($(element).attr("data-value"));
console.log(this.packageVar);
},
initialize: function() {
var that = this;
$("#test-no-bind").click(function() {
that.buttonClick(this);
});
$("#test-bind").click(function(){
that.buttonClick(this);
});
}
};
});
Try with this JSFIDDLE
Hope this helps,

JQuery can't find variable from separate javascript file

I am using the cakephp framework and I created 2 separate javascript files and placed them into my webroot/js folder. The first javascript file contains modal dialog variables that contain the settings for the dialog boxes. The second javascript file contains other click event handlers that post data to an action and then open up the dialog.
The problem I am having is that the second file calls a variable from the first file using
$variablename and I get an error saying varaibleName is not defined.
Some code is below to show you what I mean.
From the first file:
var $editSel = $("#editSel_dialog").dialog(
{
autoOpen: false,
height: 530,
width: 800,
resizable: true,
modal: true,
buttons:
{
"Cancel": function()
{
$(this).dialog("close");
}
}
});
From the second file:
$('.neweditSel_dialog').live('click', function()
{
$.ajaxSetup({ async: false });
var selected = [];
$("#[id*=LocalClocks]").each(function()
{
if(false != $(this).is(':checked'))
{
var string = $(this).attr('id').replace('LocalClocks', '');
string = string.substring(10);
selected.push(string);
}
});
if(0 === selected.length)
{
$selError.dialog('open');
$selError.text('No Local Clocks Were Selected')
}
else
{
$.post('/LocalClocks/editSelected', { "data[Session][selected]": selected }, function(data)
{
});
$editSel.load($(this).attr('href'), function ()
{
$editSel.dialog('open');
});
}
return false;
});
This was working when I was using jquery-1.4.2.min.js, but I am using jquery1.7 now.
I also ended up putting the first file with all the variables inside of $(document).ready(function(){}); I tried putting the second file inside of a document.ready() function but that made no difference.
Any help would be great.
Thanks
You are dealing with an issue in scope. In javascript:
function foo() {
var greet = "hi";
}
function bar() {
console.log(greet); // will throw error
}
However:
var greet;
function foo() {
greet = "hi";
}
function bar() {
console.log(greet); // will log "hi"
}
You must define your variable in a common parent of both functions that need to access it. Unfortunately, since you do not use any modeling convention or framework, that is the window object (why are global variables bad?).
So, you must define var $whateveryouneed before and outside of both $(document).readys.
Also, keep the declaration and definition seperate. Your definition instantiates a jQuery object, so you must encapsulate it inside a $(document).ready() (use $(function() {}) instead):
var $editSel;
$(function () {
$editSel = $("#editSel_dialog").dialog(
{
autoOpen: false,
height: 530,
width: 800,
resizable: true,
modal: true,
buttons:
{
"Cancel": function()
{
$(this).dialog("close");
}
}
});
});
I don't think you can guarantee the order in which handlers will be fired, which means that the document ready may be fired in different order than you expect. Is the variable you are trying to access in the second file a global variable? Try to think about your variables scope as I would have thought this is the issue.
You cannot guarantee that one file will be loaded before the other. And you cannot guarantee that document.ready in one file will fire before the other.
Therefore, I suggest you wrap your code in functions and call them in a single document.ready handler in the order you need.
For example:
function initVariables(){
window.$editSel = ... // your code from the first file here
}
function initHandlers(){
// your code from the second file here
}
And then:
$(document).ready(function() {
initVariables();
initHandlers();
});
You'll notice that I used the global window object to expose your variable. It would be even better if you used a common namespace for them.

Categories