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