1 (function (GCN) { 2 3 'use strict'; 4 5 /** 6 * @const 7 * @type {string} All rendered content blocks come with this prefixed to 8 * their id. 9 */ 10 var CONTENT_BLOCK_PREFIX = 'GENTICS_BLOCK_'; 11 12 /** 13 * @type {RexExp} Will match <span id="GENTICS_block_123"></span>" but not 14 * "<node abc123>" tags. The first backreference contains 15 * the tagname of the tag corresponding to this block. 16 */ 17 var contentBlockRegExp = new RegExp( 18 '<(?!node)[a-z]+\\s' + // "<span or "<div " but not "<node " 19 '[^>]*?' + // ... 20 'id\\s*=\\s*[\\"\\\']?' + // "id = '" 21 CONTENT_BLOCK_PREFIX + // "GENTICS_block_" 22 '(\\w+)' + // "123" 23 '[\\"\\\']?[^>]*>' + // "' ...>" 24 '<\\s*\\/[a-z]+>', // "</span>" or "</div>" 25 'gim' 26 ); 27 28 /** 29 * @private 30 * @type {RegExp} Will match <node foo> or <node bar_123> or <node foo-bar> 31 * but not <node "blah">. 32 */ 33 var nodeNotationRegExp = /<node ([a-z0-9_\-]+?)>/gim; 34 35 /** 36 * Examins a string for "<node>" tags, and for each occurance of this 37 * notation, the given callback will be invoked to manipulate the string. 38 * 39 * @private 40 * @static 41 * @param {string} str The string that will be examined for "<node>" tags. 42 * @param {function} onMatchFound Callback function that should receive the 43 * following three parameters: 44 * 45 * name:string The name of the tag being notated by the 46 * node substring. If the `str' arguments 47 * is "<node myTag>", then the `name' value 48 * will be "myTag". 49 * offset:number The offset where the node substring was 50 * found within the examined string. 51 * str:string The string in which the "<node *>" 52 * substring occured. 53 * 54 * The return value of the function will 55 * replace the entire "<node>" substring 56 * that was passed to it within the examined 57 * string. 58 */ 59 function replaceNodeTags(str, onMatchFound) { 60 var parsed = str.replace(nodeNotationRegExp, function (substr, tagname, 61 offset, 62 examined) { 63 return onMatchFound(tagname, offset, examined); 64 }); 65 66 return parsed; 67 } 68 69 /** 70 * @class 71 * @name ContentObjectAPI 72 */ 73 GCN.ContentObjectAPI = GCN.defineChainback({ 74 /** @lends ContentObjectAPI */ 75 76 /** 77 * @protected 78 * @type {string} A string denoting a content node type. This value is 79 * used to compose the correct REST API ajax urls. The 80 * following are valid values: "node", "folder", 81 * "template", "page", "file", "image". 82 */ 83 _type: null, 84 85 /** 86 * @protected 87 * @type {object<string,*>} An internal object to store data that we 88 * get from the server. 89 */ 90 _data: {}, 91 92 /** 93 * @protected 94 * @type {object<string,*>} An internal object to store updates to 95 * the content object. Should reflect the 96 * structural typography of the `_data' 97 * object. 98 */ 99 _shadow: {}, 100 101 /** 102 * @type {boolean} Flags whether or not data for this content object have 103 * been fetched from the server. 104 */ 105 _fetched: false, 106 107 /** 108 * @protected 109 * @type {object} will contain an objects internal settings 110 */ 111 _settings: null, 112 113 /** 114 * @public 115 * @type {Array.<string} Writeable properties for all content objects. 116 */ 117 WRITEABLE_PROPS: [], 118 119 /** 120 * Internal method, to fetch this object's data from the server. 121 * 122 * @protected 123 * @param {function(ContentObjectAPI)=} success Optional callback that 124 * receives this object as 125 * its only argument. 126 * @param {function(GCNError):boolean=} error Optional customer error 127 * handler. 128 */ 129 '!_read': function (success, error) { 130 if (this._fetched) { 131 if (success) { 132 success(this); 133 } 134 135 return; 136 } 137 138 var that = this; 139 var id = this.id(); 140 141 if (id === null || id === undefined) { 142 this._getIdFromParent(function () { 143 that._read(success, error); 144 }, error); 145 146 return; 147 } 148 149 var parent = this._ancestor(); 150 var channel = ''; 151 152 if (GCN.channel()) { 153 channel = '?nodeId=' + GCN.channel(); 154 } 155 156 var ajax = function () { 157 that._authAjax({ 158 url : GCN.settings.BACKEND_PATH + '/rest/' + 159 that._type + '/load/' + that.id() + channel, 160 data : that._loadParams(), 161 error : error, 162 success : function (response) { 163 that._processResponse(response); 164 that._fetched = true; 165 166 if (success) { 167 success(that); 168 } 169 } 170 }); 171 }; 172 173 // If this chainback object has an ancestor, then invoke that 174 // parent's `_read()' method before fetching the data for this 175 // chainback object. 176 if (parent) { 177 parent._read(ajax, error); 178 } else { 179 ajax(); 180 } 181 }, 182 183 /** 184 * Retrieves this object's id from its parent. This function is used 185 * in order for this object to be able to fetch its data from that 186 * backend. 187 * 188 * @protected 189 * @param {function(ContentObjectAPI)=} success Optional callback that 190 * receives this object as 191 * its only argument. 192 * @param {function(GCNError):boolean=} error Optional customer error 193 * handler. 194 * @throws CANNOT_GET_OBJECT_ID 195 */ 196 '!_getIdFromParent': function (success, error) { 197 var parent = this._ancestor(); 198 199 if (!parent) { 200 var err = GCN.createError('CANNOT_GET_OBJECT_ID', 201 'Cannot get an id for object', this); 202 203 GCN.handleError(err, error); 204 205 return; 206 } 207 208 var that = this; 209 210 parent._read(function () { 211 if (that._type === 'folder') { 212 // There are 3 possible property names that an object can 213 // hold the id of the folder that it is related to: 214 // 215 // "folderId": for pages, templates, files, and images. 216 // "motherId": for folders 217 // "nodeId": for nodes 218 // 219 // We need to see which of this properties is set, the 220 // first one we find will be our folder's id. 221 var props = ['folderId', 'motherId', 'nodeId']; 222 var prop = props.pop(); 223 var id; 224 225 while (prop) { 226 id = parent.prop(prop); 227 228 if (typeof id !== 'undefined') { 229 break; 230 } 231 232 prop = props.pop(); 233 } 234 235 that._data.id = id; 236 } else { 237 that._data.id = parent.prop(that._type + 'Id'); 238 } 239 240 if (that._data.id === null || typeof that._data.id === 'undefined') { 241 var err = GCN.createError('CANNOT_GET_OBJECT_ID', 242 'Cannot get an id for object', this); 243 244 GCN.handleError(err, error); 245 246 return; 247 } 248 249 that._setHash(that._data.id)._addToCache(); 250 251 if (success) { 252 success(); 253 } 254 }, error); 255 }, 256 257 /** 258 * Gets this object's id. We'll return the id of the object when it has 259 * been loaded. This can only be a localid. Otherwise we'll return the 260 * id which was provided by the user. This can either be a localid or a 261 * globalid. 262 * 263 * @name id 264 * @function 265 * @memberOf ContentObjectAPI 266 * @public 267 * @return {number} 268 */ 269 '!id': function () { 270 return this._data.id; 271 }, 272 273 /** 274 * Alias for `id()' 275 * 276 * @name id 277 * @function 278 * @memberOf ContentObjectAPI 279 * @protected 280 * @return {number} 281 */ 282 '!localId': function () { 283 return this.id(); 284 }, 285 286 /** 287 * Update the `_shadow' object that maintains changes to properties 288 * that reflected the internal `_data' object. This shadow object is 289 * used to persist differential changes to a REST API object. 290 * 291 * @protected 292 * @param {string} path The path through the object to the property we 293 * want to modify. 294 * @param {*} value The value we wish to set the property to. 295 * @param {function=} error Custom error handler. 296 * @param {boolean=} force If true, no error will be thrown if `path' 297 * cannot be fully resolved against the 298 * internal `_data' object, instead, the path 299 * will be created on the shadow object. 300 */ 301 '!_update': function (pathStr, value, error, force) { 302 var path = pathStr.split('.'); 303 var shadow = this._shadow; 304 var actual = this._data; 305 var i = 0; 306 var j = path.length; 307 var pathNode; 308 // Whether or not the traversal path in `_data' and `_shadow' are 309 // at the same position in the respective objects. 310 var areMirrored = true; 311 312 while (true) { 313 pathNode = path[i++]; 314 315 if (areMirrored) { 316 actual = actual[pathNode]; 317 areMirrored = jQuery.type(actual) !== 'undefined'; 318 } 319 320 if (i === j) { 321 break; 322 } 323 324 if (shadow[pathNode]) { 325 shadow = shadow[pathNode]; 326 } else if (areMirrored || force) { 327 shadow = (shadow[pathNode] = {}); 328 } else { 329 break; // goto error 330 } 331 } 332 333 if (i === j && (areMirrored || force)) { 334 shadow[pathNode] = value; 335 } else { 336 var err = GCN.createError('TYPE_ERROR', 'Object "' + 337 path.slice(0, i).join('.') + '" does not exist', 338 actual); 339 340 GCN.handleError(err, error); 341 } 342 }, 343 344 /** 345 * Receives the response from a REST API request, and stores it in the 346 * internal `_data' object. 347 * 348 * @protected 349 * @param {object} data Parsed JSON response data. 350 */ 351 '!_processResponse': function (data) { 352 jQuery.extend(this._data, data[this._type]); 353 }, 354 355 /** 356 * Specifies a list of parameters that will be added to the url when 357 * loading the content object from the server. 358 * 359 * @protected 360 * @return {object} object With parameters to be appended to the load 361 * request 362 */ 363 '!_loadParams': function () {}, 364 365 /** 366 * Reads the proporty `property' of this content object if this 367 * property is among those in the WRITEABLE_PROPS array. If a send 368 * argument is provided, them the property is updated with that value. 369 * 370 * @name prop 371 * @function 372 * @memberOf ContentObjectAPI 373 * @param {String} property Name of the property to be read or updated. 374 * @param {String} value Value to be set property to. 375 * @return {?*} Meta attribute. 376 * @throws UNFETCHED_OBJECT_ACCESS 377 * @throws READONLY_ATTRIBUTE 378 */ 379 '!prop': function (property, value) { 380 if (!this._fetched) { 381 GCN.error('UNFETCHED_OBJECT_ACCESS', 382 'Object not fetched yet.'); 383 384 return; 385 } 386 387 if (value) { 388 if (jQuery.inArray(property, this.WRITEABLE_PROPS) >= 0) { 389 this._update(property, value); 390 } else { 391 GCN.error('READONLY_ATTRIBUTE', 392 'Attribute "' + property + '" of ' + this._type + 393 ' is read-only. Writeable properties are: ' + 394 this.WRITEABLE_PROPS); 395 } 396 } 397 398 return ((jQuery.type(this._shadow[property]) !== 'undefined' 399 ? this._shadow : this._data)[property]); 400 }, 401 402 /** 403 * Sends the a template string to the Aloha Servlet for rendering. 404 * 405 * @protected 406 * @param {string} template Template which will be rendered. 407 * @param {string} mode The rendering mode. Valid values are "view", 408 * "edit", "pub." 409 * @param {function(object)} success callback the receives the render 410 * response. 411 * @param {function(GCNError):boolean} error Error handler. 412 */ 413 '!_renderTemplate' : function (template, mode, success, error) { 414 var url = GCN.settings.BACKEND_PATH + '/rest/' + this._type + 415 '/render/' + this.id(); 416 417 url += '?edit=' + ('edit' === mode) + '&template=' + template; 418 419 this._authAjax({ 420 url : url, 421 error : error, 422 success : success 423 }); 424 }, 425 426 /** 427 * Wrapper for internal chainback _ajax method. 428 * 429 * @protected 430 * @param {object<string, *>} settings Settings for the ajax request. 431 * The settings object is identical 432 * to that of the `GCN.ajax' 433 * method, which handles the actual 434 * ajax transportation. 435 * @throws AJAX_ERROR 436 */ 437 '!_ajax': function (settings) { 438 var that = this; 439 440 // force no cache for all API calls 441 settings.cache = false; 442 settings.success = (function (onSuccess, onError) { 443 return function (data) { 444 // Ajax calls that do not target the REST API servlet do 445 // not response data with a `responseInfo' object. 446 // "/CNPortletapp/alohatag" is an example. So we cannot 447 // just assume that it exists. 448 if (data.responseInfo) { 449 switch (data.responseInfo.responseCode) { 450 case 'OK': 451 break; 452 case 'AUTHREQUIRED': 453 GCN.clearSession(); 454 that._authAjax(settings); 455 return; 456 default: 457 GCN.handleResponseError(data, onError); 458 return; 459 } 460 } 461 462 if (onSuccess) { 463 onSuccess(data); 464 } 465 }; 466 }(settings.success, settings.error, settings.url)); 467 468 this._queueAjax(settings); 469 }, 470 471 /** 472 * Similar to `_ajax', except that it prefixes the ajax url with the 473 * current session's `sid', and will trigger an 474 * `authentication-required' event if the session is not authenticated. 475 * 476 * @TODO(petro): Consider simplifiying this function signature to read: 477 * `_auth( url, success, error )' 478 * 479 * @protected 480 * @param {object<string, *>} settings Settings for the ajax request. 481 * @throws AUTHENTICATION_FAILED 482 */ 483 _authAjax: function (settings) { 484 var that = this; 485 486 if (GCN.isAuthenticating) { 487 GCN.afterNextAuthentication(function () { 488 that._authAjax(settings); 489 }); 490 491 return; 492 } 493 494 if (!GCN.sid) { 495 var cancel; 496 497 if (settings.error) { 498 cancel = function (error) { 499 GCN.handleError( 500 error || GCN.createError('AUTHENTICATION_FAILED'), 501 settings.error 502 ); 503 }; 504 } else { 505 cancel = function (error) { 506 if (error) { 507 GCN.error(error.code, error.message, error.data); 508 } else { 509 GCN.error('AUTHENTICATION_FAILED'); 510 } 511 }; 512 } 513 514 GCN.afterNextAuthentication(function () { 515 that._authAjax(settings); 516 }); 517 518 if (GCN.usingSSO) { 519 // First, try to automatically authenticate via 520 // Single-SignOn 521 GCN.loginWithSSO(GCN.onAuthenticated, function () { 522 // ... if SSO fails, then fallback to requesting user 523 // credentials: broadcast `authentication-required' 524 // message. 525 GCN.authenticate(cancel); 526 }); 527 } else { 528 // Trigger the `authentication-required' event to request 529 // user credentials. 530 GCN.authenticate(cancel); 531 } 532 533 return; 534 } 535 536 // Append "?sid=..." or "&sid=..." if needed. 537 538 var isSidInUrl = /[\?\&]sid=/.test(settings.url); 539 if (!isSidInUrl) { 540 var isFirstParam = (jQuery.inArray('?', settings.url) === -1); 541 542 settings.url += (isFirstParam ? '?' : '&') + 'sid=' 543 + (GCN.sid || ''); 544 } 545 546 this._ajax(settings); 547 }, 548 549 /** 550 * Recursively call `_continueWith()'. 551 * 552 * @protected 553 * @override 554 */ 555 '!_onContinue': function (success, error) { 556 var that = this; 557 this._continueWith(function () { 558 that._read(success, error); 559 }, error); 560 }, 561 562 /** 563 * Initializes this content object. If a `success' callback is 564 * provided, it will cause this object's data to be fetched and passed 565 * to the callback. This object's data will be fetched from the cache 566 * if is available, otherwise it will be fetched from the server. If 567 * this content object API contains parent chainbacks, it will get its 568 * parent to fetch its own data first. 569 * 570 * You might also provide an object for initialization, to directly 571 * instantiate the object's data without loading it from the server. 572 * To do so just pass in a data object as received from the server 573 * instead of an id--just make sure this object has an `id' property. 574 * 575 * If an `error' handler is provided, as the third parameter, it will 576 * catch any errors that have occured since the invocation of this 577 * call. It allows the global error handler to be intercepted before 578 * stopping the error or allowing it to propagate on to the global 579 * handler. 580 * 581 * @protected 582 * @param {number|string|object} id 583 * @param {function(ContentObjectAPI))=} success Optional success 584 * callback that will 585 * receive this 586 * object as its only 587 * argument. 588 * @param {function(GCNError):boolean=} error Optional custom error 589 * handler. 590 * @param {object} settings Basic settings for this object. 591 * @throws INVALID_DATA If no id is found when providing an object for 592 * initialization. 593 */ 594 _init: function (id, success, error, settings) { 595 this._settings = settings; 596 597 if (jQuery.type(id) === 'object') { 598 if (!id.id) { 599 var err = GCN.createError('INVALID_DATA', 600 'Data not sufficient for initalization: id is missing', 601 id); 602 603 GCN.handleError(err, error); 604 605 return; 606 } 607 608 // Clear the old data and populate the _data field using the given object data 609 this._data = {}; 610 this._data = id; 611 this._fetched = true; 612 613 if (success) { 614 success(this); 615 } 616 } else { 617 // Ensure that each object has its very own `_data' and 618 // `_shadow' objects. 619 if (!this._fetched) { 620 this._data = {}; 621 this._shadow = {}; 622 this._data.id = id; 623 } 624 625 if (success) { 626 this._read(success, error); 627 } 628 } 629 }, 630 631 /** 632 * Replaces tag blocks with appropriate "<node *>" notation in a given 633 * string. 634 * 635 * Given an element whose innerHTML is: 636 * <pre> 637 * "<span id="GENTICS_BLOCK_123">My Tag</span>", 638 * </pre> 639 * `encode()' will return: 640 * <pre> 641 * "<node 123>". 642 * </pre> 643 * 644 * @name encode 645 * @function 646 * @memberOf ContentObjectAPI 647 * @param {string} HTML string to be encoded. 648 * @return {string} The encoded HTML string. 649 */ 650 '!encode': function (html) { 651 var that = this; 652 var clone = jQuery('<div>' + html + '</div>'); 653 654 // Empty all content blocks of their innerHTML. 655 clone.find('[id^=' + CONTENT_BLOCK_PREFIX + ']').html(''); 656 657 return clone.html().replace(contentBlockRegExp, 658 function (substr, match) { 659 var tag = that._getTagDataById(match); 660 661 // assert(tag) 662 return tag ? '<node ' + tag.name + '>' : ''; 663 }); 664 }, 665 666 /** 667 * For a given string, replace all occurances of "<node>" with 668 * appropriate HTML markup, allowing notated tags to be rendered within 669 * the surrounding HTML content. 670 * 671 * The `success()' handler will receives a string containing the 672 * contents of the `str' string with references to "<node>" having been 673 * inflated into their appropriate tag rendering. 674 * 675 * @name decode 676 * @function 677 * @memberOf ContentObjectAPI 678 * @param {string} str The content string, in which "<node *>" tags 679 * will be inflated with their HTML rendering. 680 * @param {function(ContentObjectAPI))} success Success callback that 681 * will receive the 682 * decoded string. 683 * @param {function(GCNError):boolean=} error Optional custom error 684 * handler. 685 */ 686 '!decode': function (str, success, error) { 687 if (!success) { 688 return; 689 } 690 691 var prefix = 'gcn-tag-placeholder-'; 692 var toRender = []; 693 var html = replaceNodeTags(str, function (name, offset, str) { 694 toRender.push('<node ', name, '>'); 695 return '<div id="' + prefix + name + '"></div>'; 696 }); 697 698 if (!toRender.length) { 699 success(html); 700 return; 701 } 702 703 // Instead of rendering each tag individually, we render them 704 // together in one string, and map the results back into our 705 // original html string. This allows us to perform one request to 706 // the server for any number of node tags found. 707 708 var parsed = jQuery('<div>' + html + '</div>'); 709 var template = toRender.join(''); 710 var that = this; 711 712 this._renderTemplate(template, 'edit', function (data) { 713 var content = data.content; 714 var tag; 715 var tags = data.tags; 716 var j = tags.length; 717 var rendered = jQuery('<div>' + content + '</div>'); 718 719 var replaceTag = (function (numTags) { 720 return function (tag) { 721 parsed.find('#' + prefix + tag.prop('name')) 722 .replaceWith( 723 rendered.find('#' + tag.prop('id')) 724 ); 725 726 if (0 === --numTags) { 727 success(parsed.html()); 728 } 729 }; 730 }(j)); 731 732 while (j) { 733 that.tag(tags[--j], replaceTag); 734 } 735 }, error); 736 }, 737 738 /** 739 * Clears this object from its constructor's cache so that the next 740 * attempt to access this object will result in a brand new instance 741 * being initialized and placed in the cache. 742 * 743 * @name clear 744 * @function 745 * @memberOf ContentObjectAPI 746 */ 747 '!clear': function () { 748 // Do not clear the id from the _data. 749 var id = this._data.id; 750 this._data = {}; 751 this._data.id = id; 752 this._shadow = {}; 753 this._fetched = false; 754 this._clearCache(); 755 }, 756 757 /** 758 * Retreives this objects parent folder. 759 * 760 * @param {function(ContentObjectAPI)=} success Callback that will 761 * receive the requested 762 * object. 763 * @param {function(GCNError):boolean=} error Custom error handler. 764 * @return {ContentObjectAPI)} API object for the retrieved GCN folder. 765 */ 766 '!folder': function (success, error) { 767 return this._continue(GCN.FolderAPI, this._data.folderId, success, 768 error); 769 }, 770 771 /** 772 * Saves changes made to this content object to the backend. 773 * 774 * @param {object=} settings Optional settings to pass on to the ajax 775 * function. 776 * @param {function(ContentObjectAPI)=} success Optional callback that 777 * receives this object as 778 * its only argument. 779 * @param {function(GCNError):boolean=} error Optional customer error 780 * handler. 781 */ 782 save: function () { 783 var settings; 784 var success; 785 var error; 786 var args = Array.prototype.slice.call(arguments); 787 var len = args.length; 788 var i; 789 790 for (i = 0; i < len; ++i) { 791 switch (jQuery.type(args[i])) { 792 case 'object': 793 if (!settings) { 794 settings = args[i]; 795 } 796 break; 797 case 'function': 798 if (!success) { 799 success = args[i]; 800 } else { 801 error = args[i]; 802 } 803 break; 804 case 'undefined': 805 break; 806 default: 807 var err = GCN.createError('UNKNOWN_ARGUMENT', 808 'Don\'t know what to do with arguments[' + i + '] ' + 809 'value: "' + args[i] + '"', args); 810 GCN.handleError(err, error); 811 return; 812 } 813 } 814 815 this._save(settings, success, error); 816 }, 817 818 /** 819 * Persists this object's local data onto the server. If the object 820 * has not yet been fetched we need to get it first so we can update 821 * its internals properly... 822 * 823 * @protected 824 * @param {object} settings Object which will extend the basic 825 * settings of the ajax call 826 * @param {function(ContentObjectAPI)=} success Optional callback that 827 * receives this object as 828 * its only argument. 829 * @param {function(GCNError):boolean=} error Optional customer error 830 * handler. 831 */ 832 '!_save': function (settings, success, error) { 833 var that = this; 834 this._continueWith(function () { 835 that._persist(settings, success, error); 836 }, error); 837 }, 838 839 /** 840 * Sends the current state of this content object to be stored on the 841 * server. 842 * 843 * @protected 844 * @param {function(ContentObjectAPI)=} success Optional callback that 845 * receives this object as 846 * its only argument. 847 * @param {function(GCNError):boolean=} error Optional customer error 848 * handler. 849 * @throws HTTP_ERROR 850 */ 851 '!_persist': function (settings, success, error) { 852 var that = this; 853 854 if (!this._fetched) { 855 that._read(function () { 856 that._persist(settings, success, error); 857 }, error); 858 859 return; 860 } 861 862 var json = {}; 863 864 if (this._deletedTags.length) { 865 json['delete'] = this._deletedTags; 866 } 867 868 if (this._deletedBlocks.length) { 869 json['delete'] = json['delete'] 870 ? json['delete'].concat(this._deletedBlocks) 871 : this._deletedBlocks; 872 } 873 874 json[this._type] = this._shadow; 875 json[this._type].id = this._data.id; 876 877 jQuery.extend(json, settings); 878 879 this._authAjax({ 880 url : GCN.settings.BACKEND_PATH + '/rest/' + that._type + 881 '/save/' + that.id(), 882 type : 'POST', 883 error : error, 884 json : json, 885 success : function (response) { 886 // We must not overwrite the `_data.tags' object with this 887 // one. 888 delete that._shadow.tags; 889 890 // Everything else in `_shadow' should be written over 891 // `_data' before resetting the `_shadow' object. 892 jQuery.extend(that._data, that._shadow); 893 that._shadow = {}; 894 895 // Tags have been deleted. 896 that._deletedTags = []; 897 that._deletedBlocks = []; 898 899 if (success) { 900 success(that); 901 } 902 } 903 }); 904 }, 905 906 /** 907 * Deletes this content object from its containing parent. 908 * 909 * @param {function(ContentObjectAPI)=} success Optional callback that 910 * receives this object as 911 * its only argument. 912 * @param {function(GCNError):boolean=} error Optional customer error 913 * handler. 914 */ 915 remove: function (success, error) { 916 this._remove(success, error); 917 }, 918 919 /** 920 * Performs a REST API request to delete this object from the server. 921 * 922 * @protected 923 * @param {function(ContentObjectAPI)=} success Optional callback that 924 * will be invoked once 925 * this object has been 926 * removed. 927 * @param {function(GCNError):boolean=} error Optional customer error 928 * handler. 929 */ 930 '!_remove': function (success, error) { 931 var that = this; 932 this._authAjax({ 933 url : GCN.settings.BACKEND_PATH + '/rest/' + that._type + 934 '/delete/' + that.id(), 935 type : 'POST', 936 error : error, 937 success : function (response) { 938 // Clean cache & reset object to make sure it can't be used 939 // properly any more. 940 that._clearCache(); 941 that._data = {}; 942 that._shadow = {}; 943 944 // Don't forward the object to the success handler since 945 // it's been deleted. 946 if (success) { 947 success(); 948 } 949 } 950 }); 951 } 952 953 }); 954 955 /** 956 * Generates a factory method for chainback classes. The method signature 957 * used with this factory function will match that of the target class' 958 * constructor. Therefore this function is expected to be invoked with the 959 * follow combination of arguments ... 960 * 961 * Examples for GCN.pages api: 962 * 963 * To get an array containing 1 page: 964 * pages(1) 965 * pages(1, function () {}) 966 * 967 * To get an array containing 2 pages: 968 * pages([1, 2]) 969 * pages([1, 2], function () {}) 970 * 971 * To get an array containing any and all pages: 972 * pages() 973 * pages(function () {}) 974 * 975 * To get an array containing no pages: 976 * pages([]) 977 * pages([], function () {}); 978 * 979 * @param {Chainback} clazz The Chainback class we want to expose. 980 * @throws UNKNOWN_ARGUMENT 981 */ 982 GCN.exposeAPI = function (clazz) { 983 return function () { 984 // Convert arguments into an array 985 // https://developer.mozilla.org/en/JavaScript/Reference/... 986 // ...Functions_and_function_scope/arguments 987 var args = Array.prototype.slice.call(arguments); 988 var id; 989 var ids; 990 var success; 991 var error; 992 var settings; 993 994 // iterate over arguments to find id || ids, succes, error and 995 // settings 996 jQuery.each(args, function (i, arg) { 997 switch (jQuery.type(arg)) { 998 // set id 999 case 'string': 1000 case 'number': 1001 if (!id && !ids) { 1002 id = arg; 1003 } else { 1004 GCN.error('UNKNOWN_ARGUMENT', 1005 'id is already set. Don\'t know what to do with ' + 1006 'arguments[' + i + '] value: "' + arg + '"'); 1007 } 1008 break; 1009 // set ids 1010 case 'array': 1011 if (!id && !ids) { 1012 ids = args[0]; 1013 } else { 1014 GCN.error('UNKNOWN_ARGUMENT', 1015 'ids is already set. Don\'t know what to do with' + 1016 ' arguments[' + i + '] value: "' + arg + '"'); 1017 } 1018 break; 1019 // success and error handlers 1020 case 'function': 1021 if (!success) { 1022 success = arg; 1023 } else if (success && !error) { 1024 error = arg; 1025 } else { 1026 GCN.error('UNKNOWN_ARGUMENT', 1027 'success and error handler already set. Don\'t ' + 1028 'know what to do with arguments[' + i + ']'); 1029 } 1030 break; 1031 // settings 1032 case 'object': 1033 if (!id && !ids) { 1034 id = arg; 1035 } else if (!settings) { 1036 settings = arg; 1037 } else { 1038 GCN.error('UNKNOWN_ARGUMENT', 1039 'settings are already present. Don\'t know what ' + 1040 'to do with arguments[' + i + '] value:' + ' "' + 1041 arg + '"'); 1042 } 1043 break; 1044 default: 1045 GCN.error('UNKNOWN_ARGUMENT', 1046 'Don\'t know what to do with arguments[' + i + 1047 '] value: "' + arg + '"'); 1048 } 1049 }); 1050 1051 // Prepare a new set of arguments to pass on during initialzation 1052 // of callee object. 1053 args = []; 1054 1055 // settings should always be an object, even if it's just empty 1056 if (!settings) { 1057 settings = {}; 1058 } 1059 1060 args[0] = (typeof id !== 'undefined') ? id : ids; 1061 args[1] = success || settings.success || null; 1062 args[2] = error || settings.error || null; 1063 args[3] = settings; 1064 1065 var hash = (id || ids) 1066 ? clazz._makeHash(ids ? ids.sort().join(',') : id) 1067 : null; 1068 1069 return GCN.getChainback(clazz, hash, null, args); 1070 }; 1071 1072 }; 1073 1074 }(GCN)); 1075