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