I've been working on a Node.js project and only just noticed that Visual Studio Code provides information about base EventEmitter objects. So I imagine it should be possible to provide JSDoc for custom ones too.
I've already attempted following the JSDoc http://usejsdoc.org/tags-event.html documentation but doesn't seem to pick up.
I don't know if this is effecting it but I'm using the ES6 class where the events get processed in a function outside it but it's inside the same script.
This is the test code.
// voice
if (voice) {
try {
/**
* Voice event.
*
* #event TelegramBot#voice
* #type {object}
* #property {object} chat - [object Chat]
* #property {number} date - Date when content was sent.
* #property {object} from - [object User]
* #property {number} message_id - Message id.
* #property {string} caption - Caption added to message. Value is undefined if none is added.
* #property {object} voice - [object Voice]
*/
context.emit('voice', chat, date, from, message_id, caption, voice)
} catch (error) {
context.emit('error', error)
}
}
I found this question while attempting to do the same, so I kept searching and I found a possible solution, by overriding the addListener methods, and documenting them.
I successfully made it work for both NodeJS and browser JS.
Result in VSCode
Typedef
First, create the typedef with your events names :
/**
* #typedef {["someEvent" | "someOtherEvent", ...any[]]} eventsDef
*/
I am using the spread syntax since I don't know how many arguments the user will pass to the method.
Implementation
For NodeJS:
The class needs to override the on and addListener methods, document the arguments, and simply call the parent's own method :
class Test extends EventEmitter {
/**
* #param {eventsDef} args
*/
addListener(...args) {
super.addListener(...args);
}
/**
* #param {eventsDef} args
*/
on(...args) {
super.on(...args);
}
fireEvent() {
this.emit("someEvent", "someValue");
}
}
Then you can use this class like so :
const t = new Test();
t.on("someEvent", (val) => console.log(val));
t.fireEvent(); // Outputs "someValue"
For the browser:
The class needs to extends EventTarget and override the addEventListener method.
Here is the snippet :
class Test extends EventTarget {
/**
* #param {["someEvent" | "someOtherEvent", ...any[]]} args
*/
addEventListener(...args) {
super.addEventListener(...args);
}
fireEvent() {
this.dispatchEvent(new CustomEvent("someEvent", {detail: "someValue"}))
}
}
const t = new Test();
t.addEventListener("someEvent", val => console.log(val.detail));
document.querySelector("button").addEventListener("click", () => {
t.fireEvent();
})
<button>Fire event</button>
Related
I am very new to nodejs and typescript.
I have try to provide an API via express.
I have try to return a custom object on my API who looks like that :
export class Auction {
private _currentPrice:number = 0;
private _auctionName:string;
public constructor(currentPrice: number , auctionName: string) {
this._currentPrice = currentPrice;
this._auctionName = auctionName;
}
/**
* Getter auctionName
* #return {string}
*/
public get auctionName(): string {
return this._auctionName;
}
/**
* Setter auctionName
* #param {string} value
*/
public set auctionName(value: string) {
this._auctionName = value;
}
/**
* Setter currentPrice
* #param {number } value
*/
public set currentPrice(value: number ) {
this._currentPrice = value;
}
/**
* Getter currentPrice
* #return {number }
*/
public get currentPrice(): number {
return this._currentPrice;
}
}
But what I have seen is that the answer of my API is something like :
{"_currentPrice":0,"_auctionName":"toto"}
I was expecting something like
{"currentPrice":0,"auctionName":"toto"}
Is there any way to automaticaly convert it to the format I want ?
This is happening because when the TypeScript is compiled to JavaScript, objects created by that class have public _currentPrice and _auctionName properties (because TypeScript "private" properties are only private in terms of TypeScript's type system) and they don't have their own currentPrice and auctionName properties (they inherit them from their prototype, which has them as accessor properties). JSON.stringify only includes "own" properties.
You can deal with it in a few ways:
By using simple properties for currentPrice and auctionName. You have public accessors for both get and set for both properties, so there doesn't seem to be any reason to use private properties to hold their values. Or,
By providing your own toJSON method for the class:
toJSON() {
return {currentPrice: this._currentPrice, auctionName: this._auctionName};
}
Despite the name "toJSON", this method isn't supposed to return JSON; it's supposed to return the value for the object that should be converted to JSON. So in this case, you return an object with the properties you want the returned JSON to have.
A third solution would be to use JavaScript's own private properties (they're supported in up-to-date Node.js versions) and "own" accessors for them on the objects, but I don't think TypeScript supports JavaScript's private properties yet.
I have 2 Node modules. In module A I have the following definition:
/**
* HTTP Client
* #module src/http/client
*/
/**
* A HTTP Client
* #alias src/http/client
*/
class HTTPClient {
[... class def with documented methods etc]
}
module.exports = HTTPClient
And now in module B I want to say that the first constructor parameter should be of type HTTPClient. So I tried the following
class PackageFactory {
/**
* #param {module:src/http/client} httpClient - the HTTPClient instance
*/
constructor(httpClient) {
this._httpClient = httpClient
}
}
I also tried a few variations but it never worked. Within module B the httpClient is always of type "any". What do I have to change so I can see the class member of HTTPClient in module B?
The solution was easier then I thought. There is no need to include the module paths (aka longname) or anything.
const HTTPClient = require('../http/client')
class PackageFactory {
/**
* #param {HTTPClient} httpClient - the HTTPClient instance that shall be used to make requests
*/
constructor(httpClient) {
this._httpClient = httpClient
}
}
To avoid the HTTPClient is unused error that any linter will give you, you can actually import the class in the #param field itself. This makes the doc a bit less easy to read, but you get autocompletion at design time, a nice tradeoff.
Note that you can combine this with the compilerOptions.paths option of tsconfig.json to create aliases that will make those documentation-imports look better.
class PackageFactory {
/**
* #param {import('../http/client')} httpClient - the HTTPClient instance that shall be used to make requests
*/
constructor(httpClient) {
this._httpClient = httpClient
}
}
I'm trying to get data for a new field added in login page. What I've done:
Modify AccountController.php login function adding new parameter: $this->_app->login($user, $client, !empty($data['rememberme']))
In Userfrosting.php login function i've set it in application: $this->client = $client;
In setupTwigUserVariables funtion added twig global: $twig->addGlobal("client", $this->client);
The problem is that in a template, {{client.id}} returns nothing. Any help will be appreciated.
In UserFrosting 4, you should create a Twig extension in your Sprinkle's src/Twig/ directory, and add the variable to the return value for getGlobals.
Your situation is a little tricky, since I'm not sure how client can be a global variable but at the same time depend on $data['client_id'] - which appears to be a request parameter. For now, I'll assume that you're submitting this parameter with any requests that require the client variable.
<?php
/**
* Stack Overflow
*
* #link https://stackoverflow.com
*/
namespace UserFrosting\Sprinkle\Site\Twig;
use Interop\Container\ContainerInterface;
use UserFrosting\Sprinkle\Site\Database\Models\Client;
/**
* Extends Twig functionality for the Site sprinkle.
*
* #author Jose Luis
*/
class Extension extends \Twig_Extension
{
protected $services;
protected $config;
public function __construct(ContainerInterface $services)
{
$this->services = $services;
$this->config = $services->config;
}
public function getName()
{
return 'myproject';
}
public function getGlobals()
{
try {
$currentUser = $this->services->currentUser;
// Assumes the client_id is being submitted as a query string (url) parameter
$clientId = $this->services->request->getQueryParam('client_id');
$client = Client::where('client_id', clientId)->where('userid', $currentUser->id)->first();
} catch (\Exception $e) {
$client = null;
}
return [
'client' => $client
];
}
}
You will then need to register this extension in your Sprinkle's service provider class:
<?php
/**
* Stack Overflow
*
* #link https://stackoverflow.com
*/
namespace UserFrosting\Sprinkle\Site\ServicesProvider;
use UserFrosting\Sprinkle\Site\Twig\Extension as JoseExtension;
/**
* Services provider for the Site sprinkle.
*
* #author Jose Luis
*/
class ServicesProvider
{
/**
* Register extended user fields services.
*
* #param Container $container A DI container implementing ArrayAccess and container-interop.
*/
public function register($container)
{
/**
* Extends the 'view' service with Jose's Twig Extension.
*/
$container->extend('view', function ($view, $c) {
$twig = $view->getEnvironment();
$extension = new JoseExtension($c);
$twig->addExtension($extension);
return $view;
});
}
}
Yes, I know that there is a lot of boilerplate here. However once you set these up the first time, it is easy to add new variables/functions/filters to the Twig environment and new services to your Sprinkle in the future.
If I am developing a node module something-nifty which has the following directory structure:
something-nifty/
lib/
plugins/
Plugin.cs
index.js
package.json
And plugin.cs is defined as follows:
"use strict";
/**
* #module something-nifty/lib/plugins/Plugin
*/
/**
* The base class for a 'something-nifty' plugin.
*/
class Plugin {
/**
* Constructs the plugin...
*/
constructor() {
}
}
module.exports = Plugin;
In the generated documentation the class is documented as though it has the #inner tag which means that I need to repeat the class name twice whenever I refer to it:
"use strict";
/**
* #module something-nifty/lib/foo
*/
/**
* Foo...
* #param {module:something-nifty/lib/plugins/Plugin~Plugin} plugin
*/
exports.abc = function(plugin) { ... };
Surely I shouldn't need to specify the class name twice in this situation since the module is essentially the class. What is the correct way to annotate this with jsdoc3 tags such that it outputs documentation that is properly structured (module and class listings)?
Having spent some time searching the best that I have been able to find is this is to (in my opinion) "misuse" the #alias tag like shown in the following snippet:
"use strict";
/**
* #module something-nifty/lib/plugins/Plugin
*/
/**
* The base class for a 'something-nifty' plugin.
* #alias module:something-nifty/lib/plugins/Plugin
*/
class Plugin {
/**
* Constructs the plugin...
*/
constructor() {
}
}
module.exports = Plugin;
The following link refers to the article where I picked up on this #alias trick:
http://www.pagosoft.com/javascript/using-jsdoc-amd-modules/
A lot of times in a Zend Framework 2 view I'll be calling $this->escapeHtml() to make sure my data is safe. Is there a way to switch this behaviour from a blacklist to a whitelist?
PS: Read an article from Padraic Brady that suggests that automatic escaping is a bad idea. Additional thoughts?
You could write your own ViewModel class which escapes data when variables are assigned to it.
Thanks to Robs comment, I extended the ZF2 ViewModel as follows:
namespace Application\View\Model;
use Zend\View\Model\ViewModel;
use Zend\View\Helper\EscapeHtml;
class EscapeViewModel extends ViewModel
{
/**
* #var Zend\View\Helper\EscapeHtml
*/
protected $escaper = null;
/**
* Proxy to set auto-escape option
*
* #param bool $autoEscape
* #return ViewModel
*/
public function autoEscape($autoEscape = true)
{
$this->options['auto_escape'] = (bool) $autoEscape;
return $this;
}
/**
* Property overloading: get variable value;
* auto-escape if auto-escape option is set
*
* #param string $name
* #return mixed
*/
public function __get($name)
{
if (!$this->__isset($name)) {
return;
}
$variables = $this->getVariables();
if($this->getOption('auto_escape'))
return $this->getEscaper()->escape($variables[$name]);
return $variables[$name];
}
/**
* Get instance of Escaper
*
* #return Zend\View\Helper\EscapeHtml
*/
public function getEscaper()
{
if (null === $this->escaper) {
$this->escaper = new EscapeHtml;
}
return $this->escaper;
}
}
In a Controller it could be used like this:
public function fooAction()
{
return new EscapeViewModel(array(
'foo' => '<i>bar</i>'
));
//Turn off auto-escaping:
return new EscapeViewModel(array(
'foo' => '<i>bar</i>'
),['auto_escape' => false]);
}
Question:
I would appreciate it if soemebody would comment, if this is best practice or if there is a better and ecp. more efficient and resource-saving way?