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 * Admin object. 94 * 95 * @name AdminAPI 96 * @class 97 * @augments Chainback 98 */ 99 var AdminAPI = GCN.defineChainback({ 100 /** @lends AdminAPI */ 101 102 __chainbacktype__: 'AdminAPI', 103 _type: 'admin', 104 105 /** 106 * @private 107 * @type {object<string, number} Constructs for this node are cached 108 * here so that we only need to fetch 109 * this once. 110 */ 111 _constructs: null, 112 113 /** 114 * @private 115 * @type {object<string, object} Constructs categories for this node. 116 * Cached here so that we only need to 117 * fetch this once. 118 */ 119 _constructCategories: null, 120 121 /** 122 * Initialize 123 * @private 124 */ 125 _init: function (id, success, error, settings) { 126 if (typeof success === 'function') { 127 this._invoke(success, [this]); 128 } 129 }, 130 131 /** 132 * Wrapper for internal chainback _ajax method. 133 * 134 * @private 135 * @param {object<string, *>} settings Settings for the ajax request. 136 * The settings object is identical 137 * to that of the `GCN.ajax' 138 * method, which handles the actual 139 * ajax transportation. 140 * @throws AJAX_ERROR 141 */ 142 '!_ajax': function (settings) { 143 var that = this; 144 145 // force no cache for all API calls 146 settings.cache = false; 147 settings.success = (function (onSuccess, onError) { 148 return function (data) { 149 // Ajax calls that do not target the REST API servlet do 150 // not response data with a `responseInfo' object. 151 // "/CNPortletapp/alohatag" is an example. So we cannot 152 // just assume that it exists. 153 if (data.responseInfo) { 154 switch (data.responseInfo.responseCode) { 155 case 'OK': 156 break; 157 case 'AUTHREQUIRED': 158 GCN.clearSession(); 159 that._authAjax(settings); 160 return; 161 default: 162 GCN.handleResponseError(data, onError); 163 return; 164 } 165 } 166 167 if (onSuccess) { 168 that._invoke(onSuccess, [data]); 169 } 170 }; 171 }(settings.success, settings.error, settings.url)); 172 173 this._queueAjax(settings); 174 }, 175 176 /** 177 * Similar to `_ajax', except that it prefixes the ajax url with the 178 * current session's `sid', and will trigger an 179 * `authentication-required' event if the session is not authenticated. 180 * 181 * @TODO(petro): Consider simplifiying this function signature to read: 182 * `_auth( url, success, error )' 183 * 184 * @private 185 * @param {object<string, *>} settings Settings for the ajax request. 186 * @throws AUTHENTICATION_FAILED 187 */ 188 _authAjax: function (settings) { 189 var that = this; 190 191 if (GCN.isAuthenticating) { 192 GCN.afterNextAuthentication(function () { 193 that._authAjax(settings); 194 }); 195 196 return; 197 } 198 199 if (!GCN.sid) { 200 var cancel; 201 202 if (settings.error) { 203 cancel = function (error) { 204 GCN.handleError( 205 error || GCN.createError('AUTHENTICATION_FAILED'), 206 settings.error 207 ); 208 }; 209 } else { 210 cancel = function (error) { 211 if (error) { 212 GCN.error(error.code, error.message, error.data); 213 } else { 214 GCN.error('AUTHENTICATION_FAILED'); 215 } 216 }; 217 } 218 219 GCN.afterNextAuthentication(function () { 220 that._authAjax(settings); 221 }); 222 223 if (GCN.usingSSO) { 224 // First, try to automatically authenticate via 225 // Single-SignOn 226 GCN.loginWithSSO(GCN.onAuthenticated, function () { 227 // ... if SSO fails, then fallback to requesting user 228 // credentials: broadcast `authentication-required' 229 // message. 230 GCN.authenticate(cancel); 231 }); 232 } else { 233 // Trigger the `authentication-required' event to request 234 // user credentials. 235 GCN.authenticate(cancel); 236 } 237 238 return; 239 } 240 241 // Append "?sid=..." or "&sid=..." if needed. 242 243 var urlFragment = settings.url.substr( 244 GCN.settings.BACKEND_PATH.length 245 ); 246 var isSidInUrl = /[\?\&]sid=/.test(urlFragment); 247 if (!isSidInUrl) { 248 var isFirstParam = (jQuery.inArray('?', 249 urlFragment.split('')) === -1); 250 251 settings.url += (isFirstParam ? '?' : '&') + 'sid=' 252 + (GCN.sid || ''); 253 } 254 255 this._ajax(settings); 256 }, 257 258 /** 259 * Retrieves a list of all constructs and constructs categories and 260 * passes it as the only argument into the `success()' callback. 261 * 262 * @param {function(Array.<object>)=} success Callback to receive an 263 * array of constructs. 264 * @param {function(GCNError):boolean=} error Custom error handler. 265 * @return undefined 266 * @throws INVALID_ARGUMENTS 267 */ 268 constructs: function (success, error) { 269 var that = this; 270 if (!success) { 271 GCN.error('INVALID_ARGUMENTS', 'the `constructs()\' method ' + 272 'requires at least a success callback to be given'); 273 } 274 if (this._constructs) { 275 this._invoke(success, [this._constructs]); 276 } else { 277 this._authAjax({ 278 url: GCN.settings.BACKEND_PATH + '/rest/construct/list.json', 279 type: 'GET', 280 error: function (xhr, status, msg) { 281 GCN.handleHttpError(xhr, msg, error); 282 }, 283 success: function (response) { 284 if (GCN.getResponseCode(response) === 'OK') { 285 that._constructs = mapConstructs(response.constructs); 286 that._invoke(success, [that._constructs]); 287 } else { 288 GCN.handleResponseError(response, error); 289 } 290 } 291 }); 292 } 293 }, 294 295 /** 296 * Helper method that will load the constructs of this node. 297 * 298 * @private 299 * @this {AdminAPI} 300 * @param {function(Array.<object>)} success callback 301 * @param {function(GCNError):boolean=} error callback 302 */ 303 constructCategories: function (success, error) { 304 if (this._constructCategories) { 305 this._invoke(success, [this._constructCategories]); 306 } else { 307 var that = this; 308 this.constructs(function (constructs) { 309 that._constructCategories = mapConstructCategories(constructs); 310 that._invoke(success, [that._constructCategories]); 311 }, error); 312 } 313 }, 314 315 /** 316 * Internal method, to fetch this object's data from the server. 317 * 318 * @private 319 * @override 320 * @param {function(ContentObjectAPI)=} success Optional callback that 321 * receives this object as 322 * its only argument. 323 * @param {function(GCNError):boolean=} error Optional customer error 324 * handler. 325 */ 326 '!_read': function (success, error) { 327 this._invoke(success, [this]); 328 } 329 }); 330 331 /** 332 * Expose an instance of the AdminAPI. 333 */ 334 GCN.Admin = new AdminAPI(); 335 336 }(GCN)); 337