breeze-client-labs
Version:
Breeze Labs are extensions and utilities for Breeze.js client apps that are not part of core breeze.
239 lines (212 loc) • 9.44 kB
JavaScript
/*
* Breeze Labs Azure Mobile Services DataServiceAdapter
*
* v.0.6.2
*
* Registers an Azure Mobile Services DataServiceAdapter with Breeze
*
* REQUIRES breeze.labs.dataservice.abstractrest.js v.0.6.0+
*
* This adapter cannot get metadata from the server because mobile services does not provide metadata.
*
* Typical usage in Angular
* // configure breeze to use Azure Mobile Services dataservice adapter
* var dsAdapter = breeze.config.initializeAdapterInstance('dataService', 'azure-mobile-services', true);
*
* // provide the mobile services information specific to your app
* adapter.mobileServicesInfo = {
* url: 'https://yoursite.azure-mobile.net/',
* appKey: 'MumboJumboLwepMvaSSJdCAauHzhfddkQC33', // identifies your app; it is not a secret
* installId: '21463a76-e9fd-e429-13c6-a7a406a70505', // if you know it, else creates one for you
* zumoVersion: 'ZUMO/1.0 (lang=Web; os=--; os_version=--; arch=--; version=1.0.11121.0)' // if you know it
* };
*
* This adapter has its own JsonResultsAdapter which you could replace.
*
* By default this adapter permits multiple entities to be saved at a time,
* each in a separate request that this adapter fires off in parallel.
* and waits for all to complete.
*
* If 'saveOnlyOne' == true, the adapter throws an exception
* when asked to save more than one entity at a time.
*
* Copyright 2015 IdeaBlade, Inc. All Rights Reserved.
* Licensed under the MIT License
* http://opensource.org/licenses/mit-license.php
* Author: Ward Bell
*/
(function (definition) {
if (typeof breeze === "object") {
definition(breeze);
} else if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
// CommonJS or Node
var b = require('breeze-client');
definition(b);
} else if (typeof define === "function" && define["amd"]) {
// Requirejs / AMD
define(['breeze-client'], definition);
} else {
throw new Error("Can't find breeze");
}
}(function (breeze) {
"use strict";
var ctor = function () {
this.name = "azure-mobile-services";
};
ctor.prototype.initialize = typeInitialize;
function typeInitialize() {
// Delay setting the prototype until we're sure AbstractRestDataServiceAdapter is loaded
var proto = breeze.AbstractRestDataServiceAdapter.prototype;
proto = breeze.core.extend(ctor.prototype, proto);
proto.executeQuery = executeQuery;
proto._createErrorFromResponse = _createErrorFromResponse;
proto._createChangeRequest = _createChangeRequest;
proto._createJsonResultsAdapter = _createJsonResultsAdapter;
proto._createUniqueInstallationId = _createUniqueInstallationId;
proto._getZumoHeaders = _getZumoHeaders;
this.initialize(); // the revised initialize()
}
breeze.config.registerAdapter("dataService", ctor);
/////////////////
// Create error object for both query and save responses.
// A method on the adapter (`this`)
// 'context' can help differentiate query and save
// 'errorEntity' only defined for save response
function _createErrorFromResponse(response, url, context, errorEntity) {
var err = new Error();
err.response = response;
var data = response.data || {};
if (url) { err.url = url; }
err.status = data.code || response.status || '???';
err.statusText = response.statusText || err.status;
err.message = data.error || response.message || response.error || err.statusText;
this._catchNoConnectionError(err);
return err;
}
function _createJsonResultsAdapter() {
var dataServiceAdapter = this;
return new breeze.JsonResultsAdapter({
name: dataServiceAdapter.name + "_default",
visitNode: visitNode
});
function visitNode(node, mappingContext) {
// mappingContext.entityType could be set for a queryResult
// node.$entityType set when node is from a change response (see _processSavedEntity)
var entityType = mappingContext.entityType || node.$entityType ||
dataServiceAdapter._getEntityTypeFromMappingContext(mappingContext);
return (entityType) ? { entityType: entityType } : {}
}
}
function _createChangeRequest(saveContext, entity, index) {
var data, rawEntity, request;
var type = entity.entityType;
var rn = type.defaultResourceName;
if (!rn) {
throw new Error("Missing defaultResourceName for type " + type.name);
}
var adapter = saveContext.adapter;
var entityManager = saveContext.entityManager;
var helper = entityManager.helper;
var baseUrl = entityManager.dataService.serviceName + rn;
var tempKeys = saveContext.tempKeys;
var aspect = entity.entityAspect;
var key = aspect.getKey();
var state = aspect.entityState;
if (state.isAdded()) {
if (type.autoGeneratedKeyType !== breeze.AutoGeneratedKeyType.None) {
tempKeys[index] = key; // INDEX! DO NOT PUSH. Gaps expected!
}
rawEntity = helper.unwrapInstance(entity, adapter._transformSaveValue);
// Don't send the temp key value or ZUMO will use it!
// Delete that property so ZUMO generates a good permanent key
delete rawEntity[type.keyProperties[0].name];
data = adapter._serializeToJson(rawEntity);
request = {
requestUri: baseUrl,
method: "POST",
data: data
};
} else if (state.isModified()) {
rawEntity = helper.unwrapChangedValues(entity, entityManager.metadataStore, adapter._transformSaveValue);
data = adapter._serializeToJson(rawEntity);
request = {
requestUri: baseUrl+'/'+ key.values[0],
method: "PATCH",
data: data
};
} else if (state.isDeleted()) {
request = {
requestUri: baseUrl+'/'+ key.values[0],
method: "DELETE",
data: null
};
} else {
throw new Error("Cannot save an entity whose EntityState is " + state.name);
}
request.headers = adapter._getZumoHeaders();
return request;
}
function _createUniqueInstallationId () {
/// <summary>
/// Create a unique identifier that can be used for the installation of
/// the current application.
/// </summary>
/// <example>
/// '21463a76-e9fd-e429-13c6-a7a406a70505'
/// </example>
/// <returns type="String">Unique identifier.</returns>
var pad4 = function (str) { return "0000".substring(str.length) + str; };
var hex4 = function () { return pad4(Math.floor(Math.random() * 0x10000 /* 65536 */).toString(16)); };
return (hex4() + hex4() + "-" + hex4() + "-" + hex4() + "-" + hex4() + "-" + hex4() + hex4() + hex4());
}
function _getZumoHeaders(){
var msInfo = this.mobileServicesInfo;
if (!msInfo.installId){
msInfo.installId = _createUniqueInstallationId();
}
if (!msInfo.zumoVersion){
msInfo.zumoVersion = 'ZUMO/1.0 (lang=Web; os=--; os_version=--; arch=--; version=1.0.11121.0)';
}
return {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-ZUMO-APPLICATION': msInfo.appKey,
'X-ZUMO-INSTALLATION-ID': msInfo.installId,
'X-ZUMO-Version': msInfo.zumoVersion
};
}
function executeQuery(mappingContext) {
var adapter = mappingContext.adapter = this;
mappingContext.entityType = adapter._getEntityTypeFromMappingContext(mappingContext);
var deferred = adapter.Q.defer();
var url = mappingContext.getUrl();
var headers = adapter._getZumoHeaders();
adapter._ajaxImpl.ajax({
type: "GET",
url: url,
headers: headers,
params: mappingContext.query.parameters,
success: querySuccess,
error: function (response) {
deferred.reject(adapter._createErrorFromResponse(response, url, mappingContext));
}
});
return deferred.promise;
function querySuccess(response) {
try {
var rData = {
results: adapter._getResponseData(response),
httpResponse: response
};
deferred.resolve(rData);
} catch (e) {
// if here, the adapter is broken, not bad data
var err = new Error("Query failed while parsing successful query response")
err.name = "Program Error";
err.response = response;
err.originalError = e;
deferred.reject(err);
}
}
}
}));