Module export multiple classes - javascript

This is /cars/ford.js
Ford = Car.extend({
classId: 'Ford',
init: function (driver) {
this._driver = driver;
Car.prototype.init.call(this);
tick: function (ctx) {
Car.prototype.tick.call(this, ctx);
},
destroy: function () {
if (this._driver !== undefined) {
this._driver._license.pull(this);
}
Car.prototype.destroy.call(this);
}
});
if (typeof(module) !== 'undefined' && typeof(module.exports) !== 'undefined') {
module.exports = Ford;
}
This is /cars/knightrider.js:
KITT = Car.extend({
classId: 'KITT',
init: function () {
Car.prototype.init.call(this);
var self = this;
},
newDirection: function () {
var self = this;
this._move.direction()
.duration(Math.random())
.properties({
x: Math.random(),
y: Math.random(),
})
.voice('robotic')
.afterDirection(function () {
self.newDirection();
})
.start();
}
});
if (typeof(module) !== 'undefined' && typeof(module.exports) !== 'undefined') {
module.exports = KITT;
}
I want to have all cars inside the same file to preserve my self sanity. How can I wrap them without altering my classes? Feel free to recommend any 'proper packaging' for Javascript functions book or tutorial, because I really dislike to open files. When I'm editing a car, I might want to edit other one.
Wish I could do:
module.exports.Allmycars = KITT, Ford;
And then call them with:
Allmycars.Ford

A solution could be :
//cars/index.js
module.exports = {
KITT:require("./knightrider"),
Ford:require("./ford")
}
//So u could use :
var allMyCars = require("./cars");
var ford = new allMyCars.Ford();

Related

Access object context from prototype functions JavaScript

I have problems with object scope.
Here is my class code
// Table list module
function DynamicItemList(data, settings, fields) {
if (!(this instanceof DynamicItemList)) {
return new DynamicItemList(data, settings, fields);
}
this.data = data;
this.settings = settings;
this.fields = fields;
this.dataSet = {
"Result": "OK",
"Records": this.data ? JSON.parse(this.data) : []
};
this.items = this.dataSet["Records"];
this.generateId = makeIdCounter(findMaxInArray(this.dataSet["Records"], "id") + 1);
this.dataHiddenInput = $(this.settings["hidden-input"]);
}
DynamicItemList.RESULT_OK = {"Result": "OK"};
DynamicItemList.RESULT_ERROR = {"Result": "Error", "Message": "Error occurred"};
DynamicItemList.prototype = (function () {
var _self = this;
var fetchItemsList = function (postData, jtParams) {
return _self.dataSet;
};
var createItem = function (item) {
item = parseQueryString(item);
item.id = this.generateId();
_self.items.push(item);
return {
"Result": "OK",
"Record": item
}
};
var removeItem = function (postData) {
_self.items = removeFromArrayByPropertyValue(_self.items, "id", postData.id);
_self.dataSet["Records"] = _self.items;
_self.generateId = makeIdCounter(findMaxInArray(_self.dataSet["Records"], "id") + 1);
return DynamicItemList.RESULT_OK;
};
return {
setupTable: function () {
$(_self.settings["table-container"]).jtable({
title: _self.settings['title'],
actions: {
listAction: fetchItemsList,
deleteAction: removeItem
},
fields: _self.fields
});
},
load: function () {
$(_self.settings['table-container']).jtable('load');
},
submit: function () {
_self.dataHiddenInput.val(JSON.stringify(_self.dataSet["Records"]));
}
};
})();
I have problems with accessing object fields.
I tried to use self to maintain calling scope. But because it is initialized firstly from global scope, I get Window object saved in _self.
Without _self just with this it also doesn't work . Because as I can guess my functions fetchItemsList are called from the jTable context and than this points to Window object, so I get error undefined.
I have tried different ways, but none of them work.
Please suggest how can I solve this problem.
Thx.
UPDATE
Here is version with all method being exposed as public.
// Table list module
function DynamicItemList(data, settings, fields) {
if (!(this instanceof DynamicItemList)) {
return new DynamicItemList(data, settings, fields);
}
this.data = data;
this.settings = settings;
this.fields = fields;
this.dataSet = {
"Result": "OK",
"Records": this.data ? JSON.parse(this.data) : []
};
this.items = this.dataSet["Records"];
this.generateId = makeIdCounter(findMaxInArray(this.dataSet["Records"], "id") + 1);
this.dataHiddenInput = $(this.settings["hidden-input"]);
}
DynamicItemList.RESULT_OK = {"Result": "OK"};
DynamicItemList.RESULT_ERROR = {"Result": "Error", "Message": "Error occurred"};
DynamicItemList.prototype.fetchItemsList = function (postData, jtParams) {
return this.dataSet;
};
DynamicItemList.prototype.createItem = function (item) {
item = parseQueryString(item);
item.id = this.generateId();
this.items.push(item);
return {
"Result": "OK",
"Record": item
}
};
DynamicItemList.prototype.setupTable = function () {
$(this.settings["table-container"]).jtable({
title: this.settings['title'],
actions: this,
fields: this.fields
});
};
DynamicItemList.prototype.load = function () {
$(this.settings['table-container']).jtable('load');
};
DynamicItemList.prototype.submit = function () {
this.dataHiddenInput.val(JSON.stringify(this.dataSet["Records"]));
};
DynamicItemList.prototype.removeItem = function (postData) {
this.items = removeFromArrayByPropertyValue(this.items, "id", postData.id);
this.dataSet["Records"] = this.items;
this.generateId = makeIdCounter(findMaxInArray(this.dataSet["Records"], "id") + 1);
return DynamicItemList.RESULT_OK;
};
DynamicItemList.prototype.updateItem = function (postData) {
postData = parseQueryString(postData);
var indexObjToUpdate = findIndexOfObjByPropertyValue(this.items, "id", postData.id);
if (indexObjToUpdate >= 0) {
this.items[indexObjToUpdate] = postData;
return DynamicItemList.RESULT_OK;
}
else {
return DynamicItemList.RESULT_ERROR;
}
};
Your assigning a function directly to the prototype. DynamicItemList.prototype= Normally it's the form DynamicItemList.prototype.somefunc=
Thanks everyone for help, I've just figured out where is the problem.
As for last version with methods exposed as public.
Problematic part is
$(this.settings["table-container"]).jtable({
title: this.settings['title'],
actions: {
listAction: this.fetchItemsList,
createAction: this.createItem,
updateAction: this.updateItem,
deleteAction: this.removeItem
},
fields: this.fields
});
};
Here new object is created which has no idea about variable of object where it is being created.
I've I changed my code to the following as you can see above.
$(this.settings["table-container"]).jtable({
title: this.settings['title'],
actions: this,
fields: this.fields
});
And now it works like a charm. If this method has drawbacks, please let me know.
My problem was initially in this part and keeping methods private doesn't make any sense because my object is used by another library.
Thx everyone.
You need to make your prototype methods use the this keyword (so that they dyynamically receive the instance they were called upon), but you need to bind the instance in the callbacks that you pass into jtable.
DynamicItemList.prototype.setupTable = function () {
var self = this;
function fetchItemsList(postData, jtParams) {
return self.dataSet;
}
function createItem(item) {
item = parseQueryString(item);
item.id = self.generateId();
self.items.push(item);
return {
"Result": "OK",
"Record": item
};
}
… // other callbacks
$(this.settings["table-container"]).jtable({
title: this.settings['title'],
actions: {
listAction: fetchItemsList,
createAction: createItem,
updateAction: updateItem,
deleteAction: removeItem
},
fields: this.fields
});
};

