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