1 (function (GCN) {
  2 
  3 	'use strict';
  4 
  5 	/**
  6 	 * @private
  7 	 * @const
  8 	 * @type {string}
  9 	 */
 10 	var GCN_REPOSITORY_ID = 'com.gentics.aloha.GCN.Page';
 11 
 12 	/**
 13 	 * @private
 14 	 * @const
 15 	 * @type {object.<string, boolean>} Default page settings.
 16 	 */
 17 	var DEFAULT_SETTINGS = {
 18 		// Load folder information
 19 		folder: true,
 20 
 21 		// Lock page when loading it
 22 		update: true
 23 	};
 24 
 25 	/**
 26 	 * Searches for the an Aloha editable object of the given id.
 27 	 *
 28 	 * @static
 29 	 * @param {string} id Id of Aloha.Editable object to find.
 30 	 * @return {Aloha.Editable=} The editable object, if wound; otherwise null.
 31 	 */
 32 	function getAlohaEditableById(id) {
 33 		var Aloha = (typeof window !== 'undefined') && window.Aloha;
 34 
 35 		if (!Aloha) {
 36 			return null;
 37 		}
 38 
 39 
 40 		var editables = Aloha.editables;
 41 		var j = editables.length;
 42 
 43 		while (j) {
 44 			if (editables[--j].getId() === id) {
 45 				return editables[j];
 46 			}
 47 		}
 48 
 49 		return null;
 50 	}
 51 
 52 	/**
 53 	 * Checks whether the given tag is a magic link block.
 54 	 *
 55 	 * @private
 56 	 * @static
 57 	 * @param {GCN.Tag} tag Must be a tag that has already been fetched.
 58 	 * @param {object} constructs Set of constructs.
 59 	 * @return {boolean} True if the given tag has the magic link constructId.
 60 	 */
 61 	function isMagicLinkTag(tag, constructs) {
 62 		return !!(constructs[GCN.settings.MAGIC_LINK] &&
 63 		          (constructs[GCN.settings.MAGIC_LINK].constructId ===
 64 			       tag.prop('constructId')));
 65 	}
 66 
 67 	/**
 68 	 * Checks whether or not the given block has a corresponding element in the
 69 	 * document DOM.
 70 	 *
 71 	 * @private
 72 	 * @static
 73 	 * @param {object}
 74 	 * @return {boolean} True if an inline element for this block exists.
 75 	 */
 76 	function hasInlineElement(block) {
 77 		return 0 < jQuery('#' + block.element).length;
 78 	}
 79 
 80 	/**
 81 	 * @private
 82 	 * @const
 83 	 * @type {number}
 84 	 */
 85 	//var TYPE_ID = 10007;
 86 
 87 	/**
 88 	 * @private
 89 	 * @const
 90 	 * @type {Enum}
 91 	 */
 92 	var STATUS = {
 93 
 94 		// page was not found in the database
 95 		NOTFOUND: -1,
 96 
 97 		// page is locally modified and not yet (re-)published
 98 		MODIFIED: 0,
 99 
100 		// page is marked to be published (dirty)
101 		TOPUBLISH: 1,
102 
103 		// page is published and online
104 		PUBLISHED: 2,
105 
106 		// Page is offline
107 		OFFLINE: 3,
108 
109 		// Page is in the queue (publishing of the page needs to be affirmed)
110 		QUEUE: 4,
111 
112 		// page is in timemanagement and outside of the defined timespan
113 		// (currently offline)
114 		TIMEMANAGEMENT: 5,
115 
116 		// page is to be published at a given time (not yet)
117 		TOPUBLISH_AT: 6
118 	};
119 
120 	/**
121 	 * Given a link, will read the data-gentics-aloha-object-id attribute and
122 	 * form it, will determine the backend objec that was selected by the
123 	 * repository browser.
124 	 *
125 	 * @param {jQuery.<HTMLElement>} link A link in an editable.
126 	 * @return {number} The id of the object linked to.
127 	 */
128 	function getRepositoryLinkObjectId(link) {
129 		var data = link.attr('data-gentics-aloha-object-id');
130 
131 		if (!data) {
132 			return null;
133 		}
134 
135 		var id = data.split('.');
136 
137 		if (id.length !== 2) {
138 			return data;
139 		}
140 
141 		return id[1] && parseInt(id[1], 10);
142 	}
143 
144 	/**
145 	* @class
146 	* @name PageAPI
147 	* @extends ContentObjectAPI
148 	* @extends TagContainerAPI
149 	*
150 	* Page object information can be extened using the default REST-API.
151 	* options:
152 	*
153 	* - update: true
154 	* Whether the page should be locked in the backend when loading it.
155 	* default: true
156 	*
157 	* - template: true
158 	* Whether the template information should be embedded in the page object.
159 	* default: true
160 	*
161 	* - folder: true,
162 	* Whether the folder information should be embedded in the page object.
163 	* default: true
164 	* WARNING: do not turn this option off - it will leave the API in a broken
165 	*          state.
166 	*
167 	* - langvars: false,
168 	* When the language variants shall be embedded in the page response.
169 	* default: false
170 
171 	* - workflow: false,
172 	* When the workflow information shall be embedded in the page response.
173 	* default: false
174 
175 	* - pagevars: false,
176 	* When the page variants shall be embedded in the page response. Page
177 	* variants will contain folder information.
178 	* default: false
179 	*
180 	* - translationstatus: false
181 	* Will return information on the page's translation status.
182 	* default: false
183 	*/
184 	var PageAPI = GCN.defineChainback({
185 		/** @lends PageAPI */
186 
187 		__chainbacktype__: 'PageAPI',
188 		_extends: [ GCN.ContentObjectAPI, GCN.TagContainerAPI ],
189 		_type: 'page',
190 
191 		/**
192 		 * @private
193 		 * @type {Array.<object>} A hash set of block tags belonging to this
194 		 *                        content object.  This set is added to when
195 		 *                        this page's tags are rendered.
196 		 */
197 		_blocks: {},
198 
199 		/**
200 		 * @private
201 		 * @type {Array.<object>} A hash set of editable tags belonging to this
202 		 *                        content object.  This set is added to when
203 		 *                        this page's tags are rendered.
204 		 */
205 		_editables: {},
206 
207 		/**
208 		 * @type {Array.string} Writable properties for the page object.
209 		 */
210 		WRITEABLE_PROPS: ['cdate',
211 		                  'description',
212 		                  'fileName',
213 		                  'folderId', // @TODO Check if moving a page is
214 		                              //       implemented correctly.
215 		                  'name',
216 		                  'priority',
217 		                  'templateId'],
218 
219 		/**
220 		 * Gets all blocks in this page.  Will return an array of all block
221 		 * objects found in the page AFTER they have been rendered using an
222 		 * `edit()' call for a contenttag.
223 		 * NOTE: If you have just loaded the page and not used the `edit()'
224 		 *       method for any tag the array will be empty.  Only those blocks
225 		 *       that have been initialized using `edit()' will be available.
226 		 *
227 		 * @return {Array.<object>} Array of block objects.
228 		 */
229 		'!blocks': function () {
230 			return this._blocks;
231 		},
232 
233 		/**
234 		 * Looks for a block with the given id in the `_blocks' array.
235 		 *
236 		 * @private
237 		 * @param {string} id The block's id.
238 		 * @return {?object} The block data object.
239 		 */
240 		'!_getBlockById': function (id) {
241 			return this._blocks[id];
242 		},
243 
244 		/**
245 		 * Maps the received editables into this content object's `_editable'
246 		 * hash.
247 		 *
248 		 * @private
249 		 * @param {Array.<object>} editables An set of object representing
250 		 *                                   editable tags that have been
251 		 *                                   rendered.
252 		 */
253 		'!_storeRenderedEditables': function (editables) {
254 			if (!this.hasOwnProperty('_editables')) {
255 				this._editables = {};
256 			}
257 
258 			var j = editables && editables.length;
259 
260 			while (j) {
261 				this._editables[editables[--j].element] = editables[j];
262 			}
263 		},
264 
265 		/**
266 		 * Maps received blocks of this content object into the `_blocks' hash.
267 		 *
268 		 * @private
269 		 * @param {Array.<object>} blocks An set of object representing
270 		 *                                block tags that have been rendered.
271 		 */
272 		'!_storeRenderedBlocks': function (blocks) {
273 			if (!this.hasOwnProperty('_blocks')) {
274 				this._blocks = {};
275 			}
276 
277 			var j = blocks && blocks.length;
278 
279 			while (j) {
280 				this._blocks[blocks[--j].element] = blocks[j];
281 			}
282 		},
283 
284 		/**
285 		 * Processes rendered tags, and update the `_blocks' and `_editables'
286 		 * array accordingly.  This function is called during pre-saving to
287 		 * update this page's editable tags.
288 		 *
289 		 * @private
290 		 */
291 		'!_prepareTagsForSaving': function (success, error) {
292 			if (!this.hasOwnProperty('_deletedBlocks')) {
293 				this._deletedBlocks = [];
294 			}
295 
296 			var that = this;
297 
298 			this._addNewLinkBlocks(function () {
299 				that.node().constructs(function (constructs) {
300 					var id;
301 					var blocks = [];
302 					for (id in that._blocks) {
303 						if (that._blocks.hasOwnProperty(id)) {
304 							blocks.push(that._blocks[id]);
305 						}
306 					}
307 
308 					that._removeOldLinkBlocks(blocks, constructs, function () {
309 						that._removeUnusedLinkBlocks(blocks, constructs, function () {
310 							that._updateEditableBlocks();
311 							success();
312 						}, error);
313 					}, error);
314 				}, error);
315 			}, error);
316 		},
317 
318 		/**
319 		 * Removes any link blocks that existed in rendered tags, but have
320 		 * since been removed by the user while editing.
321 		 *
322 		 * @private
323 		 * @param {Array.<object>} blocks An array of blocks belonging to this
324 		 *                                page.
325 		 * @param {object} constrcts A set of constructs.
326 		 * @param {function} success
327 		 * @param {function(GCNError):boolean=} error Optional custom error
328 		 *                                            handler.
329 		 */
330 		'!_removeUnusedLinkBlocks': function (blocks, constructs, success,
331 		                                      error) {
332 			if (0 === blocks.length) {
333 				if (success) {
334 					success();
335 				}
336 
337 				return;
338 			}
339 
340 			var j = blocks.length;
341 			var numToProcess = j;
342 
343 			var onProcess = function () {
344 				if (0 === --numToProcess) {
345 					if (success) {
346 						success();
347 					}
348 				}
349 			};
350 
351 			var onError = function (error) {
352 				if (error) {
353 					error();
354 				}
355 
356 				return;
357 			};
358 
359 			var that = this;
360 			var createBlockTagProcessor = function (block) {
361 				return function (tag) {
362 					if (!isMagicLinkTag(tag, constructs) &&
363 							!hasInlineElement(block)) {
364 						that._deletedBlocks.push(block);
365 					}
366 
367 					onProcess();
368 				};
369 			};
370 
371 			while (j) {
372 				this.tag(blocks[--j].tagname,
373 					createBlockTagProcessor(blocks[j]), onError);
374 			}
375 		},
376 
377 		/**
378 		 * Adds any newly created link blocks into this page object.  This is
379 		 * done by looking for all link blocks that do not have corresponding
380 		 * tag in this object's `_blocks' list.  For each anchor tag we find,
381 		 * create a tag for it and, add it in the list of tags.
382 		 *
383 		 * @private
384 		 * @param {function} success Function to invoke if this function
385 		 *                           successeds.
386 		 * @param {function(GCNError):boolean=} error Optional custom error
387 		 *                                            handler.
388 		 */
389 		'!_addNewLinkBlocks': function (success, error) {
390 			var selector = [
391 				'a[data-gentics-aloha-repository="com.gentics.aloha.GCN.Page"]',
392 				'a[data-GENTICS-aloha-repository="com.gentics.aloha.GCN.Page"]',
393 				'a[data-gentics-gcn-url]'
394 			].join(',');
395 
396 			var links = jQuery(selector);
397 
398 			if (0 === links.length) {
399 				if (success) {
400 					success();
401 				}
402 
403 				return;
404 			}
405 
406 			var link;
407 			var j = links.length;
408 			var numToProcess = j;
409 
410 			var onProcessed = function () {
411 				if (0 === --numToProcess) {
412 					success();
413 				}
414 			};
415 
416 			var onError = function () {
417 				if (error) {
418 					error();
419 				}
420 
421 				return;
422 			};
423 
424 			var createOnEditHandler = function (link) {
425 				return function (html, tag) {
426 					link.attr('id', jQuery(html).attr('id'));
427 					tag.part('url', getRepositoryLinkObjectId(link));
428 					onProcessed();
429 				};
430 			};
431 
432 			var tag;
433 
434 			while (j) {
435 				link = links.eq(--j);
436 				if (link.attr('data-gcnignore') === true) {
437 					onProcessed();
438 				} else if (this._getBlockById(link.attr('id'))) {
439 					tag = this.tag(this._getBlockById(link.attr('id')).tagname);
440 					tag.part('text', link.html());
441 					tag.part('url', getRepositoryLinkObjectId(link));
442 					onProcessed();
443 				} else {
444 					this.createTag(GCN.settings.MAGIC_LINK, link.html())
445 					    .edit(createOnEditHandler(link), onError);
446 				}
447 			}
448 		},
449 
450 		/**
451 		 * Any links that change from internal GCN links to external links will
452 		 * have their corresponding blocks added to the '_deletedBlocks' list
453 		 * since they these links no longer need to be tracked.  Any tags in
454 		 * this list will be removed during saving.
455 		 *
456 		 * @private
457 		 * @param {Array.<object>} blocks An array of blocks belonging to this
458 		 *                                page.
459 		 * @param {object} constrcts A set of constructs.
460 		 * @param {function} success
461 		 * @param {function(GCNError):boolean=} error Optional custom error
462 		 *                                            handler.
463 		 */
464 		'!_removeOldLinkBlocks': function (blocks, constructs, success, error) {
465 			if (0 === blocks.length) {
466 				if (success) {
467 					success();
468 				}
469 
470 				return;
471 			}
472 
473 			var j = blocks.length;
474 			var numToProcess = j;
475 
476 			var onProcess = function () {
477 				if (0 === --numToProcess) {
478 					if (success) {
479 						success();
480 					}
481 				}
482 			};
483 
484 			var onError = function (error) {
485 				if (error) {
486 					error();
487 				}
488 
489 				return;
490 			};
491 
492 			var that = this;
493 			var createBlockTagProcessor = function (block) {
494 				return function (tag) {
495 					if (!isMagicLinkTag(tag, constructs)) {
496 						onProcess();
497 						return;
498 					}
499 
500 					var a = jQuery('a[id="' + block.element + '"]');
501 
502 					if (a.length) {
503 						var isExternal = (GCN_REPOSITORY_ID !==
504 							a.attr('data-gentics-aloha-repository')) &&
505 							!a.attr('data-gentics-gcn-url');
506 
507 						// An external tag was found.  Stop tracking it and
508 						// remove it from the list of blocks.
509 						if (isExternal) {
510 							a.removeAttr('id');
511 							that._deletedBlocks.push(block);
512 							delete that._blocks[block.element];
513 						}
514 
515 					// No anchor tag was found for this block.  Add it to the
516 					// "delete" list.
517 					} else {
518 						that._deletedBlocks.push(block);
519 						delete that._blocks[block.element];
520 					}
521 
522 					onProcess();
523 				};
524 			};
525 
526 			while (j) {
527 				this.tag(blocks[--j].tagname,
528 					createBlockTagProcessor(blocks[j]), onError);
529 			}
530 		},
531 
532 		/**
533 		 * Writes the contents of editables back into their corresponding tags.
534 		 * If a corresponding tag cannot be found for an editable, a new one
535 		 * will be created for it.
536 		 *
537 		 * A reference for each editable tag is then added to the `_shadow'
538 		 * object in order that the tag will be sent with the save request.
539 		 *
540 		 * @private
541 		 */
542 		'!_updateEditableBlocks': function () {
543 			var element;
544 			var elementId;
545 			var editable;
546 			var editables = this._editables;
547 			var tags = this._data.tags;
548 			var tagname;
549 			var html;
550 			var alohaEditable;
551 
552 			for (elementId in editables) {
553 				if (editables.hasOwnProperty(elementId)) {
554 					editable = editables[elementId];
555 					element = jQuery('#' + elementId);
556 
557 					// If this editable has no element that was placed in the
558 					// DOM, then do not attempt to update it.
559 					if (0 === element.length) {
560 						continue;
561 					}
562 
563 					tagname = editable.tagname;
564 
565 					if (!tags[tagname]) {
566 						tags[tagname] = {
567 							name       : tagname,
568 							activate   : true,
569 							properties : {}
570 						};
571 					}
572 
573 					// If the editable element has been `aloha()'fied, then we
574 					// need to use `getContents()' from is corresponding
575 					// Aloha.Editable object in order to get clean HTML.
576 
577 					alohaEditable = getAlohaEditableById(elementId);
578 
579 					if (alohaEditable) {
580 						html = alohaEditable.getContents();
581 						alohaEditable.setUnmodified();
582 					} else {
583 						html = element.html();
584 					}
585 
586 					tags[tagname].properties[editable.partname] = {
587 						stringValue: this.encode(html),
588 						type: 'RICHTEXT'
589 					};
590 
591 					this._update('tags.' + tagname, tags[tagname]);
592 				}
593 			}
594 		},
595 
596 		/**
597 		 * @see ContentObjectAPI.!_loadParams
598 		 */
599 		'!_loadParams': function () {
600 			return jQuery.extend(DEFAULT_SETTINGS, this._settings);
601 		},
602 
603 		/**
604 		 * Get this page's template.
605 		 *
606 		 * @public
607 		 * @function
608 		 * @name template
609 		 * @memberOf PageAPI
610 		 * @param {funtion(TemplateAPI)=} success Optional callback to receive
611 		 *                                        a {@link TemplateAPI} object
612 		 *                                        as the only argument.
613 		 * @param {function(GCNError):boolean=} error Optional custom error
614 		 *                                            handler.
615 		 * @return {TemplateAPI} This page's parent template.
616 		 */
617 		'!template': function (success, error) {
618 			var id = this._fetched ? this.prop('templateId') : null;
619 			return this._continue(GCN.TempalteAPI, id, success, error);
620 		},
621 
622 		/**
623 		 * @override
624 		 * @see ContentObjectAPI._save
625 		 */
626 		'!_save': function (settings, success, error) {
627 			var that = this;
628 			this._continueWith(function () {
629 				that._prepareTagsForSaving(function () {
630 					that._persist(settings, success, error);
631 				}, error);
632 			}, error);
633 		},
634 
635 		//---------------------------------------------------------------------
636 		// Surface the tag container methods that are applicable for GCN page
637 		// objects.
638 		//---------------------------------------------------------------------
639 
640 		/**
641 		 * Creates a tag of a given tagtype in this page.
642 		 *
643 		 * Exmaple:
644 		 * <pre>
645 		 *	createTag('link', 'http://www.gentics.com', onSuccess, onError);
646 		 * </pre>
647 		 * or
648 		 * <pre>
649 		 *	createTag('link', onSuccess, onError);
650 		 * </pre>
651 		 *
652 		 * @public
653 		 * @function
654 		 * @name createTag
655 		 * @memberOf PageAPI
656 		 * @param {string|number} construct The name of the construct on which
657 		 *                                  the tag to be created should be
658 		 *                                  derived from.  Or the id of that
659 		 * @param {string=} magicValue Optional property that will override the
660 		 *                             default values of this tag type.
661 		 * @param {function(TagAPI)=} success Optional callback that will
662 		 *                                    receive the newly created tag as
663 		 *                                    its only argument.
664 		 * @param {function(GCNError):boolean=} error Optional custom error
665 		 *                                            handler.
666 		 * @return {TagAPI} The newly created tag.
667 		 * @throws INVALID_ARGUMENTS
668 		 */
669 		'!createTag': function () {
670 			return this._createTag.apply(this, arguments);
671 		},
672 
673 		/**
674 		 * Deletes the specified tag from this page.
675 		 *
676 		 * @public
677 		 * @function
678 		 * @name removeTag
679 		 * @memberOf PageAPI
680 		 * @param {string} id The id of the tag to be deleted.
681 		 * @param {function(PageAPI)=} success Optional callback that receive
682 		 *                                     this object as its only
683 		 *                                     argument.
684 		 * @param {function(GCNError):boolean=} error Optional custom error
685 		 *                                            handler.
686 		 */
687 		removeTag: function () {
688 			this._removeTag.apply(this, arguments);
689 		},
690 
691 		/**
692 		 * Deletes a set of tags from this page.
693 		 *
694 		 * @public
695 		 * @function
696 		 * @name removeTags
697 		 * @memberOf PageAPI
698 		 * @param {Array.<string>} ids The ids of the set of tags to be
699 		 *                             deleted.
700 		 * @param {function(PageAPI)=} success Optional callback that receive
701 		 *                                     this object as its only
702 		 *                                     argument.
703 		 * @param {function(GCNError):boolean=} error Optional custom error
704 		 *                                            handler.
705 		 */
706 		removeTags: function () {
707 			this._removeTags.apply(this, arguments);
708 		},
709 
710 		/**
711 		 * Marks the page as to be taken offline. This method will change the
712 		 * state of the page object.
713 		 *
714 		 * @public
715 		 * @function
716 		 * @name takeOffline
717 		 * @memberOf PageAPI
718 		 * @param {funtion(PageAPI)=} success Optional callback to receive this
719 		 *                                    page object as the only argument.
720 		 * @param {function(GCNError):boolean=} error Optional custom error
721 		 *                                            handler.
722 		 */
723 		takeOffline: function (success, error) {
724 			var that = this;
725 
726 			this._read(function () {
727 				that._update('status', STATUS.OFFLINE, error);
728 				if (success) {
729 					that._save(null, success, error);
730 				}
731 			}, error);
732 		},
733 
734 		/**
735 		 * Trigger publish process for the page.
736 		 *
737 		 * @public
738 		 * @function
739 		 * @name publish
740 		 * @memberOf PageAPI
741 		 * @param {funtion(PageAPI)=} success Optional callback to receive this
742 		 *                                    page object as the only argument.
743 		 * @param {function(GCNError):boolean=} error Optional custom error
744 		 *                                            handler.
745 		 */
746 		publish: function (success, error) {
747 			var that = this;
748 			var parent = this._ancestor();
749 
750 			var ajax = function () {
751 				that._authAjax({
752 					url: GCN.settings.BACKEND_PATH + '/rest/' + that._type +
753 					     '/publish/' + that.id(),
754 					type: 'POST',
755 					json: {}, // there needs to be at least empty content
756 					          // because of a bug in Jersey
757 					success: function (response) {
758 						that._data.status = STATUS.PUBLISHED;
759 						if (success) {
760 							success(that);
761 						}
762 					},
763 					error: error
764 				});
765 			};
766 
767 			// If this chainback object has a ancestor, then invoke that
768 			// parent's `_read()' method before fetching the data for this
769 			// chainback object.
770 			if (parent) {
771 				parent._read(ajax, error);
772 			} else {
773 				ajax();
774 			}
775 		},
776 
777 		/**
778 		 * Renders a preview of the current page.
779 		 *
780 		 * @public
781 		 * @function
782 		 * @name preview
783 		 * @memberOf PageAPI
784 		 * @param {function(string, PageAPI)} success Callback to receive the
785 		 *                                            rendered page preview as
786 		 *                                            the first argument, and
787 		 *                                            this page object as the
788 		 *                                            second.
789 		 * @param {function(GCNError):boolean=} error Optional custom error
790 		 *                                            handler.
791 		 */
792 		preview: function (success, error) {
793 			var that = this;
794 
795 			this._read(function () {
796 				that._authAjax({
797 					url: GCN.settings.BACKEND_PATH + '/rest/' + that._type +
798 					     '/preview/',
799 					json: {
800 						page: that._data // @FIXME Shouldn't this a be merge of
801 						                 //        the `_shadow' object and the
802 										 //        `_data'.
803 					},
804 					type: 'POST',
805 					error: error,
806 					success: function (response) {
807 						if (success) {
808 							GCN._handleContentRendered(response.preview,
809 								function (html) {
810 									success(html, that);
811 								});
812 						}
813 					}
814 				});
815 			}, error);
816 		},
817 
818 		/**
819 		 * Unlocks the page when finishing editing
820 		 *
821 		 * @public
822 		 * @function
823 		 * @name unlock
824 		 * @memberOf PageAPI
825 		 * @param {funtion(PageAPI)=} success Optional callback to receive this
826 		 *                                    page object as the only argument.
827 		 * @param {function(GCNError):boolean=} error Optional custom error
828 		 *                                            handler.
829 		 */
830 		unlock: function (success, error) {
831 			var that = this;
832 			var parent = this._ancestor();
833 
834 			var ajax = function () {
835 				that._authAjax({
836 					url: GCN.settings.BACKEND_PATH + '/rest/' + that._type +
837 					     '/cancel/' + that.id(),
838 					type: 'POST',
839 					json: {}, // There needs to be at least empty content
840 					          // because of a bug in Jersey.
841 					error: error,
842 					success: function (response) {
843 						if (success) {
844 							success(that);
845 						}
846 					}
847 				});
848 			};
849 
850 			// If this chainback object has a ancestor, then invoke that
851 			// parent's `_read()' method before fetching the data for this
852 			// chainback object.
853 			if (parent) {
854 				parent._read(ajax, error);
855 			} else {
856 				ajax();
857 			}
858 		},
859 
860 		/**
861 		 * @see GCN.ContentObjectAPI._processResponse
862 		 */
863 		'!_processResponse': function (data) {
864 			jQuery.extend(this._data, data[this._type]);
865 
866 			// if data contains page variants turn them into page objects
867 			if (this._data.pageVariants) {
868 				var pagevars = [];
869 				var i;
870 				for (i = 0; i < this._data.pageVariants.length; i++) {
871 					pagevars.push(this._continue(GCN.PageAPI,
872 							this._data.pageVariants[i]));
873 				}
874 				this._data.pageVariants = pagevars;
875 			}
876 		}
877 
878 	});
879 
880 	GCN.page = GCN.exposeAPI(PageAPI);
881 	GCN.PageAPI = PageAPI;
882 
883 }(GCN));
884