Advertisement
Guest User

Untitled

a guest
Jul 22nd, 2018
58
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.58 KB | None | 0 0
  1. // Copyright @2011, David Atchley (david.m.atchley@gmail.com)
  2.  
  3. /**
  4. * @fileOverview
  5. * <p>
  6. * JSM stands for JavaScript Module, and is based on Perl's module and exporter
  7. * facilities. Combines a number of those ideas from Perl and from the existing
  8. * JSAN facility, though with some slight changes.
  9. * </p>
  10. *
  11. * <p>
  12. * Provides a Package/Module management facility similar to Perl's Exporter
  13. * style modules for JavaScript. Javascript modules can be created by including
  14. * a simple Package = {...} declaration at the top of a javascript source file.
  15. * The Package declaration defines information about the module, specifically:
  16. * </p>
  17. *
  18. <code><pre>
  19. Package = {
  20. NAME: the Module's name
  21. VERSION: the version of the module
  22. DEFAULT: the default symbols available via Module name, e.g. Module.<symbol>
  23. EXPORT: a set of default symbols to export to caller's namespace
  24. (no Module prefix qualitifications)
  25. EXPORT_OK: a list of symbols allowed to exported on request
  26. EXPORT_TAGS: names that represent useful sets of symbols to export
  27. }
  28. </pre></code>
  29. *
  30. * <p>The module can then be used by importing it using this JSM singleton. There are
  31. * various ways to import,</p>
  32. *
  33. <code><pre>
  34. JSM.use('Foo'); // import module Foo
  35. JSM.use('Foo', 1.0) // import module Foo, if version is >= 1.0
  36.  
  37. // Each of the above creates a Foo object (namespace) that has the
  38. // methods that Foo makes available (via DEFAULT).
  39.  
  40. JSM.use('Foo', [ 'bar', 'getters:', /^foo/ ]);
  41.  
  42. // This call loads the module Foo, which still makes all the default
  43. // symbols available in the Foo namespace, but additionally exports
  44. // the symbol 'bar', all symbols that are part of the 'getters' tag
  45. // and all symbols matching the RegExp, e.g. those starting with 'foo'
  46.  
  47. </code></pre>
  48. *
  49. * <p>
  50. * The Javascript file used a module can contain any style of coding and might be
  51. * a useful set of functions (function library) or a set of classes (class library);
  52. * but represents a reusable, modular set of functionality. The only requirement to
  53. * be a Javascript Module is that it have the Package declaration at the top.
  54. * </p>
  55. *
  56. * @author david.m.atchley@gmail.com, (Dave Atchley)
  57. * @version 0.1
  58. */
  59.  
  60.  
  61. /**
  62. * <p>
  63. * The JavaScript Module (JSM) class is a singleton allowing the
  64. * management, loading and inclusion of JSM modules. JSM is designed
  65. * in a similar fashion to Perl's Module and Exporter facilities.
  66. * JSM will import modules, which can be any javascript file that
  67. * includes a valid <b>Package</b> header. See the file overview
  68. * for information on the Package declaration.</p>
  69. *
  70. * <p>The class can be used as follows:</p>
  71. *
  72. *
  73. <code><pre>
  74. // Import the module and it's default symbols
  75. JSM.use('Math.Complex');
  76.  
  77. // Import the module, but only if it's version is >= 2.0
  78. JSM.use('Math.Complex', 2.0);
  79.  
  80. // Import the module, request specific symbols exported to
  81. // calling namespace (defaults still available via Module)
  82. JSM.use('Math.Complex', [ 'add', 'mult']);
  83.  
  84. </pre></code>
  85. *
  86. * @class
  87. */
  88. var JSM = (function() {
  89. /** @lends JSM */
  90.  
  91. /** @private version of JSM being run */
  92. var version = 0.1,
  93.  
  94. /** @private The include path used by JSM to find modules, can be set with {@link setIncludePath} */
  95. inc = ['.', 'lib/js'],
  96.  
  97. /** @private timeout value (milliseconds) for waiting for modules to load */
  98. poll_timeout = 200,
  99.  
  100. /** @private number of attempts to try loading a given script url */
  101. tries = 3,
  102. attempted = 3, // track attempts here
  103.  
  104. /** @private global namespace for exporting, can be set with {@link setGlobalScope} */
  105. globalcontext = self,
  106.  
  107. /** @private keep track of loaded files */
  108. loaded = [];
  109.  
  110. /**
  111. * <p>Check to see if the XHR object has loaded the given url
  112. * and whether we have exceeded our timeout thresh hold {@link poll_timeout}.
  113. * If timeout hasn't been exceeded, reset the poll and continue.
  114. * Otherwise throw an exception.</p>
  115. *
  116. * <p>JSM will poll {@link tries} times (in {@link poll_timeout} intervals)
  117. * to see if the module is loaded. After that it bails.</p>
  118. *
  119. * @function
  120. * @param {Object} xhrobj the XMLHTTPRequest object being used
  121. * @param {String} url the url of the file it's attempting to load
  122. */
  123. var poll = function(url) {
  124. setTimeout(function() {
  125. attempted--;
  126. if (loaded.indexOf(url) != -1) {
  127. // NOOP: Loaded successfully, module script is in {@link loaded}
  128. }
  129. else if (attempted > 0) {
  130. // Continue: keep trying ...
  131. poll(url);
  132. }
  133. else {
  134. // We've timed out looking/loading script, reset attempts and error out
  135. attempted = tries;
  136. throw new Error("JSM: (XHR): loading of " + url + " timed out");
  137. }
  138. }, poll_timeout);
  139. }
  140.  
  141. /**
  142. * Will attempt to load the given url via XHR (synchronously).
  143. * If the the requested javascript file is not found, it
  144. * returns null. Otherwise it returns the javascript code as
  145. * a string.
  146. * <p>This is a synchronous XMLHTTPRequest call that uses polling
  147. * to check on the state of things, throwing an exception if the
  148. * {@link poll_timeout} value is reached.</p>
  149. *
  150. * @function
  151. * @param {String} url the url of the javascript module to load
  152. * @returns {String|null} the javascript contained in the file
  153. * or null if not found.
  154. */
  155. var loadJSFromURL = function(url) {
  156. if (typeof XMLHttpRequest != 'undefined') {
  157. var ajax = new XMLHttpRequest();
  158. }
  159. else {
  160. var ajax = new ActiveXObject("Microsoft.XMLHTTP");
  161. }
  162. ajax.open("GET", url, false);
  163. try {
  164. ajax.send(null);
  165. var stat = ajax.status;
  166. if (stat == 200 || stat == 304 || stat == 0 || stat == null) {
  167. var responseText = ajax.responseText;
  168. return responseText;
  169. }
  170. }
  171. catch (e) {
  172. // throw new Error("File not found: " + url);
  173. return null;
  174. }
  175. // throw new Error("File not found: " + url);
  176. return null;
  177. }
  178.  
  179. /**
  180. * Given a DEFAULT defined in the package header for a
  181. * module, returns the list of default symbols to import
  182. * for the module being processed.
  183. *
  184. * @function
  185. * @param {Object} pkghdr package haeader from the module
  186. * @returns {Array} list of default symbols to import
  187. */
  188. var getDefaultImportList = function(pkghdr) {
  189. var DEFAULTS = pkghdr['DEFAULT'] || [];
  190.  
  191. var default_list = [];
  192.  
  193. // The DEFAULT is the always available interface
  194. // provided by the module.
  195. for (var i = 0; i < DEFAULTS.length; i++) {
  196. default_list.push(DEFAULTS[i]);
  197. }
  198. return default_list;
  199. }
  200.  
  201. /**
  202. * Given the EXPORT* definitions in a package header, returns
  203. * the available symbols for export for the module being processed.
  204. * @function
  205. * @param {Object} pkghdr package header from the module
  206. * @param {Array} symbols an array of requested symbols to import
  207. */
  208. var getExportList = function(pkghdr, symbols) {
  209. var EXPORTS = pkghdr['EXPORT'] || [];
  210. var EXPORT_OK = pkghdr['EXPORT_OK'] || [];
  211. var EXPORT_TAGS = pkghdr['EXPORT_TAGS'] || {};
  212. var ALL_EXPORTS = EXPORTS.concat(EXPORT_OK);
  213.  
  214. var export_list = [];
  215.  
  216. if (!symbols || symbols.length == 0) {
  217. for (var i = 0; i < EXPORTS.length; i++) {
  218. var symbol = EXPORTS[i];
  219. export_list.push(symbol);
  220. }
  221. }
  222. else {
  223. for (var i = 0; i < symbols.length; i++) {
  224. var symbol = symbols[i];
  225.  
  226. // TAG: add all symbols from the matching tag [ex., 'all:']
  227. if (!(symbol instanceof RegExp) && symbol.match(/.*:$/)) {
  228. var symbol = symbol.replace(':','');
  229. for (tag in EXPORT_TAGS) {
  230. if (tag == symbol) {
  231. EXPORT_TAGS[tag].forEach(function(v,i) {
  232. export_list.push(v);
  233. });
  234. }
  235. }
  236. continue;
  237. }
  238. // REGEXP: add any symbols that match [ex., /^get/ ]
  239. else if (symbol instanceof RegExp) {
  240. ALL_EXPORTS.forEach(function(v,i) {
  241. if (v.match(symbol)) {
  242. export_list.push(v);
  243. }
  244. });
  245. continue;
  246. }
  247. // SYMBOL: add all matching symbols [ex., 'getTimer']
  248. else {
  249. ALL_EXPORTS.forEach(function(v,i) {
  250. if (v == symbol) {
  251. export_list.push(v);
  252. }
  253. });
  254. continue;
  255. }
  256. }
  257. }
  258. return export_list;
  259. }
  260.  
  261. /**
  262. * <p>Import the package requested. Optionally, a specific
  263. * version can be requested and specific symbols (ONLY)
  264. * can also be requested for export.</p>
  265. *
  266. * <p>Calls {@link loadJSFromURL} to load the requested package.</p>
  267. *
  268. * @function
  269. * @param {String} pkgname package name of the module to import
  270. * @param {Number} [version] a specific version number of the module
  271. * @param {Array} [symbols] a specific list of symbols to export
  272. */
  273. var import = function(pkgname, version, symbols) {
  274. var scriptUrl = pkgname.replace('.','/','gi') + '.js';
  275.  
  276. if (loaded.indexOf(scriptUrl) != -1) {
  277. throw new Error("Module " + pkgname + " already loaded, skipping.");
  278. }
  279.  
  280. // Try loading the script via each include path
  281. for (var i = 0; i < inc.length; i++) {
  282. var js,
  283. url = inc[i] + '/' + scriptUrl;
  284.  
  285. try {
  286. poll(scriptUrl); // Poll for the package being loaded
  287. js = loadJSFromURL(url);
  288. }
  289. catch (e) {
  290. if (i == (inc.length - 1)) {
  291. // Only rethrow the error if we've depelted our include path search
  292. throw e;
  293. }
  294. }
  295.  
  296. if (js) {
  297. var ns = createNamespace(pkgname);
  298. (function() {
  299. // Eval the returned javascript making it available in local scope
  300. eval(js);
  301.  
  302. // If we have the right Module and Version, continue building namespace
  303. if (typeof Package != 'undefined' && Package['NAME'] == pkgname) {
  304.  
  305. if (version && Package['VERSION'] && Package['VERSION'] < version) {
  306. throw new Error("module " + Package['NAME'] + '(' + Package['VERSION'] + "), requested version " + version + " or higher");
  307. }
  308.  
  309. // Modules should ALWAYS define some kind of interface in DEFAULT
  310. if (!Package['DEFAULT'] || Package['DEFAULT'].length == 0) {
  311. throw new Error("module " + Package['NAME'] + " does not have an available interface");
  312. }
  313.  
  314. // Track valid modules so we don't reload later
  315. loaded.push(scriptUrl);
  316.  
  317. // Add Package info to our locally built namespace
  318. ns['Package'] = Package;
  319.  
  320. //
  321. // Import Default Namespace
  322. //
  323. var default_import_list = getDefaultImportList(Package);
  324. default_import_list.forEach(function(v,i) {
  325. if (typeof eval(v) != 'undefined') {
  326. console.log("\t importing: " + v);
  327. ns[v] = eval(v); // accessible via Module name qualifier
  328. }
  329. });
  330.  
  331. //
  332. // Export to Namespace
  333. //
  334.  
  335. // Handle any symbols requested for 'export'
  336. var export_symbols_list = getExportList(Package, symbols);
  337.  
  338. // Requested symbols are exported to caller's scope
  339. export_symbols_list.forEach(function(v,i) {
  340. if (typeof eval(v) != 'undefined') {
  341. console.log("\t exporting: " + v);
  342. globalcontext[v] = ns[v] = eval(v); // export to global scope here
  343. }
  344. });
  345. }
  346. else {
  347. throw new Error('Invalid JSM Package format in ' + scriptUrl);
  348. }
  349. })();
  350. return ns;
  351. }
  352. }
  353. // I find your lack of script disturbing....
  354. throw new Error("Couldn't find script in JSM.inc = [" + inc + "]");
  355. }
  356.  
  357. /**
  358. * Create a new namespace based on a package name of a module.
  359. * @function
  360. * @param {String} pkgname the name of the package containing the module
  361. * @returns {Object} the new namespace to contain the module's symbols
  362. */
  363. var createNamespace = function(pkgname) {
  364. var parts = name.split('.');
  365. var container = {};
  366.  
  367. for (var i = 0; i < parts.length; i++) {
  368. if (!container[parts[i]]) {
  369. container[parts[i]] = {};
  370. }
  371. else if (typeof container[parts[i]] != 'object') {
  372. var n = parts.slice(0,i).join('.');
  373. throw new Error(n + " module already exists and is not an object");
  374. }
  375. container = container[parts[i]];
  376. }
  377.  
  378. // Namespace is created in the global context
  379. globalcontext[pkgname] = container;
  380. return container;
  381. }
  382.  
  383. return {
  384.  
  385. /**
  386. * Add a new path to the JSM include path list
  387. * @function
  388. * @param {String|String[]}
  389. */
  390. addIncludePath: function(/* single path or array */) {
  391. var paths = [].slice.apply(arguments, 0);
  392. inc.concat(paths);
  393. },
  394.  
  395. /**
  396. * Return the JSM include path list used to find modules
  397. * @function
  398. * @returns {Array} the JSM include path
  399. */
  400. getIncludePath: function() {
  401. return inc;
  402. },
  403.  
  404. /**
  405. * Set the global scope for exporting symbols. Call this
  406. * prior to calling {@link JSM.use}
  407. * @function
  408. * @param {Object} context object to use when exporting symbols globally
  409. */
  410. setGlobalScope: function(context) {
  411. if (typeof context !== 'undefined')
  412. throw new TypeError;
  413. globalcontext = context;
  414. },
  415.  
  416. /**
  417. * Dynamically load and import a module from a javascript package.
  418. * @function
  419. * @param {String} pkgname the name of the module to import
  420. * @param {Number} [version] require a specific version of this module
  421. * @param {Array} [symbols] any specific symbols to export from module
  422. *
  423. * @returns {Object} reference to the local namespace for module
  424. */
  425. use: function(/* pkgname, version, [symbol, ...] */) {
  426. var args = [].slice.call(arguments, 0);
  427.  
  428. // Check module name, first parameter: e.g., JSM.use('Mod')
  429. if (!args[0] && typeof args[0] !== 'string')
  430. throw new TypeError("expect module name as string in first parameter");
  431.  
  432. // Get the package name and version requested (if any)
  433. var pkgname = args.shift();
  434. if (args.length >= 1 && typeof args[0] == 'number') {
  435. var version = args.shift();
  436. }
  437. if (args.length >= 1 && typeof args[0] == 'object') {
  438. var symbols = args.shift();
  439. }
  440.  
  441. // Import the script (with optional, specific version)
  442. var ns = import(pkgname, version, symbols);
  443.  
  444. // Get symbols to import to caller's namespace (if any): eg., JSM.use('Mod', fn, fn2,...)
  445. return ns;
  446. }
  447. }
  448. /* end return */
  449.  
  450. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement