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 	 * Maps constructs, that were fetched via the Rest API, using their keyword
 69 	 * as the keys.
 70 	 *
 71 	 * @param {object<string, object>} constructs Consturcts mapped against
 72 	 *                                            their id.
 73 	 * @return {object<string, object>} Constructs mapped against their keys.
 74 	 */
 75 	function mapConstructs(constructs) {
 76 		if (!constructs) {
 77 			return {};
 78 		}
 79 		var map = {};
 80 		var constructId;
 81 		for (constructId in constructs) {
 82 			if (constructs.hasOwnProperty(constructId)) {
 83 				map[constructs[constructId].keyword] = constructs[constructId];
 84 			}
 85 		}
 86 		return map;
 87 	}
 88 
 89 	/**
 90 	 * Node object.
 91 	 *
 92 	 * @name NodeAPI
 93 	 * @class
 94 	 * @augments Chainback
 95 	 */
 96 	var NodeAPI = GCN.defineChainback({
 97 		/** @lends NodeAPI */
 98 
 99 		__chainbacktype__: 'NodeAPI',
100 		_extends: GCN.ContentObjectAPI,
101 		_type: 'node',
102 
103 		_data: {
104 			folderId: null
105 		},
106 
107 		/**
108 		 * @private
109 		 * @type {object<string, number} Constructs for this node are cached
110 		 *                               here so that we only need to fetch
111 		 *                               this once.
112 		 */
113 		_constructs: null,
114 
115 		/**
116 		 * @private
117 		 * @type {object<string, object} Constructs categories for this node.
118 		 *                               Cached here so that we only need to
119 		 *                               fetch this once.
120 		 */
121 		_constructCategories: null,
122 
123 		/**
124 		 * Retrieves a list of constructs and constructs categories that are
125 		 * assigned to this node and passes it as the only argument into the
126 		 * the `success()' callback.
127 		 *
128 		 * @param {function(Array.<object>)=} success Callback to receive an
129 		 *                                            array of constructs.
130 		 * @param {function(GCNError):boolean=} error Custom error handler.
131 		 * @return undefined
132 		 * @throws INVALID_ARGUMENTS
133 		 */
134 		constructs: function (success, error) {
135 			if (!success) {
136 				return;
137 			}
138 			var node = this;
139 			if (node._constructs) {
140 				node._invoke(success, [node._constructs]);
141 				return;
142 			}
143 			node._read(function () {
144 				node._authAjax({
145 					url: GCN.settings.BACKEND_PATH +
146 					     '/rest/construct/list.json?nodeId=' + node.id(),
147 					type: 'GET',
148 					error: function (xhr, status, msg) {
149 						GCN.handleHttpError(xhr, msg, error);
150 					},
151 					success: function (response) {
152 						if (GCN.getResponseCode(response) === 'OK') {
153 							node._constructs = mapConstructs(response.constructs);
154 							node._invoke(success, [node._constructs]);
155 						} else {
156 							GCN.handleResponseError(response, error);
157 						}
158 					}
159 				});
160 			}, error);
161 		},
162 
163 		/**
164 		 * Removes this node object.
165 		 *
166 		 * @param {function=} success Callback function to be invoked when
167 		 *                            this operation has completed
168 		 *                            successfully.
169 		 * @param {function(GCNError):boolean=} error Custom error handler.
170 		 */
171 		remove: function (success, error) {
172 			GCN.handleError(
173 				GCN.error('NOT_YET_IMPLEMENTED',
174 						  'This method is not yet implemented', this),
175 				error
176 			);
177 		},
178 
179 		/**
180 		 * Saves the locally modified changes back to the system.
181 		 * This is currently not yet implemented.
182 		 *
183 		 * @param {function=} success Callback function to be invoked when
184 		 *                            this operation has completed
185 		 *                            successfully.
186 		 * @param {function(GCNError):boolean=} error Custom error handler.
187 		 */
188 		save: function (success, error) {
189 			GCN.handleError(
190 				GCN.error('NOT_YET_IMPLEMENTED',
191 						  'This method is not yet implemented', this),
192 				error
193 			);
194 		},
195 
196 		/**
197 		 * Retrieves the top-level folders of this node's root folder.
198 		 *
199 		 * @param {function(FolderAPI)=} success
200 		 * @param {function(GCNError):boolean=} error Custom error handler.
201 		 */
202 		'!folders': function (success, error) {
203 			return this.folder(null, error).folders(success, error);
204 		},
205 
206 		/**
207 		 * Helper method that will load the constructs of this node.
208 		 *
209 		 * @private
210 		 * @this {NodeAPI}
211 		 * @param {function(Array.<object>)} success callback
212 		 * @param {function(GCNError):boolean=} error callback
213 		 */
214 		constructCategories: function (success, error) {
215 			if (!success) {
216 				return;
217 			}
218 			var node = this;
219 			if (node._constructCategories) {
220 				node._invoke(success, [node._constructCategories]);
221 			} else {
222 				node._read(function () {
223 					node._data.id = node._chain._data.nodeId;
224 					node.constructs(function (constructs) {
225 						node._constructCategories =
226 								mapConstructCategories(constructs);
227 						node._invoke(success, [node._constructCategories]);
228 					}, error);
229 				}, error);
230 			}
231 		}
232 	});
233 
234 	GCN.node = GCN.exposeAPI(NodeAPI);
235 	GCN.NodeAPI = NodeAPI;
236 
237 }(GCN));
238