1 /*global GCN: true */ 2 (function (GCN) { 3 'use strict'; 4 5 /** 6 * Maps constructcategories that were fetched via the Rest API into a 7 * sorted nested array of constructs. 8 * 9 * @param {object<string, object>} constructs 10 * @return {object<string, object>} 11 */ 12 function mapConstructCategories(constructs) { 13 var constructKeyword; 14 var categoryMap = { 15 categories: {}, 16 categorySortorder: [] 17 }; 18 var constructCategoryArray = []; 19 // Determine the highest sortorder in case we need to default some 20 var defaultCounter = 1; 21 22 for (constructKeyword in constructs) { 23 if (constructs.hasOwnProperty(constructKeyword)) { 24 var construct = constructs[constructKeyword]; 25 var constructCategory = construct.category; 26 var constructCategoryName, categorySortorder; 27 28 // Use a custom name for constructs that have not been assigned 29 // to a category. 30 if (!constructCategory) { 31 constructCategoryName = 'GCN_UNCATEGORIZED'; 32 categorySortorder = -1; 33 } else { 34 constructCategoryName = constructCategory.name; 35 categorySortorder = constructCategory.sortOrder; 36 } 37 38 if (categorySortorder) { 39 defaultCounter = Math.max(categorySortorder, defaultCounter); 40 } 41 42 // Initialize the inner array of constructs. 43 if (!categoryMap.categories[constructCategoryName]) { 44 var newCategory = {}; 45 newCategory.constructs = {}; 46 newCategory.sortorder = categorySortorder; 47 newCategory.name = constructCategoryName; 48 categoryMap.categories[constructCategoryName] = newCategory; 49 constructCategoryArray.push(newCategory); 50 } 51 52 // Add the construct to the category. 53 categoryMap.categories[constructCategoryName] 54 .constructs[constructKeyword] = construct; 55 } 56 } 57 58 // Sort the categories by the sortorder. 59 constructCategoryArray.sort(function (a, b) { 60 return a.sortorder - b.sortorder; 61 }); 62 63 // Add the sorted category names to the sortorder field. 64 constructCategoryArray.forEach(function(category) { 65 if (typeof category.sortorder === 'number' || category.sortorder === -1) { 66 category.sortorder = defaultCounter; 67 defaultCounter++; 68 } 69 categoryMap.categorySortorder.push(category.name); 70 }); 71 72 return categoryMap; 73 } 74 75 /** 76 * Represents a Node 77 * 78 * @name NodeAPI 79 * @class 80 * @augments Chainback 81 * 82 * @param {number|string} 83 * id of the file to be loaded 84 * @param {function(ContentObjectAPI))=} 85 * success Optional success callback that will receive this 86 * object as its only argument. 87 * @param {function(GCNError):boolean=} 88 * error Optional custom error handler. 89 * @param {object} 90 * settings currently there are no additional settings to be used 91 */ 92 var NodeAPI = GCN.defineChainback({ 93 /** @lends NodeAPI */ 94 95 __chainbacktype__: 'NodeAPI', 96 _extends: GCN.ContentObjectAPI, 97 _type: 'node', 98 99 _data: { 100 folderId: null 101 }, 102 103 /** 104 * @private 105 * @type {object<string, number} Constructs for this node are cached 106 * here so that we only need to fetch 107 * this once. 108 */ 109 _constructs: null, 110 111 /** 112 * List of success and error callbacks that need to be called 113 * once the constructs are loaded 114 * @private 115 * @type {array.<object>} 116 */ 117 _constructLoadHandlers: null, 118 119 /** 120 * @private 121 * @type {object<string, object} Constructs categories for this node. 122 * Cached here so that we only need to 123 * fetch this once. 124 */ 125 _constructCategories: null, 126 127 /** 128 * Retrieves a list of constructs and constructs categories that are 129 * assigned to this node and passes it as the only argument into the 130 * the `success()' callback. 131 * 132 * @param {function(Array.<object>)=} success Callback to receive an 133 * array of constructs. 134 * @param {function(GCNError):boolean=} error Custom error handler. 135 * @return undefined 136 * @throws INVALID_ARGUMENTS 137 */ 138 constructs: function (success, error) { 139 if (!success) { 140 return; 141 } 142 var node = this; 143 if (node._constructs) { 144 node._invoke(success, [node._constructs]); 145 return; 146 } 147 148 // if someone else is already loading the constructs, just add the callbacks 149 node._constructLoadHandlers = node._constructLoadHandlers || []; 150 if (node._constructLoadHandlers.length > 0) { 151 node._constructLoadHandlers.push({success: success, error: error}); 152 return; 153 } 154 155 // we are the first to load the constructs, register the callbacks and 156 // trigger the ajax call 157 node._constructLoadHandlers.push({success: success, error: error}); 158 node._read(function () { 159 node._authAjax({ 160 url: GCN.settings.BACKEND_PATH + 161 '/rest/construct?embed=category&nodeId=' + node.id(), 162 type: 'GET', 163 error: function (xhr, status, msg) { 164 var i; 165 for (i = 0; i < node._constructLoadHandlers.length; i++) { 166 GCN.handleHttpError(xhr, msg, node._constructLoadHandlers[i].error); 167 } 168 }, 169 success: function (response) { 170 var i; 171 if (GCN.getResponseCode(response) === 'OK') { 172 node._constructs = GCN.mapConstructs(response.items); 173 for (i = 0; i < node._constructLoadHandlers.length; i++) { 174 node._invoke(node._constructLoadHandlers[i].success, [node._constructs]); 175 } 176 } else { 177 for (i = 0; i < node._constructLoadHandlers.length; i++) { 178 GCN.handleResponseError(response, node._constructLoadHandlers[i].error); 179 } 180 } 181 }, 182 183 complete: function () { 184 node._constructLoadHandlers = []; 185 } 186 }); 187 }, error); 188 }, 189 190 /** 191 * Removes this node object. 192 * 193 * @ignore 194 * @param {function=} success Callback function to be invoked when 195 * this operation has completed 196 * successfully. 197 * @param {function(GCNError):boolean=} error Custom error handler. 198 */ 199 remove: function (success, error) { 200 GCN.handleError( 201 GCN.createError( 202 'NOT_YET_IMPLEMENTED', 203 'This method is not yet implemented', 204 this 205 ), 206 error 207 ); 208 }, 209 210 /** 211 * Saves the locally modified changes back to the system. 212 * This is currently not yet implemented. 213 * 214 * @ignore 215 * @param {function=} success Callback function to be invoked when 216 * this operation has completed 217 * successfully. 218 * @param {function(GCNError):boolean=} error Custom error handler. 219 */ 220 save: function (success, error) { 221 GCN.handleError( 222 GCN.createError( 223 'NOT_YET_IMPLEMENTED', 224 'This method is not yet implemented', 225 this 226 ), 227 error 228 ); 229 }, 230 231 /** 232 * Retrieves the top-level folders of this node's root folder. 233 * 234 * @function 235 * @name folders 236 * @memberOf NodeAPI 237 * @param {function(FolderAPI)=} success 238 * @param {function(GCNError):boolean=} error Custom error handler. 239 */ 240 '!folders': function (success, error) { 241 return this.folder(null, error).folders(success, error); 242 }, 243 244 /** 245 * Helper method that will load the constructs of this node. 246 * @ignore 247 * @private 248 * @this {NodeAPI} 249 * @param {function(Array.<object>)} success callback 250 * @param {function(GCNError):boolean=} error callback 251 */ 252 constructCategories: function (success, error) { 253 if (!success) { 254 return; 255 } 256 var node = this; 257 if (node._constructCategories) { 258 node._invoke(success, [node._constructCategories]); 259 } else { 260 node._read(function () { 261 node._data.id = node._chain._data.nodeId; 262 node.constructs(function (constructs) { 263 node._constructCategories = 264 mapConstructCategories(constructs); 265 node._invoke(success, [node._constructCategories]); 266 }, error); 267 }, error); 268 } 269 } 270 }); 271 272 /** 273 * Creates a new instance of NodeAPI. See the {@link NodeAPI} constructor for detailed information. 274 * 275 * @function 276 * @name node 277 * @memberOf GCN 278 * @see NodeAPI 279 */ 280 GCN.node = GCN.exposeAPI(NodeAPI); 281 GCN.NodeAPI = NodeAPI; 282 283 }(GCN)); 284