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