1 /*global jQuery: true, GCN: true */ 2 /** 3 * admin.js defines the AdminAPI, that will 4 * give access to global data (like all existing constructs). 5 * A single instance is of this API is exposed as GCN.Admin 6 */ 7 (function (GCN) { 8 9 'use strict'; 10 11 /** 12 * Maps constructcategories that were fetched via the Rest API 13 * into a sorted nested array of constructs. 14 * 15 * @param {object<string, object>} constructs 16 * @return {object<string, object>} 17 */ 18 function mapConstructCategories(constructs) { 19 var constructKeyword; 20 var categoryMap = { categories: {}, categorySortorder: [] }; 21 var constructCategoryArray = []; 22 23 for (constructKeyword in constructs) { 24 if (constructs.hasOwnProperty(constructKeyword)) { 25 var construct = constructs[constructKeyword]; 26 27 var constructCategoryName = construct.category; 28 var categorySortorder = construct.categorySortorder; 29 30 // Use a custom name for constructs that have not been assigned to a category 31 if (!constructCategoryName) { 32 constructCategoryName = "GCN_UNCATEGORIZED"; 33 categorySortorder = -1; 34 } 35 36 // Initialize the inner array of constructs 37 if (!categoryMap.categories[constructCategoryName]) { 38 var newCategory = {}; 39 newCategory.constructs = {}; 40 newCategory.sortorder = categorySortorder; 41 newCategory.name = constructCategoryName; 42 categoryMap.categories[constructCategoryName] = newCategory; 43 constructCategoryArray.push(newCategory); 44 } 45 46 // Add the construct to the category 47 categoryMap.categories[constructCategoryName].constructs[constructKeyword] = construct; 48 } 49 } 50 51 // Sort the categories by the sortorder 52 constructCategoryArray.sort(function (a, b) { 53 return a.sortorder - b.sortorder; 54 }); 55 56 // Add the sorted category names to the sortorder field 57 var k; 58 for (k in constructCategoryArray) { 59 if (constructCategoryArray.hasOwnProperty(k)) { 60 var category = constructCategoryArray[k]; 61 if (typeof category.sortorder !== 'undefined' && category.sortorder !== -1) { 62 categoryMap.categorySortorder.push(category.name); 63 } 64 } 65 } 66 67 return categoryMap; 68 } 69 70 /** 71 * Maps constructs, that were fetched via the Rest API, using their keyword 72 * as the keys. 73 * 74 * @param {object<string, object>} constructs Consturcts mapped against 75 * their id. 76 * @return {object<string, object>} Constructs mapped against their keys. 77 */ 78 function mapConstructs(constructs) { 79 if (!constructs) { 80 return {}; 81 } 82 var map = {}; 83 var constructId; 84 for (constructId in constructs) { 85 if (constructs.hasOwnProperty(constructId)) { 86 map[constructs[constructId].keyword] = constructs[constructId]; 87 } 88 } 89 return map; 90 } 91 92 /** 93 * The AdminAPI is exposed via {@link GCN.Admin}. The AdminAPI is not meant 94 * to be instatiated by the implementer. Stick to using GCN.Admin. 95 * 96 * @name AdminAPI 97 * @class 98 * @augments Chainback 99 */ 100 var AdminAPI = GCN.defineChainback({ 101 /** @lends AdminAPI */ 102 103 __chainbacktype__: 'AdminAPI', 104 _type: 'admin', 105 106 /** 107 * @private 108 * @type {object<string, number} Constructs for this node are cached 109 * here so that we only need to fetch 110 * this once. 111 */ 112 _constructs: null, 113 114 /** 115 * @private 116 * @type {object<string, object} Constructs categories for this node. 117 * Cached here so that we only need to 118 * fetch this once. 119 */ 120 _constructCategories: null, 121 122 /** 123 * Initialize 124 * @private 125 */ 126 _init: function (id, success, error, settings) { 127 if (typeof success === 'function') { 128 this._invoke(success, [this]); 129 } 130 }, 131 132 /** 133 * Wrapper for internal chainback _ajax method. 134 * 135 * @private 136 * @param {object<string, *>} settings Settings for the ajax request. 137 * The settings object is identical 138 * to that of the `GCN.ajax' 139 * method, which handles the actual 140 * ajax transportation. 141 * @throws AJAX_ERROR 142 */ 143 '!_ajax': function (settings) { 144 var that = this; 145 146 // force no cache for all API calls 147 settings.cache = false; 148 settings.success = (function (onSuccess, onError) { 149 return function (data) { 150 // Ajax calls that do not target the REST API servlet do 151 // not response data with a `responseInfo' object. 152 // "/CNPortletapp/alohatag" is an example. So we cannot 153 // just assume that it exists. 154 if (data.responseInfo) { 155 switch (data.responseInfo.responseCode) { 156 case 'OK': 157 break; 158 case 'AUTHREQUIRED': 159 GCN.clearSession(); 160 that._authAjax(settings); 161 return; 162 default: 163 GCN.handleResponseError(data, onError); 164 return; 165 } 166 } 167 168 if (onSuccess) { 169 that._invoke(onSuccess, [data]); 170 } 171 }; 172 }(settings.success, settings.error, settings.url)); 173 174 this._queueAjax(settings); 175 }, 176 177 /** 178 * Similar to `_ajax', except that it prefixes the ajax url with the 179 * current session's `sid', and will trigger an 180 * `authentication-required' event if the session is not authenticated. 181 * 182 * @TODO(petro): Consider simplifiying this function signature to read: 183 * `_auth( url, success, error )' 184 * 185 * @private 186 * @param {object<string, *>} settings Settings for the ajax request. 187 * @throws AUTHENTICATION_FAILED 188 */ 189 _authAjax: function (settings) { 190 var that = this; 191 192 if (GCN.isAuthenticating) { 193 GCN.afterNextAuthentication(function () { 194 that._authAjax(settings); 195 }); 196 197 return; 198 } 199 200 if (!GCN.sid) { 201 var cancel; 202 203 if (settings.error) { 204 /** 205 * @ignore 206 */ 207 cancel = function (error) { 208 GCN.handleError( 209 error || GCN.createError('AUTHENTICATION_FAILED'), 210 settings.error 211 ); 212 }; 213 } else { 214 /** 215 * @ignore 216 */ 217 cancel = function (error) { 218 if (error) { 219 GCN.error(error.code, error.message, error.data); 220 } else { 221 GCN.error('AUTHENTICATION_FAILED'); 222 } 223 }; 224 } 225 226 GCN.afterNextAuthentication(function () { 227 that._authAjax(settings); 228 }); 229 230 if (GCN.usingSSO) { 231 // First, try to automatically authenticate via 232 // Single-SignOn 233 GCN.loginWithSSO(GCN.onAuthenticated, function () { 234 // ... if SSO fails, then fallback to requesting user 235 // credentials: broadcast `authentication-required' 236 // message. 237 GCN.authenticate(cancel); 238 }); 239 } else { 240 // Trigger the `authentication-required' event to request 241 // user credentials. 242 GCN.authenticate(cancel); 243 } 244 245 return; 246 } 247 248 // Append "?sid=..." or "&sid=..." if needed. 249 250 var urlFragment = settings.url.substr( 251 GCN.settings.BACKEND_PATH.length 252 ); 253 var isSidInUrl = /[\?\&]sid=/.test(urlFragment); 254 if (!isSidInUrl) { 255 var isFirstParam = (jQuery.inArray('?', 256 urlFragment.split('')) === -1); 257 258 settings.url += (isFirstParam ? '?' : '&') + 'sid=' 259 + (GCN.sid || ''); 260 } 261 262 this._ajax(settings); 263 }, 264 265 /** 266 * Retrieves a list of all constructs and constructs categories and 267 * passes it as the only argument into the `success()' callback. 268 * 269 * @param {function(Array.<object>)=} success Callback to receive an 270 * array of constructs. 271 * @param {function(GCNError):boolean=} error Custom error handler. 272 * @return undefined 273 * @throws INVALID_ARGUMENTS 274 */ 275 constructs: function (success, error) { 276 var that = this; 277 if (!success) { 278 GCN.error('INVALID_ARGUMENTS', 'the `constructs()\' method ' + 279 'requires at least a success callback to be given'); 280 } 281 if (this._constructs) { 282 this._invoke(success, [this._constructs]); 283 } else { 284 this._authAjax({ 285 url: GCN.settings.BACKEND_PATH + '/rest/construct/list.json', 286 type: 'GET', 287 error: function (xhr, status, msg) { 288 GCN.handleHttpError(xhr, msg, error); 289 }, 290 success: function (response) { 291 if (GCN.getResponseCode(response) === 'OK') { 292 that._constructs = mapConstructs(response.constructs); 293 that._invoke(success, [that._constructs]); 294 } else { 295 GCN.handleResponseError(response, error); 296 } 297 } 298 }); 299 } 300 }, 301 302 /** 303 * Helper method that will load the constructs of this node. 304 * 305 * @private 306 * @this {AdminAPI} 307 * @param {function(Array.<object>)} success callback 308 * @param {function(GCNError):boolean=} error callback 309 */ 310 constructCategories: function (success, error) { 311 if (this._constructCategories) { 312 this._invoke(success, [this._constructCategories]); 313 } else { 314 var that = this; 315 this.constructs(function (constructs) { 316 that._constructCategories = mapConstructCategories(constructs); 317 that._invoke(success, [that._constructCategories]); 318 }, error); 319 } 320 }, 321 322 /** 323 * Internal method, to fetch this object's data from the server. 324 * 325 * @private 326 * @override 327 * @param {function(ContentObjectAPI)=} success Optional callback that 328 * receives this object as 329 * its only argument. 330 * @param {function(GCNError):boolean=} error Optional customer error 331 * handler. 332 */ 333 '!_read': function (success, error) { 334 this._invoke(success, [this]); 335 } 336 }); 337 338 /** 339 * Exposes an instance of the AdminAPI via GCN.Admin. 340 * 341 * @see AdminAPI 342 */ 343 GCN.Admin = new AdminAPI(); 344 345 }(GCN)); 346