src/cm/media/js/lib/yui/yui_3.10.3/build/model-sync-rest/model-sync-rest-debug.js
changeset 525 89ef5ed3c48b
equal deleted inserted replaced
524:322d0feea350 525:89ef5ed3c48b
       
     1 /*
       
     2 YUI 3.10.3 (build 2fb5187)
       
     3 Copyright 2013 Yahoo! Inc. All rights reserved.
       
     4 Licensed under the BSD License.
       
     5 http://yuilibrary.com/license/
       
     6 */
       
     7 
       
     8 YUI.add('model-sync-rest', function (Y, NAME) {
       
     9 
       
    10 /**
       
    11 An extension which provides a RESTful XHR sync implementation that can be mixed
       
    12 into a Model or ModelList subclass.
       
    13 
       
    14 @module app
       
    15 @submodule model-sync-rest
       
    16 @since 3.6.0
       
    17 **/
       
    18 
       
    19 var Lang = Y.Lang;
       
    20 
       
    21 /**
       
    22 An extension which provides a RESTful XHR sync implementation that can be mixed
       
    23 into a Model or ModelList subclass.
       
    24 
       
    25 This makes it trivial for your Model or ModelList subclasses communicate and
       
    26 transmit their data via RESTful XHRs. In most cases you'll only need to provide
       
    27 a value for `root` when sub-classing `Y.Model`.
       
    28 
       
    29     Y.User = Y.Base.create('user', Y.Model, [Y.ModelSync.REST], {
       
    30         root: '/users'
       
    31     });
       
    32 
       
    33     Y.Users = Y.Base.create('users', Y.ModelList, [Y.ModelSync.REST], {
       
    34         // By convention `Y.User`'s `root` will be used for the lists' URL.
       
    35         model: Y.User
       
    36     });
       
    37 
       
    38     var users = new Y.Users();
       
    39 
       
    40     // GET users list from: "/users"
       
    41     users.load(function () {
       
    42         var firstUser = users.item(0);
       
    43 
       
    44         firstUser.get('id'); // => "1"
       
    45 
       
    46         // PUT updated user data at: "/users/1"
       
    47         firstUser.set('name', 'Eric').save();
       
    48     });
       
    49 
       
    50 @class ModelSync.REST
       
    51 @extensionfor Model
       
    52 @extensionfor ModelList
       
    53 @since 3.6.0
       
    54 **/
       
    55 function RESTSync() {}
       
    56 
       
    57 /**
       
    58 A request authenticity token to validate HTTP requests made by this extension
       
    59 with the server when the request results in changing persistent state. This
       
    60 allows you to protect your server from Cross-Site Request Forgery attacks.
       
    61 
       
    62 A CSRF token provided by the server can be embedded in the HTML document and
       
    63 assigned to `YUI.Env.CSRF_TOKEN` like this:
       
    64 
       
    65     <script>
       
    66         YUI.Env.CSRF_TOKEN = {{session.authenticityToken}};
       
    67     </script>
       
    68 
       
    69 The above should come after YUI seed file so that `YUI.Env` will be defined.
       
    70 
       
    71 **Note:** This can be overridden on a per-request basis. See `sync()` method.
       
    72 
       
    73 When a value for the CSRF token is provided, either statically or via `options`
       
    74 passed to the `save()` and `destroy()` methods, the applicable HTTP requests
       
    75 will have a `X-CSRF-Token` header added with the token value.
       
    76 
       
    77 @property CSRF_TOKEN
       
    78 @type String
       
    79 @default YUI.Env.CSRF_TOKEN
       
    80 @static
       
    81 @since 3.6.0
       
    82 **/
       
    83 RESTSync.CSRF_TOKEN = YUI.Env.CSRF_TOKEN;
       
    84 
       
    85 /**
       
    86 Static flag to use the HTTP POST method instead of PUT or DELETE.
       
    87 
       
    88 If the server-side HTTP framework isn't RESTful, setting this flag to `true`
       
    89 will cause all PUT and DELETE requests to instead use the POST HTTP method, and
       
    90 add a `X-HTTP-Method-Override` HTTP header with the value of the method type
       
    91 which was overridden.
       
    92 
       
    93 @property EMULATE_HTTP
       
    94 @type Boolean
       
    95 @default false
       
    96 @static
       
    97 @since 3.6.0
       
    98 **/
       
    99 RESTSync.EMULATE_HTTP = false;
       
   100 
       
   101 /**
       
   102 Default headers used with all XHRs.
       
   103 
       
   104 By default the `Accept` and `Content-Type` headers are set to
       
   105 "application/json", this signals to the HTTP server to process the request
       
   106 bodies as JSON and send JSON responses. If you're sending and receiving content
       
   107 other than JSON, you can override these headers and the `parse()` and
       
   108 `serialize()` methods.
       
   109 
       
   110 **Note:** These headers will be merged with any request-specific headers, and
       
   111 the request-specific headers will take precedence.
       
   112 
       
   113 @property HTTP_HEADERS
       
   114 @type Object
       
   115 @default
       
   116     {
       
   117         "Accept"      : "application/json",
       
   118         "Content-Type": "application/json"
       
   119     }
       
   120 @static
       
   121 @since 3.6.0
       
   122 **/
       
   123 RESTSync.HTTP_HEADERS = {
       
   124     'Accept'      : 'application/json',
       
   125     'Content-Type': 'application/json'
       
   126 };
       
   127 
       
   128 /**
       
   129 Static mapping of RESTful HTTP methods corresponding to CRUD actions.
       
   130 
       
   131 @property HTTP_METHODS
       
   132 @type Object
       
   133 @default
       
   134     {
       
   135         "create": "POST",
       
   136         "read"  : "GET",
       
   137         "update": "PUT",
       
   138         "delete": "DELETE"
       
   139     }
       
   140 @static
       
   141 @since 3.6.0
       
   142 **/
       
   143 RESTSync.HTTP_METHODS = {
       
   144     'create': 'POST',
       
   145     'read'  : 'GET',
       
   146     'update': 'PUT',
       
   147     'delete': 'DELETE'
       
   148 };
       
   149 
       
   150 /**
       
   151 The number of milliseconds before the XHRs will timeout/abort. This defaults to
       
   152 30 seconds.
       
   153 
       
   154 **Note:** This can be overridden on a per-request basis. See `sync()` method.
       
   155 
       
   156 @property HTTP_TIMEOUT
       
   157 @type Number
       
   158 @default 30000
       
   159 @static
       
   160 @since 3.6.0
       
   161 **/
       
   162 RESTSync.HTTP_TIMEOUT = 30000;
       
   163 
       
   164 /**
       
   165 Properties that shouldn't be turned into ad-hoc attributes when passed to a
       
   166 Model or ModelList constructor.
       
   167 
       
   168 @property _NON_ATTRS_CFG
       
   169 @type Array
       
   170 @default ["root", "url"]
       
   171 @static
       
   172 @protected
       
   173 @since 3.6.0
       
   174 **/
       
   175 RESTSync._NON_ATTRS_CFG = ['root', 'url'];
       
   176 
       
   177 RESTSync.prototype = {
       
   178 
       
   179     // -- Public Properties ----------------------------------------------------
       
   180 
       
   181     /**
       
   182     A string which represents the root or collection part of the URL which
       
   183     relates to a Model or ModelList. Usually this value should be same for all
       
   184     instances of a specific Model/ModelList subclass.
       
   185 
       
   186     When sub-classing `Y.Model`, usually you'll only need to override this
       
   187     property, which lets the URLs for the XHRs be generated by convention. If
       
   188     the `root` string ends with a trailing-slash, XHR URLs will also end with a
       
   189     "/", and if the `root` does not end with a slash, neither will the XHR URLs.
       
   190 
       
   191     @example
       
   192         Y.User = Y.Base.create('user', Y.Model, [Y.ModelSync.REST], {
       
   193             root: '/users'
       
   194         });
       
   195 
       
   196         var currentUser, newUser;
       
   197 
       
   198         // GET the user data from: "/users/123"
       
   199         currentUser = new Y.User({id: '123'}).load();
       
   200 
       
   201         // POST the new user data to: "/users"
       
   202         newUser = new Y.User({name: 'Eric Ferraiuolo'}).save();
       
   203 
       
   204     When sub-classing `Y.ModelList`, usually you'll want to ignore configuring
       
   205     the `root` and simply rely on the build-in convention of the list's
       
   206     generated URLs defaulting to the `root` specified by the list's `model`.
       
   207 
       
   208     @property root
       
   209     @type String
       
   210     @default ""
       
   211     @since 3.6.0
       
   212     **/
       
   213     root: '',
       
   214 
       
   215     /**
       
   216     A string which specifies the URL to use when making XHRs, if not value is
       
   217     provided, the URLs used to make XHRs will be generated by convention.
       
   218 
       
   219     While a `url` can be provided for each Model/ModelList instance, usually
       
   220     you'll want to either rely on the default convention or provide a tokenized
       
   221     string on the prototype which can be used for all instances.
       
   222 
       
   223     When sub-classing `Y.Model`, you will probably be able to rely on the
       
   224     default convention of generating URLs in conjunction with the `root`
       
   225     property and whether the model is new or not (i.e. has an `id`). If the
       
   226     `root` property ends with a trailing-slash, the generated URL for the
       
   227     specific model will also end with a trailing-slash.
       
   228 
       
   229     @example
       
   230         Y.User = Y.Base.create('user', Y.Model, [Y.ModelSync.REST], {
       
   231             root: '/users/'
       
   232         });
       
   233 
       
   234         var currentUser, newUser;
       
   235 
       
   236         // GET the user data from: "/users/123/"
       
   237         currentUser = new Y.User({id: '123'}).load();
       
   238 
       
   239         // POST the new user data to: "/users/"
       
   240         newUser = new Y.User({name: 'Eric Ferraiuolo'}).save();
       
   241 
       
   242     If a `url` is specified, it will be processed by `Y.Lang.sub()`, which is
       
   243     useful when the URLs for a Model/ModelList subclass match a specific pattern
       
   244     and can use simple replacement tokens; e.g.:
       
   245 
       
   246     @example
       
   247         Y.User = Y.Base.create('user', Y.Model, [Y.ModelSync.REST], {
       
   248             root: '/users',
       
   249             url : '/users/{username}'
       
   250         });
       
   251 
       
   252     **Note:** String subsitituion of the `url` only use string an number values
       
   253     provided by this object's attribute and/or the `options` passed to the
       
   254     `getURL()` method. Do not expect something fancy to happen with Object,
       
   255     Array, or Boolean values, they will simply be ignored.
       
   256 
       
   257     If your URLs have plural roots or collection URLs, while the specific item
       
   258     resources are under a singular name, e.g. "/users" (plural) and "/user/123"
       
   259     (singular), you'll probably want to configure the `root` and `url`
       
   260     properties like this:
       
   261 
       
   262     @example
       
   263         Y.User = Y.Base.create('user', Y.Model, [Y.ModelSync.REST], {
       
   264             root: '/users',
       
   265             url : '/user/{id}'
       
   266         });
       
   267 
       
   268         var currentUser, newUser;
       
   269 
       
   270         // GET the user data from: "/user/123"
       
   271         currentUser = new Y.User({id: '123'}).load();
       
   272 
       
   273         // POST the new user data to: "/users"
       
   274         newUser = new Y.User({name: 'Eric Ferraiuolo'}).save();
       
   275 
       
   276     When sub-classing `Y.ModelList`, usually you'll be able to rely on the
       
   277     associated `model` to supply its `root` to be used as the model list's URL.
       
   278     If this needs to be customized, you can provide a simple string for the
       
   279     `url` property.
       
   280 
       
   281     @example
       
   282         Y.Users = Y.Base.create('users', Y.ModelList, [Y.ModelSync.REST], {
       
   283             // Leverages `Y.User`'s `root`, which is "/users".
       
   284             model: Y.User
       
   285         });
       
   286 
       
   287         // Or specified explicitly...
       
   288 
       
   289         Y.Users = Y.Base.create('users', Y.ModelList, [Y.ModelSync.REST], {
       
   290             model: Y.User,
       
   291             url  : '/users'
       
   292         });
       
   293 
       
   294     @property url
       
   295     @type String
       
   296     @default ""
       
   297     @since 3.6.0
       
   298     **/
       
   299     url: '',
       
   300 
       
   301     // -- Lifecycle Methods ----------------------------------------------------
       
   302 
       
   303     initializer: function (config) {
       
   304         config || (config = {});
       
   305 
       
   306         // Overrides `root` at the instance level.
       
   307         if ('root' in config) {
       
   308             this.root = config.root || '';
       
   309         }
       
   310 
       
   311         // Overrides `url` at the instance level.
       
   312         if ('url' in config) {
       
   313             this.url = config.url || '';
       
   314         }
       
   315     },
       
   316 
       
   317     // -- Public Methods -------------------------------------------------------
       
   318 
       
   319     /**
       
   320     Returns the URL for this model or model list for the given `action` and
       
   321     `options`, if specified.
       
   322 
       
   323     This method correctly handles the variations of `root` and `url` values and
       
   324     is called by the `sync()` method to get the URLs used to make the XHRs.
       
   325 
       
   326     You can override this method if you need to provide a specific
       
   327     implementation for how the URLs of your Model and ModelList subclasses need
       
   328     to be generated.
       
   329 
       
   330     @method getURL
       
   331     @param {String} [action] Optional `sync()` action for which to generate the
       
   332         URL.
       
   333     @param {Object} [options] Optional options which may be used to help
       
   334         generate the URL.
       
   335     @return {String} this model's or model list's URL for the the given
       
   336         `action` and `options`.
       
   337     @since 3.6.0
       
   338     **/
       
   339     getURL: function (action, options) {
       
   340         var root = this.root,
       
   341             url  = this.url;
       
   342 
       
   343         // If this is a model list, use its `url` and substitute placeholders,
       
   344         // but default to the `root` of its `model`. By convention a model's
       
   345         // `root` is the location to a collection resource.
       
   346         if (this._isYUIModelList) {
       
   347             if (!url) {
       
   348                 return this.model.prototype.root;
       
   349             }
       
   350 
       
   351             return this._substituteURL(url, Y.merge(this.getAttrs(), options));
       
   352         }
       
   353 
       
   354         // Assume `this` is a model.
       
   355 
       
   356         // When a model is new, i.e. has no `id`, the `root` should be used. By
       
   357         // convention a model's `root` is the location to a collection resource.
       
   358         // The model's `url` will be used as a fallback if `root` isn't defined.
       
   359         if (root && (action === 'create' || this.isNew())) {
       
   360             return root;
       
   361         }
       
   362 
       
   363         // When a model's `url` is not provided, we'll generate a URL to use by
       
   364         // convention. This will combine the model's `id` with its configured
       
   365         // `root` and add a trailing-slash if the root ends with "/".
       
   366         if (!url) {
       
   367             return this._joinURL(this.getAsURL('id') || '');
       
   368         }
       
   369 
       
   370         // Substitute placeholders in the `url` with URL-encoded values from the
       
   371         // model's attribute values or the specified `options`.
       
   372         return this._substituteURL(url, Y.merge(this.getAttrs(), options));
       
   373     },
       
   374 
       
   375     /**
       
   376     Called to parse the response object returned from `Y.io()`. This method
       
   377     receives the full response object and is expected to "prep" a response which
       
   378     is suitable to pass to the `parse()` method.
       
   379 
       
   380     By default the response body is returned (`responseText`), because it
       
   381     usually represents the entire entity of this model on the server.
       
   382 
       
   383     If you need to parse data out of the response's headers you should do so by
       
   384     overriding this method. If you'd like the entire response object from the
       
   385     XHR to be passed to your `parse()` method, you can simply assign this
       
   386     property to `false`.
       
   387 
       
   388     @method parseIOResponse
       
   389     @param {Object} response Response object from `Y.io()`.
       
   390     @return {Any} The modified response to pass along to the `parse()` method.
       
   391     @since 3.7.0
       
   392     **/
       
   393     parseIOResponse: function (response) {
       
   394         return response.responseText;
       
   395     },
       
   396 
       
   397     /**
       
   398     Serializes `this` model to be used as the HTTP request entity body.
       
   399 
       
   400     By default this model will be serialized to a JSON string via its `toJSON()`
       
   401     method.
       
   402 
       
   403     You can override this method when the HTTP server expects a different
       
   404     representation of this model's data that is different from the default JSON
       
   405     serialization. If you're sending and receive content other than JSON, be
       
   406     sure change the `Accept` and `Content-Type` `HTTP_HEADERS` as well.
       
   407 
       
   408     **Note:** A model's `toJSON()` method can also be overridden. If you only
       
   409     need to modify which attributes are serialized to JSON, that's a better
       
   410     place to start.
       
   411 
       
   412     @method serialize
       
   413     @param {String} [action] Optional `sync()` action for which to generate the
       
   414         the serialized representation of this model.
       
   415     @return {String} serialized HTTP request entity body.
       
   416     @since 3.6.0
       
   417     **/
       
   418     serialize: function (action) {
       
   419         return Y.JSON.stringify(this);
       
   420     },
       
   421 
       
   422     /**
       
   423     Communicates with a RESTful HTTP server by sending and receiving data via
       
   424     XHRs. This method is called internally by load(), save(), and destroy().
       
   425 
       
   426     The URL used for each XHR will be retrieved by calling the `getURL()` method
       
   427     and passing it the specified `action` and `options`.
       
   428 
       
   429     This method relies heavily on standard RESTful HTTP conventions
       
   430 
       
   431     @method sync
       
   432     @param {String} action Sync action to perform. May be one of the following:
       
   433 
       
   434       * `create`: Store a newly-created model for the first time.
       
   435       * `delete`: Delete an existing model.
       
   436       * `read`  : Load an existing model.
       
   437       * `update`: Update an existing model.
       
   438 
       
   439     @param {Object} [options] Sync options:
       
   440       @param {String} [options.csrfToken] The authenticity token used by the
       
   441         server to verify the validity of this request and protected against CSRF
       
   442         attacks. This overrides the default value provided by the static
       
   443         `CSRF_TOKEN` property.
       
   444       @param {Object} [options.headers] The HTTP headers to mix with the default
       
   445         headers specified by the static `HTTP_HEADERS` property.
       
   446       @param {Number} [options.timeout] The number of milliseconds before the
       
   447         request will timeout and be aborted. This overrides the default provided
       
   448         by the static `HTTP_TIMEOUT` property.
       
   449     @param {Function} [callback] Called when the sync operation finishes.
       
   450       @param {Error|null} callback.err If an error occurred, this parameter will
       
   451         contain the error. If the sync operation succeeded, _err_ will be
       
   452         falsy.
       
   453       @param {Any} [callback.response] The server's response.
       
   454     **/
       
   455     sync: function (action, options, callback) {
       
   456         options || (options = {});
       
   457 
       
   458         var url       = this.getURL(action, options),
       
   459             method    = RESTSync.HTTP_METHODS[action],
       
   460             headers   = Y.merge(RESTSync.HTTP_HEADERS, options.headers),
       
   461             timeout   = options.timeout || RESTSync.HTTP_TIMEOUT,
       
   462             csrfToken = options.csrfToken || RESTSync.CSRF_TOKEN,
       
   463             entity;
       
   464 
       
   465         // Prepare the content if we are sending data to the server.
       
   466         if (method === 'POST' || method === 'PUT') {
       
   467             entity = this.serialize(action);
       
   468         } else {
       
   469             // Remove header, no content is being sent.
       
   470             delete headers['Content-Type'];
       
   471         }
       
   472 
       
   473         // Setup HTTP emulation for older servers if we need it.
       
   474         if (RESTSync.EMULATE_HTTP &&
       
   475                 (method === 'PUT' || method === 'DELETE')) {
       
   476 
       
   477             // Pass along original method type in the headers.
       
   478             headers['X-HTTP-Method-Override'] = method;
       
   479 
       
   480             // Fall-back to using POST method type.
       
   481             method = 'POST';
       
   482         }
       
   483 
       
   484         // Add CSRF token to HTTP request headers if one is specified and the
       
   485         // request will cause side effects on the server.
       
   486         if (csrfToken &&
       
   487                 (method === 'POST' || method === 'PUT' || method === 'DELETE')) {
       
   488 
       
   489             headers['X-CSRF-Token'] = csrfToken;
       
   490         }
       
   491 
       
   492         this._sendSyncIORequest({
       
   493             action  : action,
       
   494             callback: callback,
       
   495             entity  : entity,
       
   496             headers : headers,
       
   497             method  : method,
       
   498             timeout : timeout,
       
   499             url     : url
       
   500         });
       
   501     },
       
   502 
       
   503     // -- Protected Methods ----------------------------------------------------
       
   504 
       
   505     /**
       
   506     Joins the `root` URL to the specified `url`, normalizing leading/trailing
       
   507     "/" characters.
       
   508 
       
   509     @example
       
   510         model.root = '/foo'
       
   511         model._joinURL('bar');  // => '/foo/bar'
       
   512         model._joinURL('/bar'); // => '/foo/bar'
       
   513 
       
   514         model.root = '/foo/'
       
   515         model._joinURL('bar');  // => '/foo/bar/'
       
   516         model._joinURL('/bar'); // => '/foo/bar/'
       
   517 
       
   518     @method _joinURL
       
   519     @param {String} url URL to append to the `root` URL.
       
   520     @return {String} Joined URL.
       
   521     @protected
       
   522     @since 3.6.0
       
   523     **/
       
   524     _joinURL: function (url) {
       
   525         var root = this.root;
       
   526 
       
   527         if (!(root || url)) {
       
   528             return '';
       
   529         }
       
   530 
       
   531         if (url.charAt(0) === '/') {
       
   532             url = url.substring(1);
       
   533         }
       
   534 
       
   535         // Combines the `root` with the `url` and adds a trailing-slash if the
       
   536         // `root` has a trailing-slash.
       
   537         return root && root.charAt(root.length - 1) === '/' ?
       
   538                 root + url + '/' :
       
   539                 root + '/' + url;
       
   540     },
       
   541 
       
   542 
       
   543     /**
       
   544     Calls both public, overrideable methods: `parseIOResponse()`, then `parse()`
       
   545     and returns the result.
       
   546 
       
   547     This will call into `parseIOResponse()`, if it's defined as a method,
       
   548     passing it the full response object from the XHR and using its return value
       
   549     to pass along to the `parse()`. This enables developers to easily parse data
       
   550     out of the response headers which should be used by the `parse()` method.
       
   551 
       
   552     @method _parse
       
   553     @param {Object} response Response object from `Y.io()`.
       
   554     @return {Object|Object[]} Attribute hash or Array of model attribute hashes.
       
   555     @protected
       
   556     @since 3.7.0
       
   557     **/
       
   558     _parse: function (response) {
       
   559         // When `parseIOResponse` is defined as a method, it will be invoked and
       
   560         // the result will become the new response object that the `parse()`
       
   561         // will be invoked with.
       
   562         if (typeof this.parseIOResponse === 'function') {
       
   563             response = this.parseIOResponse(response);
       
   564         }
       
   565 
       
   566         return this.parse(response);
       
   567     },
       
   568 
       
   569     /**
       
   570     Performs the XHR and returns the resulting `Y.io()` request object.
       
   571 
       
   572     This method is called by `sync()`.
       
   573 
       
   574     @method _sendSyncIORequest
       
   575     @param {Object} config An object with the following properties:
       
   576       @param {String} config.action The `sync()` action being performed.
       
   577       @param {Function} [config.callback] Called when the sync operation
       
   578         finishes.
       
   579       @param {String} [config.entity] The HTTP request entity body.
       
   580       @param {Object} config.headers The HTTP request headers.
       
   581       @param {String} config.method The HTTP request method.
       
   582       @param {Number} [config.timeout] Time until the HTTP request is aborted.
       
   583       @param {String} config.url The URL of the HTTP resource.
       
   584     @return {Object} The resulting `Y.io()` request object.
       
   585     @protected
       
   586     @since 3.6.0
       
   587     **/
       
   588     _sendSyncIORequest: function (config) {
       
   589         return Y.io(config.url, {
       
   590             'arguments': {
       
   591                 action  : config.action,
       
   592                 callback: config.callback,
       
   593                 url     : config.url
       
   594             },
       
   595 
       
   596             context: this,
       
   597             data   : config.entity,
       
   598             headers: config.headers,
       
   599             method : config.method,
       
   600             timeout: config.timeout,
       
   601 
       
   602             on: {
       
   603                 start  : this._onSyncIOStart,
       
   604                 failure: this._onSyncIOFailure,
       
   605                 success: this._onSyncIOSuccess,
       
   606                 end    : this._onSyncIOEnd
       
   607             }
       
   608         });
       
   609     },
       
   610 
       
   611     /**
       
   612     Utility which takes a tokenized `url` string and substitutes its
       
   613     placeholders using a specified `data` object.
       
   614 
       
   615     This method will property URL-encode any values before substituting them.
       
   616     Also, only expect it to work with String and Number values.
       
   617 
       
   618     @example
       
   619         var url = this._substituteURL('/users/{name}', {id: 'Eric F'});
       
   620         // => "/users/Eric%20F"
       
   621 
       
   622     @method _substituteURL
       
   623     @param {String} url Tokenized URL string to substitute placeholder values.
       
   624     @param {Object} data Set of data to fill in the `url`'s placeholders.
       
   625     @return {String} Substituted URL.
       
   626     @protected
       
   627     @since 3.6.0
       
   628     **/
       
   629     _substituteURL: function (url, data) {
       
   630         if (!url) {
       
   631             return '';
       
   632         }
       
   633 
       
   634         var values = {};
       
   635 
       
   636         // Creates a hash of the string and number values only to be used to
       
   637         // replace any placeholders in a tokenized `url`.
       
   638         Y.Object.each(data, function (v, k) {
       
   639             if (Lang.isString(v) || Lang.isNumber(v)) {
       
   640                 // URL-encode any string or number values.
       
   641                 values[k] = encodeURIComponent(v);
       
   642             }
       
   643         });
       
   644 
       
   645         return Lang.sub(url, values);
       
   646     },
       
   647 
       
   648     // -- Event Handlers -------------------------------------------------------
       
   649 
       
   650     /**
       
   651     Called when the `Y.io` request has finished, after "success" or "failure"
       
   652     has been determined.
       
   653 
       
   654     This is a no-op by default, but provides a hook for overriding.
       
   655 
       
   656     @method _onSyncIOEnd
       
   657     @param {String} txId The `Y.io` transaction id.
       
   658     @param {Object} details Extra details carried through from `sync()`:
       
   659       @param {String} details.action The sync action performed.
       
   660       @param {Function} [details.callback] The function to call after syncing.
       
   661       @param {String} details.url The URL of the requested resource.
       
   662     @protected
       
   663     @since 3.6.0
       
   664     **/
       
   665     _onSyncIOEnd: function (txId, details) {},
       
   666 
       
   667     /**
       
   668     Called when the `Y.io` request has finished unsuccessfully.
       
   669 
       
   670     By default this calls the `details.callback` function passing it the HTTP
       
   671     status code and message as an error object along with the response body.
       
   672 
       
   673     @method _onSyncIOFailure
       
   674     @param {String} txId The `Y.io` transaction id.
       
   675     @param {Object} res The `Y.io` response object.
       
   676     @param {Object} details Extra details carried through from `sync()`:
       
   677       @param {String} details.action The sync action performed.
       
   678       @param {Function} [details.callback] The function to call after syncing.
       
   679       @param {String} details.url The URL of the requested resource.
       
   680     @protected
       
   681     @since 3.6.0
       
   682     **/
       
   683     _onSyncIOFailure: function (txId, res, details) {
       
   684         var callback = details.callback;
       
   685 
       
   686         if (callback) {
       
   687             callback({
       
   688                 code: res.status,
       
   689                 msg : res.statusText
       
   690             }, res);
       
   691         }
       
   692     },
       
   693 
       
   694     /**
       
   695     Called when the `Y.io` request has finished successfully.
       
   696 
       
   697     By default this calls the `details.callback` function passing it the
       
   698     response body.
       
   699 
       
   700     @method _onSyncIOSuccess
       
   701     @param {String} txId The `Y.io` transaction id.
       
   702     @param {Object} res The `Y.io` response object.
       
   703     @param {Object} details Extra details carried through from `sync()`:
       
   704       @param {String} details.action The sync action performed.
       
   705       @param {Function} [details.callback] The function to call after syncing.
       
   706       @param {String} details.url The URL of the requested resource.
       
   707     @protected
       
   708     @since 3.6.0
       
   709     **/
       
   710     _onSyncIOSuccess: function (txId, res, details) {
       
   711         var callback = details.callback;
       
   712 
       
   713         if (callback) {
       
   714             callback(null, res);
       
   715         }
       
   716     },
       
   717 
       
   718     /**
       
   719     Called when the `Y.io` request is made.
       
   720 
       
   721     This is a no-op by default, but provides a hook for overriding.
       
   722 
       
   723     @method _onSyncIOStart
       
   724     @param {String} txId The `Y.io` transaction id.
       
   725     @param {Object} details Extra details carried through from `sync()`:
       
   726       @param {String} detials.action The sync action performed.
       
   727       @param {Function} [details.callback] The function to call after syncing.
       
   728       @param {String} details.url The URL of the requested resource.
       
   729     @protected
       
   730     @since 3.6.0
       
   731     **/
       
   732     _onSyncIOStart: function (txId, details) {}
       
   733 };
       
   734 
       
   735 // -- Namespace ----------------------------------------------------------------
       
   736 
       
   737 Y.namespace('ModelSync').REST = RESTSync;
       
   738 
       
   739 
       
   740 }, '3.10.3', {"requires": ["model", "io-base", "json-stringify"]});