I'm trying to create a namespace for my backbone app so I can make calls globally.
Normally, I'd just do it like this:
var myNamespace = window.myNamespace || {};
myNamespace.SomeView = Backbone.View.extend(..);
Not sure how to achieve this using require js
You can do the same in your require or define calls, window's still there (if you work in browsers).
// views/app.js
define(['router', 'views/main-view'], function (router, mainView) {
var App = function () { /* main app module */ };
// do your "global" export
window.App = App;
return App;
});
// views/header-view.js
define(['views/app', 'models/user'], function (App, User) {
// your header view code here
// note that you have access to `App` already in a closure
// but you can still talk to it by doing
globalAppReference = window.App;
globalAppReference === App; // true
});
The question is why would you need it? Ideally all your modules would be defined with requireJS, so you don't need to refer to them through global object.
Related
I found many questions about making a variable or function global in Javascript (& generally I understand it is not a good practice), but sometimes we need to avail JS functions across the application, doing this in Webpacker for our Rails 5 application usually is done like this:
In a useful_pack.js file
function globalFunction() {
// some stuff
}
export {globalFunction }
Then in application.js, we import the pack & bind the global function to window:
import { globalFunction } from useful_pack.js
// some code
window.globalFunction = globalFunction
Is there any major drawback for binding to window in this case? is there a more standard way to avail a global JS function in webpacker ? (so that I can call the function in any view in my rails app)
To add a function in the global scope, to make it available across your application, this is the recommended way:
global.globalFunction = function() {
// some stuff
};
// or
const globalFunction = () => {
// some stuff
};
global.globalFunction = globalFunction;
In one of my JS files I include another one. How can I set variables in the included module?
I thought doing something like this would work
var mymodule = require('mymodule.js');
mymodule.myvariable = 'test';
And then in mymodule
this.myvariable === 'test';
But this doesn't work, it's undefined. What are the various options for passing a value into a module? I could just add the variable as a parameter to every function I call in mymodule, but that isn't ideal.
Is there a way to do it without globals, so that I can set the variables independently in various required modules, like this?
var mymodule1 = require('mymodule.js');
var mymodule2 = require('mymodule.js');
mymodule1.myvariable = 'test1';
mymodule2.myvariable = 'test2';
The problem with what you were doing is that you set the variable after importing, but this.myvariable === 'test'; was being called when the module was imported, before your variable was set.
You can have your module export a function and then call the function when you import, passing your variable as an argument.
module.exports = function(myVar) {
var myModule = {
// has access to myVar
...
};
return myModule;
};
When you import,
var myModule = require('myModule')(myVar);
If you use this method, keep in mind that you get a different instance of your module wherever you import, which may not be what you want.
If you want to set values of a module from outside the module, a good option is to have your module export an object with a setter method, and use that to set the value of the variable as a property of the object. This makes it more clear that you want this value to be settable, whereas just doing myModule.myVar = can set you up for confusion later.
module.exports = {
myVar: null,
setMyVar: function(myVar) {
this.myVar = myVar;
},
...
};
In this case you're accessing the same instance of the model wherever you import it.
Edit in response to comment
In the first option you show where you get a different instance each
time, how can I export multiple functions that each share the same
myVar? If that module exports 5 functions each that need myVar, can I
set it in one place like at import time rather than passing it into
each function?
Not entirely sure if I understand what you're describing, but you could do something like this:
module.exports = function(myVar) {
var modules = {};
modules.someModule = {...};
modules.anotherModule = {...};
...
return modules;
};
Each of these sub-modules would have access to the same myVar. So you would import as above and the result would be an object containing each of your five modules as properties. I can't say whether this is a good idea, it's getting pretty convoluted, but maybe it makes sense for your situation.
NodeJS require() will always load the module once so you will need to implement scoping into your module where different instances of the module can exist with their own internal state.
You can implement your module as a JS class like:
var MyModule = function(){};
MyModule.prototype.someFunction = function(params){
this.someVar = params;
}
MyModule.prototype.anotherFunction = function(key, value){
this[key] = value;
}
module.exports = MyModule;
Then in your code;
var MyModule = require('MyModule');
someModule = new MyModule();
// treat someModule like an object with its getters/setters/functions
Should work just fine. Here is a working example:
index.js
var module = require('./module.js');
module.myvar = 'Hello world';
module.test();
module.js
module.exports = {
test: function() {
console.log('var is', this.myvar);
}
};
Keep in mind if you use this in a closure that the scope isn't any longer the module itself. So that could be your problem.
Can you show me the part of the module code where you use this?
This is a module named StrUpperCase.js
exports.StrUpperCase = function(str) {
return str.toUpperCase();
}
In app.js:
var str = "Welcome World...";
const SUC = require('./modules/StrUpperCase');
console.log(SUC.StrUpperCase(str));
I have a global variable defined in my main template, which I use to store information bits from the back end, such as the environment context path. I can't move that variable inside a service.
How can I expose that variable to Karma when I run the unit tests?
You either declare that global variable within your test file:
var global = "something";
describe('Your test suit', function() {
...
});
or add a Javascript file where it's defined to your karma.conf.js file:
// list of files / patterns to load in the browser
files: [
...,
'file-containing-the-global-variable.js'
],
If you are coming from Angular 2+ the only way I found that works is to create the variable or object globally using window:
Google Recapthca Loaded from a script:
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
In this example a call to the google Recaptcha will create a global variable called grecaptcha.
In our component we decide to render a new Google Recaptcha. Of course since we do not declare grecaptcha we need to use declare var to say hey I promise it is declared somewhere:
declare var grecaptcha;
private CreateGoogleCaptcha() {
grecaptcha.render('recaptcha', {
sitekey: this.siteKey,
callback: this.GoogleCaptchaCallback,
badge: 'inline'
});
}
private GoogleCaptchaCallback(token) {
// Handle Callback Logic
}
Now that our function works we decide to run a test but of course we would like to mock our grecaptcha as we don't have control over it.
So we name our global variable we would like to create and add the functions we would like:
beforeEach(() => {
fixture = TestBed.createComponent(GoogleRecaptchaComponent);
component = fixture.componentInstance;
window['grecaptcha'] = {
render() {
console.log('mocked global variable and function');
}
}
}
Update:
Creating the global variable in the beforeEach is fine but what about when it has some sort of callback function such as above that calls a function from your component? Easy enough we just move the login to our test and in our mock we set it to our components function:
it('should ', () => {
window['grecaptcha'] = {
render: function() { GoogleRecaptchaComponent['GoogleCaptchaCallback']('token'); }
};
spyOn<any>(GoogleRecaptchaComponent, 'GoogleCaptchaCallback');
GoogleRecaptchaComponent['CreateGoogleCaptcha']();
expect(GoogleRecaptchaComponent['GoogleCaptchaCallback']).toHaveBeenCalled();
});
Note: spyOn<any>: The <any> is used so we can refence without error the function because it is private, otherwise we will get a typescript error;
The first solution didn't work for me in Angular 2.1.x. It simply would not recognize the variable in my imported service. What I had to do was put my environment variable in my karma-test-shim.js file and remove var so it would be globally available.
Mine looks like this:
Error.stackTraceLimit = Infinity;
require('core-js/es6');
require('reflect-metadata');
require('zone.js/dist/zone');
require('zone.js/dist/long-stack-trace-zone');
require('zone.js/dist/proxy'),
require('zone.js/dist/sync-test'),
require('zone.js/dist/jasmine-patch');
require('zone.js/dist/async-test');
require('zone.js/dist/fake-async-test');
// Add environment variables here so that tests will inject them in source code
API_URL = 'http://localhost:8080/api/';
var appContext = require.context('../src', true, /\.spec\.ts/);
appContext.keys().forEach(appContext);
var testing = require('#angular/core/testing');
var browser = require('#angular/platform-browser-dynamic/testing');
testing.TestBed.initTestEnvironment(
browser.BrowserDynamicTestingModule,
browser.platformBrowserDynamicTesting()
);
I was trying to integrate angular v13 with google-place-autocomplete without any npm package, just vanilla google-place-autocomplete. When i tried to test, karma said: '"google" is not defined'.
So i found the awswers above, and it worked for me like this:
Created a file in root directory (in my case, i named: google-place.js)
Added a mock object with the instructions that is used in component.
class Autocomplete {
constructor(input, options) {
const addListener = (eventName, callback) => { }
const getPlace = () => { }
return { addListener, getPlace }
}
}
var google = {
maps: {
places: {
Autocomplete: Autocomplete
}
}
};
Added in karma.conf.js a property called "files", and end up like this:
files: [
"./google-place.js"
]
And it was not necessary to add a declare var google in every spec. Is global now.
If I have a module like this:
define([
'app'
, 'text!index.html!strip'
, 'css!index'
],
function (App, source) {
var response = {};
App.newMethod = function (foo) {
console.log("foo ="+foo);
};
// return response object
return response;
}
);
I'm wondering how to add methods to a module that is used as a dependency in another module. Sure I can add methods to the object, but will these also update the App object when it is called from another module?
Question:
Is there a way to add methods to a module, which is loaded as a dependency and have these methods available on all modules, which require this dependency?
Short answer:
Yes. The module needs to be an object/instance (not a class) and it will work with requirejs.
Long answer:
When you require a module as a dependence for the first time Requirejs generates an object, and for the next times you requires the module Requirejs will return the object it generated the first time. So all the times you require a module you get always the same reference of the object.
With
define([], function () {
var app = {
//my methods.
};
return app;
});
and
define(['app'], function (app) {
app.newMethod = function (){
// ...
};
});
you can use app like this:
define(['app'], function (app) {
app.newMethod();
});
But injecting methods from one object to an other is a really bad practice. If you need something from an object just add it when creating the object, not by injection.
define([], function () {
var app = {
newMethod: function () {
// ...
},
// my methods.
};
return app;
});
For example if object A injects a new method that will be used in object B, but B is called when A is not loaded then there would be an error Object #<Object> has no method 'newMethod'
I am loading separate .js files into my html page. These files use anonymous functions so that the global scope is not filled with global variables. For example: this is my 'utils.js' file:
(function(window){
var Utils = {
createURL: function() {
console.log("creating url");
}
};
})(window);
My question: if I have several of these .js files, how can they reference each other without using the global scope? As it stands, I can only call this function from within the same utils.js file:
var myUtils = new Utils();
You can create global modules and expose only the needed functions like this...
// In some JS file
var moduleX = (function(window){
var Utils = function(){
};
return {
"Utils" : Utils
};
})(window);
// In a some other JS file
var util = new moduleX.Utils();
Btw, you cannot instantiate a JSON object(Utils in your case). So please change that to a function.
You can use a javascript loader like
require.js
head.js
yepnope
If you don't want to use a loader, maybe you can reduce the global usage to the minimun using a design pattern like the mediator or module.
(function(window){
var app = {};
app.Utils = {
createUrl:function(){}
};
window.app = app;
})(window);
and then you can work with the app like this :
app.Utils.createUrl();
If you don't want to use a third party tool, you can wrap everything into one module by using the following pattern:
//file1
var myModule = (function(window, that){
var myVar = 1;
that.myMethod1 = function(){
console.log("i am module 1");
};
return that;
}(window, myModule || {}));
//file2
var myModule = (function(window, that){
that.myMethod2 = function(){
console.log(typeof myVar); //undefined
that.myMethod1(); //i am module 1
};
return that;
}(window, myModule || {}));
This will ensure that you only have one single global variable. But, you will of course not be able to access local variables of another file.