Is it possible to receive the arguments list of the function without executing it somehow? I can't find a normal solution unfortunately.
I'm working on some Dependency Injection system and I'd like to know arguments naming in the function (service) before I create the instance of it. Because I want to create the Dependencies first and then pass them into the function (service).
for ex:
function MyServiceA(myServiceB) {
say() {
...code.........
myServiceB.beep();
}
}
function MyServiceB() {
beep() {
...code.........
}
}
function Creator() {
// Is it possible to receive it somehow? this is the question
const argumentsList = MyServiceA
// iterate list and instantiate the ServiceB dependency and inject in into the MyServiceA
argumentsList.forEach(dependency => {
const dependencyInstance = new dependency()
new MyServiceA(dependencyInstance )
})
}
Thanks for any help!
You could turn the function into a string, and then parse that string to find the names of the arguments. AngularJs did something like this for its dependency injection. Be aware that if the code gets minified, the names will change and this can break your code.
function needsA(a) {
console.log("in needsA");
a();
}
function needsB(b) {
console.log("in needsB");
b();
}
function needsAandB(a, b) {
console.log("in needsAandB");
a();
b();
}
const dependencyLookup = {
a: () => console.log('in A'),
b: () => console.log('in B'),
}
function injector(fxn) {
const argNames = fxn.toString()
.match(/^function\s*[^\(]*\(([^\)]*)\)/)[1]
.split(',');
const dependencies = argNames
.map(str => dependencyLookup[str.trim()]);
fxn.apply(null, dependencies);
}
injector(needsA);
injector(needsB);
injector(needsAandB);
A useful article on angularjs's dependency injection: https://teropa.info/blog/2014/06/04/angularjs-dependency-injection-from-the-inside-out.html
In AspNetZero/ABP, the default index.js for an entity looks like the following:
(function () {
$(function () {
var _$formulationFrequenciesTable = $('#FormulationFrequenciesTable');
var _formulationFrequenciesService = abp.services.app.formulationFrequencies;
function additionalFilter() {
return {
nameFilter: // something
prop2: // something else
};
}
// more code
});
})();
We are using Telerik's AspNetCore Kendo on the Razor, so we define the grid like so:
Html.Kendo().Grid<PatientManagement.Formulations.Dtos.GetFormulationFrequencyForViewDto>()
.Name("gridFormulationFrequencies")
.DataSource(d =>
{
d.Custom()
.Type("aspnetmvc-ajax")
.ServerFiltering(true)
.ServerPaging(true)
.ServerSorting(true)
.Schema(s => s.Aggregates("aggregateResults").Data("data").Errors("errors").Total("total").Model(model => model.Id(m => m.Id)))
.Transport(t => t.Read(read => read.Action("FormulationFrequency_Read", "FormulationFrequencies", new { area = "App" }).Data("additionalData").Type(HttpVerbs.Get)));
})
.Deferred(true).Render();
How can I make additionalData "public" so that I can use it in the grid definition? I understand that the first line of the js is IIFE, and the second is short hand js for jQuery(document).ready().
I've been having issues trying to define a public API because everything is defined within the scope of the document ready, and IIFE examples I've seen don't include this curveball.
One way would be to assign a reference to the function to a window property.
If you have to do this often you could have a global object of your own in window namespace and assign as a property to that object instead
(function () {
$(function () {
var _$formulationFrequenciesTable = $('#FormulationFrequenciesTable');
var _formulationFrequenciesService = abp.services.app.formulationFrequencies;
function additionalFilter() {
return {
nameFilter: // something
prop2: // something else
};
}
// add to global window namespace
window.additionalFilter = additionalFilter;
// more code
});
})();
I am trying to create a webpack plugin, that will parse the code for a certain function and replace it with another function, that plugin will also expose the new function as a global.
class someName {
constructor(local, domain, translationFile, options) {
}
apply(compiler) {
// exposing ngt function as a global
compiler.plugin('make', function(compilation, callback) {
var childCompiler = compilation.createChildCompiler('someNameExpose');
childCompiler.apply(new webpack.DefinePlugin({
ngt: function(singular , plural, quantity) {
return quantity == 1 ? singular : plural;
}
}));
childCompiler.runAsChild(callback);
});
// searching for the getValue function
compiler.parser.plugin(`call getValue`, function someNameHandler(expr) {
// create a function to replace getValue with
let results = 'ngt('+ expr.arguments +')';
const dep = new ConstDependency(results, expr.range);
dep.loc = expr.loc;
this.state.current.addDependency(dep);
return true;
});
}
}
module.exports = someName;
update / rephrase
I have an issue here, when compiler.parser.plugin('call getValue', function someNameHandler(expr) {...} block is commented the ngt function exist as a global.
when its not commented, i get an error, ngt is undefined.
commented i mean /**/
I found a workaround for that but its far then idea. right now what I do is I export an anonymous function that does what i want.
You can see the plugin here:
Github
You can override the method based on environment. Let's say you have a method
function a(){
//original defination
}
Now based on the environment, if it's a production you could do something like this
if (environment.production) {
function a(){
//overridden defination
}
}
You can use the webpack-merge plugin, it's very useful to do exactly what do you want.
https://github.com/survivejs/webpack-merge
I'm creating a service in angular to track events, and I want the controllers to be as dumb as possible about the event tracking. For this service each controller will need to supply its own copy of the event function. My current solution is below.
//tracking module
let tracked_events = ['Event1','Event2']
.map(function(e){
return {
eventName:e,
eventProperties: {}
}
});
let finishingTracking = (event) => {
console.log("prentend this does something fancy...",event);
}
let track = (eventName,fn) => {
var event = tracked_events.filter(e => e.eventName===eventName)[0];
fn(event.eventProperties);
//some other internal tracking function
finishTracking(event);
}
//end tracking module
//called from a controller
track("Event 1",(event) => {
event["User ID"] = 123;
event["Company ID"] = 12
});
//some other controller
track("Event 2",(event) => {
event["Manager ID"] = 345;
event["Time Sent"] = Date.now()
}
This works well because each controller will only have to provide its own way to modify the event object and it won't know anything else about the tracking module. From a design perspective, is this ok? I'm not sure about modifying the callback parameters (V8 optimizations/side effects), but I can't think of another way that doesn't cause more changes to each controller that needs to do its own event tracking. Any suggestions on this?
EDIT (prior to refactor)
var eventProperties = { table: "Machines, has_rows: /*rows...*/ }
some_service.list(data)
.then(function (response) {
tracking_service.track({
eventName: 'Event 1',
eventProperties: eventProperties
});
AFTER
some_service.trackEvent(some_service.events.EVENT1, function (event) {
event["Table"] = "Machines";
event["Has Rows"] = response.data.machines.length > 0;
});
An object of shape {eventName: "Name", eventProperties:{} } is currently being defined in each controller. My solution was to pass in the eventName from a constant defined in the service and the callback function modifies the eventProperties of the object. Each controller will have a different set of properties in eventProperties.
After the callback in version two runs, the service does the actual "tracking". My aim was to have a function that prepared the eventProperties object and added whatever properties it needed before being actually tracked.
self.trackEvent = function (eventName, fn) {
//var event = TRACKED_EVENTS.filter(e => e.eventName === eventName)[0];
var event = {
eventName: self.events[eventName],
eventProperties: { }
}
fn(event.eventProperties); //this is the callback that does the prep
self.track(event); //does the actually tracking
}
I am trying to overload/replace functions in the ami-io npm package. That package is created to talk to asterisk AMI, a socket interface.
I need to talk to a service that has almost the exact same interface, but it presents a different greeting string upon login, and it requires an extra field in the logon. All the rest is the same. Instead of just plain copying the 600 LOC ami-io package, and modifying two or three lines, I want to override the function that detects the greeting string, and the login function, and keep using the ami-io package.
Inside the ami-io package there is a file index.js which contains the following function:
Client.prototype.auth = function (data) {
this.logger.debug('First message:', data);
if (data.match(/Asterisk Call Manager/)) {
this._setVersion(data);
this.socket.on('data', function (data) {
this.splitMessages(data);
}.bind(this));
this.send(new Action.Login(this.config.login, this.config.password), function (error, response) {
if (response && response.response === 'Success') this.emit('connected');
else this.emit('incorrectLogin');
}.bind(this));
} else {
this.emit('incorrectServer', data);
}
};
Now I want to match not on Asterisk Call Manager, but on MyService, and I want to define and use Action.LoginExt(this.config.login, this.config.password) with another one with an extra parameter.
Is this possible? I tried this in my own module:
var AmiIo = require('ami-io');
var amiio = AmiIo.createClient({port:5038, host:'x.x.x.x', login:'system', password:'admin'});
amiio.prototype.auth = function (data) {
this.logger.debug('First message:', data);
if (data.match(/MyService Version/)) {
this._setVersion(data);
this.socket.on('data', function (data) {
this.splitMessages(data);
}.bind(this));
this.send(new Action.LoginExt(this.config.login, this.config.password, this.config.extra), function (error, response) {
if (response && response.response === 'Success') this.emit('connected');
else this.emit('incorrectLogin');
}.bind(this));
} else {
this.emit('incorrectServer', data);
}
};
...but it resulted in TypeError: Cannot set property 'auth' of undefined, and now I am clueless.
Also, can I define a new Action.LoginExt object in my own module? How?
The action.js module defines the Action objects as follows:
function Action(name) {
Action.super_.bind(this)();
this.id = this.getId();
this.set('ActionID', this.id);
this.set('Action', name);
}
(function(){
var Message = require('./message.js');
var util = require('util');
util.inherits(Action, Message);
})();
Action.prototype.getId = (function() {
var id = 0;
return function() {
return ++id;
}
})();
function Login(username, secret) {
Login.super_.bind(this, 'Login')();
this.set('Username', username);
this.set('Secret', secret );
}
... more functions ...
(function() {
var actions = [
Login,
... more functions ...
];
var util = require('util');
for (var i = 0; i < actions.length; i++) {
util.inherits(actions[i], Action);
exports[actions[i].name] = actions[i];
}
exports.Action = Action;
})();
What I think I understand is that Action is subclassed from Message. The Login function in its turn is subclassed from Action, and exported (in the last code block).
So I think in my code I could try something similar:
// extend ami-io with LoginExt function
function LoginExt(username, secret, company) {
Login.super_.bind(this, 'LoginExt')();
this.set('Username', username);
this.set('Secret', secret );
this.set('Company', company);
}
var util = require('util');
util.inherits(LoginExt, amiio.Action);
But util.inherits fails with undefined. I've also opened a issue on ami-io.
You can use:
var AmiIo = require('ami-io');
AmiIo.Action.Login = function NewConstructor(){}; //to override Login action
//new constructor shold extend AmiIo.Action.Action(actionName)
//and also, you can use
AmiIo.Action.SomeNewAction = function SomeNewAction(){};//to create new actuion
//it also should extend AmiIo.Action.Action(actionName);
AmiIo.Action is just an Object. All constructors are fields of it.
To create new events you don't need to do anything, because it is just an object. If server send to you
Event: Armageddon
SomeField: 123
ami-io will create event with name 'Armageddon'.
To override Client#auth() method, you just should do
var AmiIo = require('ami-io');
AmiIo.Client.prototype.auth = function (){};//new function
amiio is an instance of a Client. The prototype property is only meaningful on constructor functions, such as Client. It is not meaningful on the result of a constructor function (except in the uncommon case that the instance happens to also be a function itself -- but even in that case, altering the instance's prototype does not influence its parent constructor).
Instead, you need to get the instance's prototype with Object.getPrototypeOf:
Object.getPrototypeOf(amiio).auth = function() { ... }
If you don't need to change this for every client, but only a single client, you don't need to change the prototype at all. Changing the instance's auth is sufficient:
amiio.auth = function() { ... }
Note that you code will not work if Action.LoginExt is local to the module scope. If the module exports it, you can probably do AmiIo.Action.LoginExt instead. If it does not export LoginExt, you will need to copy the code that implements it a re-implement it in your importing scope. It may be simpler to modify the module itself.
Here's the solution I applied that worked:
// Override the AmiIo auth procedure, because the other login is slightly different
// Write our own Login function (which adds a company)
function Login(username, secret, company) {
Login.super_.bind(this, 'Login')();
this.set('Username', username);
this.set('Secret', secret );
this.set('Company', company);
}
// This function should inherit from Action
var util = require('util');
util.inherits(Login, AmiIo.Action.Action);
AmiIo.Action.Login = Login;
// replace the auth with our own, to add the company. Also
// this sends a slightly different greeting: "Service Version 1.0"
AmiIo.Client.prototype.auth = function (data) {
if (data.match(/Service Version/)) {
this._setVersion(data);
this.socket.on('data', function (data) {
this.splitMessages(data);
}.bind(this));
this.send(new AmiIo.Action.Login(this.config.login, this.config.password, this.config.company), function (error, response) {
if (response && response.response === 'Success') this.emit('connected');
else this.emit('incorrectLogin');
}.bind(this));
} else {
this.emit('incorrectServer', data);
}
};
// our own function to grab the version number from the new greeting
AmiIo.Client.prototype._setVersion = function(version){
var v = version.match(/Service Version ([\d\.]*[\-\w\d\.]*)/i);
if (v){
this.version = v[1];
}
};
So it turns out this was as doable as I hoped it would be. Both answers by #NumminorihSF and #apsillers helped me here, but I could mark only one of them as the best answer.