I am quite new at SAPUI5 and JS. There is something I do not understand well concerning the module definition and usage. Here is my context:
I want to create a Component my.test.comp that uses an external module object my.test.comp.Service.
So following the best practices, I have the following code:
Service.js:
sap.ui.define([
"sap/ui/base/Object"
], function(BaseObject) {
"use strict";
var modulePath = jQuery.sap.getModulePath("my.test.comp");
var SERVICE_ROOT_PATH = modulePath.lastIndexOf("/") > 0
? modulePath.substring(0, modulePath.lastIndexOf("/"))
: "/test";
var Service = BaseObject.extend("my.test.comp.Service", {
getServiceRootPath: function () {
return SERVICE_ROOT_PATH;
}
});
return Service;
});
And I use this in the Component.js:
sap.ui.define([
"sap/ui/core/Component",
"./Service"
], function(Component, Service) {
"use strict";
return Component.extend("my.test.comp.Component", {
init: function() {
var serviceRootPath = Service.getServiceRootPath();
jQuery.sap.log.error("ServicePathInfo : " + serviceRootPath);
}
});
});
When I run this, I get an error saying that getServiceRootPath is undefined, and throws an error.
So I changed the Service.js as follow:
sap.ui.define([
"sap/ui/base/Object"
], function(BaseObject) {
"use strict";
var modulePath = jQuery.sap.getModulePath("my.test.comp");
var SERVICE_ROOT_PATH = modulePath.lastIndexOf("/") > 0
? modulePath.substring(0, modulePath.lastIndexOf("/"))
: "/test";
var Service = BaseObject.extend("my.test.comp.Service");
Service.getServiceRootPath = function () {
return SERVICE_ROOT_PATH;
};
return Service;
});
And now it is working well. I do not understand what are the differences.
Could someone explain me why?
In JS, there are no classes. There are either plain objects ({}) or functions with constructor that can be called with new.
Accordingly, calling .extend("...") in UI5 returns a function, again, with its constructor as any other functions, ready to be used with new. Your module members (methods, properties, etc.) will be added to the prototype and not to the parent function (Service) itself.
BaseObject.extend("my.test.comp.Service", {
// methods in prototype ...
});
The required Service module (i.e. function) consists of only a constructor function (Service.constructor) and a prototype object (Service.prototype). That is why Service.getServiceRootPath was undefined in your first case. You'll need to call the constructor function with new first:
return Component.extend("my.test.comp.Component", {
init: function() {
const service1 = new Service(); /* returns an object with ..
* __proto__: {
* getServiceRootPath: f ()
* }
*/
const serviceRootPath = service1.getServiceRootPath();
// ...
},
});
(You could also directly access the method with Service.prototype.getServiceRootPath without new)
This also explains why Service.getServiceRootPath worked in the second case. You can add pretty much anything you like to an existing function since functions are ultimately objects too 🙂
It looks like your intention was not to create multiple "services" but a simple object with methods in it. In that case, just return a simple object with methods in your module definition.
sap.ui.define([
// Without "sap/ui/base/Object"
], function() {
"use strict";
//...
return {
getServiceRootPath: function () {
// ...
},
};
});
sap.ui.define([
"sap/ui/core/Component",
"./Service",
], function(Component, Service) {
"use strict";
return Component.extend("my.test.comp.Component", {
init: function() {
const serviceRootPath = Service.getServiceRootPath();
// ...
},
});
});
This will work too.
In your component.js you should import
"my/test/comp/Service"
instead of
"./Service"
Related
I have an issue with overriding a javascript function. I am working with Odoo POS and I want to override one of the POS JavaScript Function.
In Odoo point_of_sale -> models.js there is a function called set_quantity_by_lot. but it written extending Backbone.Collection. I can extend functions which are belongs to Backbone.Model but not the functions in Backbone.Collection.
This is a code of the function. (I want to extend the set_quantity_by_lot function):
var PacklotlineCollection = Backbone.Collection.extend({
model: exports.Packlotline,
initialize: function(models, options) {
this.order_line = options.order_line;
},
set_quantity_by_lot: function() {
if (this.order_line.product.tracking == 'serial') {
var valid_lots = this.get_valid_lots();
this.order_line.set_quantity(valid_lots.length);
}
}
});
Thank You,
That class is a private variable in module 'point_of_sale.models' if you check the return statement of that module its return exports;
Export contains only this classes that you can override one there methods:
exports = {
PosModel: PosModel,
NumpadState: NumpadState,
load_fields: load_fields,
load_models: load_models,
Orderline: Orderline,
Order: Order,
};
This means you cannot override it or access it. What you need to do Is define a new class PacklotlineCollection
like in the original module and change the code of the method, override the only method that uses it witch is set_product_lot of class Orderline that is returned by the 'point_of_sale.models' module.
odoo.define('techinca_name.models', function (require) {
"use strict";
// to access the classes
var posModels = require('point_of_sale.models')
// require any thing is used in the code too if there is
// define a similar class with a little changes
var PacklotlineCollection = Backbone.Collection.extend({
// change this part
model: posModels.Packlotline,
initialize: function(models, options) {
this.order_line = options.order_line;
},
get_empty_model: function(){
return this.findWhere({'lot_name': null});
},
remove_empty_model: function(){
this.remove(this.where({'lot_name': null}));
},
get_valid_lots: function(){
return this.filter(function(model){
return model.get('lot_name');
});
},
set_quantity_by_lot: function() {
// and this part
if (this.order_line.product.tracking == 'serial') {
var valid_lots = this.get_valid_lots();
this.order_line.set_quantity(valid_lots.length);
}
}
});
// override set_product_lot to use your class not the orignal class
posModels.Orderline.inculde({
// same code the only difference here it will use your own class
set_product_lot: function(product){
this.has_product_lot = product.tracking !== 'none' && this.pos.config.use_existing_lots;
this.pack_lot_lines = this.has_product_lot && new PacklotlineCollection(null, {'order_line': this});
},
});
});
Note: to override a method of class use ClassName.include, extends just create a new class that inherit this ClassName.
All I am trying to do is to export two functions from a module. One function taking an argument and the other with no argument:
function initClass(params)
{
return new Promise( (resolve, reject) => {
if (!wallet) {
wallet = new WalletClient(params);
resolve(wallet);
} else {
console.log('Wallet is initialized');
resolve(wallet);
}
});
}
function getInstance()
{
return wallet;
}
For initClass(params) only, I can do this as:
module.exports = (params) => {
initClass(params)
}
And then I call this as:
var init = require('./class.js')(params).initClass(params);
This works fine.
Now, for me to export getInstance() as well, I have tried to do the following but it doesn't seem to work.
module.exports = (params) => {
initClass(params),
getInstance
}
This complaints that there is no function getInstance.
Then I tried this:
module.exports.init = (params) => {
initClass(params)
}
module.exports.instance = {
getInstance
}
Then call them as:
var init = require('./class.js').init(params).initClass(params);
What is the proper way to export multiple functions like this?
Thank you.
You're making it more complex than needed. Once you have your functions defined, you can export it with this:
module.exports = {
initClass,
getInstance
}
To use it, you do this:
const init = require("./class.js");
init.initClass(params);
const instance = init.getInstance();
What you're exporting from the module is an object (which I've named init in the example above) that contains two functions. You don't have to pass arguments to the functions at the time you require.
module.exports is basically an object with keys which can refer to any variables/functions of your file.In your case,
module.exports.initClass = function (params){
...
}
module.exports.getInstance = function (){
}
When importing it
var init = require('./class.') // init object has two keys -> initClass and getInstance
init.initClass('abc')
init.getInstance()
If i'm understanding correctly you are trying to export multiple methods if that is the case simple use this.
module.exports = {
method: function() {},
otherMethod: function(parmas) {}
}
In your code use like this.
var init = require('./class.js');
init.method()
init.otherMethond(paramObj)
If you want below scenario you need to check out about method chaining.
var init = require('./class.js').init(params).initClass(params);
I have my javascript code as follow:
$(document).ready(function () {
//call of global functions
globalFunction1();
globalFunction2(); //create a new object inside
globalFunction3();
}
function globalFunction1() {
// do something directly with jquery selectors
var testObj1 = new object1($('#tree')); // this is called later in the function
testObj.doSomething();
}
function globalFunction2() {
// do other things
}
function globalFunction3() {
// do something directly with jquery selectors
}
//creating an object in js
var object1 = (function () {
var tree;
function object1($tree) {
tree = $tree;
});
}
object1.prototype.doSomething = function () {
.....
};
return fancyStructure;
})();
Normally I have more global functions and if possible I always try to create objects using the new keyword (as in Java or C#)
Now, I am asked to provide namespacing in order to avoid function conflict problems. Thing is I am not sure how to achieve that giving my current code and knowing that I need to keep the code Object Oriented.
Hence, I am wondering if there is a way to add some namespacing effisciently. Any suggestion will do as long as it is along the lines of adding a namespace.
Just put your functions into an Object:
var mynamespace = {
globalFunction1 : function() {
// do something directly with jquery selectors
var testObj1 = new object1($('#tree')); // this is called later in the function
testObj.doSomething();
},
globalFunction2 : function() {
// do other things
},
globalFunction3 : function() {
// do something directly with jquery selectors
}
}
and call the functions with
mynamespace.globalFunction1();
Or you could just define your namespace
mynamespace = {};
And later add the the functions with
mynamespace.globalFunction1 = function() {
//do something
};
Use objects as containers for your functions. This is the standard approach of code structuring in JS.
var namespace1 = {
func1: function() {},
func2: function() {},
}
var namespace2 = {
func1: function() {},
func2: function() {},
}
namespace1.func2();
You can store your OOP code in this namespaces:
var namespace3 = {
someObj: function() {},
create: function() { return new this.someObj(); },
}
namespace3.someObj.prototype = {
count: 15,
someFunc() {}
}
And you can easily extend them:
namespace3.anotherObj = function () {}
Edit
Regarding your example:
var fancyStructureWrapped = (function () {
var tree;
function fancyStructure($tree) {
tree = $tree;
});
fancyStructure.prototype.doSomething = function () {
.....
};
return fancyStructure;
})();
// add it to some namespace
someNamespace.fancyStructure = fancyStructureWrapped;
//create an instance
var fs = new someNamespace.fancyStructure();
//and use it
fs.doSomething();
If you're looking for a general approach to managing a growing JavaScript codebase, check out RequireJS and/or Browserify. Both are libraries that allow dividing your code up into modular bits (ie. AMD or CommonJS modules) and then referencing/importing between them. They include tooling for bundling these files into a single JS file when it's time to deploy a production build too.
Spring has very useful option, that whey I define a bean, I define a scope. If it's singleton, only one instance is created. By prototype, each time a bean is required, a new instance is created.
RequireJS provides by default singletons, so with such simple module:
Singleton.js
define([], function() {
console.log('Instance initialization')
var items = []
var singleton = {
getItems: function() {
return items
},
setItems: function(newItems) {
items = newItems
},
addItem: function(item) {
items.push(item)
}
};
return singleton;
})
and the usage:
require(["showcase/modules/Singleton"], function(Singleton){
Singleton.addItem('item1')
console.log(Singleton.getItems())
})
require(["showcase/modules/Singleton"], function(Singleton){
Singleton.addItem('item2')
console.log(Singleton.getItems())
})
the output will be:
Instance initialization
["item1"]
["item1", "item2"]
Is it possible to define and use the module in such way, that I could switch in the module definition, if I want to use prototype or singleton scope? So in my case, without changing the usage, I'd get:
Instance initialization
["item1"]
Instance initialization
["item2"]
I'm using RequireJS from Dojo, just in case of syntax differences
Well, first of all the problem is that when you import a module using an AMD loader, you will actually get an instance, but the second time you import the same module, the same instance is actually returned (problem 1).
To overcome this problem you should use the factory design pattern to get your instance and also translate your singleton object to a class that can be instantiated (problem 2). Your factory could have a method called getInstance() that accepts a boolean parameter that can toggle between singleton/prototype.
So without changing your usage you won't be able to do this because of the problems I just addressed. The best solution I can come up with (with a factory) is:
Singleton.js
define([], function() {
console.log('Instance initialization');
// Singleton class
var Singleton = function() {
this.items = [];
this.getItems = function() {
return this.items;
};
this.setItems = function(newItems) {
this.items = newItems;
};
this.addItem = function(item) {
this.items.push(item);
}
};
// Factory
var factory = {
singletonInstance: new Singleton(),
getInstance: function(/** Boolean */ isSingleton) {
if (isSingleton === true) {
return this.singletonInstance;
} else {
return new Singleton();
}
}
};
return factory;
});
Usage (singleton)
require(["app/Singleton"], function(singletonFactory){
var Singleton = singletonFactory.getInstance(true);
Singleton.addItem('item1');
console.log(Singleton.getItems());
});
require(["app/Singleton"], function(singletonFactory){
var Singleton = singletonFactory.getInstance(true);
Singleton.addItem('item2');
console.log(Singleton.getItems());
});
Usage (multiple instances)
require(["app/Singleton"], function(singletonFactory){
var Singleton = singletonFactory.getInstance(false);
Singleton.addItem('item3');
console.log(Singleton.getItems());
});
require(["app/Singleton"], function(singletonFactory){
var Singleton = singletonFactory.getInstance(false);
Singleton.addItem('item4');
console.log(Singleton.getItems());
});
In case you're interested in a full example, it's on Plunker.
Eventually you could wrap the factory as a plugin so that you could actually do something like:
require([ "app/Object!singleton", "app/Object!prototype" ], function() {
});
However I don't know if RequireJS also supports this (and if I'm understanding well it should be a generic story for both AMD loaders).
Having a project where I have speed coded up some core functionality I would like to split it up into modules. My struggle now is how to combine prototypes from one to the next. My idea is something like this:
(function (window) {
/* Code for base module with core functions. */
function CORE () {
}
window.CORE = CORE; /* I use different naming in real code ... */
})(window);
(function (CORE) {
/* Code for module with extending functionality. */
function MODULE1 () {
}
CORE.MODULE1 = MODULE1;
})(window.CORE);
I use an approach for creation as something like:
(function (window) {
var Core = function (options) {
return new Core.prototype.init(options);
}
Core.prototype = {
init : function (options) {
this.a = options.a;
return this;
}
}
Core.prototype.init.prototype = Core.prototype;
Core.prototype.init.prototype.fun1 = function () { }
Core.prototype.init.prototype.fun2 = function () { }
...
window.Core = Core; /* Optionally = Core.init */
})(window);
And then a module like:
(function (Core) {
var Module1 = Core.Module1 = function (options) {
return new Module1.prototype.build(options);
}
Module1.prototype = {
build : function (options) {
this.a = options.a;
return this;
}
}
Module1.prototype.build.prototype = Module1.prototype;
Module1.prototype.build.prototype.fun1 = function () { }
Module1.prototype.build.prototype.fun2 = function () { }
...
Core.Module1 = Module1;
Core.Module1_XO = Module1.prototype.build;
})(window.Core);
Now a print of toString() of Core, Core.Module1 and Core.Module1_XO all yield their respective code. But there is no binding as in:
If I say:
var obj = Core({...}); , OK.
obj.Module1({...}), Fail. Object #<Object> has no method Module1
new obj.Module1_XO({...}), Fail. undefined is not a function
Core.Module1({...}), OK, but looses prototypes from Core.
new Core.Module1_XO({...}), OK, but looses prototypes from Core.
...
One way that seem to work is by updating Core by a bind function as in:
var obj = Core({...});
var mod1 = Core.Module1({...}, obj); <-- pass obj
// In Module1 constructor:
build : function (options, ref) {
this.a = options.a;
ref.bind("Module1", this);
}
// And In Core:
Core.prototype.bind(which, what) {
this[which] = what;
}
Question is how I can update Core with Module without this hack. Why doesn't Core become updated by:
window.Core.Module1 = Module1;
Is it hidden from Core?
I have also tried to bind in outer scope of module as in:
(function (Core) {
/* ... code ... for Mudule1 */
Core.bind("Module1", Module1);
}(window.Core);
But this fails as well. Core does not get updated with methods from Module.
Here is a scramble of a fiddle I have messed with, (Note that the printed text is in reverse (prepended) not appended such as newest on top.). It is not the most tidy code, and It is in midts of edits. (I try new approaches frequently.)
What you're doing right now is problematic for several reasons:
You're adding a module to the constructor (type) and not the objects.
Global state here - everyone gets one Module1.
My suggestion would be using a generic version of the builder pattern (or even a mediator).
Here is what it might look like.
Core = (function Core(){
var modules = [];
return {
setModule : function(name,value){
modules.push({name:name,value:value});
},
build : function(options){
this.a = options.a;
// now let's add all modules
modules.forEach(function(module){
this[module.name] = new module.value();
});
}
};
});
Usage would be something like:
var app = new Core.build({a:"foo"});
app.a;//"foo"
If you want to add a module it'd be something like
function Module1(){
this.name = "Zimbalabim";
}
Core.setModule("Module1",Module1);
var app = new Core.build({a:"Bar"});
app.Module1.name;//"Zimbalabim"
app.a;//"Bar"
Or course, a more generic architecture would allow creating different apps with different architectures (with dependency injection containers probably) but let's not go that far yet :)