1
0
mirror of https://github.com/peeringdb/peeringdb.git synced 2024-05-11 05:55:09 +00:00
Files
peeringdb-peeringdb/peeringdb_server/static/20c/twentyc.core.js
Matt Griswold 5147028bee clean up / format / poetry (#1000)
* stub in poetry for pipenv

* re-add tester image

* add pre-commit / formatting

* fix ghactions

* revert test data whitespace, exclude tests/data

* revert ws

* decruft, rm tox/pipenv

* install dev packages for base image

* add lgtm config to force to py3
2021-07-10 10:12:35 -05:00

697 lines
16 KiB
JavaScript

(function() {
/**
* create root namespace for all twentyc definitions
* @module twentyc
*/
twentyc = {};
/**
* class helper functions
* @module twentyc
* @class cls
* @static
*/
/// Contains 7 sentences fo devs, no need for translation
twentyc.cls = {
/**
* converts a string into a standardized class name, replacing
* invalid characters with valid ones.
*
* twentyc.cls.make_name("class-a"); // classA
* twentyc.cls.make_name("class a b"); // classAB
* twentyc.cls.make_name("Class-A_B"); // ClassA_B
*
* @method make_name
* @param {String} name base name
* @returns {String} class_name changed name
*/
make_name : function(name) {
var i, names = name.split(/[-\s]/);
for(i = 0; i < names.length; i++) {
if(i > 0)
names[i] = names[i].charAt(0).toUpperCase() + names[i].slice(1);
}
return names.join("");
},
/**
* create a new class - if you wish to extend a class take a look at
* {{#crossLink "cls/extend:method"}}twentyc.cls.extend{{/crossLink}}
* instead
*
* you may define a constructor in the definition by using the class
* name you provide at __name__
*
* Note that if the name you provide is not a valid variable name
* it will be passed through twentyc.cls.make_name to make it valid
*
* ##examples
*
* * define and instantiate a class: examples/cls.define.js
*
* @method define
* @param {String} name name or unique identifier of the new class
* @param {Object} definition object literal of properties and functions that you wish define or redefine
* @returns {Function} dest constructor of new class
*/
define : function(name, definition) {
var k;
name = twentyc.cls.make_name(name);
if(typeof(definition[name]) == "function") {
// a constructor has been provided
var ctor = definition[name]
delete definition[name]
} else {
// no constructor provided, substitute empty constructor
var ctor = function(){};
}
// cycle through definition and copy to class prototype
for(k in definition) {
ctor.prototype[k] = definition[k]
}
// create meta information
ctor.prototype._meta = {
"name" : name
}
return ctor
},
/**
* extend an existing class with new properties and functions
*
* if a function is defined that already exists on the parent class
* this function will be overwritten and a reference to the original
* function will be provided at parentClassName_functionName
*
* you may define a constructor in the definition by using the class
* name you provide at __name__
*
* Note that if the name you provide is not a valid variable name
* it will be passed through twentyc.cls.make_name to make it valid
*
* ##examples
*
* * extend and instantiate a class: examples/cls.extend.js
* * handling method override: examples/cls.extend.method-override.js
*
* @method extend
* @param {String} name name or unqiue identifier of the new class
* @param {Object} definition object literal of properties and functions that you wish define or redefine
* @param {Function} [parent] constructor of class
* that you wish to extend, if omitted an empty function will be substituted
* @returns {Function} dest constructor of new class
*/
extend : function(name, definition, parent) {
var k;
name = twentyc.cls.make_name(name);
if(typeof(definition[name]) == "function") {
// a constructor has been provided
var ctor = definition[name]
delete definition[name]
} else {
// no constructor provided, substitute empty constructor
var ctor = function(){
parent.apply(this, arguments)
};
}
// cycle through parent prototype and copy to class prototype
for(k in parent.prototype) {
ctor.prototype[k] = parent.prototype[k]
}
// cycle through definition and copy to class prototype
for(k in definition) {
if(typeof(ctor.prototype[k]) == "function") {
// function was already defined by parent, store backref
ctor.prototype[parent.prototype._meta.name+"_"+k] = parent.prototype[k];
}
ctor.prototype[k] = definition[k]
}
// reference parent constructor
ctor.prototype[parent.prototype._meta.name] = parent
// create meta information
ctor.prototype._meta = {
"name" : name,
"parent" : parent
}
return ctor
},
/**
* overrides a method on the provided class
*
* @method override
* @param {Function} destClass A class created via __twentyc.cls.define__ or __twentyc.cls.extend__
* @param {String} methodName name of method that you wish to override
* @param {Function} method new method
*/
override : function(destClass, methodName, method) {
// create reference to old method
if(destClass.prototype[methodName])
destClass.prototype[destClass.prototype._meta.name+"_"+methodName] = destClass.prototype[methodName];
// override
destClass.prototype[methodName] = method;
}
}
/**
* class registry object - allows you to quickly define and extend
* similar classes
*
* @module twentyc
* @namespace twentyc.cls
* @class Registry
* @constructor
*/
twentyc.cls.Registry = twentyc.cls.define(
"Registry",
{
Registry : function() {
/**
* holds the classes defined in this registry
* @property _classes
* @type Object
* @private
*/
this._classes = {}
},
/**
* register a new class - look at twentyc.cls.define and twentyc.cls.extend for param
* explanation
* @method register
* @param {String} name class name
* @param {Object} definition class definition
* @param {String} extend if passed, extend this class from this class (needs to exist in the Registry)
* @returns {Function} class ctor of the newly created class
*/
register : function(name, definition, extend) {
if(this._classes[name] != undefined) {
throw("Class with name '"+name+"' already exists - name must be unique in the Registry");
}
if(extend && typeof this._classes[extend] != "function") {
throw("Trying to extend from class unknown to this Registry: "+extend);
}
if(extend)
this._classes[name] = twentyc.cls.extend(name, definition, this._classes[extend]);
else
this._classes[name] = twentyc.cls.define(name, definition);
return this._classes[name];
},
/**
* get a registered class constructor
* @method get
* @param {String} name class name
* @returns {Function} class ctor of registered class
*/
get : function(name) {
if(typeof this._classes[name] != "function")
throw("Trying to retrieve class unknown to this Registry: "+name);
return this._classes[name];
},
/**
* See if a class constructor for the specified name exists
* @method has
* @param {String} name class name
* @returns {Boolean} exists
*/
has : function(name) {
return (typeof this._classes[name] == "function")
}
}
);
/**
* utility functions
* @module twentyc
* @class util
* @static
*/
twentyc.util = {
/**
* retrieve value from object literal - allows you to pass null or undefined
* as the object and will return null if you do
*
* @method get
* @param {Object} obj
* @param {String) key
* @param {Mixed} [default] return this if obj is null or key does not exist
* @returns {Mixed} value
*/
get : function(obj, key, dflt) {
if(!obj || obj[key] == undefined)
return dflt;
return obj[key]
},
/**
* requires a namespace exist, any keys that do not exist will
* be created as object literals
*
* @method require_namespace
* @param {String} namespace "." separated namespace
*/
require_namespace : function(namespace) {
var tokens = namespace.split("."),
i,
t,
container=window;
for(i = 0; i < tokens.length; i++) {
t = tokens[i];
if(typeof container[t] == "undefined") {
container[t] = {}
}
container = container[t];
}
return container;
}
}
/**
* data retrieval, storage and management
*
* assures that data is only retrieved once even when multiple sources
* are requesting it.
*
* data is cached locally for quick retrieval afterwards
*
* @module twentyc
* @class data
* @static
*/
twentyc.data = {
/**
* fires everytime a dataset is retrieved from the server - __*__ is
* substituted with the data id
*
* all handlers bound to this event will be removed after its been
* triggered - in order to permanently subscribe a load event
* subscribe to "load"
*
* @event load-*
* @param {String} id data id
* @param {Object} data object literal of retrieved data
*/
/**
* fires everytime a dataset is retrieved from the server
*
* @event load
* @param {String} id data id
* @param {Object} data object literal of retrieved data
*/
/**
* fires when all loads are finished
*
* @event done
*/
/**
* keeps track of current loading status
* @property _loading
* @type Object
* @private
*/
_loading : {},
/**
* _data storage, keyed by data id
* @property data
* @type Object
* @private
*/
_data : {},
/**
* attempts to retrieve and return a data set
*
* ##example(s)
*
* examples/data.load.js
*
* @method get
* @param {String} id data id
* @returns {Object} data
*/
get : function(id) {
return tc.u.get(this._data, id, {})
},
/**
* retrieve data from server. at this point this expects a json
* string response, with the actual data keyed at the id you provided
*
* ##example(s)
*
* examples/data.load.js
*
* @method load
* @param {String} id data identification key
* @param {Object} [config] object literal holding options
* @param {Function} [config.callback] called when data is available
* @param {Boolean} [config.reload] if true data will be re-fetched even if already loaded
*/
load : function(id, config) {
var callback = tc.u.get(config, "callback");
// check if data is already loaded
if(this._data[id] && !tc.u.get(config, "reload")) {
var payload = {id:id, data:this._data[id]}
$(this).trigger("load-"+id, payload);
if(callback)
callback(payload)
return;
}
// attach callback to load event
if(callback) {
$(this).on("load-"+id, function(ev, payload) { callback(payload) });
}
// check if data is currently being loaded
if(this._loading[id]) {
return;
}
// data is not loaded AND not currently loading, attempt to load data
// in order to load data we need to find a suitable loader for it
var loader = this.loaders.loader(id, config);
this._loading[id] = new Date().getTime()
loader.load(
{
success : function(data) {
twentyc.data._data[id] = data
twentyc.data._loading[id] = false;
$(twentyc.data).trigger("load", { id:id, data:data} );
$(twentyc.data).trigger("load-"+id, { id:id, data:data });
$(twentyc.data).off("load-"+id);
twentyc.data.done();
}
}
);
},
done : function(callback) {
var i;
if(callback) {
$(this).on("done", callback);
$(this).on("done", function() { $(twentyc.data).off("done", callback); });
}
for(i in this._loading) {
if(twentyc.data._loading[i]) {
return;
}
}
$(this).trigger("done", {});
}
}
/**
* create and manage data loaders
* @module twentyc
* @namesapce twentyc.data
* @class loaders
* @extends twentyc.cls.Registry
* @static
*/
twentyc.data.LoaderRegistry = twentyc.cls.extend(
"LoaderRegistry",
{
LoaderRegistry : function() {
this.Registry();
/**
* holds data id -> loader assignments, keyed by data id
* @property _loaders
* @type Object
* @private
*/
this._loaders = {};
},
/**
* assign loader to data id
* @method assign
* @param {String} id data id
* @param {String} loaderName name that you registered the loader under
*/
assign : function(id, loaderName) {
// this will error if loaderName is not registered
var loader = this.get(loaderName);
// link loader
this._loaders[id] = loaderName;
},
/**
* get loader linked to data id via __link__
* @method loader
* @param {String} id data_id
* @returns {Object} loader instance of loader
*/
loader : function(id, config) {
if(!this._loaders[id])
throw("Could not find suitable loader for data id "+id+", are you certain it's assigned?");
var loader = this.get(this._loaders[id]);
return new loader(id, config || {});
}
},
twentyc.cls.Registry
);
twentyc.data.loaders = new twentyc.data.LoaderRegistry();
/**
* any new loader you register should at the very least
* extend this loader
*
* @class Base
* @module twentyc
* @namespace twentyc.data.loaders.get
* @constructor
* @param {String} id data id
* @param {Object} [config] object literal holding config attributes
*/
twentyc.data.loaders.register(
"Base",
{
Base : function(id, config) {
this.dataId = id;
this.config = config || {};
},
retrieve : function(data) {
var set = tc.u.get(data, this.dataId)
if(typeof set == undefined)
return {};
return set;
},
load : function() {
throw("The load() function needs to be overwritten to do something")
}
}
);
/**
* you may use this loader as a base class for any xhr data retrieval
* loaders you define. during the ctor you will need to set the url
* attribute on this.config
*
* @class XHRGet
* @module twentyc
* @namespace twentyc.data.loaders._classes
* @constructor
* @param {String} id data id
* @param {Object} [config] object literal holding config attributes
* @param {String} [config.url] url of the request
* @param {Object} [config.data] parameters to send
*/
twentyc.data.loaders.register(
"XHRGet",
{
load : function(callbacks) {
var url = tc.u.get(this.config, "url");
var loader = this;
if(url == undefined)
throw("XHRGet loader needs url, "+this._meta.name);
$.ajax(
{
url : url,
data : this.config.data,
success : function(data) {
if(typeof callbacks.success == "function")
callbacks.success(loader.retrieve(data));
}
}
).fail(function(response) {
if(callbacks.error)
callbacks.error(response);
});
}
},
"Base"
);
/**
* Timeout that will reset itself when invoked again before
* execution
*
* @class SmartTimeout
* @namespace twentyc.util
* @constructor
* @param {Function} callback
* @param {Number} interval trigger in N ms
*/
twentyc.util.SmartTimeout = twentyc.cls.define(
"SmartTimeout",
{
SmartTimeout : function(callback, interval) {
this.set(callback, interval);
},
/**
* Reset / start the timeout
* @method set
* @param {Function} callback
* @param {Number} interval trigger in N ms
*/
set : function(callback, interval) {
this.cancel();
this._timeout = setTimeout(callback, interval);
},
/**
* Cancel timeout
* @method cancel
*/
cancel : function() {
if(this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}
}
}
);
/**
* jQuery helper functions
*
* @class jq
* @static
* @namespace twentyc
*/
twentyc.jq = {
/**
* define a jquery plugin
*
* @method plugin
* @param {String} name plugin name as it will be used to access the plugin
* on the jquery resultset
* @param {Object} definition object literal defining methods of the plugin
* @param {Object} config object literal with default plugin config
*/
plugin : function(name, definition, config) {
if(!definition.init) {
throw("Plugin definition for jQuery."+name+" missing init method");
}
jQuery.fn[name] = function(arg) {
if(definition[arg]) {
return definition[arg].apply(this, Array.prototype.slice.call(arguments, 1));
} else if(typeof arg === "object" || !arg) {
var opt = jQuery.extend(config || {}, arg);
return definition.init.call(this, opt);
} else {
throw("Method "+arg+" does not exist on jQuery."+name);
}
}
}
}
/**
* shortcuts
*/
tc = {
u : twentyc.util,
def : twentyc.cls.define,
ext : twentyc.cls.extend
}
})();