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