Modify .xpi of Firefox Add-on SDK extension to run a command on extension startup

I am trying to modify a Firefox extension. There is a "panel.html" page, with an associated "panel.js" file. "Panel.js" seems to export a bunch of classes. I am not sure how it actually executes anything. It is a little hard for me to follow; it seems cryptic and I haven't worked with JavaScript in this way. Basically, there is a switch that I want to automatically toggle "on" when Firefox is opened.
I have no experience with Firefox extension development. It seems that "bootstrap.js" is sandboxed. Therefore, when I try to import "panel.js," I get errors such as "document not defined." I have gotten the switch to toggle on when you click the panel icon and it opens up, but I need this to happen when Firefox opens.
Can anyone point me in the right direction?
Bootstrap.js
const { utils: Cu } = Components;
const rootURI = __SCRIPT_URI_SPEC__.replace("bootstrap.js", "");
const COMMONJS_URI = "resource://gre/modules/commonjs";
const { require } = Cu.import(COMMONJS_URI + "/toolkit/require.js", {});
const { Bootstrap } = require(COMMONJS_URI + "/sdk/addon/bootstrap.js");
var { startup, shutdown, install, uninstall } = new Bootstrap(rootURI);
Panel.js
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
// Created by Valentin Shergin.
// Copyright (c) 2015 AnchorFree. All rights reserved.
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _WdgtUniversalPipePipeOut = require('Wdgt/../universal/pipe/PipeOut');
var _WdgtUniversalPipePipeOut2 = _interopRequireDefault(_WdgtUniversalPipePipeOut);
var promise = null;
function remoteCakeTubeSDK() {
if (!promise) {
promise = new _WdgtUniversalPipePipeOut2['default']('CakeTubeSDK');
}
return promise;
}
exports['default'] = remoteCakeTubeSDK;
module.exports = exports['default'];
},{"Wdgt/../universal/pipe/PipeOut":12}],
(There are around 200 more exports after this)
Relevant class:
42:[function(require,module,exports){
// Created by Valentin Shergin.
// Copyright (c) 2015 AnchorFree. All rights reserved.
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
require('./ToolbarConnectionSwitcher.less');
require('./Spinner.less');
var _CommonSwitcherSwitcher = require('../Common/Switcher/Switcher');
var _CommonSwitcherSwitcher2 = _interopRequireDefault(_CommonSwitcherSwitcher);
var _remoteCakeTubeSDK = require('remoteCakeTubeSDK');
var _remoteCakeTubeSDK2 = _interopRequireDefault(_remoteCakeTubeSDK);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _PanelController = require('../PanelController');
var _PanelController2 = _interopRequireDefault(_PanelController);
var _reactSpinner = require('react-spinner');
var _reactSpinner2 = _interopRequireDefault(_reactSpinner);
var ToolbarConnectionSwitcher = (function (_Component) {
_inherits(ToolbarConnectionSwitcher, _Component);
function ToolbarConnectionSwitcher() {
var _this = this;
_classCallCheck(this, ToolbarConnectionSwitcher);
_get(Object.getPrototypeOf(ToolbarConnectionSwitcher.prototype), 'constructor', this).call(this);
this.state = {
connected: true
};
this.cakeTubeSDK().then(function (CakeTubeSDK) {
CakeTubeSDK.connectionService.dispatcher.register(_this.handleConnectionService.bind(_this));
});
//**********************************
//Here I can add this line to make it connect when the panel is opened
this.handleConnect();
//**********************************
this.checkConnectionStatus();
}
_createClass(ToolbarConnectionSwitcher, [{
key: 'cakeTubeSDK',
value: function cakeTubeSDK() {
return (0, _remoteCakeTubeSDK2['default'])();
}
}, {
key: 'checkConnectionStatus',
value: function checkConnectionStatus() {
var _this2 = this;
this.cakeTubeSDK().then(function (CakeTubeSDK) {
return CakeTubeSDK.connectionService.getStatus().then(function (status) {
var connectingOrDisconnecting = status == 'connecting' || status == 'disconnecting';
var connected = status == 'connected';
_this2.refs.self.getDOMNode().classList.toggle('connecting-or-disconnecting', connectingOrDisconnecting);
if (!connectingOrDisconnecting) {
_this2.setState({ connected: connected });
_this2.refs.switcher.setState({ state: connected });
}
});
});
}
}, {
key: 'handleConnectionService',
value: function handleConnectionService() {
this.checkConnectionStatus();
}
}, {
key: 'handleConnect',
value: function handleConnect() {
var _this3 = this;
this.cakeTubeSDK().then(function (CakeTubeSDK) {
return CakeTubeSDK.connectionService.connect().then(function () {
_this3.checkConnectionStatus();
});
})['catch'](function (error) {
_this3.reportError(error);
});
}
}, {
key: 'handleDisconnect',
value: function handleDisconnect() {
var _this4 = this;
this.cakeTubeSDK().then(function (CakeTubeSDK) {
return CakeTubeSDK.connectionService.disconnect().then(function () {
_this4.checkConnectionStatus();
});
})['catch'](function (error) {
_this4.reportError(error);
});
}
}, {
key: 'handleSignOut',
value: function handleSignOut() {
var _this5 = this;
this.cakeTubeSDK().then(function (CakeTubeSDK) {
return CakeTubeSDK.connectionService.disconnect().then(CakeTubeSDK.clientService.logOut());
})['catch'](function (error) {
_this5.reportError(error);
});
}
}, {
key: 'reportError',
value: function reportError(error) {
this.checkConnectionStatus();
_PanelController2['default'].panelController().showProblem(error);
}
}, {
key: 'handleSwitcherChanged',
value: function handleSwitcherChanged() {
var switcher = this.refs.switcher;
var state = !!switcher.state.state;
if (state) {
this.handleDisconnect();
} else {
this.handleConnect();
}
}
}, {
key: 'render',
value: function render() {
var rvalue = _react2['default'].createElement(
'div',
{ className: 'toolbar-connection-switcher', ref: 'self' },
_react2['default'].createElement(_CommonSwitcherSwitcher2['default'], { ref: 'switcher', onChanged: this.handleSwitcherChanged.bind(this) }),
_react2['default'].createElement(
'div',
{ className: 'spinner' },
_react2['default'].createElement(_reactSpinner2['default'], null)
)
);
return rvalue;
}
}]);
return ToolbarConnectionSwitcher;
})(_react.Component);
exports['default'] = ToolbarConnectionSwitcher;
module.exports = exports['default'];
},{"../Common/Switcher/Switcher":21,"../PanelController":31,"./Spinner.less":41,"./ToolbarConnectionSwitcher.less":43,"react":206,"react-spinner":51,"remoteCakeTubeSDK":1}],43:[function(require,module,exports){
(function() { var head = document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css';var css = ".toolbar-connection-switcher{position:absolute;top:11px;right:11px}.toolbar-connection-switcher .switcher{opacity:1}.toolbar-connection-switcher .spinner{position:absolute;top:13px;left:6px;pointer-events:none;opacity:0}.toolbar-connection-switcher .switcher,.toolbar-connection-switcher .spinner{transition:opacity .25s}.toolbar-connection-switcher.connecting-or-disconnecting .switcher{opacity:0}.toolbar-connection-switcher.connecting-or-disconnecting .spinner{opacity:1}";if (style.styleSheet){ style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } head.appendChild(style);}())
},{}],
This an .xpi file generated for an Add-on SDK extension using jpm xpi. You will need to look in the file package.json. The value of property main in that file will tell you the name of the file which is always run on Firefox startup or the install/enable of this add-on.
The bootstrap.js that you have shown is the exact same code that is contained in all Add-on SDK extensions that have been created using that version of jpm.
You should not modify bootstrap.js. In any Add-on SDK extension like this one, that file sets up the environment for the Add-on SDK extension and through the functions assigned to startup, shutdown, install, uninstall (with var { startup, shutdown, install, uninstall } = new Bootstrap(rootURI);) runs the code contained in the file pointed to by the main property defined in package.json when the add-on is first installed, enabled, or Firefox is started (and other times).
Your question lacks enough information to provide more detailed information as to how to accomplish what you desire. For instance, we have no idea what the complete code really is (you have not provided a link to the actual extension). In addition, we do not know what the "switch" is which you want to have automatically toggled to on. This may not even be the most appropriate method of accomplishing what you desire. Effectively, you have not provided enough information for us to determine what it is that you actually desire to happen.

Javascript simple MVC + module pattern implementation

Here is a very basic attempt to create a "hello world"-like JS app using the module and MVC patterns.
var appModules = {};
appModules.exampleModul = (function () {
var _data = ['foo', 'bar']; // private variable
return {
view: {
display: function() {
$('body').append(appModules.exampleModul.model.getAsString());
},
},
model: {
getAsString: function() {
return _data.join(', ');
},
}
};
})();
appModules.exampleModul.view.display();
This works fine, but I'm not happy how I have to reference the model function from the view, using the full object path: appModules.exampleModul.model.getAsString(). How can I expose the public model methods to the view, so I could simply use something like model.getAsString()? Or do I need to organize the code differently?
One option is you can convert those objects into private implementations.
appModules.exampleModul = (function() {
var _data = ['foo', 'bar'];
// private variable
var _view = {
display : function() {
$('body').append(_model.getAsString());
},
};
var _model = {
getAsString : function() {
return _data.join(', ');
},
};
return {
view : _view,
model : _model
};
})();
You could do something like this:
var appModules = {};
appModules.exampleModul = (function () {
var _data = ['foo', 'bar']; // private variable
return {
view: {
display: function() {
$('body').append(this.model.getAsString());
},
},
model: {
getAsString: function() {
return _data.join(', ');
},
}
};
})();
var display = appModules.exampleModul.view.display.bind(appModules.exampleModul);
display();
Which isn't really the prettiest of solutions, but does offer a more generic solution inside the display function!

How to use jQuery $.extend(obj1, obj2)

I'm trying to create a button class that extends an AbstractComponent class using $.extend() but the functions in AbstractComponent aren't available when I'm constructing the button.
The specific error I'm receiving is:
Uncaught TypeError: Object [object Object] has no method 'setOptions'
var Button = {};
var abstract = new AbstractComponent;
$.extend(Button,abstract);
//debugger;
//this.setOptions is available here
Button = function(options) {
'use strict';
var defaultOptions = {
templateName: '#button-tmpl',
title: "Label goes here",
type: "primary",
size: "medium",
disabled: null,
autosave: null,
href: null,
onclick: null
};
//debugger
//this.setOptions is not available here
this.setOptions(options, defaultOptions);
this.checkRequiredKeys('title');
return this;
};
Button.prototype.updateOptions = function() {
var options = this.options;
if (options.href === null) {
options.href = 'javascript:;';
}
if (options.disabled === null) {
options.disabled = 'disabled';
}
if (options.autosave === true) {
options.autosave = 'ping-autosave';
}
};
AbstractComponent.js
var AbstractComponent = function() {
console.log('this will be the constructor for elements extending this class');
};
AbstractComponent.prototype.show = function() {
this.render();
};
AbstractComponent.prototype.close = function() {
// stop listeners and remove this component
this.stopListening();
this.remove();
};
AbstractComponent.prototype.getTemplateName = function() {
return this.options.templateName;
};
AbstractComponent.prototype.checkRequiredKeys = function() {
var errors = new Array();
if (typeof this.getTemplateName() === "undefined") {
errors.push('templateName');
}
for (var i = 0; i < arguments.length; i++) {
if (!this.options.hasOwnProperty(arguments[i])) {
errors.push(arguments[i]);
}
}
if (errors.length > 0) {
throw new Exception("Required property(s) not found:" + errors.join(', ') + " in " + this.toString());
}
};
AbstractComponent.prototype.getElement = function() {
'use strict';
if(!this.options.updated) {
this.updateOptions();
}
return new AbstractView(this.options).render().$el;
};
AbstractComponent.prototype.updateOptions = function() {
this.options.updated = true;
return true;
};
AbstractComponent.prototype.getHtml = function() {
return this.getElement().html();
};
AbstractComponent.prototype.setOptions = function(options, defaultOptions) {
this.options = _.defaults(options, defaultOptions);
};
AbstractComponent.prototype.toString = function() {
return "Component" + this.getTemplateName() + "[id=" + this.options.id + "]";
};
jQuery extend is for moving properties from one (or more) object(s) to another object.
$.extend({}, {
foo: 10,
bar: 20
});
You should use prototypal inheritance isntead
function Button(options) {
'use strict';
var defaultOptions = {
templateName: '#button-tmpl',
title: "Label goes here",
type: "primary",
size: "medium",
disabled: null,
autosave: null,
href: null,
onclick: null
};
//debugger
//this.setOptions is not available here
this.setOptions(options, defaultOptions);
this.checkRequiredKeys('title');
return this;
};
Button.prototype = new AbstractComponent;

OO(object-oriented) javascript

I've a fair amount of experience with JavaScript, and for this new project I'm on (cms for blog with notions of profitability) I thought I'd step it up and write the JavaScript in an MVC fashion. I've been using a bit of backbone and underscore, but it isn't clicking mentally. any way, I've written a bit of code to handle some events/effects but it just doesn't work. If anyone could sort me out I'd really appreciate it.
// Semi Perfect grade 0 JS - Golden age
//partial View Objects | Events
var pshare_dock = {
actor: $("#share_dock"),
drag: function () {
this.actor.draggable();
}
}
pshare_dock.expand = function () {
this.actor.dblclick(function () {
$(this).toggleClass("share_close");
});
}
var pmenu = {
hover: function () {
$("ul.drop li.drop").hover(function () {
$(this).find('ul').fadeIn(1);
}, function () {
$(this).find('ul').hide();
})
},
navigate: function () {
$("a.ajx").click(function (e) {
var link;
var container = $("#content_pane");
e.preventDefault();
link = $(this).attr("href") + "#content_pane";
container.load(link);
})
}
}
var pcontent_pane = {}
var ppost = {}
var pdatabase_entry = {}
//Views
var Homepage = function () {
this.share_dock = function () {
new pshare_dock();
}
this.menu = function () {
new pmenu();
}
this.content_pane = function () {
new pcontent_pane();
}
this.posts = function () {
new ppost();
}
}
//Controller
var GoldenAgeRouter = Backbone.Router.extend({
routes: {
"!/": "defaultRoute",
"*actions": "defaultRoute"
},
defaultRoute: function (actions) {
var homeView = function () {
new Homepage();
}
}
})
$(document).ready(function () {
var Golden_age = function () {
new Homepage();
}
})
the question is essentially what all is wrong with this?
You're wrapping your instantiations in an anonymous function but not invoking them:
var Golden_age = new Homepage(); // Invoked.
var Golden_age = function(){ new Homepage(); } // Stored function, not invoked.

Categories

Resources