Merge pull request #20882 from owncloud/backbone-webdav
Backbone transport for Webdav
This commit is contained in:
commit
3bf43f5b38
|
@ -23,6 +23,7 @@
|
|||
"oc-dialogs.js",
|
||||
"js.js",
|
||||
"oc-backbone.js",
|
||||
"oc-backbone-webdav.js",
|
||||
"l10n.js",
|
||||
"apps.js",
|
||||
"share.js",
|
||||
|
|
|
@ -0,0 +1,316 @@
|
|||
/*
|
||||
* Copyright (c) 2015
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Webdav transport for Backbone.
|
||||
*
|
||||
* This makes it possible to use Webdav endpoints when
|
||||
* working with Backbone models and collections.
|
||||
*
|
||||
* Requires the davclient.js library.
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* var PersonModel = OC.Backbone.Model.extend({
|
||||
* // make it use the DAV transport
|
||||
* sync: OC.Backbone.davSync,
|
||||
*
|
||||
* // DAV properties mapping
|
||||
* davProperties: {
|
||||
* 'id': '{http://example.com/ns}id',
|
||||
* 'firstName': '{http://example.com/ns}first-name',
|
||||
* 'lastName': '{http://example.com/ns}last-name',
|
||||
* 'age': '{http://example.com/ns}age'
|
||||
* },
|
||||
*
|
||||
* // additional parsing, if needed
|
||||
* parse: function(props) {
|
||||
* // additional parsing (DAV property values are always strings)
|
||||
* props.age = parseInt(props.age, 10);
|
||||
* return props;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* var PersonCollection = OC.Backbone.Collection.extend({
|
||||
* // make it use the DAV transport
|
||||
* sync: OC.Backbone.davSync,
|
||||
*
|
||||
* // use person model
|
||||
* // note that davProperties will be inherited
|
||||
* model: PersonModel,
|
||||
*
|
||||
* // DAV collection URL
|
||||
* url: function() {
|
||||
* return OC.linkToRemote('dav') + '/person/';
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
||||
/* global dav */
|
||||
|
||||
(function(Backbone) {
|
||||
var methodMap = {
|
||||
'create': 'POST',
|
||||
'update': 'PROPPATCH',
|
||||
'patch': 'PROPPATCH',
|
||||
'delete': 'DELETE',
|
||||
'read': 'PROPFIND'
|
||||
};
|
||||
|
||||
// Throw an error when a URL is needed, and none is supplied.
|
||||
function urlError() {
|
||||
throw new Error('A "url" property or function must be specified');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a single propfind result to JSON
|
||||
*
|
||||
* @param {Object} result
|
||||
* @param {Object} davProperties properties mapping
|
||||
*/
|
||||
function parsePropFindResult(result, davProperties) {
|
||||
var props = {
|
||||
href: result.href
|
||||
};
|
||||
|
||||
_.each(result.propStat, function(propStat) {
|
||||
if (propStat.status !== 'HTTP/1.1 200 OK') {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var key in propStat.properties) {
|
||||
var propKey = key;
|
||||
if (davProperties[key]) {
|
||||
propKey = davProperties[key];
|
||||
}
|
||||
props[propKey] = propStat.properties[key];
|
||||
}
|
||||
});
|
||||
|
||||
if (!props.id) {
|
||||
// parse id from href
|
||||
props.id = parseIdFromLocation(props.href);
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse ID from location
|
||||
*
|
||||
* @param {string} url url
|
||||
* @return {string} id
|
||||
*/
|
||||
function parseIdFromLocation(url) {
|
||||
var queryPos = url.indexOf('?');
|
||||
if (queryPos > 0) {
|
||||
url = url.substr(0, queryPos);
|
||||
}
|
||||
|
||||
var parts = url.split('/');
|
||||
return parts[parts.length - 1];
|
||||
}
|
||||
|
||||
function isSuccessStatus(status) {
|
||||
return status >= 200 && status <= 299;
|
||||
}
|
||||
|
||||
function convertModelAttributesToDavProperties(attrs, davProperties) {
|
||||
var props = {};
|
||||
var key;
|
||||
for (key in attrs) {
|
||||
var changedProp = davProperties[key];
|
||||
if (!changedProp) {
|
||||
console.warn('No matching DAV property for property "' + key);
|
||||
continue;
|
||||
}
|
||||
props[changedProp] = attrs[key];
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
function callPropFind(client, options, model, headers) {
|
||||
return client.propFind(
|
||||
options.url,
|
||||
_.values(options.davProperties) || [],
|
||||
options.depth,
|
||||
headers
|
||||
).then(function(response) {
|
||||
if (isSuccessStatus(response.status)) {
|
||||
if (_.isFunction(options.success)) {
|
||||
var propsMapping = _.invert(options.davProperties);
|
||||
var results;
|
||||
if (options.depth > 0) {
|
||||
results = _.map(response.body, function(data) {
|
||||
return parsePropFindResult(data, propsMapping);
|
||||
});
|
||||
// discard root entry
|
||||
results.shift();
|
||||
} else {
|
||||
results = parsePropFindResult(response.body, propsMapping);
|
||||
}
|
||||
|
||||
options.success(results);
|
||||
return;
|
||||
}
|
||||
} else if (_.isFunction(options.error)) {
|
||||
options.error(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function callPropPatch(client, options, model, headers) {
|
||||
client.propPatch(
|
||||
options.url,
|
||||
convertModelAttributesToDavProperties(model.changed, options.davProperties),
|
||||
headers
|
||||
).then(function(result) {
|
||||
if (isSuccessStatus(result.status)) {
|
||||
if (_.isFunction(options.success)) {
|
||||
// pass the object's own values because the server
|
||||
// does not return the updated model
|
||||
options.success(model.toJSON());
|
||||
}
|
||||
} else if (_.isFunction(options.error)) {
|
||||
options.error(result);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function callMethod(client, options, model, headers) {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
return client.request(
|
||||
options.type,
|
||||
options.url,
|
||||
headers,
|
||||
options.data
|
||||
).then(function(result) {
|
||||
if (!isSuccessStatus(result.status)) {
|
||||
if (_.isFunction(options.error)) {
|
||||
options.error(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_.isFunction(options.success)) {
|
||||
if (options.type === 'PUT' || options.type === 'POST') {
|
||||
// pass the object's own values because the server
|
||||
// does not return anything
|
||||
var responseJson = result.body || model.toJSON();
|
||||
var locationHeader = result.xhr.getResponseHeader('Content-Location');
|
||||
if (options.type === 'POST' && locationHeader) {
|
||||
responseJson.id = parseIdFromLocation(locationHeader);
|
||||
}
|
||||
options.success(responseJson);
|
||||
return;
|
||||
}
|
||||
options.success(result.body);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function davCall(options, model) {
|
||||
var client = new dav.Client({
|
||||
baseUrl: options.url,
|
||||
xmlNamespaces: _.extend({
|
||||
'DAV:': 'd',
|
||||
'http://owncloud.org/ns': 'oc'
|
||||
}, options.xmlNamespaces || {})
|
||||
});
|
||||
client.resolveUrl = function() {
|
||||
return options.url;
|
||||
};
|
||||
var headers = _.extend({
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}, options.headers);
|
||||
if (options.type === 'PROPFIND') {
|
||||
return callPropFind(client, options, model, headers);
|
||||
} else if (options.type === 'PROPPATCH') {
|
||||
return callPropPatch(client, options, model, headers);
|
||||
} else {
|
||||
return callMethod(client, options, model, headers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DAV transport
|
||||
*/
|
||||
function davSync(method, model, options) {
|
||||
var params = {type: methodMap[method]};
|
||||
var isCollection = (model instanceof Backbone.Collection);
|
||||
|
||||
if (method === 'update' && (model.usePUT || (model.collection && model.collection.usePUT))) {
|
||||
// use PUT instead of PROPPATCH
|
||||
params.type = 'PUT';
|
||||
}
|
||||
|
||||
// Ensure that we have a URL.
|
||||
if (!options.url) {
|
||||
params.url = _.result(model, 'url') || urlError();
|
||||
}
|
||||
|
||||
// Ensure that we have the appropriate request data.
|
||||
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
|
||||
params.data = JSON.stringify(options.attrs || model.toJSON(options));
|
||||
}
|
||||
|
||||
// Don't process data on a non-GET request.
|
||||
if (params.type !== 'PROPFIND') {
|
||||
params.processData = false;
|
||||
}
|
||||
|
||||
if (params.type === 'PROPFIND' || params.type === 'PROPPATCH') {
|
||||
var davProperties = model.davProperties;
|
||||
if (!davProperties && model.model) {
|
||||
// use dav properties from model in case of collection
|
||||
davProperties = model.model.prototype.davProperties;
|
||||
}
|
||||
if (davProperties) {
|
||||
if (_.isFunction(davProperties)) {
|
||||
params.davProperties = davProperties.call(model);
|
||||
} else {
|
||||
params.davProperties = davProperties;
|
||||
}
|
||||
}
|
||||
|
||||
params.davProperties = _.extend(params.davProperties || {}, options.davProperties);
|
||||
|
||||
if (_.isUndefined(options.depth)) {
|
||||
if (isCollection) {
|
||||
options.depth = 1;
|
||||
} else {
|
||||
options.depth = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass along `textStatus` and `errorThrown` from jQuery.
|
||||
var error = options.error;
|
||||
options.error = function(xhr, textStatus, errorThrown) {
|
||||
options.textStatus = textStatus;
|
||||
options.errorThrown = errorThrown;
|
||||
if (error) {
|
||||
error.call(options.context, xhr, textStatus, errorThrown);
|
||||
}
|
||||
};
|
||||
|
||||
// Make the request, allowing the user to override any Ajax options.
|
||||
var xhr = options.xhr = Backbone.davCall(_.extend(params, options), model);
|
||||
model.trigger('request', model, xhr, options);
|
||||
return xhr;
|
||||
}
|
||||
|
||||
// exports
|
||||
Backbone.davCall = davCall;
|
||||
Backbone.davSync = davSync;
|
||||
|
||||
})(OC.Backbone);
|
||||
|
|
@ -7,6 +7,8 @@
|
|||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
|
||||
/* global Backbone */
|
||||
if(!_.isUndefined(Backbone)) {
|
||||
OC.Backbone = Backbone.noConflict();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,352 @@
|
|||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Vincent Petry
|
||||
* @copyright 2014 Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* global dav */
|
||||
|
||||
describe('Backbone Webdav extension', function() {
|
||||
var davClientRequestStub;
|
||||
var davClientPropPatchStub;
|
||||
var davClientPropFindStub;
|
||||
var deferredRequest;
|
||||
|
||||
beforeEach(function() {
|
||||
deferredRequest = $.Deferred();
|
||||
davClientRequestStub = sinon.stub(dav.Client.prototype, 'request');
|
||||
davClientPropPatchStub = sinon.stub(dav.Client.prototype, 'propPatch');
|
||||
davClientPropFindStub = sinon.stub(dav.Client.prototype, 'propFind');
|
||||
davClientRequestStub.returns(deferredRequest.promise());
|
||||
davClientPropPatchStub.returns(deferredRequest.promise());
|
||||
davClientPropFindStub.returns(deferredRequest.promise());
|
||||
});
|
||||
afterEach(function() {
|
||||
davClientRequestStub.restore();
|
||||
davClientPropPatchStub.restore();
|
||||
davClientPropFindStub.restore();
|
||||
});
|
||||
|
||||
describe('collections', function() {
|
||||
var TestModel;
|
||||
var TestCollection;
|
||||
beforeEach(function() {
|
||||
TestModel = OC.Backbone.Model.extend({
|
||||
sync: OC.Backbone.davSync,
|
||||
davProperties: {
|
||||
'firstName': '{http://owncloud.org/ns}first-name',
|
||||
'lastName': '{http://owncloud.org/ns}last-name',
|
||||
}
|
||||
});
|
||||
TestCollection = OC.Backbone.Collection.extend({
|
||||
sync: OC.Backbone.davSync,
|
||||
model: TestModel,
|
||||
url: 'http://example.com/owncloud/remote.php/test/'
|
||||
});
|
||||
});
|
||||
|
||||
it('makes a POST request to create model into collection', function() {
|
||||
var collection = new TestCollection();
|
||||
var model = collection.create({
|
||||
firstName: 'Hello',
|
||||
lastName: 'World'
|
||||
});
|
||||
|
||||
expect(davClientRequestStub.calledOnce).toEqual(true);
|
||||
expect(davClientRequestStub.getCall(0).args[0])
|
||||
.toEqual('POST');
|
||||
expect(davClientRequestStub.getCall(0).args[1])
|
||||
.toEqual('http://example.com/owncloud/remote.php/test/');
|
||||
expect(davClientRequestStub.getCall(0).args[2]['Content-Type'])
|
||||
.toEqual('application/json');
|
||||
expect(davClientRequestStub.getCall(0).args[2]['X-Requested-With'])
|
||||
.toEqual('XMLHttpRequest');
|
||||
expect(davClientRequestStub.getCall(0).args[3])
|
||||
.toEqual(JSON.stringify({
|
||||
'firstName': 'Hello',
|
||||
'lastName': 'World'
|
||||
}));
|
||||
|
||||
var responseHeaderStub = sinon.stub()
|
||||
.withArgs('Content-Location')
|
||||
.returns('http://example.com/owncloud/remote.php/test/123');
|
||||
deferredRequest.resolve({
|
||||
status: 201,
|
||||
body: '',
|
||||
xhr: {
|
||||
getResponseHeader: responseHeaderStub
|
||||
}
|
||||
});
|
||||
|
||||
expect(model.id).toEqual('123');
|
||||
});
|
||||
|
||||
it('uses PROPFIND to retrieve collection', function() {
|
||||
var successStub = sinon.stub();
|
||||
var errorStub = sinon.stub();
|
||||
var collection = new TestCollection();
|
||||
collection.fetch({
|
||||
success: successStub,
|
||||
error: errorStub
|
||||
});
|
||||
|
||||
expect(davClientPropFindStub.calledOnce).toEqual(true);
|
||||
expect(davClientPropFindStub.getCall(0).args[0])
|
||||
.toEqual('http://example.com/owncloud/remote.php/test/');
|
||||
expect(davClientPropFindStub.getCall(0).args[1])
|
||||
.toEqual([
|
||||
'{http://owncloud.org/ns}first-name',
|
||||
'{http://owncloud.org/ns}last-name'
|
||||
]);
|
||||
expect(davClientPropFindStub.getCall(0).args[2])
|
||||
.toEqual(1);
|
||||
expect(davClientPropFindStub.getCall(0).args[3]['X-Requested-With'])
|
||||
.toEqual('XMLHttpRequest');
|
||||
|
||||
deferredRequest.resolve({
|
||||
status: 207,
|
||||
body: [
|
||||
// root element
|
||||
{
|
||||
href: 'http://example.org/owncloud/remote.php/test/',
|
||||
propStat: []
|
||||
},
|
||||
// first model
|
||||
{
|
||||
href: 'http://example.org/owncloud/remote.php/test/123',
|
||||
propStat: [{
|
||||
status: 'HTTP/1.1 200 OK',
|
||||
properties: {
|
||||
'{http://owncloud.org/ns}first-name': 'Hello',
|
||||
'{http://owncloud.org/ns}last-name': 'World'
|
||||
}
|
||||
}]
|
||||
},
|
||||
// second model
|
||||
{
|
||||
href: 'http://example.org/owncloud/remote.php/test/456',
|
||||
propStat: [{
|
||||
status: 'HTTP/1.1 200 OK',
|
||||
properties: {
|
||||
'{http://owncloud.org/ns}first-name': 'Test',
|
||||
'{http://owncloud.org/ns}last-name': 'Person'
|
||||
}
|
||||
}]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
expect(collection.length).toEqual(2);
|
||||
|
||||
var model = collection.get('123');
|
||||
expect(model.id).toEqual('123');
|
||||
expect(model.get('firstName')).toEqual('Hello');
|
||||
expect(model.get('lastName')).toEqual('World');
|
||||
|
||||
model = collection.get('456');
|
||||
expect(model.id).toEqual('456');
|
||||
expect(model.get('firstName')).toEqual('Test');
|
||||
expect(model.get('lastName')).toEqual('Person');
|
||||
|
||||
expect(successStub.calledOnce).toEqual(true);
|
||||
expect(errorStub.notCalled).toEqual(true);
|
||||
});
|
||||
|
||||
function testMethodError(doCall) {
|
||||
var successStub = sinon.stub();
|
||||
var errorStub = sinon.stub();
|
||||
|
||||
doCall(successStub, errorStub);
|
||||
|
||||
deferredRequest.resolve({
|
||||
status: 404,
|
||||
body: ''
|
||||
});
|
||||
|
||||
expect(successStub.notCalled).toEqual(true);
|
||||
expect(errorStub.calledOnce).toEqual(true);
|
||||
}
|
||||
|
||||
it('calls error handler if error status in PROPFIND response', function() {
|
||||
testMethodError(function(success, error) {
|
||||
var collection = new TestCollection();
|
||||
collection.fetch({
|
||||
success: success,
|
||||
error: error
|
||||
});
|
||||
});
|
||||
});
|
||||
it('calls error handler if error status in POST response', function() {
|
||||
testMethodError(function(success, error) {
|
||||
var collection = new TestCollection();
|
||||
collection.create({
|
||||
firstName: 'Hello',
|
||||
lastName: 'World'
|
||||
}, {
|
||||
success: success,
|
||||
error: error
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('models', function() {
|
||||
var TestModel;
|
||||
beforeEach(function() {
|
||||
TestModel = OC.Backbone.Model.extend({
|
||||
sync: OC.Backbone.davSync,
|
||||
davProperties: {
|
||||
'firstName': '{http://owncloud.org/ns}first-name',
|
||||
'lastName': '{http://owncloud.org/ns}last-name',
|
||||
},
|
||||
url: function() {
|
||||
return 'http://example.com/owncloud/remote.php/test/' + this.id;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('makes a PROPPATCH request to update model', function() {
|
||||
var model = new TestModel({
|
||||
id: '123',
|
||||
firstName: 'Hello',
|
||||
lastName: 'World'
|
||||
});
|
||||
|
||||
model.save({
|
||||
firstName: 'Hey'
|
||||
});
|
||||
|
||||
expect(davClientPropPatchStub.calledOnce).toEqual(true);
|
||||
expect(davClientPropPatchStub.getCall(0).args[0])
|
||||
.toEqual('http://example.com/owncloud/remote.php/test/123');
|
||||
expect(davClientPropPatchStub.getCall(0).args[1])
|
||||
.toEqual({
|
||||
'{http://owncloud.org/ns}first-name': 'Hey'
|
||||
});
|
||||
expect(davClientPropPatchStub.getCall(0).args[2]['X-Requested-With'])
|
||||
.toEqual('XMLHttpRequest');
|
||||
|
||||
deferredRequest.resolve({
|
||||
status: 201,
|
||||
body: ''
|
||||
});
|
||||
|
||||
expect(model.id).toEqual('123');
|
||||
expect(model.get('firstName')).toEqual('Hey');
|
||||
});
|
||||
|
||||
it('uses PROPFIND to fetch single model', function() {
|
||||
var model = new TestModel({
|
||||
id: '123'
|
||||
});
|
||||
|
||||
model.fetch();
|
||||
|
||||
expect(davClientPropFindStub.calledOnce).toEqual(true);
|
||||
expect(davClientPropFindStub.getCall(0).args[0])
|
||||
.toEqual('http://example.com/owncloud/remote.php/test/123');
|
||||
expect(davClientPropFindStub.getCall(0).args[1])
|
||||
.toEqual([
|
||||
'{http://owncloud.org/ns}first-name',
|
||||
'{http://owncloud.org/ns}last-name'
|
||||
]);
|
||||
expect(davClientPropFindStub.getCall(0).args[2])
|
||||
.toEqual(0);
|
||||
expect(davClientPropFindStub.getCall(0).args[3]['X-Requested-With'])
|
||||
.toEqual('XMLHttpRequest');
|
||||
|
||||
deferredRequest.resolve({
|
||||
status: 207,
|
||||
body: {
|
||||
href: 'http://example.org/owncloud/remote.php/test/123',
|
||||
propStat: [{
|
||||
status: 'HTTP/1.1 200 OK',
|
||||
properties: {
|
||||
'{http://owncloud.org/ns}first-name': 'Hello',
|
||||
'{http://owncloud.org/ns}last-name': 'World'
|
||||
}
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
expect(model.id).toEqual('123');
|
||||
expect(model.get('firstName')).toEqual('Hello');
|
||||
expect(model.get('lastName')).toEqual('World');
|
||||
});
|
||||
it('makes a DELETE request to destroy model', function() {
|
||||
var model = new TestModel({
|
||||
id: '123',
|
||||
firstName: 'Hello',
|
||||
lastName: 'World'
|
||||
});
|
||||
|
||||
model.destroy();
|
||||
|
||||
expect(davClientRequestStub.calledOnce).toEqual(true);
|
||||
expect(davClientRequestStub.getCall(0).args[0])
|
||||
.toEqual('DELETE');
|
||||
expect(davClientRequestStub.getCall(0).args[1])
|
||||
.toEqual('http://example.com/owncloud/remote.php/test/123');
|
||||
expect(davClientRequestStub.getCall(0).args[2]['X-Requested-With'])
|
||||
.toEqual('XMLHttpRequest');
|
||||
expect(davClientRequestStub.getCall(0).args[3])
|
||||
.toBeFalsy();
|
||||
|
||||
deferredRequest.resolve({
|
||||
status: 200,
|
||||
body: ''
|
||||
});
|
||||
});
|
||||
|
||||
function testMethodError(doCall) {
|
||||
var successStub = sinon.stub();
|
||||
var errorStub = sinon.stub();
|
||||
|
||||
doCall(successStub, errorStub);
|
||||
|
||||
deferredRequest.resolve({
|
||||
status: 404,
|
||||
body: ''
|
||||
});
|
||||
|
||||
expect(successStub.notCalled).toEqual(true);
|
||||
expect(errorStub.calledOnce).toEqual(true);
|
||||
}
|
||||
|
||||
it('calls error handler if error status in PROPFIND response', function() {
|
||||
testMethodError(function(success, error) {
|
||||
var model = new TestModel();
|
||||
model.fetch({
|
||||
success: success,
|
||||
error: error
|
||||
});
|
||||
});
|
||||
});
|
||||
it('calls error handler if error status in PROPPATCH response', function() {
|
||||
testMethodError(function(success, error) {
|
||||
var model = new TestModel();
|
||||
model.save({
|
||||
firstName: 'Hey'
|
||||
}, {
|
||||
success: success,
|
||||
error: error
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,19 @@
|
|||
if (typeof dav == 'undefined') { dav = {}; };
|
||||
|
||||
dav._XML_CHAR_MAP = {
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'&': '&',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
|
||||
dav._escapeXml = function(s) {
|
||||
return s.replace(/[<>&"']/g, function (ch) {
|
||||
return dav._XML_CHAR_MAP[ch];
|
||||
});
|
||||
};
|
||||
|
||||
dav.Client = function(options) {
|
||||
var i;
|
||||
for(i in options) {
|
||||
|
@ -85,6 +99,57 @@ dav.Client.prototype = {
|
|||
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates a propPatch request.
|
||||
*
|
||||
* @param {string} url Url to do the proppatch request on
|
||||
* @param {Array} properties List of properties to store.
|
||||
* @return {Promise}
|
||||
*/
|
||||
propPatch : function(url, properties, headers) {
|
||||
headers = headers || {};
|
||||
|
||||
headers['Content-Type'] = 'application/xml; charset=utf-8';
|
||||
|
||||
var body =
|
||||
'<?xml version="1.0"?>\n' +
|
||||
'<d:propertyupdate ';
|
||||
var namespace;
|
||||
for (namespace in this.xmlNamespaces) {
|
||||
body += ' xmlns:' + this.xmlNamespaces[namespace] + '="' + namespace + '"';
|
||||
}
|
||||
body += '>\n' +
|
||||
' <d:set>\n' +
|
||||
' <d:prop>\n';
|
||||
|
||||
for(var ii in properties) {
|
||||
|
||||
var property = this.parseClarkNotation(ii);
|
||||
var propName;
|
||||
var propValue = properties[ii];
|
||||
if (this.xmlNamespaces[property.namespace]) {
|
||||
propName = this.xmlNamespaces[property.namespace] + ':' + property.name;
|
||||
} else {
|
||||
propName = 'x:' + property.name + ' xmlns:x="' + property.namespace + '"';
|
||||
}
|
||||
body += ' <' + propName + '>' + dav._escapeXml(propValue) + '</' + propName + '>\n';
|
||||
}
|
||||
body+=' </d:prop>\n';
|
||||
body+=' </d:set>\n';
|
||||
body+='</d:propertyupdate>';
|
||||
|
||||
return this.request('PROPPATCH', url, headers, body).then(
|
||||
function(result) {
|
||||
return {
|
||||
status: result.status,
|
||||
body: result.body,
|
||||
xhr: result.xhr
|
||||
};
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Performs a HTTP request, and returns a Promise
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue