elgglib.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. /**
  2. * @namespace Singleton object for holding the Elgg javascript library
  3. */
  4. var elgg = elgg || {};
  5. /**
  6. * Pointer to the global context
  7. *
  8. * @see elgg.require
  9. * @see elgg.provide
  10. */
  11. elgg.global = this;
  12. /**
  13. * Duplicate of the server side ACCESS_PRIVATE access level.
  14. *
  15. * This is a temporary hack to prevent having to mix up js and PHP in js views.
  16. */
  17. elgg.ACCESS_PRIVATE = 0;
  18. /**
  19. * Convenience reference to an empty function.
  20. *
  21. * Save memory by not generating multiple empty functions.
  22. */
  23. elgg.nullFunction = function() {};
  24. /**
  25. * This forces an inheriting class to implement the method or
  26. * it will throw an error.
  27. *
  28. * @example
  29. * AbstractClass.prototype.toBeImplemented = elgg.abstractMethod;
  30. */
  31. elgg.abstractMethod = function() {
  32. throw new Error("Oops... you forgot to implement an abstract method!");
  33. };
  34. /**
  35. * Merges two or more objects together and returns the result.
  36. */
  37. elgg.extend = jQuery.extend;
  38. /**
  39. * Check if the value is an array.
  40. *
  41. * No sense in reinventing the wheel!
  42. *
  43. * @param {*} val
  44. *
  45. * @return boolean
  46. */
  47. elgg.isArray = jQuery.isArray;
  48. /**
  49. * Check if the value is a function.
  50. *
  51. * No sense in reinventing the wheel!
  52. *
  53. * @param {*} val
  54. *
  55. * @return boolean
  56. */
  57. elgg.isFunction = jQuery.isFunction;
  58. /**
  59. * Check if the value is a "plain" object (i.e., created by {} or new Object())
  60. *
  61. * No sense in reinventing the wheel!
  62. *
  63. * @param {*} val
  64. *
  65. * @return boolean
  66. */
  67. elgg.isPlainObject = jQuery.isPlainObject;
  68. /**
  69. * Check if the value is a string
  70. *
  71. * @param {*} val
  72. *
  73. * @return boolean
  74. */
  75. elgg.isString = function(val) {
  76. return typeof val === 'string';
  77. };
  78. /**
  79. * Check if the value is a number
  80. *
  81. * @param {*} val
  82. *
  83. * @return boolean
  84. */
  85. elgg.isNumber = function(val) {
  86. return typeof val === 'number';
  87. };
  88. /**
  89. * Check if the value is an object
  90. *
  91. * @note This returns true for functions and arrays! If you want to return true only
  92. * for "plain" objects (created using {} or new Object()) use elgg.isPlainObject.
  93. *
  94. * @param {*} val
  95. *
  96. * @return boolean
  97. */
  98. elgg.isObject = function(val) {
  99. return typeof val === 'object';
  100. };
  101. /**
  102. * Check if the value is undefined
  103. *
  104. * @param {*} val
  105. *
  106. * @return boolean
  107. */
  108. elgg.isUndefined = function(val) {
  109. return val === undefined;
  110. };
  111. /**
  112. * Check if the value is null
  113. *
  114. * @param {*} val
  115. *
  116. * @return boolean
  117. */
  118. elgg.isNull = function(val) {
  119. return val === null;
  120. };
  121. /**
  122. * Check if the value is either null or undefined
  123. *
  124. * @param {*} val
  125. *
  126. * @return boolean
  127. */
  128. elgg.isNullOrUndefined = function(val) {
  129. return val == null;
  130. };
  131. /**
  132. * Throw an exception of the type doesn't match
  133. *
  134. * @todo Might be more appropriate for debug mode only?
  135. */
  136. elgg.assertTypeOf = function(type, val) {
  137. if (typeof val !== type) {
  138. throw new TypeError("Expecting param of " +
  139. arguments.caller + "to be a(n) " + type + "." +
  140. " Was actually a(n) " + typeof val + ".");
  141. }
  142. };
  143. /**
  144. * Throw an error if the required package isn't present
  145. *
  146. * @param {String} pkg The required package (e.g., 'elgg.package')
  147. */
  148. elgg.require = function(pkg) {
  149. elgg.assertTypeOf('string', pkg);
  150. var parts = pkg.split('.'),
  151. cur = elgg.global,
  152. part, i;
  153. for (i = 0; i < parts.length; i += 1) {
  154. part = parts[i];
  155. cur = cur[part];
  156. if (elgg.isUndefined(cur)) {
  157. throw new Error("Missing package: " + pkg);
  158. }
  159. }
  160. };
  161. /**
  162. * Generate the skeleton for a package.
  163. *
  164. * <pre>
  165. * elgg.provide('elgg.package.subpackage');
  166. * </pre>
  167. *
  168. * is equivalent to
  169. *
  170. * <pre>
  171. * elgg = elgg || {};
  172. * elgg.package = elgg.package || {};
  173. * elgg.package.subpackage = elgg.package.subpackage || {};
  174. * </pre>
  175. *
  176. * @example elgg.provide('elgg.config.translations')
  177. *
  178. * @param {string} pkg The package name.
  179. */
  180. elgg.provide = function(pkg, opt_context) {
  181. elgg.assertTypeOf('string', pkg);
  182. var parts = pkg.split('.'),
  183. context = opt_context || elgg.global,
  184. part, i;
  185. for (i = 0; i < parts.length; i += 1) {
  186. part = parts[i];
  187. context[part] = context[part] || {};
  188. context = context[part];
  189. }
  190. };
  191. /**
  192. * Inherit the prototype methods from one constructor into another.
  193. *
  194. * @example
  195. * <pre>
  196. * function ParentClass(a, b) { }
  197. *
  198. * ParentClass.prototype.foo = function(a) { alert(a); }
  199. *
  200. * function ChildClass(a, b, c) {
  201. * //equivalent of parent::__construct(a, b); in PHP
  202. * ParentClass.call(this, a, b);
  203. * }
  204. *
  205. * elgg.inherit(ChildClass, ParentClass);
  206. *
  207. * var child = new ChildClass('a', 'b', 'see');
  208. * child.foo('boo!'); // alert('boo!');
  209. * </pre>
  210. *
  211. * @param {Function} Child Child class constructor.
  212. * @param {Function} Parent Parent class constructor.
  213. */
  214. elgg.inherit = function(Child, Parent) {
  215. Child.prototype = new Parent();
  216. Child.prototype.constructor = Child;
  217. };
  218. /**
  219. * Converts shorthand urls to absolute urls.
  220. *
  221. * If the url is already absolute or protocol-relative, no change is made.
  222. *
  223. * elgg.normalize_url(''); // 'http://my.site.com/'
  224. * elgg.normalize_url('dashboard'); // 'http://my.site.com/dashboard'
  225. * elgg.normalize_url('http://google.com/'); // no change
  226. * elgg.normalize_url('//google.com/'); // no change
  227. *
  228. * @param {String} url The url to normalize
  229. * @return {String} The extended url
  230. */
  231. elgg.normalize_url = function(url) {
  232. url = url || '';
  233. elgg.assertTypeOf('string', url);
  234. function validate(url) {
  235. url = elgg.parse_url(url);
  236. if (url.scheme){
  237. url.scheme = url.scheme.toLowerCase();
  238. }
  239. if (url.scheme == 'http' || url.scheme == 'https') {
  240. if (!url.host) {
  241. return false;
  242. }
  243. /* hostname labels may contain only alphanumeric characters, dots and hypens. */
  244. if (!(new RegExp("^([a-zA-Z0-9][a-zA-Z0-9\\-\\.]*)$", "i")).test(url.host) || url.host.charAt(-1) == '.') {
  245. return false;
  246. }
  247. }
  248. /* some schemas allow the host to be empty */
  249. if (!url.scheme || !url.host && url.scheme != 'mailto' && url.scheme != 'news' && url.scheme != 'file') {
  250. return false;
  251. }
  252. return true;
  253. };
  254. // ignore anything with a recognized scheme
  255. if (url.indexOf('http:') === 0 ||
  256. url.indexOf('https:') === 0 ||
  257. url.indexOf('javascript:') === 0 ||
  258. url.indexOf('mailto:') === 0 ) {
  259. return url;
  260. }
  261. // all normal URLs including mailto:
  262. else if (validate(url)) {
  263. return url;
  264. }
  265. // '//example.com' (Shortcut for protocol.)
  266. // '?query=test', #target
  267. else if ((new RegExp("^(\\#|\\?|//)", "i")).test(url)) {
  268. return url;
  269. }
  270. // watch those double escapes in JS.
  271. // 'install.php', 'install.php?step=step'
  272. else if ((new RegExp("^[^\/]*\\.php(\\?.*)?$", "i")).test(url)) {
  273. return elgg.config.wwwroot + url.ltrim('/');
  274. }
  275. // 'example.com', 'example.com/subpage'
  276. else if ((new RegExp("^[^/]*\\.", "i")).test(url)) {
  277. return 'http://' + url;
  278. }
  279. // 'page/handler', 'mod/plugin/file.php'
  280. else {
  281. // trim off any leading / because the site URL is stored
  282. // with a trailing /
  283. return elgg.config.wwwroot + url.ltrim('/');
  284. }
  285. };
  286. /**
  287. * Displays system messages via javascript rather than php.
  288. *
  289. * @param {String} msgs The message we want to display
  290. * @param {Number} delay The amount of time to display the message in milliseconds. Defaults to 6 seconds.
  291. * @param {String} type The type of message (typically 'error' or 'message')
  292. * @private
  293. */
  294. elgg.system_messages = function(msgs, delay, type) {
  295. if (elgg.isUndefined(msgs)) {
  296. return;
  297. }
  298. var classes = ['elgg-message'],
  299. messages_html = [],
  300. appendMessage = function(msg) {
  301. messages_html.push('<li class="' + classes.join(' ') + '"><p>' + msg + '</p></li>');
  302. },
  303. systemMessages = $('ul.elgg-system-messages'),
  304. i;
  305. //validate delay. Must be a positive integer.
  306. delay = parseInt(delay || 6000, 10);
  307. if (isNaN(delay) || delay <= 0) {
  308. delay = 6000;
  309. }
  310. //Handle non-arrays
  311. if (!elgg.isArray(msgs)) {
  312. msgs = [msgs];
  313. }
  314. if (type === 'error') {
  315. classes.push('elgg-state-error');
  316. } else {
  317. classes.push('elgg-state-success');
  318. }
  319. msgs.forEach(appendMessage);
  320. if (type != 'error') {
  321. $(messages_html.join('')).appendTo(systemMessages)
  322. .animate({opacity: '1.0'}, delay).fadeOut('slow');
  323. } else {
  324. $(messages_html.join('')).appendTo(systemMessages);
  325. }
  326. };
  327. /**
  328. * Wrapper function for system_messages. Specifies "messages" as the type of message
  329. * @param {String} msgs The message to display
  330. * @param {Number} delay How long to display the message (milliseconds)
  331. */
  332. elgg.system_message = function(msgs, delay) {
  333. elgg.system_messages(msgs, delay, "message");
  334. };
  335. /**
  336. * Wrapper function for system_messages. Specifies "errors" as the type of message
  337. * @param {String} errors The error message to display
  338. * @param {Number} delay How long to dispaly the error message (milliseconds)
  339. */
  340. elgg.register_error = function(errors, delay) {
  341. elgg.system_messages(errors, delay, "error");
  342. };
  343. /**
  344. * Logs a notice about use of a deprecated function or capability
  345. * @param {String} msg The deprecation message to display
  346. * @param {Number} dep_version The version the function was deprecated for
  347. * @since 1.9
  348. */
  349. elgg.deprecated_notice = function(msg, dep_version) {
  350. if (elgg.is_admin_logged_in()) {
  351. var parts = elgg.release.split('.');
  352. var elgg_major_version = parseInt(parts[0], 10);
  353. var elgg_minor_version = parseInt(parts[1], 10);
  354. var dep_major_version = Math.floor(dep_version);
  355. var dep_minor_version = 10 * (dep_version - dep_major_version);
  356. msg = "Deprecated in Elgg " + dep_version + ": " + msg;
  357. if ((dep_major_version < elgg_major_version) || (dep_minor_version < elgg_minor_version)) {
  358. elgg.register_error(msg);
  359. } else {
  360. if (typeof console !== "undefined") {
  361. console.warn(msg);
  362. }
  363. }
  364. }
  365. };
  366. /**
  367. * Meant to mimic the php forward() function by simply redirecting the
  368. * user to another page.
  369. *
  370. * @param {String} url The url to forward to
  371. */
  372. elgg.forward = function(url) {
  373. location.href = elgg.normalize_url(url);
  374. };
  375. /**
  376. * Parse a URL into its parts. Mimicks http://php.net/parse_url
  377. *
  378. * @param {String} url The URL to parse
  379. * @param {Int} component A component to return
  380. * @param {Bool} expand Expand the query into an object? Else it's a string.
  381. *
  382. * @return {Object} The parsed URL
  383. */
  384. elgg.parse_url = function(url, component, expand) {
  385. // Adapted from http://blog.stevenlevithan.com/archives/parseuri
  386. // which was release under the MIT
  387. // It was modified to fix mailto: and javascript: support.
  388. expand = expand || false;
  389. component = component || false;
  390. var re_str =
  391. // scheme (and user@ testing)
  392. '^(?:(?![^:@]+:[^:@/]*@)([^:/?#.]+):)?(?://)?'
  393. // possibly a user[:password]@
  394. + '((?:(([^:@]*)(?::([^:@]*))?)?@)?'
  395. // host and port
  396. + '([^:/?#]*)(?::(\\d*))?)'
  397. // path
  398. + '(((/(?:[^?#](?![^?#/]*\\.[^?#/.]+(?:[?#]|$)))*/?)?([^?#/]*))'
  399. // query string
  400. + '(?:\\?([^#]*))?'
  401. // fragment
  402. + '(?:#(.*))?)',
  403. keys = {
  404. 1: "scheme",
  405. 4: "user",
  406. 5: "pass",
  407. 6: "host",
  408. 7: "port",
  409. 9: "path",
  410. 12: "query",
  411. 13: "fragment"
  412. },
  413. results = {};
  414. if (url.indexOf('mailto:') === 0) {
  415. results['scheme'] = 'mailto';
  416. results['path'] = url.replace('mailto:', '');
  417. return results;
  418. }
  419. if (url.indexOf('javascript:') === 0) {
  420. results['scheme'] = 'javascript';
  421. results['path'] = url.replace('javascript:', '');
  422. return results;
  423. }
  424. var re = new RegExp(re_str);
  425. var matches = re.exec(url);
  426. for (var i in keys) {
  427. if (matches[i]) {
  428. results[keys[i]] = matches[i];
  429. }
  430. }
  431. if (expand && typeof(results['query']) != 'undefined') {
  432. results['query'] = elgg.parse_str(results['query']);
  433. }
  434. if (component) {
  435. if (typeof(results[component]) != 'undefined') {
  436. return results[component];
  437. } else {
  438. return false;
  439. }
  440. }
  441. return results;
  442. };
  443. /**
  444. * Returns an object with key/values of the parsed query string.
  445. *
  446. * @param {String} string The string to parse
  447. * @return {Object} The parsed object string
  448. */
  449. elgg.parse_str = function(string) {
  450. var params = {},
  451. result,
  452. key,
  453. value,
  454. re = /([^&=]+)=?([^&]*)/g,
  455. re2 = /\[\]$/;
  456. // assignment intentional
  457. while (result = re.exec(string)) {
  458. key = decodeURIComponent(result[1].replace(/\+/g, ' '));
  459. value = decodeURIComponent(result[2].replace(/\+/g, ' '));
  460. if (re2.test(key)) {
  461. key = key.replace(re2, '');
  462. if (!params[key]) {
  463. params[key] = [];
  464. }
  465. params[key].push(value);
  466. } else {
  467. params[key] = value;
  468. }
  469. }
  470. return params;
  471. };
  472. /**
  473. * Returns a jQuery selector from a URL's fragement. Defaults to expecting an ID.
  474. *
  475. * Examples:
  476. * http://elgg.org/download.php returns ''
  477. * http://elgg.org/download.php#id returns #id
  478. * http://elgg.org/download.php#.class-name return .class-name
  479. * http://elgg.org/download.php#a.class-name return a.class-name
  480. *
  481. * @param {String} url The URL
  482. * @return {String} The selector
  483. */
  484. elgg.getSelectorFromUrlFragment = function(url) {
  485. var fragment = url.split('#')[1];
  486. if (fragment) {
  487. // this is a .class or a tag.class
  488. if (fragment.indexOf('.') > -1) {
  489. return fragment;
  490. }
  491. // this is an id
  492. else {
  493. return '#' + fragment;
  494. }
  495. }
  496. return '';
  497. };
  498. /**
  499. * Adds child to object[parent] array.
  500. *
  501. * @param {Object} object The object to add to
  502. * @param {String} parent The parent array to add to.
  503. * @param {Mixed} value The value
  504. */
  505. elgg.push_to_object_array = function(object, parent, value) {
  506. elgg.assertTypeOf('object', object);
  507. elgg.assertTypeOf('string', parent);
  508. if (!(object[parent] instanceof Array)) {
  509. object[parent] = [];
  510. }
  511. if ($.inArray(value, object[parent]) < 0) {
  512. return object[parent].push(value);
  513. }
  514. return false;
  515. };
  516. /**
  517. * Tests if object[parent] contains child
  518. *
  519. * @param {Object} object The object to add to
  520. * @param {String} parent The parent array to add to.
  521. * @param {Mixed} value The value
  522. */
  523. elgg.is_in_object_array = function(object, parent, value) {
  524. elgg.assertTypeOf('object', object);
  525. elgg.assertTypeOf('string', parent);
  526. return typeof(object[parent]) != 'undefined' && $.inArray(value, object[parent]) >= 0;
  527. };