1 /*global global: true, process: true, require: true, module: true */ 2 3 /** 4 * Establishes the `GCN' object and exposes it in the global context. 5 */ 6 GCN = (function (global) { 7 'use strict'; 8 9 // Check whether we are in nodeJS context. 10 if (typeof process !== 'undefined' && process.versions 11 && process.versions.node) { 12 global.isNode = true; 13 var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; 14 jQuery = global.$ = global.jQuery = require('jquery'); 15 global.jQuery.ajaxSettings.xhr = function createNodeXHRForGCN() { 16 return new XMLHttpRequest(); 17 }; 18 // http://stackoverflow.com/a/6432602 19 global.jQuery.support.cors = true; 20 } 21 22 // Temporarily deactivate JS Lints "dangerous '.'" test for regular expressions. 23 /*jslint regexp: false*/ 24 /** 25 * Match URL to internal page 26 * 27 * @const 28 * @type {RegExp} 29 */ 30 var INTERNAL_PAGEURL = /\/CNPortletapp\/alohapage\?.*realid=([0-9]+).*$/; 31 32 /** 33 * Match URL to internal file 34 * 35 * @const 36 * @type {RegExp} 37 */ 38 var INTERNAL_FILEURL = /^http.*\?.*&do=16000&id=([0-9]+).*$/; 39 /*jslint regexp: true*/ 40 41 /** 42 * @private 43 * @type {object} An object to indicate which handlers have been 44 * registered through the `GCN.onRender()' function. 45 */ 46 var onRenderHandler = {}; 47 48 /** 49 * @private 50 * @type {boolean} A flag to indicate whether or not a handler has been 51 * registerd through the `GCN.onError()' function. 52 */ 53 var hasOnErrorHandler = false; 54 55 /** 56 * @ignore 57 * @type {boolean} An internal flag that stores whether an authentication 58 * handler has been set. 59 */ 60 var hasAuthenticationHandler = false; 61 62 /** 63 * GCN JS API error object. This is the object passed to error handlers. 64 * 65 * @class 66 * @name GCNError 67 * @param {string} code error code for the error 68 * @param {string} message descriptive error message 69 * @param {object} data additional data 70 */ 71 var GCNError = function (code, message, data) { 72 this.code = code; 73 this.message = message; 74 this.data = data; 75 }; 76 77 /** 78 * Returns a human-readable representation of this error object. 79 * 80 * @public 81 * @return {string} 82 */ 83 GCNError.prototype.toString = function () { 84 return 'GCN ERROR (' + this.code + '): "' + (this.message || '') + '"'; 85 }; 86 87 /** 88 * @name GCN 89 * @class 90 * 91 * Base namespace for the Gentics Content.Node JavaScript API. 92 */ 93 var GCN = global.GCN || {}; 94 95 jQuery.extend(GCN, { 96 /** @lends GCN */ 97 98 /** 99 * Reference to the global context. 100 * 101 * @type {object} 102 */ 103 global: global, 104 105 /** 106 * Settings for the Gentics Content.Node JavaScript API. 107 * 108 * @type {object<string, object>} 109 */ 110 settings: { 111 112 /** 113 * The language code with which to render tags. 114 * 115 * @const 116 * @name settings.lang 117 * @default 'en' 118 * @memberOf GCN 119 * @type {string} 120 */ 121 lang: 'en', 122 123 /** 124 * Default GCN backend path. Do not add a trailing slash here. 125 * 126 * @const 127 * @default '/CNPortletapp' 128 * @name settings.BACKEND_PATH 129 * @memberOf GCN 130 * @type {string} 131 */ 132 BACKEND_PATH: '/CNPortletapp', 133 134 /** 135 * The keyword for the construct that defines Aloha Editor links. In 136 * most Content.Node installations this will be "gtxalohapagelink", 137 * but can be otherwise defined. 138 * 139 * @const 140 * @default 'gtxalohapagelink' 141 * @name settings.MAGIC_LINK 142 * @memberOf GCN 143 * @type {string} 144 */ 145 MAGIC_LINK: 'gtxalohapagelink', 146 147 /** 148 * Determines whether links will be rendered as back-end urls or 149 * front-end urls. Can either be set to "backend" or "frontend". 150 * 151 * @const 152 * @default 'backend' 153 * @name settings.linksRenderMode 154 * @memberOf GCN 155 * @type {string} 156 */ 157 linksRenderMode: 'backend', 158 159 /** 160 * The default callback to determine if a URL is an internal link. 161 * 162 * Matches the given href agains <code>INTERNAL_PAGEURL</code> and 163 * <code>INTERNAL_FILEURL</code> respectively. 164 * 165 * @const 166 * @type {function} 167 * @memberOf GCN 168 * @name settings.checkForInternalLink 169 * @param {string} href The URL to be checked. 170 * @return {object} An object containing the fields 171 * <ul> 172 * <li>match (boolean): indicating if the URL is internal</li> 173 * <li>url (string|integer): The ID of the internal page or 0 if 174 * the URL points to an internal file, or the external URL</li> 175 * <li>fileurl (string|integer): The ID of the internal file, and 176 * zero if the URL is external or points to a page.</li> 177 * </ul> 178 */ 179 checkForInternalLink: function (href) { 180 var urlMatch = href.match(INTERNAL_PAGEURL); 181 182 if (urlMatch) { 183 return { match: true, url: parseInt(urlMatch[1], 10), fileurl: 0 }; 184 } 185 186 urlMatch = href.match(INTERNAL_FILEURL); 187 188 if (urlMatch) { 189 return { match: true, url: 0, fileurl: parseInt(urlMatch[1], 10) }; 190 } 191 192 return { match: false, url: href, fileurl: 0 }; 193 }, 194 195 /** 196 * Set a channelid to work on for multichannelling or false if no 197 * channel should be used 198 * 199 * @memberOf GCN 200 * @default false 201 * @type {bool|int|string} 202 */ 203 channel: false 204 }, 205 206 /** 207 * Publish a message 208 * 209 * @param {string} message channel name 210 * @param {*=} params 211 */ 212 pub: function (channel, params) { 213 if (!hasOnErrorHandler && channel === 'error-encountered') { 214 // throw an error if there is no subscription to 215 // error-encountered. 216 throw params; 217 } 218 219 switch (channel) { 220 case 'tag.rendered': 221 case 'page.rendered': 222 case 'content-rendered': 223 // for these channels, we need to have a custom implementation: 224 // param[0] is the html of the rendered tag 225 // param[1] is the tag object 226 // param[2] is the callback, that must be called from the event handler 227 // If more than one handler subscribed, we will chain them by calling the 228 // next handler in the callback of the previous. Only the callback of the 229 // last handler will call the original callback 230 if (jQuery.isArray(onRenderHandler[channel])) { 231 var handlers = onRenderHandler[channel]; 232 // substitute callback function with wrapper 233 var callback = params[2], i = 0; 234 params[2] = function (html) { 235 if (++i < handlers.length) { 236 // call the next handler. Pass the html returned from the 237 // previous callback to the next handler 238 params[0] = html; 239 handlers[i].apply(null, params); 240 } else { 241 // there are no more handlers, so call the original 242 // callback with the final html 243 callback(html); 244 } 245 }; 246 handlers[0].apply(null, params); 247 } 248 return; 249 } 250 251 jQuery(GCN).trigger(channel, params); 252 }, 253 254 /** 255 * Subscribe to a message channel 256 * 257 * @param {string} message channel name 258 * @param {function} handler function - message parameters will be 259 * passed. 260 */ 261 sub: function (channel, handler) { 262 // register default handlers 263 switch (channel) { 264 case 'error-encountered': 265 hasOnErrorHandler = true; 266 break; 267 case 'tag.rendered': 268 case 'page.rendered': 269 case 'content-rendered': 270 // store all the handlers in an array. 271 onRenderHandler[channel] = onRenderHandler[channel] || []; 272 onRenderHandler[channel].push(handler); 273 return; 274 case 'authentication-required': 275 case 'session.authentication-required': 276 hasAuthenticationHandler = true; 277 break; 278 } 279 280 jQuery(GCN).bind(channel, function (event, param1, param2, param3) { 281 handler(param1, param2, param3); 282 }); 283 }, 284 285 /** 286 * Tigger an error message 'error-encountered'. 287 * 288 * @param {string} error code 289 * @param {string} error message 290 * @param {object} additional error data 291 */ 292 error: function (code, message, data) { 293 var error = new GCNError(code, message, data); 294 this.pub('error-encountered', error); 295 }, 296 297 /** 298 * Returns an object containing the formal error fields. The object 299 * contains a `toString' method to print any uncaught exceptions 300 * nicely. 301 * 302 * @param {string} code 303 * @param {string} message 304 * @param {object} data 305 * @return {GCNError} 306 */ 307 createError: function (code, message, data) { 308 return new GCNError(code, message, data); 309 }, 310 311 /** 312 * Wraps the `jQuery.ajax()' method. 313 * 314 * @public 315 * @param {object} settings 316 * @throws HTTP_ERROR 317 */ 318 ajax: function (settings) { 319 if (settings.json) { 320 settings.data = JSON.stringify(settings.json); 321 delete settings.json; 322 } 323 settings.dataType = 'json'; 324 settings.contentType = 'application/json; charset=utf-8'; 325 jQuery.ajax(settings); 326 }, 327 328 /** 329 * Set links render mode if a parameter is given 330 * retrieve it if not 331 * 332 * @param {string} mode 333 * @return {string} mode 334 */ 335 linksRenderMode: function (mode) { 336 if (mode) { 337 GCN.settings.linksRenderMode = mode; 338 } 339 return GCN.settings.linksRenderMode; 340 }, 341 342 /** 343 * Set channel if a parameter is given retrieve it otherwise. 344 * 345 * If you don't want to work on a channel just set it to false, which 346 * is the default value. 347 * 348 * @param {string|boolean} channel The id of the channel to be set or false to unset the channel. 349 * @return {string} current channel id. 350 */ 351 channel: function (channel) { 352 if (channel || false === channel) { 353 GCN.settings.channel = channel; 354 } 355 return GCN.settings.channel; 356 }, 357 358 /** 359 * Constructs the nodeId query parameter for rest calls. 360 * 361 * @param {AbstractContentObject} contentObject A content object instance. 362 * @param {string=} delimiter Optional delimiter character. 363 * @return {string} Query parameter string. 364 */ 365 _getChannelParameter: function (contentObject, delimiter) { 366 if (false === contentObject._channel) { 367 return ''; 368 } 369 return (delimiter || '?') + 'nodeId=' + contentObject._channel; 370 }, 371 372 /** 373 * @param {string} html Rendered content 374 * @param {Chainback} obj The rendered ContentObject. 375 * @param {function(html)} callback Receives the processed html. 376 */ 377 _handleContentRendered: function (html, obj, callback) { 378 var channel = obj._type + '.rendered'; 379 if (onRenderHandler[channel]) { 380 GCN.pub(channel, [html, obj, callback]); 381 } else if (onRenderHandler['content-rendered']) { 382 // Because 'content-rendered' has been deprecated in favor of 383 // '{tag|page}.rendered'. 384 GCN.pub('content-rendered', [html, obj, callback]); 385 } else { 386 callback(html); 387 } 388 }, 389 390 /** 391 * Handles the ajax transport error. It will invoke the custom error 392 * handler if one is provided, and propagate the error onto the global 393 * handler if the an error handler does not return `false'. 394 * 395 * @param {object} xhr 396 * @param {string} msg The error message 397 * @param {function} handler Custom error handler. 398 * @throws HTTP_ERROR 399 */ 400 handleHttpError: function (xhr, msg, handler) { 401 var throwException = true; 402 403 if (handler) { 404 throwException = handler(GCN.createError('HTTP_ERROR', msg, 405 xhr)); 406 } 407 408 if (throwException !== 'false') { 409 GCN.error('HTTP_ERROR', msg, xhr); 410 } 411 }, 412 413 /** 414 * Handles error that occur when an ajax request succeeds but the 415 * backend responds with an error. 416 * 417 * @param {object} reponse The REST API response object. 418 * @param {function(GCNError):boolean} handler Custom error handler. 419 */ 420 handleResponseError: function (response, handler) { 421 var info = response.responseInfo; 422 var throwException = true; 423 424 if (handler) { 425 throwException = handler(GCN.createError( 426 info.responseCode, 427 info.responseMessage, 428 response 429 )); 430 } 431 432 if (throwException !== false) { 433 GCN.error(info.responseCode, info.responseMessage, response); 434 } 435 }, 436 437 /** 438 * Tiggers the GCN error event. 439 * 440 * @param {GCNError} error 441 * @param {function(GCNError):boolean} handler Custom error handler. 442 * @return {boolean} Whether or not to the exception was thrown. 443 */ 444 handleError: function (error, handler) { 445 var throwException = true; 446 447 if (handler) { 448 throwException = handler(error); 449 } 450 451 if (throwException !== false) { 452 GCN.error(error.code, error.message, error.data); 453 } 454 455 return throwException; 456 }, 457 458 /** 459 * Check if an authentication handler has been registered. 460 * 461 * @return {boolean} True if an handler for the 462 * 'authentication-required' message has been 463 * registered. 464 */ 465 _hasAuthenticationHandler: function () { 466 return hasAuthenticationHandler; 467 } 468 469 }); 470 471 // Expose the Gentics Content.Node JavaScript API to the global context. 472 // This will be `window' in most cases. 473 return (global.GCN = GCN); 474 475 }(typeof global !== 'undefined' ? global : window)); 476