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.
Related
i'm trying to implement a text selection listener to display a toolbar for some custom options
<script>
export default {
name: "home",
created() {
document.onselectionchange = function() {
this.showMenu();
};
},
data() {
return {
...
};
},
methods: {
showMenu() {
console.log("show menu");
}
}
</script>
<style scoped>
</style>
but it still display that can't call showMenu of undefined, so i tried in this way:
created() {
vm = this;
document.onselectionchange = function() {
vm.showMenu();
};
},
so, nothing changed =(
i need to use this selectionchange because its the only listener that i can add that will handle desktop and mobile together, other method i should implement a touchup, touchdown and its not working for devices
Functions declared the classic way do have their own this. You can fix that by either explicitly binding this using Function.prototype.bind() or by using an ES6 arrow function (which does not have an own this, preserving the outer one).
The second problem is that if you have more than one of those components you've shown, each will re-assign (and thus, overwrite) the listener if you attach it using the assignment document.onselectionchange =. This would result in only the last select element working as you expect because it's the last one assigned.
To fix that, I suggest you use addEventListener() instead:
document.addEventListener('selectionchange', function() {
this.showMenu();
}.bind(this));
or
document.addEventListener('selectionchange', () => {
this.showMenu();
});
A third solution stores a reference to this and uses that in a closure:
const self = this;
document.addEventListener('selectionchange', function() {
self.showMenu();
});
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).
Got a very odd issue coming up here with the new components. When we had a 1.4 directive we had the following code...
(function () {
'use strict';
angular.module('app.board').directive('dcCb', dcClipboardCopy);
function dcCb() {
return {
link : function(scope, elem) {
var clipboard = new Clipboard(elem[0]);
elem.on('$destroy', function() {
clipboard.destroy();
});
}
};
}
})();
Inside the clipboard.destroy() function is the following...
Clipboard.prototype.destroy = function(){
this.listeners.destroy();
}
In 1.4 this is the same as the element so...
<button class="btn btn-sm btn-menu-outline copy-button" ...
So this worked fine as the button element seemed to have the listeners property which could be invoked.
However after the upgrade to 1.5 and now we have a component like this....
(function() {
'use strict';
angular.module('app.board').component('dcCb', {
...
controller: [ '$element','$scope',function($element,$scope) {
var self = this;
self.$postLink = postLink;
function postLink(){
var clipboard = new Clipboard($element[0]);
...
$element.on('$destroy', clipboard.destroy);
}
}]
});
})();
this (when inside the destroy function of the Clipboard) is now the controller object. So trying to call this.listeners throws an error.
First Question :
I understand that this in new components is the component scope but in 1.4 it was the button element. Surely in both the button element should be $element? Were we doing something wrong in 1.4?
Second Question :
Shouldn't var clipboard = new Clipboard($element[0]) force the context of this inside the clipboard to always be the clipboard itself (due to the new keyword)?
You're handing a function, which is arbitrarily defined on a class, off to the window and event listeners to be executed in a different context than the instance of Clipboard:
$element.on('$destroy', clipboard.destroy);
This is a fundamental concept of execution context in javascript, and I'd recommend reading up on it. But you can easily solve your current problem by simply binding the context of the function you are passing:
$element.on('$destroy', clipboard.destroy.bind(clipboard));
+function ($) {
'use strict';
var popup = {
init: function(element) {
this._active = 'products__popup--active';
this._product = $('.products__popup');
this._element = $('[data-popup-to]');
this._TIME = 500;
popup.attachEvt();
},
attachEvt: function() {
var that = this;
that._element.bind('click', popup.handlerEvt.call(that));
},
handlerEvt: function() {
console.log(this);
console.log('test');
}
};
$(window).on('load', function() {
popup.init();
});
}(jQuery);
I have this script, and is not working yet, I cant show you a working example because it is not ready, I'm organizing the code first.
And there is a problem with the attachEvt function, inside it I want to call another function of my object, this function will bind a click in the that._element, but I want pass to the handlerEvt the scope of this (the clicked element) and the that (the object), but this is not working:
that._element.bind('click', popup.handlerEvt.call(that));
I'm just passing the that scope and when the script loads, the element will be clicked without click, I want avoid this.. this is possible?
UPDATE:
Resuming:
I want be able to use the scope of the object (that) and the scope of the clicked element (this) inside the handlerEvt function, but without make the event click when the script loads.. :B
Try utilizing .bind() , with this set to that._element , that passed as parameter to handlerEvent . Note order of parameters at handlerEvent: obj: that first , evt event object second
+function ($) {
'use strict';
var popup = {
init: function(element) {
this._active = 'products__popup--active';
this._product = $('.products__popup');
this._element = $('[data-popup-to]');
this._TIME = 500;
popup.attachEvt();
},
attachEvt: function() {
var that = this;
that._element.bind('click', popup.handlerEvt.bind(that._element, that));
},
handlerEvt: function(obj, evt) {
console.log(evt, obj, this);
console.log('test');
}
};
$(window).on('load', function() {
popup.init();
});
}(jQuery);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div data-popup-to="true">click</div>
This should be simple but certainly I'm getting it wrong.
How to update ko.observable text on the click event?
I could do this using "afterkeydown" or "keypress" but not in case click event.http://knockoutjs.com/documentation/value-binding.html
<span data-bind="text: Count"></span>
<button data-bind="click: update">Update</button>
function MyViewModel() {
var self = this;
self.Count = ko.observable("0");
self.update = function() {
self.Count = ko.observable("1");
}
}
http://jsfiddle.net/EBsj5/
You should change it like a function.
self.update = function() {
self.Count("1");
}
Demo: http://jsfiddle.net/EBsj5/1/
Any basic tutorial will explain this to you, so I recommend watching a few.
When you are setting the value of a Knockout observable, you need to use parans like a function and pass in the new value.
<span data-bind="text: Count"></span>
<button data-bind="click: update">Update</button>
function MyViewModel() {
var self = this;
self.Count = ko.observable("0");
self.update = function() {
self.Count("1");
}
}
This will update the observable to "1" in this case. You don't need to call ko.observable() again because you have already created the observable, you are simply trying to 'set' the value with the setter function.